7

I plan to implement a private message system between members. I'm wondering what are the preferred approaches to this.

Requirements are

  1. I should be able to retrieve them easily as something like this

    @user.conversations               #Should return User objects that I sent or received messages from (but not me)
    @user.conversations.messages      #Messages from all or specific user objects.
    @user.conversations.messages.unread      #Unread messages
    
  2. When calling @user.conversations should retrieve only the people that sent me messages or people I send messages to. current_user should be excluded.

  3. If i'm sender_id=5 and send to_id=10 then, the other person would reply as sender=10 to_id=5. This should be considered and understood as the same conversation object.


Regarding last point. I'm not sure what's the preferred approach to modeling.

It's preferred to use one Conversation model to handle all messages such as

    attr_accessible :user_id, :friend_id, :message, :read
    belongs_to :user

Or it's preferred to create a Conversation model to handle association and a Message model for messages.

I would like to see sample cases of how to implement this relationship and if there's additional method to implement.

I'm a bit lost here.

Martin
  • 11,216
  • 23
  • 83
  • 140

4 Answers4

13

A much simpler model is to capture each Message:

class Message < ActiveRecord::Base
  belongs_to :from_user,  :class_name => 'User' # from_user_id field fk Users
  belongs_to :to_user,    :class_name => 'User' # to_user_id field fk Users
  belongs_to :thread, :class_name => 'Message'  # Reference to parent message
  has_many :replies,  :class_name => 'Message', :foreign_key => 'thread_id'

  named_scope :in_reply_to, lambda { |message| :conditions => {:thread => message}, :order => 'created_at' }
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

If you need to capture the message threads, any Message that's a reply can store a reference to the initial message starting the Conversation (aka Thread). For example:

first_msg   = Message.new(:to_user => bob, :from_user => sally, :body => 'Hello!')
sally_reply = first_msg.replies.build(:to_user => bob, :from_user => sally, :body => 'hi back')
bob_reply   = first_msg.replies.build(:to_user => sally, :from_user => bob, :body => 'later')
Winfield
  • 18,985
  • 3
  • 52
  • 65
  • @Winfield How can I get the root document using the in_reply_to ? I want to have something like twitter DM – Seif Sallam Apr 16 '13 at 19:34
  • Root messages will have a null in_reply_to value. – Winfield Apr 16 '13 at 21:09
  • @Winfield Thanks, I solved it by adding before_create filter, to add thread_id as message_id incase there is was no thread_id provided. This returns all documents including the main one. – Seif Sallam Apr 17 '13 at 17:34
4

acts_as_messageable is a bit long in the tooth, but should give you some ideas.

zetetic
  • 47,184
  • 10
  • 111
  • 119
  • I would prefer this, as many act_as_* plugins/gems out there. And according to requirement which @Martin mentioned above it fits his needs. – Milan Jaric Mar 02 '11 at 11:51
2

If you are using this DB model:

User id:integer ....

Message id:integer body:string

UserMessages id:integre sender_id:integer to_id:integer message_id

I guess you will need to provide :finder_sql in :has_many, something like this

class User < ActiveRecord::Base
   has_many :conversations, :class_name => "UserMessage", :finder_sql =>
      'SELECT * ' +
      'FROM user_messages' +
      'WHERE sender_id = #{id} OR to_id = #{id} ' 

end
Milan Jaric
  • 5,556
  • 2
  • 26
  • 34
  • Also you can use .column_names["attr_accessible"].name for db column name you want and .table_name to avoid naming issues – Milan Jaric Feb 27 '11 at 01:29
1

Were you, I would have went with IMAP instead of using the DB for such modeling.

Check this for more details.

khelll
  • 23,590
  • 15
  • 91
  • 109