简体   繁体   中英

RoR 4 belongs_to foreign_key not checked

I'm a newbie in RoR and I'm trying to make a simple experiment. I want to have a User model and a Role model. Each user a single Role, but a single Role may refer to multiple Users.

So, here are my models:

class Role < ActiveRecord::Base
  has_many :user
end

class User < ActiveRecord::Base
  has_one :role
end

And here are migrations:

class CreateRoles < ActiveRecord::Migration
  def change
    create_table :roles do |t|
      t.string :name, null: false, index: true, unique: true, limit: 16
      t.integer :permissions, null: false
    end
  end
end

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string :email, null: false, index: true,  unique: true, limit: 128
      t.string :password_digest, null: false, limit: 40
      t.string :password_salt, null: false, limit: 40
      t.string :screen_name, default: '', limit: 32
      t.belongs_to :role, index: true, foreign_key: true

      t.timestamps null: false
    end
  end
end

What I want is to make it raise an exception when I try to connect user with a role that does not exist:

user = User.create(email: 'user@example.com', password_digest: 'pwd_dig',
                   password_salt: 'salt', role_id: 10)

Unfortunately this works and the new user is created, no matter that the role with id 10 does not exist.

So, how can I force foreign_key check here?

And another question about that. If I try to do like this:

user = User.create(email: 'user@example.com', password_digest: 'pwd_dig',
                   password_salt: 'salt', role: role)

it raises an exception because role does not have attribute user_id. There is no way to do like this, does it?

I'm a newbie in RoR

Welcome!!


#app/models/role.rb
class Role < ActiveRecord::Base
   has_many :users
end

#app/models/user.rb
class User < ActiveRecord::Base
   belongs_to :role

   validates :role_id, presence: true, on: :create
   validate :check_role, on: :create

   private

   def check_role
      errors.add(:role_id, "Role doesn't exist") unless Role.exists? role_id
   end
end

This will allow you the following:

#app/controllers/users_controller.rb
class UsersController < ApplicationController
   def new
      @user = User.new
      @roles = Role.all
   end

   def create
      @user = User.new user_params
      @user.save
   end

   private

   def user_params
      params.require(:user).permit(:email, :etc, :role)
   end
end

#app/views/users/new.html.erb
<%= form_for @user do |f| %>
   <%= f.email_field :email %>
   <%= f.collection_select :role_id, @roles, :id, :name %>
   <%= f.submit %>
<% end %>

Because you're new, I'll give you some info:

1. Association

Your association is slightly incorrect - you'll be best using a belongs_to/has_many relationship :

在此处输入图片说明

The belongs_to will assign the foreign_key in your User model, allowing you to both reference and validate against it.

-

2. Custom validator

Secondly, you'll be able to use a validation to check whether the role has been set correctly.

Each time a Rails model is saved, it calls a series of validators you've set -- allowing you to call such functionality as checking whether a role exists.

What you can do is add a custom validation:

class User < ActiveRecord::Base
  belongs_to :role
  validate :existance_of_role

  private
    def existance_of_role
      if role_id && !Role.exists?(role_id)
        errors.add(:role, "must already exist.")
      end
    end
end

Also you need to use belongs_to :role . has_one would place the foreign_key in the relationship on the Role model instead of on the user.

If the user must have a role.

Also if you want to enforce on the database level that the user has a role you would add a NOT NULL constraint to users.role_id .

Run: rails g migration AddNotNullConstraintToUsers

And then edit the migration file created:

class AddNotNullConstraintToUsers < ActiveRecord::Migration
  def change
    change_column_null(:users, :role_id, false)
  end
end

After running the migration you can change the validation so that it will add an error also when the role_id is nil.

private
  def existance_of_role
    errors.add(:role, "must already exist.") unless Role.exists?(role_id)
  end

You could do the same thing with validates_presence_of :role but that will not enforce that the Role is pre-existing.

You can add this validator at your User model

class User < ActiveRecord::Base
    has_one :role

    validate :role_id_exists

    def role_id_exists
      return false if Role.find_by_id(self.role_id).nil?
    end
end

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM