[英]Implementing a facebook-style messaging system, where a user can be in either of two different columns of another model
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. 我正在尝试建立一个类似于facebook的消息系统,你有一个按两个用户之间的对话排序的消息列表(这对于目前的多个收件人来说并不重要,但也许如果我使用更智能的设计那么它可以将来很容易实现。我认为添加到我现在的东西并不容易。)我有一些工作,但我无法实现一些功能。 I am pretty new to rails / web programming, so any and all tips/hints/solutions is much appreciated.
我对rails / web编程很新,因此非常感谢任何和所有提示/提示/解决方案。
So I have three relevant models: User, Conversation, and Messages. 所以我有三个相关的模型:User,Conversation和Messages。 User has many conversations, Conversation has many messages and belongs to Users, and Message belongs to Conversation.
用户有很多会话,Conversation有很多消息,属于Users,Message属于Conversation。 Okay, so this is what the models look like:
好的,这就是模型的样子:
User: has relevant fields ID:int, username:string 用户:具有相关字段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 relevant fields: ID:int, user1_id:int, user2_id:int, user1_deleted:boolean, user2_deleted:boolean, created_at:datetime, updated_at:datetime 会话:有相关字段: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 relevant fields: id:int, conversation_id:int, author_id:int, content:text, created_at:datetime, updated_at:datetime 消息:具有相关字段:id:int,conversation_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 really sure if I need participant_one and participant_two, but I use 我不确定我是否需要participant_one和participant_two,但我使用了
def conversation_partner(conversation)
conversation.participant_one == current_user ? conversation.participant_two : conversation.participant_one
end
in a ConversationHelper so that in views, I can show the other participant. 在ConversationHelper中,以便在视图中,我可以显示其他参与者。
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.
但我遇到的一个复杂问题是我在Conversation中并没有真正区分用户,用户可以在user1字段或user2字段中。 So I need to constantly look for the user in one or the other field, eg in the finder_sql of the User has_many declaration.
所以我需要不断地在一个或另一个字段中查找用户,例如在User has_many声明的finder_sql中。 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.
此外,当我创建新消息时,我首先搜索是否存在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:
(您可以从会话索引发送消息(如回复),或者current_user可以查看另一个用户并单击“向该用户发送消息”链接.messagecontroller看起来像这样,并使用该自我。在Conversation模型中的方法之间:
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 now, I am having problems trying to set the user1_deleted or user2_deleted flags. 所以现在,我在尝试设置user1_deleted或user2_deleted标志时遇到问题。 (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.
问题是因为同一个用户可以有很多对话,但他可以是user1或user2,很难找到他。 I was thinking I can do something like this in the Conversation model:
我以为我可以在Conversation模型中做这样的事情:
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 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. 但是,除非你逐个遍历用户的每个对话,否则你无法运行整个对话,因为有时他是user1,有时候他是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?
如果是这样,有没有人可能更优雅/更好/实际工作并仍然满足相同的需求?
This is a pretty long question, so I appreciate anyone willing to wade through all this with me. 这是一个很长的问题,所以我很欣赏任何愿意和我一起讨论这一切的人。 Thanks.
谢谢。
kindofgreat, kindofgreat,
This question intrigued me a bit, so I spent a couple hours experimenting, and here are my findings. 这个问题引起了我的兴趣,所以我花了几个小时进行实验,这是我的发现。 The result is an app where 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
; 我选择了一个数据模型,它在
User
和Conversation
之间有一个名为UserConveration
的中间模型; it's a join model and holds data about the state of a user and a conversation together (namely, whether the conversation is read, deleted, etc.) 它是一个连接模型,一起保存有关用户状态和对话的数据(即,是否读取,删除对话等)
The implementation is on GitHub, and you can see a diff of the code I wrote (versus the code that was automatically generated, to keep all the cruft out) at https://github.com/BinaryMuse/so_association_expirement/compare/53f2263...master . 实现在GitHub上,您可以在https://github.com/BinaryMuse/so_association_expirement/compare/53f2263上看到我编写的代码的差异(与自动生成的代码相比,以保持所有可能性) 。 ..master 。
Here are my models, stripped down to only the 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 here's 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 present the user with a "conversation," when in reality behind the scenes we're managing UserConversation
s for all the users involved in a conversation. 基本思想是向用户呈现“对话”,而实际幕后我们正在为对话中涉及的所有用户管理
UserConversation
。 See especially the method create_user_conversations
on the UserConversation
model, which is responsible for creating an entry in the join table for every user associated with a conversation. 尤其请参阅
UserConversation
模型上的方法create_user_conversations
,该模型负责为与会话关联的每个用户在连接表中创建条目。
There are also lots of has_many :through
and delegates
calls in the models to make it as painless as possible to get the data we want... eg instead of @user_conversation.conversation.subject
you can use @user_conversation.subject
; 在模型中还有很多
has_many :through
和delegates
调用,以尽可能轻松地获取我们想要的数据...例如,不是@user_conversation.conversation.subject
你可以使用@user_conversation.subject
; the same goes for the messages
attribute. 对于
messages
属性也是如此。
I realize it's quite a bit of code, so I encourage you to get the source and play around with it. 我意识到这是相当多的代码,所以我鼓励你获取源代码并使用它。 It all works, other than "deleting" conversations (I didn't bother with this, but marking messages as read/unread does work).
这一切都有效,除了“删除”对话(我没有打扰这个,但将消息标记为已读/未读确实有效)。 Be aware that you have to be "signed in" as a user to perform some operations, eg creating a new conversation, etc. You can sign in by clicking on a user from the main page and choosing "Sign In as This User."
请注意,您必须以用户身份“登录”以执行某些操作,例如创建新对话等。您可以通过从主页面单击用户并选择“以此用户身份登录”来登录。
One other thing to keep in mind is that the URLs say "conversation" to keep things nice for the user, even though the controller in use is the "UserConversations" controller--check out the routes file. 要记住的另一件事是,即使正在使用的控制器是“UserConversations”控制器,URL也会说“对话”以保持用户的好处 - 检查路由文件。
If you have any longer/in-depth questions, feel free to contact me via GitHub or via the contact details on my StackOverflow profile. 如果您有任何更深入/更深入的问题,请随时通过GitHub或我的StackOverflow配置文件上的联系方式与我联系。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.