【问题标题】:Check if a conversation between two or more specific users already exists, where users-conversations have a HABTM association检查两个或多个特定用户之间的对话是否已经存在,其中用户对话具有 HABTM 关联
【发布时间】:2015-12-06 04:30:14
【问题描述】:

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

一个非常简单的关系模型,最初运行良好,但现在让我很头疼,因为我将它重构为适用于群组消息,即两个以上用户之间的消息。

我以前只对 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

控制器/conversations_controller.rb

[snip]

def create
  if Conversation.between(params[:users]).present?
    @conversation = Conversation.between(params[:users]).first
  else
    @conversation = Conversation.create!(conversation_params)
  end
    redirect_to conversation_messages_path(@conversation)
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/migrate/create_conversations_users_join

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 的解决方案,但在作用域的方法上得到了以下 NameError:

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'

【问题讨论】:

  • 您希望只在用户之间进行对话,还是对话还包括其他人?
  • 仅在特定用户之间。

标签: ruby-on-rails ruby ruby-on-rails-3 has-and-belongs-to-many


【解决方案1】:

试试这样的。

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

不过,同时选择太多用户会使查询变得庞大。

【讨论】:

  • 我认为,您也许可以将连接删除到 users 表,并仅依赖 conversation_users 表。
  • 不幸的是,我在作用域方法上遇到了 NameError。我已将回溯添加到我的原始帖子中,因为 cmets 不保留换行符,看起来像。
  • @KalynHorn 尝试使用all 而不是scoped
【解决方案2】:

相对容易编码的实现依赖于选择存在三个用户对话连接记录的对话,其中 user_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))

(未检查语法)

【讨论】:

  • 啊!看起来是一个非常优雅的解决方案。我试图调查如何访问连接表,但找不到明确的答案。 (可能是因为它很简单。)我必须为桌子制作模型吗?
  • 更新:我找到了this的解释。如果我使用你的方法,用户对话是否有一个 has_many :through 关系会更好?我不确定有什么区别。 (我会继续调查。)
  • 双重更新:啊....问题是事情被写入连接表......然后被立即删除......我不确定为什么。
  • 是的,我认为 has_many through 会更好地支持这一点。您还可以将其用于与该用户和对话相关的属性,例如他们第一次加入的时间。
猜你喜欢
  • 2016-11-19
  • 1970-01-01
  • 2016-06-11
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多