Ruby on rails 检查两个或多个特定用户之间是否已存在对话,其中用户对话具有HABTM关联

Ruby on rails 检查两个或多个特定用户之间是否已存在对话,其中用户对话具有HABTM关联,ruby-on-rails,ruby,ruby-on-rails-3,has-and-belongs-to-many,Ruby On Rails,Ruby,Ruby On Rails 3,Has And Belongs To Many,用户拥有并属于许多对话。一个对话拥有并属于许多用户,并且有许多消息。消息属于用户,也属于对话 一个非常简单的关系模型,最初工作得很好,但现在当我重构它以处理组消息(即两个以上用户之间的消息)时让我头疼 我以前只对HABTM关联进行了肤浅的研究,不知道如何检查给定用户阵列之间是否已经存在对话。此检查将在控制器中进行,可能通过模型中定义的范围进行。编辑:此检查可能仅适用于两个用户之间的对话。比如说, A conversation between User 1 and User 2 already e

用户拥有并属于许多对话。一个对话拥有并属于许多用户,并且有许多消息。消息属于用户,也属于对话

一个非常简单的关系模型,最初工作得很好,但现在当我重构它以处理组消息(即两个以上用户之间的消息)时让我头疼

我以前只对HABTM关联进行了肤浅的研究,不知道如何检查给定用户阵列之间是否已经存在对话。此检查将在控制器中进行,可能通过模型中定义的范围进行。编辑:此检查可能仅适用于两个用户之间的对话。比如说,

A conversation between User 1 and User 2 already exists.
User 1 sends a message to User 2 via the "Compose New Message" page.
The preexisting conversation is found and selected, and the new message is inserted into it.

A conversation between User 1, User 2, and User 3 already exists.
User 1 sends a message to User 2 and User 3 via the "Compose New Message" page.
A new conversation is made, rather than searching for a pre-existing conversation.
models/conversation.rb

class Conversation < ActiveRecord::Base
  has_and_belongs_to_many :users
  has_many :messages, dependent: :destroy

  scope :between, -> users do
    uhhhhhhhhh???
    something like
    users.each do |u|
      then a where("conversations.user.id = ?").yadayadayada query
    end
  end
end
models/user.rb

class User < ActiveRecord::Base
  has_and_belongs_to_many :conversations
  [snip lots of other stuff]
end
models/message.rb

class Message < ActiveRecord::Base
  belongs_to :conversation
  belongs_to :user
  validates_presence_of :body, :conversation_id, :user_id
end
db/migrate/create_messages.rb

class CreateMessages < ActiveRecord::Migration
  def up
    create_table :messages do |t|
      t.text :body
      t.references :conversation, index: true
      t.references :user, index: true
      t.boolean :read, :default => false

      t.timestamps
    end
  end

  def down
    drop_table :messages
  end
end
db/migrate/create_conversations.rb

class CreateConversations < ActiveRecord::Migration
  def change
    create_table :conversations do |t|
      t.string :subject
      t.timestamps
    end
    end

  def down
    drop_table :conversations
  end
end
db/迁移/创建\对话\用户\加入

class CreateConversationsUsersJoin < ActiveRecord::Migration
  def change
    create_table :conversations_users, id: false do |t|
        t.belongs_to :conversation, index: true
        t.belongs_to :user, index: true
    end
  end
end
更新:尝试了@DavidStosik的解决方案,但在方法作用域上出现以下名称错误:

Started POST "/conversations" for 127.0.0.1 at 2015-09-10 16:53:51 -0400
Processing by ConversationsController#create as */*
  Parameters: {"user_ids"=>"1 2"}
  [1m[35mUser Load (1.0ms)[0m  SELECT  "users".* FROM "users"  WHERE "users"."id" = 1  ORDER BY "users"."id" ASC LIMIT 1
Completed 500 Internal Server Error in 3ms

NameError (undefined local variable or method `scoped' for #<Class:0x5afc2b8>):
  app/models/conversation.rb:25:in `block in <class:Conversation>'
  app/controllers/conversations_controller.rb:13:in `create'
  app/controllers/application_controller.rb:19:in `user_time_zone'

试试这样的

class Conversation < ActiveRecord::Base
  scope :between, ->(users) do
    base = all
    conditions = []
    users.each.with_index do |user, i|
      base = base
              .joins("JOIN conversations_users AS cu#{i} ON cu#{i}.conversation_id = conversations.id")

      cu_table = Arel::Table.new("cu#{i}")
      if condition
        condition = condition.and cu_table[:id].eq(user.id)
      else
        condition = cu_table[:id].eq(user.id)
      end
    end

    base.where(condition)      
  end
end

但是,同时选择太多的用户会使查询变得庞大。

相对容易编码的实现依赖于选择对话,其中存在三个用户对话连接记录,其中用户ID是您感兴趣的

您确实需要访问用户和对话之间的连接表才能使其高效,但逻辑如下:

Conversation.where(:id => 
  UserConversation.where(:user_id => [array of user ids]).
                   group(:conversation_id).
                   having("count(*) = ?", [array of user ids].size).
                   pluck(:conversation_id))

未检查语法

我认为,您可能可以将连接删除到那里的用户表,并且仅依赖于对话用户表。不幸的是,我在作用域方法上遇到了一个名称错误。我已将回溯添加到我的原始帖子中,因为注释不保留换行符,看起来像是。@KalynHorn Try all而不是scoped。您希望对话仅在用户之间进行,还是对话还可以包括其他人?仅在这些特定用户之间。啊!看起来是一个非常优雅的解决方案。我试图研究如何访问联接表,但找不到明确的答案。可能是因为它很简单。我必须为表格制作模型吗?更新:我找到了解释。如果我使用你的方法,用户对话是否会有一种“有很多:通过”的关系?我不知道有什么区别。我将继续调查。双重更新:啊。。。。这样做的问题是,事情正在写入联接表中。。。。。。。然后立即删除。。。我不知道为什么。是的,我认为很多人都支持这一点。您还可以将其用于与该用户和对话相关的属性,比如他们第一次加入时。
Conversation.where(:id => 
  UserConversation.where(:user_id => [array of user ids]).
                   group(:conversation_id).
                   having("count(*) = ?", [array of user ids].size).
                   pluck(:conversation_id))