ActiveRecord 和 Arel 的过滤链问题 (SQL)

Filtering chain issue with ActiveRecord and Arel (SQL)

设置如下:

class Title
  has_many :managed_rights
end
class ManagedRight
  belongs_to :title
  has_many :managed_right_countries
  enumerize :territory_rights, in: [:all, :include, :exclude], default: :all
end
class ManagedRightCountry
  belongs_to :managed_right
  belongs_to :country
end
class Country; end

我想按国家/地区过滤标题,其中 country_ids = [3, 5, 8, 9].

title.managed_rights[0].territory_rights == :include 
AND title.managed_rights[0].managed_right_countries IN country_ids
title.managed_rights[0].territory_rights == :exclude
AND title.managed_rights[0].managed_right_countries ALL NOT IN country_ids

这就是我对 Arel 的尝试:

managed_rights = ManagedRight.arel_table
managed_right_countries = ManagedRightCountry.arel_table

Title.left_outer_joins(managed_rights: :managed_right_countries)
  .where(
    # some other filters not related to "managed_rights"
    managed_rights[:territory_rights].eq(:all)
      .or(
        managed_rights[:territory_rights].eq(:include)
        .and(managed_right_countries[:country_id].in(country_ids))
        .or(
          managed_rights[:territory_rights].eq(:exclude)
          .and(managed_right_countries[:country_id].not_in(country_ids))
        )
      )
  ).distinct

它适用于大多数情况,除非 managed_right_countries.country_id 有一些不在 country_ids 数组中的 ID。上面的查询的意思是 :exclude 只有当所有国家都在数组中时才有效。 但是如果数组中有任何国家/地区,我需要从结果中排除标题。

我怎样才能让它正常工作?

最好使用 AREL,但如果仅使用 Arel 无法做到,原始 SQL(PostgreSQL) 也可以工作。

示例:

title_01 has `managed_rights[0].territory_rights = :all`
title_02 has `managed_rights[0].territory_rights = :include`
         and `managed_rights[0].managed_right_countries = [1, 2]`
title_03 has `managed_rights[0].territory_rights = :exclude`
         and `managed_rights[0].managed_right_countries = [2, 3]`

通过创建嵌套子查询解决。

SQL解法:

SELECT titles.* FROM titles
LEFT OUTER JOIN managed_rights ON managed_rights.title_id = titles.id
LEFT OUTER JOIN managed_right_countries ON managed_right_countries.managed_right_id = managed_rights.id
WHERE (titles.rights = 'all' OR managed_rights.title_id is NULL)
OR managed_rights.territory_rights = 'all'
OR (
    managed_rights.territory_rights = 'include' AND managed_right_countries.country_id in (country_ids)
)
OR (
    managed_rights.territory_rights = 'exclude' 
        AND NOT titles.id in (
            SELECT titles.id FROM titles
            LEFT OUTER JOIN managed_rights ON managed_rights.title_id = titles.id
            LEFT OUTER JOIN managed_right_countries ON managed_right_countries.managed_right_id = managed_rights.id
            WHERE managed_rights.territory_rights = 'exclude' 
                AND managed_right_countries.country_id in (country_ids)
        )
)

AREL 解决方案:

titles = Title.arel_table
managed_rights = ManagedRight.arel_table
managed_right_countries = ManagedRightCountry.arel_table

excluded_title_ids = titles.project('titles.id')
  .join(managed_rights, Arel::Nodes::OuterJoin)
  .on(managed_rights[:title_id].eq(titles[:id]))
  .join(managed_right_countries, Arel::Nodes::OuterJoin)
  .on(managed_right_countries[:managed_right_id].eq(managed_rights[:id]))
  .where(
    managed_rights[:territory_rights].eq(:exclude)
      .and(managed_right_countries[:country_id].in(available_countries))
  )

Title.left_outer_joins(managed_rights: :managed_right_countries)
  .where(
    managed_rights[:territory_rights].eq(:all)
    .or(
      managed_rights[:territory_rights].eq(:include)
        .and(managed_right_countries[:country_id].in(available_countries))
        .or(
          managed_rights[:territory_rights].eq(:exclude)
          .and(titles[:id].not_in(excluded_title_ids))
        )
    )
  )