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?