Why does Rails not reset the association when the id is changed?

64 views
Skip to first unread message

Paul

unread,
Feb 4, 2011, 3:30:44 PM2/4/11
to Ruby on Rails: Core
Rails is great and most things just work easily. However, I've never
been able to get a definite answer on whether one should do,

validates :parent, :presence => true

or,

validates :parent_id, :presence => true

given,

class Parent
end

class Child
belongs_to :parent
end

I've always thought that validating the :parent (and not the foreign
key) is the *more* correct thing to do ... but I don't understand why
Rails does not reset the parent association when the parent_id is
changed as demonstrated here,

child = Child.find(..)
child.parent_id = nil
puts child.valid? # outputs false

child = Child.find(..)
child.parent
child.parent_id = nil
puts child.valid? # outputs true!

Any thoughts?

Michael Pavling

unread,
Feb 4, 2011, 3:36:35 PM2/4/11
to rubyonra...@googlegroups.com
On 4 February 2011 20:30, Paul <pkm...@gmail.com> wrote:
> I've always thought that validating the :parent (and not the foreign
> key) is the *more* correct thing to do ... but I don't understand why
> Rails does not reset the parent association when the parent_id is
> changed as demonstrated here,
>
> Any thoughts?

yep... don't update foreign ids - update associated objects:

> child = Child.find(..)
> child.parent = nil

one less step ;-)

> I've never
> been able to get a definite answer on whether one should do,
>
> validates :parent, :presence => true

yep - always validate the object - no sense validating a foreign key
field, when the foreign key might not link to a row in the association
table.

That's my preference anyhoo...

Michael Pavling

unread,
Feb 4, 2011, 3:37:49 PM2/4/11
to rubyonra...@googlegroups.com
On 4 February 2011 20:36, Michael Pavling <pav...@gmail.com> wrote:
> That's my preference anyhoo...


oops! This is rails-core list... take it over to "talk"

apologies...

Ernie Miller

unread,
Feb 4, 2011, 3:39:22 PM2/4/11
to rubyonra...@googlegroups.com

This is due to the way that association proxies lazy load their targets. In the first case, if you only loaded the child record, and modified the parent_id attribute, then you never load the parent object, because you never accessed the association proxy.

In the latter case, you did access the association proxy, so the parent got loaded, but then you modified the parent_id, directly. I'd recommend that you be consistent -- if you're checking if the associated object exists, set the association to nil, instead of the id, and you shouldn't have a problem.

Jon Leighton

unread,
Feb 4, 2011, 5:43:03 PM2/4/11
to rubyonra...@googlegroups.com
Hey,

In edge rails there is a mechanism for checking whether the loaded
association target is "stale" - so if you do record.foo_id = x, then
record.foo will load the target afresh.

I'm not sure whether it necessarily works with validation like this, but
hopefully it does. [I haven't tried.]

Just to emphasise, this is new in edge - it is not in the 3-0-stable
branch.

Jon

--
http://jonathanleighton.com/

signature.asc

Paul

unread,
Feb 4, 2011, 7:13:04 PM2/4/11
to Ruby on Rails: Core
Thanks for confirming validate :parent is the preferred way.

> yep... don't update foreign ids - update associated objects:
>
> > child = Child.find(..)
> > child.parent = nil
>
> one less step ;-)

Sure ... sometimes the parent_id though is passed via form params.
Which works in 99% cases, however, I had one or two cases where
something loaded the parent before setting parent_id and
validating :).



Paul

unread,
Feb 4, 2011, 7:14:08 PM2/4/11
to Ruby on Rails: Core
This seems helpful. I'll take a look at edge.
>  signature.asc
> < 1KViewDownload

Michael Koziarski

unread,
Feb 5, 2011, 11:53:14 PM2/5/11
to rubyonra...@googlegroups.com
On 5/02/2011, at 9:30 AM, Paul <pkm...@gmail.com> wrote:

> Rails is great and most things just work easily. However, I've never
> been able to get a definite answer on whether one should do,
>
> validates :parent, :presence => true
>
> or,
>
> validates :parent_id, :presence => true
>

Validate the object, not the foreign key. Otherwise a record with
parent_id of -99, 0 or some other nonsense will still pass.

> given,
>
> class Parent
> end
>
> class Child
> belongs_to :parent
> end
>
> I've always thought that validating the :parent (and not the foreign
> key) is the *more* correct thing to do ... but I don't understand why
> Rails does not reset the parent association when the parent_id is
> changed as demonstrated here,

