Ruby on Rails外键字段不能为null

时间:2016-11-19 14:24:41

标签: ruby-on-rails

我有一个Rails应用程序,其中有User模型和Role模型。

角色可以是超级用户,管理员或用户。

我的用户表迁移如下所示:

class CreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      t.string :first_name
      t.string :last_name
      t.string :username
      t.string :email
      t.string :password
      t.string :password_digest
      t.boolean :banned
      t.references :role, foreign_key: true

      t.timestamps
    end
  end
end

每当我使用HTTP POST创建用户时,我都会收到错误:

Role must exist

Rails告诉我,我的用户模型的字段role不能为空。

所以我想,好吧,我希望通过HTTP POST创建的用户默认为“User”角色,所以我在我的用户模型中有这个:

class User < ApplicationRecord
  belongs_to :role
  has_secure_password
  # validates :role, allow_nil: true

  before_validation do
    self.banned = false

    # Users created through HTTP POST request should always
    # default to 'User' role.
    # Rails console created users will be explicitly set
    # to 'Superuser' or 'Admin' roles.
    self.role = Role.find_by({title: 'User'})
    puts "User model set role fired!"
  end
end

这可以解决错误,但现在我遇到了问题。

当管理员用户尝试将现有用户的角色从“用户”更改为“管理员”时,before_validation方法始终会触发,永远覆盖我的POST参数以指定用户的新角色。

实际上,我永远无法改变任何用户的角色。

我做错了吗?

当然有一种方法可以创建一个没有在开始时分配任何角色的用户,然后再设置角色......

我几个小时都在摸不着头脑。

更新

我发现了这个:http://blog.bigbinary.com/2016/02/15/rails-5-makes-belong-to-association-required-by-default.html

哦,伙计。我忘记了Rails 5 belongs_to现在已更改为默认情况下需要,需要为我的初始问题设置optional: true:D

在尝试了Steve的建议之后我的新问题是我的Rails测试给了我错误:

ActiveRecord::RecordInvalid: Validation failed: Password can't be blank

即使我正在进行更新操作而不是创建用户操作。我想知道Bcrypt是否会以某种方式干扰?

当我使用rails console命令生成User模型时,可能只需要password_digest字段?

需要更多调查......

更新2

我想我已经解决了......至少我的所有测试现在都已经过去了,尽管我仍然持怀疑态度:D

我创建了一个新的迁移文件并从Users表中删除了Password列,我的测试仍然通过,包括创建一个新用户。

我仍然可以在rails console --sandbox

中创建用户

我认为其他一些错误是我的用户强大的参数需要将字段从:role更改为:role_id。虽然用户模型字段名为role,但在数据库中,外键实际上是role_id

2 个答案:

答案 0 :(得分:2)

您可以根据是否已有值来确定作业...

  before_validation do
    self.banned = false if banned.nil?
    self.role ||= Role.find_by(title: 'User')
  end

或者你只能在创建....

  before_validation do
    if new_record?
      self.banned = false
      self.role = Role.find_by(title: 'User')
    end
  end

但坦率地说,我通常看到初始默认设置的方式是在控制器的create方法中

def create
  @user = User.assign_attributes(user_params)
  @user.banned = false
  @user.role = Role.find_by(title: 'User')
  if @user.save
    # ...

答案 1 :(得分:0)

您只能在创建时使用before_action

before_validation :my_action, on: :create