【问题标题】:Rails "can't write unknown attribute"Rails“无法写入未知属性”
【发布时间】:2020-03-01 22:55:01
【问题描述】:

我一整天都在尝试解决这个问题,但我无法解决。

我收到此错误:

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 注册后,用户必须填写另一个表格。 现在我还需要更新用户以使驾驶执照和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 中的主键应该是你最后的选择。您可以通过 rails & db 验证来制作复合键,并将通常的 rails id 字段保留为主要字段吗?您甚至可以设置路由以使用复合键作为查询参数,如果这就是您希望它成为主要的原因。为什么该复合键必须是您应用中的主键?
  • @robertoplancarte 我希望它成为主键,因为它实际上使我的记录独一无二。 drivlicense and birth_nation这对夫妇是我申请的关键因素。至少那是大学里教给我的,我可能记错了,也许正如你所说,我应该将所有内容都恢复为 id 字段。核心思想是我想通过这个键访问用户和人。我犯了一些错误吗?

标签: ruby-on-rails ruby activerecord rails-activerecord


【解决方案1】:

Rails 将 id 字段用作surrogate key,然后在应用程序和数据库层中强制该字段的唯一性。这样您就不必担心创建复合主键(就像在学校一样)。

Rails 依赖于很多 convention over configuration,主键只是其中之一。您可以配置任何您喜欢的主键,但 Rails 会与您抗衡,特别是如果您想使用复合键。如果您确实需要,有一个 gem 可以帮助您完成此操作,但我会远离它,因为它会引入复杂性。

【讨论】:

  • 所以基本上就我的理解而言,您非常不鼓励我更改主键,唯一的解决方法是通过这个 gem,对吗?感觉很糟糕,没有一种简单的方法来处理这个问题。特别是我不明白为什么我不能通过将值放入主键并仍然收到错误来解决我的问题:/。现在这更像是一种好奇心,但很可能我认为我会选择让 id 字段保持唯一。
  • 我取出了所有的主键,但我仍然得到同样的错误。我不知道发生了什么事。从表单中我传递了正确的值,但我仍然得到[:drivlicense_nr, :birth_nation]。我不知道
  • 我认为您将不得不添加一个新的迁移来删除名为 [:drivlicense_nr, :birth_nation] 的列,当您在迁移中使用 primary_key 选项时,rails 会创建该列:edgeguides.rubyonrails.org/…
  • 已解决,谢谢。我也忘了从person.rb 中取出primary_key,我太专注了,完全没有想到它。你是神
猜你喜欢
  • 2016-01-07
  • 1970-01-01
  • 2021-01-14
  • 2014-01-11
  • 2019-06-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多