【问题标题】:Rails - Searching for messages and conversations. How to show only the latest message of each conversation?Rails - 搜索消息和对话。如何仅显示每个对话的最新消息?
【发布时间】:2016-09-10 07:38:19
【问题描述】:

我使用本教程为我的 Rails 应用程序创建了一个消息传递系统:Create a Simple Messaging System on Rails,作者是 Dana Mulder。它可以创造奇迹,但我在稍微调整它时遇到了问题。 是否有人对创建显示每个对话的最新消息的对话收件箱有见解?更多详细信息如下。

系统中有两种模型:Conversation和Message:

  • 对话有一个 user1_id 和一个 user2_id。
  • 消息具有 user_id、body 和 conversation_id。

因此,每个会话可以有多条消息,并且每条消息都属于发送它的用户。

我构建了一个 ConversationsController 来显示索引中的所有消息。 不过,我想要的是只按顺序显示每个对话的最新消息。

我将在此处粘贴当前版本的 Conversations#Index 操作,以及我设想的操作(这会产生一个错误,我将在下面描述)。

Working Conversations#Index(显示所有消息):

@conversations = Conversation.where("user1_id = #{current_user.id} OR user2_id = #{current_user.id}")

@array = []
@conversations.each do |item|
    @array.push(item.id)
end
@messages = Message.where("conversation_id in (?)",  @array)

Conversations#Index 无效(应仅显示每个对话的最新消息:

@conversations = Conversation.where("user1_id = #{current_user.id} OR user2_id = #{current_user.id}").order("updated_at DESC")

@array = []
@conversations.each do |item|
    @array.push(item.id)
end

@list = []
@array.each do |i|
    @id = Message.where("conversation_id = (?)", i).last
    @list.push(@id.id)
end

@messages = Message.where("id in (?)", @list)

当我输入后一段代码时,收件箱根本没有显示任何消息。任何想法为什么?

日志

    Started GET "/conversations" for 99.234.104.113 at 2016-05-14 04:19:05 +0000
