Note that there are some explanatory texts on larger screens.

plurals
  1. POImplementing a facebook-style messaging system, where a user can be in either of two different columns of another model
    text
    copied!<p>I am trying to set up a messaging system similar to facebook where you have a list of messages sorted by conversations between two users (it is not important for multiple recipients at the moment, but maybe if I'd used a smarter design then it can be easily implemented in the future. I don't think it would be easy to add to what I currently have.) I have something kind of working, but I am unable to implement a few features. I am pretty new to rails / web programming, so any and all tips/hints/solutions is much appreciated.</p> <p>So I have three relevant models: User, Conversation, and Messages. User has many conversations, Conversation has many messages and belongs to Users, and Message belongs to Conversation. Okay, so this is what the models look like:</p> <p>User: has relevant fields ID:int, username:string</p> <pre><code>class User &lt; ActiveRecord::Base has_many :conversations, :class_name =&gt; "Conversation", :finder_sql =&gt; proc { "SELECT * FROM conversations " + "WHERE conversations.user1_id = #{id} OR conversations.user2_id = #{id} " + "ORDER BY conversations.updated_at DESC" } </code></pre> <p>Conversation: has relevant fields: ID:int, user1_id:int, user2_id:int, user1_deleted:boolean, user2_deleted:boolean, created_at:datetime, updated_at:datetime</p> <pre><code>class Conversation &lt; ActiveRecord::Base has_many :messages belongs_to :participant_one, :class_name =&gt; "User", :foreign_key =&gt; :user1_id belongs_to :participant_two, :class_name =&gt; "User", :foreign_key =&gt; :user2_id private def self.between(user1, user2) c = Conversation.arel_table Conversation.where(c[:user1_id].eq(user1).and(c[:user2_id].eq(user2)).or(c[:user1_id].eq(user2).and(c[:user2_id].eq(user1)))) </code></pre> <p>Message: has relevant fields: id:int, conversation_id:int, author_id:int, content:text, created_at:datetime, updated_at:datetime</p> <pre><code>class Message &lt; ActiveRecord::Base belongs_to :conversation, :touch =&gt; true </code></pre> <p>I'm not really sure if I need participant_one and participant_two, but I use </p> <pre><code> def conversation_partner(conversation) conversation.participant_one == current_user ? conversation.participant_two : conversation.participant_one end </code></pre> <p>in a ConversationHelper so that in views, I can show the other participant. </p> <p>So this basically works. But one of the complications I have is that I do not really distinguish the users very well in the Conversation, a user can be in either the user1 field or the user2 field. So I need to constantly look for the user in one or the other field, e.g. in the finder_sql of the User has_many declaration. Also, when I create a new message, I first search to see if there's a Conversation parameter, or if there isn't one, see if there's a conversation between the two users, and if not, then create a new conversation. (You can either send a message from the conversation index (like a reply), or the current_user can be viewing the another user and click on the "send this user a message" link. The messagecontroller looks like this, and uses that self.between method in the Conversation model:</p> <pre><code>class MessagesController &lt; ApplicationController before_filter :get_user before_filter :find_or_create_conversation, :only =&gt; [:new, :create] def new @message = Message.new end def create @message = @conversation.messages.build(params[:message]) @message.author_id = current_user.id if @message.save redirect_to user_conversation_path(current_user, @conversation), :notice =&gt; "Message sent!" else redirect_to @conversation end end private def get_user @user = User.find(params[:user_id]) end def find_or_create_conversation if params[:conversation_id] @conversation = Conversation.find(params[:conversation_id]) else @conversation = Conversation.between(@user.id, current_user.id).first or @conversation = Conversation.create!(:user1_id =&gt; current_user.id, :user2_id =&gt; @user.id) end end </code></pre> <p>(my routes look like this:)</p> <pre><code> resources :users do resources :conversations, :only =&gt; [:index, :create, :show, :destroy] do resources :messages, :only =&gt; [:new, :create] end resources :messages, :only =&gt; [:new] end </code></pre> <p>So now, I am having problems trying to set the user1_deleted or user2_deleted flags. (and similarly if/when i implement a read/up-to-date flag). The problem is that because the same User can have many conversations, but he can either be the user1 or the user2, it becomes difficult to find him. I was thinking I can do something like this in the Conversation model:</p> <pre><code>def self.active(user) Conversation.where(which_user?(user) + "_deleted = ?", false) end def self.which_user?(user) :user1_id == user ? 'user1' : 'user2' end </code></pre> <p>But then you can't run it an entire conversation unless you iterate through each of the User's conversation one by one, because sometimes he is user1, and sometimes he is user2. Should I ditch this whole approach and try a new design? If so, does anyone a possible approach that would be more elegant/perform better/actually work and still meet the same needs?</p> <p>This is a pretty long question, so I appreciate anyone willing to wade through all this with me. Thanks.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload