Strange validation failure on Rails 2.1

I think that this is more appropriate for rails-core than the general list.

I’ve been doing a lot of frustrating spelunking in the new ActiveRecord code in Rails 2.1 trying to figure out why our app is not working. Here’s a simplified version of the section of our app which is causing trouble…

We’ve these classes:

class Role << ActiveRecord::Base
end

class Membership << ActiveRecord::Base
belongs_to :user
belongs_to :organization
has_many :membership_roles, :dependent => :delete_all
has_many :roles, :through => membership_roles

def add_role(role)
unless roles(true).include?(role)
membership_role = MembershipRole.create!(:membership => self, :role => role)
#…
end
end

class MembershipRole << ActiveRecord::Base
belongs_to :membership
belongs_to :role
validates_uniqueness_of :role_id, :scope => :membership_id
end

And there’s this observer

class MembershipObserver < ActiveRecord::Observer
def after_create(membership)
membership.add_role(Role.find_by_name(“Base”)
end
end

Now what I’m seeing when I do something like

Membership.create!(:user => some_user, :organization => some_organization)

is that the membership observer’s after_create method gets triggered which calls add_role on the membership which creates the MembershipRole join model, which passes the validates_uniqueness validation.

So far so good,

But then after the observer notifications triggered within the processing of Membership.create!, but before the create! call returns, I’m seeing the validation being run on what looks like an unsaved copy of the MembershipRole which was created in the Membership.add_role method, and this validation fails since the just created MembershipRole with the particular membership and role ids exists in the DB.

This code all worked fine on Rails 2.0.2, something in Rails 2.1 is causing this spurious validation.

I’d welcome any ideas on how to shoot this bug.

Could you reproduce this behaviour in a test? That would facilitate
tracking down the problem for all of us.

thanks,
Jan De Poorter

I experienced exactly the same problem and I think it is an important
issue.

It comes from the fact that associated objects are validate twice in
Rails 2.1 : First when parent is not saved yet and a second time when
parent is saved (I don't understand this behaviour).

Here is the result of a quick test in console where I log every
validation and callback calls :

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

post = Post.new
post.comments.build

before_add : post
after_add : post

post.save

before_validation : post
before_validation_on_create : post

# FIRST COMMENT VALIDATION (PARENT IS NOT SAVED)
before_validation : comment
before_validation_on_create : comment
validate : comment
validate_on_create : comment
after_validation : comment
after_validation_on_create : comment

validate : post
validate_on_create : post
after_validation : post
after_validation_on_create : post

before_save : post
before_create : post

-- DB INSERT : post

# SECOND COMMENT VALIDATION (PARENT IS SAVED NOW)
before_validation : comment
before_validation_on_create : comment
validate : comment
validate_on_create : comment
after_validation : comment
after_validation_on_create : comment

before_save : comment
before_create : comment

-- DB INSERT : comment

after_create : comment
after_save : comment

after_create : post
after_save : post

IMHO, unless I missed something, the first comment validation should
not happen.

In your program, when the first validation is run, membership_id does
not exist yet, hence the validation fail.

Related discussions:

http://rails.lighthouseapp.com/projects/8994/tickets/483-automatic-validation-on-has_many-should-not-be-performed-twice
http://rails.lighthouseapp.com/projects/8994/tickets/338-invalid-has_one-association-causes-parent-object-save-to-fail

Jean-Baptiste

I experienced exactly the same problem and I think it is an important

issue.

Thanks so much for the reply. I worked a bit last night just before leaving work on Jan De Poorter’s suggestion of writing a test. Since it requires database access, I decided to try to do that in the context of the rails activerecord test database setup, but ran out of time and patience trying to replicate it using the existing tables like author, category and categorization, with minimal changes, and decided to let it rest overnight.

It comes from the fact that associated objects are validate twice in

Rails 2.1 : First when parent is not saved yet and a second time when

parent is saved (I don’t understand this behaviour).

Thanks so much for the reply.

I suspected as much, some of the other tests which are failing for our app are cases where we do something like

# set up x to be invalid in some way
x.valid?
assert_equal x.errors, ["blah"]

which is failing because x.errors is evaluating to [“blah”, “blah”]

Here is the result of a quick test in console where I log every

validation and callback calls :

I cut out your logs for brevity.

IMHO, unless I missed something, the first comment validation should

not happen.

In your program, when the first validation is run, membership_id does

not exist yet, hence the validation fail.

I think it’s a bit different, in my case the first validation happens (and succeeds) when the after_create callback in the observer adds the role to the membership, which has already been stored and has an id.

But then after that happens, and before the create! call which saved the new membership returns, it seems to be revalidating the elements of the membership_roles association, AND those elements are new records, so the validates uniqueness fails because a cooresponding membership_role was already saved in the after create callback.

I haven’t yet been able to figure out just where the new membership_role which is being validated is coming from.

So this might be the same problem or not, but I suspect that it’s strongly related.

Related discussions:

http://rails.lighthouseapp.com/projects/8994/tickets/483-automatic-validation-on-has_many-should-not-be-performed-twice

http://rails.lighthouseapp.com/projects/8994/tickets/338-invalid-has_one-association-causes-parent-object-save-to-fail

So the new validate option might be a workaround or fix, but it looks like I’m going to have to move to edge rails to get that., and hope for Rails 2.1.1 to be released soon.

This app got ported from Rails 1.2.x to 2.0.x before I came on board, but in talking to the other guys, it looks like the step from 2.0.2 to 2.1 has been much more disruptive to us than 1.2.x to 2.0.

Some of the changes, particularly in ActiveRecord have subtle, but drastically changed behavior in ways that bedevil more sophisticated Rails apps. The change in the implementation of eager loading is another thing which is making the port a bigger issue than we thought it would be.

Oh well, that’s what makes life interesting!

So the new validate option might be a workaround or fix, but it looks like I'm going to have to move to edge rails to get that., and hope for Rails 2.1.1 to be released soon.

Indeed, it might be. Though you have to append ':validate => false' to every 'has_many' in your app.

And be careful : http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/486-make-sure-associated-has_many-habtm-objects-get-saved-even-when-validate-false-is-used

I'm going to investigate this.

Jean-Baptiste

If that patch could get some +1's all should be fine.