5

I have the following two models:

class Message < ActiveRecord::Base
  belongs_to :to_user, :class_name => 'User'
  belongs_to :from_user, :class_name => 'User'

  has_ancestry #Using the 'ancestry' gem
end

class User < ActiveRecord::Base
  has_many :messages_received, :class_name => 'Message', :foreign_key => 'to_user_id'
  has_many :messages_sent, :class_name => 'Message', :foreign_key => 'from_user_id'
end

Each user is allowed to have one conversation with another user and all the replies should be threaded from the original message.

In my 'index' controller action how do I query both sent messages and received messages? For example, if User1 hits '/users/2/messages/' they should see the whole conversation between user1 and user2 (regardless of who sent the first message). Do I need to add a 'Thread' model or is there a way to accomplish this with my current structure?

Thanks.

user1032752
  • 751
  • 1
  • 11
  • 28

1 Answers1

16

You may be better off restructuring this as a conversation to which you can join people than a series of interconnected messages in a chain. For instance:

class Conversation < ActiveRecord::Base
  has_many :messages
  has_many :participants
  has_many :users, :through => :participants
end

class Message < ActiveRecord::Base
  belongs_to :conversation
end

class Participant < ActiveRecord::Base
  belongs_to :conversation
  belongs_to :user
end

class User < ActiveRecord::Base
  has_many :conversations
  has_many :participants
end

When a message is sent to someone, create a conversation for it and invite the appropriate parties by adding them to the users list.

Threaded messaging could be added here by building a parent relationship into the Message itself or using ancestry, though in practice this tends to be over-kill as simple chronological ordering of replies is usually sufficient for most people.

To track read/unread status you will need an association table between user and messages directly, and this can be tricky, so avoid it unless you need it.

Keep in mind that some names are reserved by either Ruby or Rails, and Thread is one of them, so you can't have a model with that name.

tadman
  • 208,517
  • 23
  • 234
  • 262
  • 1
    Was looking for a way to make simple conversations between 2 users when I found this. Looks like it'll work. Thanks! But shouldn't it be `has_many :conversations, :through => :participants` in the User model? – Vickash Dec 23 '11 at 05:07
  • 1
    You're right about the missing `:through` in the User model. It should also have `has_many :participants` as well. – tadman Dec 24 '11 at 03:33
  • I've just used this model in my app but I'm really struggling to get a controller written for creating and replying to messages. Is there any chance of a simple example? I can start it as a new question if that's better. – Dave Mar 05 '12 at 19:51
  • Post what you have as a new question and I'm sure you can get a quick answer. It's hard to say where you might be going wrong. – tadman Mar 05 '12 at 22:09
  • Hi tadman, I think this is exactly what I was looking for, but I have a question here. I am trying to create private messaging that is threaded by subject much like how emails are designed, I guess. In order to do that, I was looking at what you have above, and I thought that having conversation_title under Conversation table and treating it like a subject would enable threaded messaging. But, don't you think it's necessary to have User has_many Messages as well? – railslearner Apr 08 '12 at 19:53
  • For a threaded messaging system, add a new layer called MessageThread (as Thread is a reserved class), and associate all messages with that. A user then has many message_threads, not messages. Replies are always grouped to the same thread. The Conversation model in this case is probably very close in concept, though. I would choose one or the other, but not both. – tadman Apr 09 '12 at 15:20
  • Thanks a lot! I didnt realize that you left a message here. If you can, I'd appreciate it if you look at this: http://stackoverflow.com/questions/10220467/rails-forms-for-private-messaging – railslearner Apr 19 '12 at 17:20
  • Hey, a year later, I have a question. Wouldn't you still want to have each `Message` `belongs_to :user`? Otherwise when you display, how would you know which `User` sent which `Message` in a `Conversation`? – wuliwong Jun 01 '13 at 16:06
  • That could be useful information if you're interested in it, and would easily be added as you describe. Then `User has_many :messages` as well. – tadman Jun 01 '13 at 16:33