As Jon mentioned there is some code for this kind of thing in master.
As for the historical reason it did that, it's trickier than it looks
:)

There can be multiple (or zero) associations for a given _id column,
and a patch we tried a while back didn't handle all those potential
cases. There's no deep philosophical reason it works that way, it's
just historical / evolutionary artifacts sneaking up on you.


>
> child = Child.find(.


> child.parent_id = nil
> puts child.valid? # outputs false
>
> child = Child.find(..)
> child.parent
> child.parent_id = nil
> puts child.valid? # outputs true!
>
> Any thoughts?
>

> --
> You received this message because you are subscribed to the Google Groups "Ruby on Rails: Core" group.
> To post to this group, send email to rubyonra...@googlegroups.com.
> To unsubscribe from this group, send email to rubyonrails-co...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/rubyonrails-core?hl=en.
>

Rainer Frey

unread,
Feb 6, 2011, 8:10:35 AM2/6/11
to rubyonra...@googlegroups.com
On Fri, Feb 4, 2011 at 9:36 PM, Michael Pavling <pav...@gmail.com> wrote:
> On 4 February 2011 20:30, Paul <pkm...@gmail.com> wrote:
>> I've always thought that validating the :parent (and not the foreign
>> key) is the *more* correct thing to do
>> I've never
>> been able to get a definite answer on whether one should do,
>>
>> validates :parent, :presence => true
>
> yep - always validate the object - no sense validating a foreign key
> field, when the foreign key might not link to a row in the association
> table.

Unfortunately the Rails Guide on Active Record Validation and
Callbacks says (Section 3.9):
"If you want to be sure that an association is present, you’ll need to
test whether the foreign key used to map the association is present,
and not the associated object itself."

Maybe the guide needs to be updated.

Rainer

Xavier Noria

unread,
Feb 7, 2011, 5:58:46 AM2/7/11
to rubyonra...@googlegroups.com
On Sun, Feb 6, 2011 at 2:10 PM, Rainer Frey <frey....@gmail.com> wrote:

> Unfortunately the Rails Guide on Active Record Validation and
> Callbacks says (Section 3.9):
> "If you want to be sure that an association is present, you’ll need to
> test whether the foreign key used to map the association is present,
> and not the associated object itself."
>
> Maybe the guide needs to be updated.

Yeah, I think the wording is unfortunate. Certainly you can't be sure
the association is present by checking that the FK attribute is
present.

Rather, this topic deserves a warning in my view. Something in the
line that if you check whether the FK attribute is present then you
*don't know* whether it is valid. You can decide to take the risk,
that's up to you, but the reader should be warned.

A pointer to the validates_existence plugin would be nice. Also to FK
constraints as the most robust solution, though they are kinda weird
to explain in a generic way nowadays because then #save,
#update_attributes and friends can throw exceptions for ordinary
validation errors, and that doesn't fit well with standard idioms.
This would deserve its own section in the guide with all practicals
details and gotchas.

If you'd like to have a stab at any of these revisions please give it
a go through docrails.

Rainer Frey

unread,
Feb 7, 2011, 8:28:56 AM2/7/11
to rubyonra...@googlegroups.com
On Mon, Feb 7, 2011 at 11:58 AM, Xavier Noria <f...@hashref.com> wrote:
> On Sun, Feb 6, 2011 at 2:10 PM, Rainer Frey <frey....@gmail.com> wrote:
>
>> Unfortunately the Rails Guide on Active Record Validation and
>> Callbacks says (Section 3.9):
>> "If you want to be sure that an association is present, you’ll need to
>> test whether the foreign key used to map the association is present,
>> and not the associated object itself."
>>
>> Maybe the guide needs to be updated.
>
> Yeah, I think the wording is unfortunate. Certainly you can't be sure
> the association is present by checking that the FK attribute is
> present.
>
> Rather, this topic deserves a warning in my view. Something in the
> line that if you check whether the FK attribute is present then you
> *don't know* whether it is valid. You can decide to take the risk,
> that's up to you, but the reader should be warned.

But this thread seems to suggest one should simply validate the
association attribute instead. Is that not sufficient then?

> If you'd like to have a stab at any of these revisions please give it
> a go through docrails.

Sorry, my understanding is too limited.

Rainer

Jon Leighton