Processing by ConversationsController#index as HTML
  User Load (0.3ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  Conversation Load (0.3ms)  SELECT "conversations".* FROM "conversations" WHERE (1 IN (sender_id, recipient_id))  ORDER BY updated_at DESC
  Message Load (0.4ms)  SELECT  "messages".* FROM "messages" WHERE (conversation_id = 2)  ORDER BY "messages"."id" DESC LIMIT 1
  Message Load (0.3ms)  SELECT  "messages".* FROM "messages" WHERE (conversation_id = 1)  ORDER BY "messages"."id" DESC LIMIT 1
   (0.2ms)  SELECT COUNT(*) FROM "conversations" WHERE (1 IN (sender_id, recipient_id))
  Conversation Load (0.3ms)  SELECT  "conversations".* FROM "conversations" WHERE (1 IN (sender_id, recipient_id))  ORDER BY updated_at DESC LIMIT 10 OFFSET 0
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 3]]
  User Load (0.1ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
  Rendered conversations/_convo.html.erb (150.4ms)
  Message Load (0.3ms)  SELECT  "messages".* FROM "messages" WHERE (id in (10,9)) LIMIT 10 OFFSET 0
  Rendered conversations/_message.html.erb (1.0ms)
  Rendered conversations/index.html.erb within layouts/application (157.4ms)
  Rendered layouts/_shim.html.erb (0.1ms)
  Rendered layouts/_header.html.erb (1.6ms)
  Rendered layouts/_footer.html.erb (0.5ms)
Completed 200 OK in 299ms (Views: 292.1ms | ActiveRecord: 2.3ms)

【问题讨论】:

    标签: ruby-on-rails ruby search


    【解决方案1】:

    您在查询中输入了错误的字段名称。试试这个:

    @conversations = Conversation.
      where("user1_id = #{current_user.id} OR user2_id = #{current_user.id}").
      order("updated_at DESC")
    

    这应该可以帮助您处理该查询。最好也显示这样的控制台日志,以便可以跟踪操作的执行。

    要改进您的查询,您可以使用查询参数。它们允许您在不使用字符串插值的情况下包含所需的值(这可能会导致SQL injection 问题)。你可以这样做:

    @conversations = Conversation.
      where("user1_id = ? OR user2_id = ?", current_user.id, current_user.id).
      order("updated_at DESC")
    

    而且,您可以通过使用 IN 运算符并使用更少的参数来稍微缩短相同的查询:

    @conversations = Conversation.
      where("? IN (user1_id, user2_id)", current_user.id).
      order("updated_at DESC")
    

    对于您方法的其余部分,这里有一些更改可能会提高性能并稍微减少代码:

    @messages = Message.
      where(conversation_id: @conversations).
      group(:conversation_id).
      having("id = MAX(id)")
    

    第一行根据@conversations 中的所有对话构建item.ids 数组。这使用了 Ruby Array#map 方法来高效地完成任务。

    第二行使用where 的哈希参数通过使用ActiveRecord 生成SQL IN 子句来实现更严格的查询(有关更多信息,请参阅Active Record Query InterfaceHash Conditions 部分)。然后它按conversation_id 对消息进行分组,并使用having 子句过滤最新消息。

    还请注意,您不必使用 @ 变量,除非您在其他地方隐式地传达它们,例如与视图或同一类中的其他方法。局部变量可以只使用常规名称,例如 arraymessages

    【讨论】:

    • 并没有解决问题,而是让它更有说服力。非常感谢。您指出的第一件事(user1 而不是 user1_id)只是 StackOverflow 上的一个错字,所以我仍然遇到问题。如果您仍然可以查看问题并查看是否可以帮助我找到解决方案,我将不胜感激。再次,非常感谢:)
    • 没问题。问题:您需要使用@conversations,还是只是为了查找消息?您能否发布该操作的日志,以便我查看参数、生成的 SQL 和其他操作详细信息?
    • 我确实需要@conversations,并将日志添加到问题中。谢谢!
    • 我更新了@messages 的查询。试一试,看看它是否返回您期望的消息。
    • 只需将集合传递到Message.where(conversation_id: @conversations) - ActiveRecord 会自行将其转换为 id 数组。
    【解决方案2】:

    我将首先在 UserConversation 之间建立适当的 M2M 关系,因为本教程还有很多不足之处:

    • 根据设计,对话仅限于 2 个用户
    • 由于对话将两个用户外键存储在同一行中,因此您需要那些笨拙的 IF a_id = :id OR b_id = :id 查询和过于复杂的关系。

    更好的领域模型

    class User < ActiveRecord::Base
      has_many :user_conversations
      has_many :conversations, 
               through: :user_conversations
    end
    
    # this is the join table between users and conversations
    class UserConversation < ActiveRecord::Base
      belongs_to :user
      belongs_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 :conversation
      belongs_to :user
    end
    

    您可以使用以下方法创建 UserConversation 模型和迁移:

    rails g model UserConversation user:belongs_to conversation:belongs_to
    

    您还需要创建迁移以删除不需要的 conversations.user1_idconversations.user2_id 列 - see the rails guides for how do that

    使用关联

    这将让我们通过以下方式进行对话:

    @conversations = current_user.conversations
    

    可以通过将组和顺序应用于查询来完成每个对话获取一条消息:

    class Message < ActiveRecord::Base
      belongs_to :user
      belongs_to :conversation
    
      def self.recent(conversations)
        self.order(:conversation_id, :created_at)
            .group(:conversation_id)
            .where(conversation_id: conversations)
      end
    end
    

    因此,要将其整合到您的控制器中,您可以这样做:

    class ConversationsController < ApplicationController 
      def index
        @conversations = current_user.conversations
        @recent_messages = Message.recent(@conversations)
      end
    end
    

    见:

    【讨论】:

    • 恕我直言,这是一个非常糟糕的教程——她掉进了一个领域里的每一个菜鸟陷阱,这个领域实际上已经被做死了。
    • 感谢您的提示,最大值。在这种情况下,您能否扩展视图和控制器?很高兴能够编写更好的教程,我显然会 100% 归功于你。
    • 嗯,这有点难以继续 - 如果您针对问题的特定部分发布后续问题可能会更好。 stackoverflow 格式并不适合长篇教程式的答案。
    猜你喜欢
    • 1970-01-01
    • 2018-11-15
    • 2019-09-12
    • 1970-01-01
    • 2018-03-27
    • 1970-01-01
    • 1970-01-01
    • 2017-05-13
    • 1970-01-01
    相关资源
    最近更新 更多