Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Validating association after save?

I have a User model which has many roles. Roles contains a user_id field, which I want to validate_presence_of

The issue is: if I assign a role to user upon create, the validation fails because no user_id is set. Now, I do want to validate that a user_id exists, but I need to save the user before checking that.

The code currently looks like this:

@user = User.new(params[:user])
@user.roles << Role.new(:name => 'Peon') unless @user.has_roles?
if @user.save
  # ...

The only ways I can think of getting around the problem involves either disabling the validation, which I don't want to do, or double-saving to the DB, which isn't exactly efficient.

What's the standard way for handling this issue?

like image 716
PlankTon Avatar asked Jan 04 '13 01:01

PlankTon


People also ask

What is the difference between validate and validates in rails?

validates is used for normal validations presence , length , and the like. validate is used for custom validation methods validate_name_starts_with_a , or whatever crazy method you come up with. These methods are clearly useful and help keep data clean. That test fails.

What happens on Save rails?

The purpose of this distinction is that with save! , you are able to catch errors in your controller using the standard ruby facilities for doing so, while save enables you to do the same using standard if-clauses.

How does validate work in Rails?

Rails validation defines valid states for each of your Active Record model classes. They are used to ensure that only valid details are entered into your database. Rails make it easy to add validations to your model classes and allows you to create your own validation methods as well.


2 Answers

After researching a bit, this solution seems to be easiest. First, in your Role model, instead of validating user_id, validate user:

validates :user, :presence => true

Then, in your User model, add :inverse_of => :user to your has_many call:

has_many :roles, :inverse_of => :user

Then it works as expected:

irb(main):001:0> @user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):002:0> @user.roles << Role.new(:name => "blah")
=> [#<Role id: nil, user_id: nil, name: "blah", created_at: nil, updated_at: nil>]
irb(main):003:0> @user.roles[0].user
=> #<User id: nil, created_at: nil, updated_at: nil>
irb(main):004:0> @user.save
  (0.1ms)  begin transaction
 SQL (3.3ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00], ["updated_at", Fri, 04 Jan 2013 02:29:33 UTC +00:00]]
 User Load (0.1ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 3 LIMIT 1
 SQL (0.2ms)  INSERT INTO "roles" ("created_at", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?)  [["created_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["name", "blah"], ["updated_at", Fri, 04 Jan 2013 02:29:34 UTC +00:00], ["user_id", 3]]
  (1.9ms)  commit transaction
=> true
irb(main):005:0> @user.roles.first
=> #<Role id: 4, user_id: 3, name: "blah", created_at: "2013-01-04 02:29:34", updated_at: "2013-01-04 02:29:34">

Note, however, that this still produces two SQL transactions, one to save the user and one to save the role. I don't see how you can avoid that.

See also: How can you validate the presence of a belongs to association with Rails?

like image 57
Chris Salzberg Avatar answered Oct 19 '22 00:10

Chris Salzberg


I think you can get around the validation problem if you change your code to look like this:

@user = User.new(params[:user])
@user.roles.new(:name => 'Peon') unless @user.has_roles?
if @user.save
  # ...

If that doesn't work, you could try changing you validation to this:

class Role < ActiveRecord::Base
  belongs_to :user
  validates :user_id, :presence => true, :unless => Proc.new() {|r| r.user}
end
like image 28
Andrew Hubbs Avatar answered Oct 19 '22 01:10

Andrew Hubbs