unread,
Feb 7, 2011, 9:13:40 AM2/7/11
to rubyonra...@googlegroups.com
On Mon, 2011-02-07 at 14:28 +0100, Rainer Frey wrote:
> On Mon, Feb 7, 2011 at 11:58 AM, Xavier Noria <f...@hashref.com> wrote:
> > On Sun, Feb 6, 2011 at 2:10 PM, Rainer Frey <frey....@gmail.com> wrote:
> >
> >> Unfortunately the Rails Guide on Active Record Validation and
> >> Callbacks says (Section 3.9):
> >> "If you want to be sure that an association is present, you’ll need to
> >> test whether the foreign key used to map the association is present,
> >> and not the associated object itself."
> >>
> >> Maybe the guide needs to be updated.
> >
> > Yeah, I think the wording is unfortunate. Certainly you can't be sure
> > the association is present by checking that the FK attribute is
> > present.
> >
> > Rather, this topic deserves a warning in my view. Something in the
> > line that if you check whether the FK attribute is present then you
> > *don't know* whether it is valid. You can decide to take the risk,
> > that's up to you, but the reader should be warned.
>
> But this thread seems to suggest one should simply validate the
> association attribute instead. Is that not sufficient then?

I'd say validating the association attribute would be the best practice
in 3.1, but it may result in an extra query to the database to fetch the
associated record, if it's not loaded or if it's stale.

If users wish to avoid that overhead, they can check the FK, but should
be aware that this does not guarantee the associated record actually
exists.

--
http://jonathanleighton.com/

signature.asc

Xavier Noria

unread,
Feb 7, 2011, 9:22:00 AM2/7/11
to rubyonra...@googlegroups.com
On Mon, Feb 7, 2011 at 2:28 PM, Rainer Frey <frey....@gmail.com> wrote:

> But this thread seems to suggest one should simply validate the
> association attribute instead. Is that not sufficient then?

You can't still be sure the association is valid, because the
associated object is cached if previously fetched, and the FK can be
changed directly:

fxn@halmos:~/tmp/test_belongs_to ∵ cat app/models/post.rb
class Post < ActiveRecord::Base
has_many :comments
end

fxn@halmos:~/tmp/test_belongs_to ∵ cat app/models/comment.rb
class Comment < ActiveRecord::Base
belongs_to :post

validates :post, :presence => true
end

fxn@halmos:~/tmp/test_belongs_to ∵ cat bypass_validation.rb
post = Post.create
comment = post.comments.create

comment.post_id = -1
p comment.save

comment.reload
p comment.post_id

fxn@halmos:~/tmp/test_belongs_to ∵ rails runner bypass_validation.rb
true
-1

You're going to store the post_id in the database anyway. So if you're
going to take the risk of having dangling records, in my view it's
better to take the risk on the post_id rather than on the association.
I believe that's what the quote from the guide tries to say.

The validates_existence plugin performs a query. That's closer to
checking the association holds an existing record, but there's still
subject to race conditions (say, a concurrent request deleting the
associated record outside your transaction). The only way to be
totally sure the association does exist is to move the check to who
has the key to guarantee that, which is the database with FK
constraints.

Jon Leighton

unread,
Feb 7, 2011, 1:44:04 PM2/7/11
to rubyonra...@googlegroups.com
Hey,

On Mon, 2011-02-07 at 15:22 +0100, Xavier Noria wrote:
> On Mon, Feb 7, 2011 at 2:28 PM, Rainer Frey <frey....@gmail.com> wrote:
>
> > But this thread seems to suggest one should simply validate the
> > association attribute instead. Is that not sufficient then?
>
> You can't still be sure the association is valid, because the
> associated object is cached if previously fetched, and the FK can be
> changed directly:

This does work 'properly' on edge, due to the stale-checking mechanism.
I just tried it. Voila:

$ rails c
Loading development environment (Rails 3.1.0.beta)
ruby-1.9.2-p136 :001 > post = Post.create
=> #<Post id: 1>
ruby-1.9.2-p136 :002 > comment = post.comments.create
=> #<Comment id: 1, post_id: 1>
ruby-1.9.2-p136 :003 > comment.post_id = -1
=> -1
ruby-1.9.2-p136 :004 > comment.save
=> false
ruby-1.9.2-p136 :005 > comment.errors
=> {:post=>["can't be blank"]}
ruby-1.9.2-p136 :006 > comment.post
=> nil

Jon

--
http://jonathanleighton.com/

signature.asc
Reply all
Reply to author
Forward
0 new messages