在给定潜在成员时查找现有的消息传递组

Find an existing messaging group when given the potential members

我有一个应用程序,用户可以在其中创建消息传递组。 MessageGroups 通过 MessageMemberships 拥有成员。 MessageMemberships 属于 'profile',这是多态的,因为它们是数据库中不同类型的 'profiles'。

消息组

class MessageGroup < ApplicationRecord
  has_many :message_memberships, dependent: :destroy
  has_many :coach_profiles, through: :message_memberships, source: :profile, source_type: "CoachProfile"
  has_many :parent_profiles, through: :message_memberships, source: :profile, source_type: "ParentProfile"
  has_many :customers, through: :message_memberships, source: :profile, source_type: "Customer"
end

消息会员

class MessageMembership < ApplicationRecord
  belongs_to :message_group
  belongs_to :profile, polymorphic: true
end

在我的 UI 中,我希望能够首先查询以查看是否存在恰好包含 x 个成员的消息传递组,以便我可以使用它,而不是创建一个全新的消息传递组(类似了解 Slack 或 iMessages 如何为您找到现有话题)。

您将如何查询?

下面的代码(未测试)假定:

  • 您有(或可以添加)message_memberships_count counter_cache 列到 message_groups table。 (并且可能向 message_memberships_count 列添加索引以加快查询速度)
  • 您在 message_memberships table 中有适当的唯一索引,这将防止一个配置文件被多次添加到相同的 message_group

工作原理:

  • 有一个循环会在同一个 table 上执行多个 inner joins 以确保每个配置文件都存在关联
  • 然后查询将检查群组中的成员总数是否等于配置文件的数量
class MessageGroup < ApplicationRecord
 ...
 def self.for_profiles(profiles)
    query = "SELECT \"message_groups\".* FROM \"message_groups\""

    profiles.each do |profile|
      klass = profile.class.name
      # provide an alias to the table to prevent `PG::DuplicateAlias: ERROR
      table_alias = "message_memberships_#{Digest::SHA1.hexdigest("#{klass}_#{profile.id}")[0..6]}"
      query += " INNER JOIN \"message_memberships\" \"#{table_alias}\" ON \"#{table_alias}\".\"message_group_id\" = \"message_groups\".\"id\" AND \"#{table_alias}\".\"profile_type\" = #{klass} AND \"#{table_alias}\".\"profile_id\" = #{profile.id}"
    end
    
    query += " where \"message_groups\".\"message_memberships_count\" = #{profiles.length}"

    self.find_by_sql(query)
  end

end

根据@AbM 的回答,我得出了以下结论。这与之前的答案具有相同的假设,计数器缓存和唯一索引应该到位。

  def self.find_direct_with_profiles!(profiles)
    # Not present, some authorization checks that may raise (hence the bang method name)
 
    # Loop through the profiles and join them all together so we get a join that contains
    # all the data we need in order to filter it down
    join = ""
    conditions = ""
    profiles.each_with_index do |profile, index|
      klass = profile.class.name
      # provide an alias to the table to prevent `PG::DuplicateAlias: ERROR
      table_alias = "message_memberships_#{Digest::SHA1.hexdigest("#{klass}_#{profile.id}")[0..6]}"
      join += " INNER JOIN \"message_memberships\" \"#{table_alias}\" ON \"#{table_alias}\".\"message_group_id\" = \"message_groups\".\"id\""
      condition_join = index == 0 ? 'where' : ' and'
      conditions += "#{condition_join} \"#{table_alias}\".\"profile_type\" = '#{klass}' and \"#{table_alias}\".\"profile_id\" = #{profile.id}"
    end

    # Add one
    size_conditional = " and \"message_groups\".\"message_memberships_count\" = #{profiles.size}"
    # Add any other conditions you may need
    conditions += "#{size_conditional}"
    query = "SELECT \"message_groups\".* FROM \"message_groups\" #{join} #{conditions}"

    # find_by_sql returns an array with hydrated models from the select statement. In this case I am just grabbing the first one to match other finder active record method conventions
    self.find_by_sql(query).first
  end