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.
--
Rick DeNatale
My blog on Ruby
http://talklikeaduck.denhaven2.com/
on 2008-06-26 00:04
on 2008-06-26 00:23
Could you reproduce this behaviour in a test? That would facilitate tracking down the problem for all of us. thanks, Jan De Poorter
on 2008-06-26 12:55
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/ticke... http://rails.lighthouseapp.com/projects/8994/ticke... Jean-Baptiste
on 2008-06-26 14:44
On Thu, Jun 26, 2008 at 6:55 AM, Jean-Baptiste Escoyez <jbescoyez@gmail.com> wrote: > > 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/ticke... > > http://rails.lighthouseapp.com/projects/8994/ticke... > > 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! <G> -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/
on 2008-06-26 16:03
> 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-... I'm going to investigate this. Jean-Baptiste