Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: Why can't you set an association to nil in a where clause?

I have photos that belong to collections and users. Photos always belong to a user, but may not be assigned to a collection.

In my controller, this works perfectly:

@collection_photos = Photo.where( :collection => @collection, :user => current_user )

However, this fails...

@other_photos = Photo.where( :collection => nil, :user => current_user )

...but this works:

@other_photos = Photo.where( :collection_id => nil, :user => current_user )

When collection is set to nil I get this error message: No attribute named 'collection' exists for table photos.

If I pass an object, it knows to search for collection_id from the symbol :collection, but if I don't pass an object it doesn't seem to be aware of the association.

Am I understanding this correctly? Could anyone explain a little better why :collection=>nil doesn't work?

like image 413
Andrew Avatar asked Mar 05 '11 21:03

Andrew


3 Answers

when you use pass in the conditions into ActiveRecord, it actually tries to analyze the objects that you passed in, is it a string? an array? a hash? and what's in the string, array or hash?

and in your case, a hash, so it's trying to analyze what's in the hash, in the first statement (which works), you passed in a model instance as the value, so it tries to find if there are any associations that mapped to the key your specified and voila, it found it and everything works as planned

in the second case, you passed in nil as the value, now, ActiveRecord sees that it's a nil object, so it decided that it's not an association. note that it doesn't look at the key, but it only looked at the value, thus it tries to find if there's any column that mapped to the key, but it couldn't find, returning an error

in the last case, you passed in nil as the value, same thing, it tried to find a column which mapped to :collection_id, thus it passed in nil as the value in the SQL statement, and it returned successfully

so it's just an unfortunate considerations taken by ActiveRecord that makes the second case not working =)

hope this clarifies! =D

like image 124
Staelen Avatar answered Oct 13 '22 01:10

Staelen


My guess is that it's like the famous rails .find vs .find_by_id.

.find is designed to throw an exception if it cannot find any association.

where as .find_by_id will just return nil if doesn't find any association.

so in your .where statement, when you search for the collection it's probably treating that like a .find and when you search by collection_id it will return nil just like .find_by_id does if it can't find any associated collection.

I'm not sure how these two methods differ in Activerecord's inner workings, but they are designed to react differently to nil results.

like image 33
David Barlow Avatar answered Oct 13 '22 00:10

David Barlow


I think your answer is in ActiveRecord::PredicateBuilder.build_from_hash. There is a case statement in there that checks the class of each value in the hash, and it specifically looks for ActiveRecord::Relation

like image 28
Jordan Avatar answered Oct 13 '22 01:10

Jordan