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 来帮助您完成它,如果您真的需要的话,但我会远离它,因为它会引入复杂性。