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 : #486 Make sure associated has_many/habtm objects get saved even when :validate => false is used. - Ruby on Rails - rails

I'm going to investigate this.

Jean-Baptiste

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