So, I’ve got 3 models:
User -< UserConversation >- Conversation -< Message (where -< is has many, >- is belongs to, ... etc)
I wanted, in this application, to be able to filter to all messages for a given user.
User#messages.find(message_id)
So, naturally, I first reached for this:
class User < ActiveRecord::Base ... has_many :user_conversations has_many :conversations, :through => :user_conversations has_many :messages, :through => :conversations end class UserConversation < ActiveRecord::Base belongs_to :user belongs_to :conversation end class Conversation < ActiveRecord::Base has_many :user_conversations, :dependent => :destroy has_many :messages, :dependent => :destroy end class Message < ActiveRecord::Base belongs_to :conversation end
Which… is completly WRONG and yields results like the following:
>> User.first.messages ActiveRecord::StatementInvalid: SQLite3::SQLException: no such column: conversations.user_id: SELECT "messages".* FROM "messages" INNER JOIN conversations ON messages.conversation_id = conversations.id WHERE (("conversations".user_id = 1))
So, apparently ActiveRecord doesn’t support joins like that. Sure, I could resort to manually specifying the finder_sql in the join and skip the has_many :through business, but then I’d lose the that cool ability to apply additional scopes after this – major FAIL.
So, I came up with a clever way to get around it.
class User < ActiveRecord::Base ... has_many :user_conversations has_many :conversations, :through => :user_conversations def messages Message.for_user(self) end end class Message < ActiveRecord::Base belongs_to :conversation named_scope :for_user, lambda { |user| user = user.id if user.is_a?(User); { :joins => {:conversation => :user_conversations}, :conditions => ["user_conversations.user_id = ?", user]}} end
And… now we can do things like:
user.messages.find(1) user.messages.matching_subject("boogy man")
etc., etc. Hurray for named_scope. So.. there's still a few issues. Namely, it's not REALLY an association - no "create" or "build" methods.
Dear web: what do think about this? Got a better way to implement the above scenario?
4 comments:
I had a similar issue a week ago and found this:
http://github.com/ianwhite/nested_has_many_through
There is an open ticket (6461 in Trac and 1152 in Lighthouse) to support this in Rails.
Jon
Well sir, you truly rock. That is a much better solution than my home-brewed work-around.
Thanks for sharing
Thank you - this was a big help to me! :)
I like the solution, though I would prefer to keep as much code on the User side (in this scenario) as possible. To that effect, and with the inclusion of AREL into Rails 3, you can do this:
class User < ActiveRecord::Base
...
has_many :user_conversations
has_many :conversations, :through => :user_conversations
def messages
Message.where(:user_conversations => conversations)
end
end
This will remove any need to add an extra method on the Messages side of the relationship.
Post a Comment