3

This seems like a bug in Rails to me, but there's probably not much I can do about that. So how can I accomplish my expected behavior?

Suppose we have:

class User < ActiveRecord::Base
  has_many :awesome_friends, :class_name => "Friend", :conditions => {:awesome => true}
end

And execute the code:

>> my_user.awesome_friends << Friend.new(:name=>'jim')

Afterwards, when I inspect this friend object, I see that the user_id field is populated. But I would also expect to see the "awesome" column set to 'true', which it is not.

Furthermore, if I execute the following from the console:

>> my_user.awesome_friends << Friend.new(:name=>'jim')
>> my_user.awesome_friends
= [#<Friend id:1, name:"jim", awesome:nil>]
# Quit and restart the console
>> my_user.awesome_friends
= []

Any thoughts on this? I suppose the conditions hash could be arbitrarily complex, making integration into the setter impossible. But in a way it feels like by default we are passing the condition ":user_id => self.id", and that gets set, so shouldn't others?

Thanks, Mike

EDIT:

I found that there are callbacks for has_many, so I think I might define the relationship like this:

has_many :awesome_friends,
         :class_name => "Friend",
         :conditions => {:awesome => true},
         :before_add => Proc.new{|p,c| c.awesome = true},
         :before_remove => Proc.new{|p,c| c.awesome = false}

Although, it's starting to feel like maybe I'm just implementing some other, existing design pattern. Maybe I should subclass AwesomeFriend < Friend? Ultimately I need a couple of these has_many relationships, and subclassing get's messy with all the extra files..

EDIT 2:

Okay, thanks to everyone who commented! I ultimately wrapped up the method above into a nice little ActiveRecord extension, 'has_many_of_type'. Which works like follows:

has_many_of_type :awesome_friends, :class_name => "Friend", :type=>:awesome

Which just translates to has_many with the appropriate conditions, before_add, and before_remove params (and it assumes the existence of a column named friend_type).

CambridgeMike
  • 4,562
  • 1
  • 28
  • 37

3 Answers3

3

You need use:

my_user.awesome_friends.create(:name=>'jim') or my_user.awesome_friends.build(:name=>'jim')

In documentation: has_many (:conditions)

Record creations from the association are scoped if a hash is used. has_many :posts, :conditions => {:published => true} will create published posts with @blog.posts.create or @blog.posts.build.

Sector
  • 1,210
  • 12
  • 14
  • Nice! I forgot about this. Although it doesn't solve the problem of adding/removing an existing Friend. Thanks though, I'll probably use this for my implementation. – CambridgeMike Apr 26 '11 at 15:08
  • Yeah. I don't think it's ever going to modify an existing object when you add it to a collection other than to set the foreign key. – Austin Taylor Apr 26 '11 at 15:13
  • Accepted this because it was the best solution to the problem I posed. However, see the edits in the question for the final implementation. – CambridgeMike Apr 26 '11 at 18:51
  • I would create add_to_awesome_friends and remove_from_awesome_friends methods and put into it logic by use of existing "Friend" object. – Sector Apr 27 '11 at 14:27
1

It's :class_name rather than :class, for one thing.

Austin Taylor
  • 5,437
  • 1
  • 23
  • 29
1

This isn't a bug I don't think. The :conditions hash only deterimines how you query for the objects. But I don't think it's rational to just assume that any object you stuff in the collection could be made to conform to the conditions.

In your simple example it makes sense, but you could also put more complex logic in there.

The documentation seems pretty clear on this as well:

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

:conditions

Specify the conditions that the associated object must meet in order to be included as a WHERE SQL fragment, such as authorized = 1.

Community
  • 1
  • 1
Ben Scheirman
  • 40,531
  • 21
  • 102
  • 137
  • I agree the documentation is pretty clear, that was the first thing I checked. But in practice it doesn't really make sense to me. I guess I just need to be careful to set the appropriate fields in my "Friend" object before adding to the collection. – CambridgeMike Apr 26 '11 at 04:42
  • In this case I'd favor creating an add_friend method which does the logic for you. – Ben Scheirman Apr 26 '11 at 13:02