简体   繁体   English

实现facebook风格的消息传递系统,其中用户可以位于另一个模型的两个不同列中

[英]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 ; 我选择了一个数据模型,它在UserConversation之间有一个名为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 :throughdelegates调用,以尽可能轻松地获取我们想要的数据...例如,不是@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.

相关问题 你能推荐一个使用 Rails 3 生成 facebook 风格消息收件箱的插件或 gem 吗? - Can you recommend a plugin or gem that generates a facebook-style message inbox using Rails 3? 在Rails应用程序中实现oauth:一个还是两个用户模型(用户+身份验证)? - Implementing oauth in a Rails app: one user model or two (user + authentication)? 专用消息传递系统的数据模型 - Data model for a private messaging system 在带有Rails的页面上显示下一组结果,例如Facebook风格的“旧帖子”链接,以查看下一组新闻 - Show next set of results in page with Rails, like Facebook-style “older posts” link to see next set of news items 一个模型可以属于其他两个模型之一吗 - Can a model belong to either one of two other models 我可以有一个 model 属于另一个 model 在导轨中的两个不同名称下吗? - Can i have a model that belongs_to another model under two different names in rails? 用两个名称不同但类型相同的单独属性来实现模型 - Implementing a model with two separate attributes with different names but with same type Simple Rails用户消息系统 - Simple Rails user messaging system 我想在我的 Ruby on Rails 应用程序中添加一个 Facebook 风格的信使,以便用户模型中的用户可以相互对话 - I want to add a Facebook style messenger to my Ruby on Rails application so Users from the User model can have a conversation with each other Rails —如何设置可以属于3种不同模型之一的模型 - Rails — How to setup model that can belong to either of 3 different models
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM