Rails "can't write unknown attribute"
Rails "can't write unknown attribute"
这个问题我想了一天了,还是解决不了。
我收到这个错误:
ActiveModel::MissingAttributeError in Users::RegistrationsController#add_data
can't write unknown attribute `[:drivlicense_nr, :birth_nation]`
user.rb:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_one :person
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable,
:omniauthable, omniauth_providers: %i[facebook twitter google_oauth2]
#validate :password_complexity
private
def password_complexity
if password.present? && !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)./)
errors.add :password, 'must include at least one lowercase letter, one uppercase letter, and one digit'
end
end
def self.from_omniauth(auth)
# Either create a User record or update it based on the provider (Google) and the UID
where(email: auth.email, uid: auth.uid).first_or_create do |user|
user.token = auth.credentials.token
user.expires = auth.credentials.expires
user.expires_at = auth.credentials.expires_at
user.refresh_token = auth.credentials.refresh_token
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.skip_confirmation!
user.save!
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session['devise.facebook_data'] && session['devise.facebook_data']['extra']['raw_info']
user.email = data['email'] if user.email.blank?
end
if data = session['devise.google_data'] && session['devise.google_data']['extra']['raw_info']
user.email = data['email'] if user.email.blank?
end
end
end
end
在 person.rb 中,我正在使用自定义 primary_key,因为它们对我的应用程序非常重要。
class Person < ApplicationRecord
belongs_to :user, optional: true
has_many :cars
self.primary_key = %i[drivlicense_nr birth_nation]
VALID_FISCAL_CODE_REGEX = /\A^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$\z/
validates :fiscal_code, presence: true, length: {is: 16}, format: { with: VALID_FISCAL_CODE_REGEX }
end
人口迁移:
class CreatePeople < ActiveRecord::Migration[5.2]
def change
create_table :people, primary_key: %i[drivlicense_nr birth_nation] do |t|
t.integer :usercode
t.string :email, null: false, default: ''
t.string :plate_nr, limit: 8, null: false, default: ''
t.string :drivlicense_nr, null: false, default: ''
t.string :fiscal_code, limit: 16
t.string :name
t.string :surname
t.string :phone_number
t.date :birth_date
t.string :birth_nation, limit: 2, null: false, default: 'IT'
t.string :birth_place
t.string :current_address
t.string :city
t.string :sex
t.string :region
t.string :zipcode, limit: 5
t.string :state, limit: 2
t.timestamps null: false
end
add_index :people, %i[drivlicense_nr birth_nation], name: 'index_people', unique: true
add_index :people, :usercode, name: 'index_people_on_usercode', unique: true
add_index :people, :fiscal_code, unique: true
# add_index :people, :pcode, unique: true
end
end
人口迁移:
class DeviseCreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.integer :usercode
t.belongs_to :person, index: true
t.boolean :admin, default: false
t.string :drivlicense_nr, null: false, default: ''
t.string :birth_nation, limit: 2, null: false, default: 'IT'
t.string :tpoliceman_id
## Database authenticatable
t.string :email
t.string :encrypted_password
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, null: false, default: 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
#t.datetime :updated_at
## Omniauthable
t.string :provider
t.string :uid
t.string :refresh_token
t.string :token
t.boolean :expires
t.integer :expires_at
## Lockable
t.integer :failed_attempts, null: false, default: 0 # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :usercode, unique: true
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
add_index :users, :unlock_token, unique: true
#add_index :users, %i[drivlicense_nr birth_nation], name: 'index_users_on_person'#, unique: true
# validates :drivlicense_nr, uniqueness: { scope: :birth_nation }
end
end
我在这里尝试做的是在我通过 google/facebook 注册后,用户必须填写另一份表格。
现在我还需要更新用户,使 drivlicense 和 birth_nation(我的主键)具有相同的值。一旦程序执行@person.save,就会触发错误!
Registrations_Controller#add_data:
def add_data
@user = User.find_by_email(params[:email])
if request.get?
@person = Person.new()
render 'oauth_add_data'
elsif request.put?
@person = Person.new(person_params)
@user.update(drivlicense_nr: @person['drivlicense_nr'], birth_nation: @person['birth_nation'])
if @person.save!
flash[:success] = "Sign up process successful"
bypass_sign_in(@user)
redirect_to root_url
else
render 'new'
end
end
end
在此先感谢您的帮助和耐心等待!
更新
schema.rb:
ActiveRecord::Schema.define(version: 2020_02_29_181024) do
create_table "car_associated_person", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "plate_nr", limit: 8
t.string "drivlicense_nr"
t.string "birth_nation", limit: 2
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "cars", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "plate_nr", limit: 8, default: "", null: false
t.string "chassis_nr", default: "", null: false
t.string "owner_drivlicense_nr", default: "", null: false
t.string "owner_birth_nation", limit: 2
t.date "enrollment_date", default: "2018-01-01", null: false
t.string "enrollment_nr", default: "", null: false
t.string "enrollment_nation", default: "", null: false
t.string "brand", default: "", null: false
t.string "model", default: "", null: false
t.integer "infraction_nr", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["chassis_nr"], name: "index_cars_on_chassis_nr", unique: true
t.index ["owner_birth_nation"], name: "index_cars_on_owner_birth_nation", unique: true
t.index ["owner_drivlicense_nr"], name: "index_cars_on_owner_drivlicense_nr", unique: true
t.index ["plate_nr"], name: "index_cars_on_plate_nr", unique: true
end
create_table "fines", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "car_id"
t.integer "fine_nr", default: 0
t.string "plate_nr", limit: 8, default: "", null: false
t.datetime "fine_datetime", default: "2018-01-01 00:00:00", null: false
t.string "fine_address", default: "", null: false
t.string "infraction_article", limit: 10, default: "", null: false
t.string "infraction_informcode"
t.string "infraction_motivation", default: "", null: false
t.integer "deduction_points", default: 0
t.float "fine_amount_reduced", default: 0.0
t.float "procedures_amount", default: 0.0
t.float "fine_total_amount", default: 0.0
t.integer "days_nr_payment", limit: 1, default: 5
t.string "paid", default: "0"
t.string "optional_penalty", default: "None"
t.string "tpoliceman_idnr_1"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["car_id"], name: "index_fines_on_car_id"
t.index ["fine_nr"], name: "index_fines_on_fine_nr", unique: true
t.index ["infraction_informcode"], name: "index_fines_on_infraction_informcode", unique: true
t.index ["plate_nr"], name: "index_fines_on_plate_nr", unique: true
end
create_table "people", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "usercode"
t.string "email", default: "", null: false
t.string "plate_nr", limit: 8, default: "", null: false
t.string "drivlicense_nr", default: ""
t.string "fiscal_code", limit: 16
t.string "name"
t.string "surname"
t.string "phone_number"
t.date "birth_date"
t.string "birth_nation", limit: 2, default: "IT"
t.string "birth_place"
t.string "current_address"
t.string "city"
t.string "sex"
t.string "region"
t.string "zipcode", limit: 5
t.string "state", limit: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["fiscal_code"], name: "index_people_on_fiscal_code", unique: true
t.index ["usercode"], name: "index_people_on_usercode", unique: true
end
create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "usercode"
t.integer "person_id"
t.boolean "admin", default: false
t.string "drivlicense_nr", default: "", null: false
t.string "birth_nation", limit: 2, default: "IT", null: false
t.string "tpoliceman_id"
t.string "email"
t.string "encrypted_password"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.string "provider"
t.string "uid"
t.string "refresh_token"
t.string "token"
t.boolean "expires"
t.integer "expires_at"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["person_id"], name: "index_users_on_person_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
t.index ["usercode"], name: "index_users_on_usercode", unique: true
end
end
Rails 使用 id 字段作为 surrogate key 它然后在应用程序和数据库层中强制该字段的唯一性。这样您就不必担心创建复合主键(就像在学校一样)。
Rails 依赖很多 convention over configuration, primary keys being just one of those things. You could configure any primary key you like, but Rails will fight you especially if you want to use a composite key. There is a gem 来帮助您完成它,如果您真的需要的话,但我会远离它,因为它会引入复杂性。
这个问题我想了一天了,还是解决不了。
我收到这个错误:
ActiveModel::MissingAttributeError in Users::RegistrationsController#add_data
can't write unknown attribute `[:drivlicense_nr, :birth_nation]`
user.rb:
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
has_one :person
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable,
:omniauthable, omniauth_providers: %i[facebook twitter google_oauth2]
#validate :password_complexity
private
def password_complexity
if password.present? && !password.match(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)./)
errors.add :password, 'must include at least one lowercase letter, one uppercase letter, and one digit'
end
end
def self.from_omniauth(auth)
# Either create a User record or update it based on the provider (Google) and the UID
where(email: auth.email, uid: auth.uid).first_or_create do |user|
user.token = auth.credentials.token
user.expires = auth.credentials.expires
user.expires_at = auth.credentials.expires_at
user.refresh_token = auth.credentials.refresh_token
user.provider = auth.provider
user.uid = auth.uid
user.email = auth.info.email
user.password = Devise.friendly_token[0,20]
user.skip_confirmation!
user.save!
end
end
def self.new_with_session(params, session)
super.tap do |user|
if data = session['devise.facebook_data'] && session['devise.facebook_data']['extra']['raw_info']
user.email = data['email'] if user.email.blank?
end
if data = session['devise.google_data'] && session['devise.google_data']['extra']['raw_info']
user.email = data['email'] if user.email.blank?
end
end
end
end
在 person.rb 中,我正在使用自定义 primary_key,因为它们对我的应用程序非常重要。
class Person < ApplicationRecord
belongs_to :user, optional: true
has_many :cars
self.primary_key = %i[drivlicense_nr birth_nation]
VALID_FISCAL_CODE_REGEX = /\A^[A-Z]{6}[0-9]{2}[A-Z][0-9]{2}[A-Z][0-9]{3}[A-Z]$\z/
validates :fiscal_code, presence: true, length: {is: 16}, format: { with: VALID_FISCAL_CODE_REGEX }
end
人口迁移:
class CreatePeople < ActiveRecord::Migration[5.2]
def change
create_table :people, primary_key: %i[drivlicense_nr birth_nation] do |t|
t.integer :usercode
t.string :email, null: false, default: ''
t.string :plate_nr, limit: 8, null: false, default: ''
t.string :drivlicense_nr, null: false, default: ''
t.string :fiscal_code, limit: 16
t.string :name
t.string :surname
t.string :phone_number
t.date :birth_date
t.string :birth_nation, limit: 2, null: false, default: 'IT'
t.string :birth_place
t.string :current_address
t.string :city
t.string :sex
t.string :region
t.string :zipcode, limit: 5
t.string :state, limit: 2
t.timestamps null: false
end
add_index :people, %i[drivlicense_nr birth_nation], name: 'index_people', unique: true
add_index :people, :usercode, name: 'index_people_on_usercode', unique: true
add_index :people, :fiscal_code, unique: true
# add_index :people, :pcode, unique: true
end
end
人口迁移:
class DeviseCreateUsers < ActiveRecord::Migration[5.0]
def change
create_table :users do |t|
t.integer :usercode
t.belongs_to :person, index: true
t.boolean :admin, default: false
t.string :drivlicense_nr, null: false, default: ''
t.string :birth_nation, limit: 2, null: false, default: 'IT'
t.string :tpoliceman_id
## Database authenticatable
t.string :email
t.string :encrypted_password
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
t.integer :sign_in_count, null: false, default: 0
t.datetime :current_sign_in_at
t.datetime :last_sign_in_at
t.string :current_sign_in_ip
t.string :last_sign_in_ip
## Confirmable
t.string :confirmation_token
t.datetime :confirmed_at
t.datetime :confirmation_sent_at
t.string :unconfirmed_email # Only if using reconfirmable
#t.datetime :updated_at
## Omniauthable
t.string :provider
t.string :uid
t.string :refresh_token
t.string :token
t.boolean :expires
t.integer :expires_at
## Lockable
t.integer :failed_attempts, null: false, default: 0 # Only if lock strategy is :failed_attempts
t.string :unlock_token # Only if unlock strategy is :email or :both
t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :usercode, unique: true
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
add_index :users, :confirmation_token, unique: true
add_index :users, :unlock_token, unique: true
#add_index :users, %i[drivlicense_nr birth_nation], name: 'index_users_on_person'#, unique: true
# validates :drivlicense_nr, uniqueness: { scope: :birth_nation }
end
end
我在这里尝试做的是在我通过 google/facebook 注册后,用户必须填写另一份表格。 现在我还需要更新用户,使 drivlicense 和 birth_nation(我的主键)具有相同的值。一旦程序执行@person.save,就会触发错误! Registrations_Controller#add_data:
def add_data
@user = User.find_by_email(params[:email])
if request.get?
@person = Person.new()
render 'oauth_add_data'
elsif request.put?
@person = Person.new(person_params)
@user.update(drivlicense_nr: @person['drivlicense_nr'], birth_nation: @person['birth_nation'])
if @person.save!
flash[:success] = "Sign up process successful"
bypass_sign_in(@user)
redirect_to root_url
else
render 'new'
end
end
end
在此先感谢您的帮助和耐心等待!
更新 schema.rb:
ActiveRecord::Schema.define(version: 2020_02_29_181024) do
create_table "car_associated_person", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "plate_nr", limit: 8
t.string "drivlicense_nr"
t.string "birth_nation", limit: 2
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "cars", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "plate_nr", limit: 8, default: "", null: false
t.string "chassis_nr", default: "", null: false
t.string "owner_drivlicense_nr", default: "", null: false
t.string "owner_birth_nation", limit: 2
t.date "enrollment_date", default: "2018-01-01", null: false
t.string "enrollment_nr", default: "", null: false
t.string "enrollment_nation", default: "", null: false
t.string "brand", default: "", null: false
t.string "model", default: "", null: false
t.integer "infraction_nr", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["chassis_nr"], name: "index_cars_on_chassis_nr", unique: true
t.index ["owner_birth_nation"], name: "index_cars_on_owner_birth_nation", unique: true
t.index ["owner_drivlicense_nr"], name: "index_cars_on_owner_drivlicense_nr", unique: true
t.index ["plate_nr"], name: "index_cars_on_plate_nr", unique: true
end
create_table "fines", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.bigint "car_id"
t.integer "fine_nr", default: 0
t.string "plate_nr", limit: 8, default: "", null: false
t.datetime "fine_datetime", default: "2018-01-01 00:00:00", null: false
t.string "fine_address", default: "", null: false
t.string "infraction_article", limit: 10, default: "", null: false
t.string "infraction_informcode"
t.string "infraction_motivation", default: "", null: false
t.integer "deduction_points", default: 0
t.float "fine_amount_reduced", default: 0.0
t.float "procedures_amount", default: 0.0
t.float "fine_total_amount", default: 0.0
t.integer "days_nr_payment", limit: 1, default: 5
t.string "paid", default: "0"
t.string "optional_penalty", default: "None"
t.string "tpoliceman_idnr_1"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["car_id"], name: "index_fines_on_car_id"
t.index ["fine_nr"], name: "index_fines_on_fine_nr", unique: true
t.index ["infraction_informcode"], name: "index_fines_on_infraction_informcode", unique: true
t.index ["plate_nr"], name: "index_fines_on_plate_nr", unique: true
end
create_table "people", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "usercode"
t.string "email", default: "", null: false
t.string "plate_nr", limit: 8, default: "", null: false
t.string "drivlicense_nr", default: ""
t.string "fiscal_code", limit: 16
t.string "name"
t.string "surname"
t.string "phone_number"
t.date "birth_date"
t.string "birth_nation", limit: 2, default: "IT"
t.string "birth_place"
t.string "current_address"
t.string "city"
t.string "sex"
t.string "region"
t.string "zipcode", limit: 5
t.string "state", limit: 2
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["fiscal_code"], name: "index_people_on_fiscal_code", unique: true
t.index ["usercode"], name: "index_people_on_usercode", unique: true
end
create_table "users", id: :integer, options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "usercode"
t.integer "person_id"
t.boolean "admin", default: false
t.string "drivlicense_nr", default: "", null: false
t.string "birth_nation", limit: 2, default: "IT", null: false
t.string "tpoliceman_id"
t.string "email"
t.string "encrypted_password"
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.string "provider"
t.string "uid"
t.string "refresh_token"
t.string "token"
t.boolean "expires"
t.integer "expires_at"
t.integer "failed_attempts", default: 0, null: false
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["person_id"], name: "index_users_on_person_id"
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true
t.index ["usercode"], name: "index_users_on_usercode", unique: true
end
end
Rails 使用 id 字段作为 surrogate key 它然后在应用程序和数据库层中强制该字段的唯一性。这样您就不必担心创建复合主键(就像在学校一样)。
Rails 依赖很多 convention over configuration, primary keys being just one of those things. You could configure any primary key you like, but Rails will fight you especially if you want to use a composite key. There is a gem 来帮助您完成它,如果您真的需要的话,但我会远离它,因为它会引入复杂性。