Rails - 搜索消息和对话。如何仅显示每个对话的最新消息?

时间:2016-05-14 00:51:00

标签: ruby-on-rails ruby search

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

系统中有两种模式:会话和消息:

  • A Conversation有一个user1_id和一个user2_id。
  • 消息具有user_id,body和conversation_id。

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

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

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

工作对话#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)

不工作对话#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)

2 个答案:

答案 0 :(得分:2)

我首先要在UserConversation之间创建一个合适的M2M关系,因为教程还有很多不足之处:

  • 会话限制为2个用户
  • 由于会话将两个用户外键存储在同一行上,因此您需要查询这些kludgy 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

请参阅:

答案 1 :(得分: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.id s数组。这使用Ruby Array#map方法有效地完成任务。

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

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