Implementing a Facebook-style messaging system where the user can be in either of two different columns of another model

I'm trying to set up a messaging system similar to facebook, where you have a list of messages sorted by chain between two users (this is not important for several recipients at the moment, but maybe if I used a more intelligent design it could be easily implemented in the future. I don’t think it would be easy to add to what I have now.) I have something like work, but I can’t implement several functions. I am new to rails / web programming, so all the tips and tricks / solutions are very appreciated.

So, I have three suitable models: User, Conversation and Messages. The user has a lot of talk. There are a lot of messages in the conversation and belongs to the Users, and Message belongs to the conversation. So, the models look like this:

User: has corresponding fields ID: int, username: string

class User < ActiveRecord::Base has_many :conversations, :class_name => "Conversation", :finder_sql => proc { "SELECT * FROM conversations " + "WHERE conversations.user1_id = #{id} OR conversations.user2_id = #{id} " + "ORDER BY conversations.updated_at DESC" } 

Conversation: has corresponding fields: ID: int, user1_id: int, user2_id: int, user1_deleted: boolean, user2_deleted: boolean, created_at: datetime, updated_at: datetime

 class Conversation < ActiveRecord::Base has_many :messages belongs_to :participant_one, :class_name => "User", :foreign_key => :user1_id belongs_to :participant_two, :class_name => "User", :foreign_key => :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)))) 

Message: has corresponding fields: id: int, convers_id: int, author_id: int, content: text, created_at: datetime, updated_at: datetime

 class Message < ActiveRecord::Base belongs_to :conversation, :touch => true 

I'm not sure if I need participants_participant_participant_participant, and I use

  def conversation_partner(conversation) conversation.participant_one == current_user ? conversation.participant_two : conversation.participant_one end 

in the dialog box so that in the views I can show another participant.

So this basically works. But one of the complications that I have is that I do not distinguish users very well in the conversation, the user can be in the user1 field or in the user2 field. Therefore, I need to constantly search for a user in one or another field, for example. in finder_sql has_many user’s declarations. In addition, when I create a new message, I first search to see if there is a conversation parameter, or if it is not, see if there is a dialogue between the two users, and if not, create a new session. (You can either send a message from the conversation index (for example, the answer), or current_user can view another user and click the “Send this message to the user” link. This mechanism looks like this and uses it. Between the method in the “Conversation” model:

 class MessagesController < ApplicationController before_filter :get_user before_filter :find_or_create_conversation, :only => [: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 => "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 => current_user.id, :user2_id => @user.id) end end 

(my routes look like this :)

  resources :users do resources :conversations, :only => [:index, :create, :show, :destroy] do resources :messages, :only => [:new, :create] end resources :messages, :only => [:new] end 

So, I am having problems setting the user1_deleted or user2_deleted flags. (and similarly, if / when I implement the read / update flag). The problem is that since the same user can have a lot of conversations, but he can be user1 or user2, it becomes difficult to find him. I thought I could do something like this in the Conversation model:

 def self.active(user) Conversation.where(which_user?(user) + "_deleted = ?", false) end def self.which_user?(user) :user1_id == user ? 'user1' : 'user2' end 

But then you cannot start it with the whole conversation if you do not iterate through each of the user's conversations one by one, because sometimes he is user1, and sometimes he is user2. Should I rip off this whole approach and try a new design? If so, can anyone find a possible approach that will be more elegant / work better / actually work and still meet the same needs?

This is a rather long question, so I appreciate anyone who wants to get through all this with me. Thanks.

+6
source share
1 answer

kindofgreat,

This question intrigued me a little, so I spent several hours experimenting, and here are my conclusions. The result is an application in which any number of users can participate in a conversation.

I went with a data model that has an intermediate model between User and Conversation called UserConveration ; This is a consolidation model and contains data on the state of the user and the conversation together (namely, whether the message is read, deleted, etc.).

The implementation is on GitHub, and you can see the difference of the code I wrote (compared to the code that was automatically generated to save the whole breakthrough) at https: //github.com/BinaryMuse/so_association_expirement/compare/53f2263 ... master .

Here are my models, divided only into associations:

 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 has_many :messages, :through => :conversation delegate :subject, :to => :conversation delegate :users, :to => :conversation end class Conversation < ActiveRecord::Base has_many :user_conversations has_many :users, :through => :user_conversations has_many :messages end class Message < ActiveRecord::Base belongs_to :user belongs_to :conversation end 

And this is what the database looks like:

 create_table "conversations", :force => true do |t| t.string "subject" t.datetime "created_at" t.datetime "updated_at" end create_table "messages", :force => true do |t| t.integer "user_id" t.integer "conversation_id" t.text "body" t.datetime "created_at" t.datetime "updated_at" end create_table "user_conversations", :force => true do |t| t.integer "user_id" t.integer "conversation_id" t.boolean "deleted" t.boolean "read" t.datetime "created_at" t.datetime "updated_at" end create_table "users", :force => true do |t| t.string "name" t.datetime "created_at" t.datetime "updated_at" end 

The basic idea is to introduce a “conversation” to the user, when in fact behind the scenes we are managing UserConversation for all the users involved in the conversation. See especially the create_user_conversations method in the UserConversation model, which is responsible for creating an entry in the connection table for each user associated with the conversation.

There are also many has_many :through and delegates calls in models to make it so painless as to get the data we need ... for example. instead of @user_conversation.conversation.subject you can use @user_conversation.subject ; the same applies to the messages attribute.

I understand this is quite a bit of code, so I urge you to get the source and play with it. All this works, except for “deleting” conversations (I didn’t worry about this, but noting messages when reading / unread really works). Keep in mind that you must be “signed up” as a user to perform certain operations, for example. creating a new conversation, etc. You can log in by clicking on a user from the main page and selecting "Log in as this user."

Another thing to keep in mind is that the URLs say “talk” so that everything is good for the user, even though the controller used is the “UserConversations” controller - check the routes file.

If you have more or more detailed questions, feel free to contact me through GitHub or through the contact information in my StackOverflow profile.

+13
source

Source: https://habr.com/ru/post/901963/


All Articles