Повторно использовать область в запросе с отношением has_many к той же таблице STI

Child1 и Child2 имеют отношения STI с Entity, а Child2 has_many Child1. Child1 имеет столбец состояния, управляемый AASM.

class Entity < ActiveRecord::Base
end

class Child1 < Entity
  include AASM
  aasm_column 'status' do
    state :owned #also creates scope Child1.owned
    state :sold
  end

  belongs_to :child2
end

class Child2 < Entity
  has_many :child1s
end

Я хотел бы создать область действия для Child2 для каждого состояния Child1. Он должен вернуть все записи Child2, которые имеют одну или несколько записей Child1, которые находятся в этом состоянии. В идеале это будет использовать области, которые AASM создает автоматически, например

scope :owned, -> {joins(:child1s).merge(Child1.owned)} #in Child2

... что красиво и чисто и сухо. К сожалению, SQL, который это генерирует, смущен соединением в той же таблице:

irb(main):001:0> Child2.owned
  Child2 Load (35.5ms)  SELECT "entities".* FROM "entities"
INNER JOIN "entities" "child1_entities" ON "child1_entities"."child2_id" = "entities"."id" AND "child1_entities"."type" IN ('Child1')
WHERE "entities"."type" IN ('Child2') AND "entities"."status" = 'owned'

Последняя часть предложения where должна быть child1_entities.status = 'owned',

Я мог бы написать весь запрос на SQL или Arel, но я надеюсь найти что-то такое, что, даже если мне придется пойти туда, чтобы указать псевдоним для соединения child1, я все равно смогу повторно использовать области, которые у меня уже есть в Child1.

1 ответ

Вы можете сделать это без использования каких-либо сторонних драгоценных камней с парой определенных отношений

class Child2 < Entity
  has_many :child1s
  has_many :owned_child1s, -> {owned}, class_name: "Child1"
  has_many :sold_child1s, -> {sold}, class_name: "Child1"
end

затем, чтобы найти все Child2, которые владеют или продают:child1s соответственно, вы бы использовали следующие запросы

Child2.joins(:owned_child1s) #=> All Child2 having owned children
Child2.joins(:sold_child1s)  #=> All Child2 having sold children

Вы можете сделать это областью применения Child2, используя:

class Child2 < Entity    
  has_many :child1s
  has_many :owned_child1s, -> {owned}, class_name: "Child1"
  has_many :sold_child1s, -> {sold}, class_name: "Child1"

  scope :with_owned_child1s, -> {joins(:owned_child1s)}
  scope :with_sold_child1s, -> {joins(:solid_child1s)}
end

Отношения с заданной областью делают код немного более читабельным

Child2.with_owned_child1s.where(....) etc.
Другие вопросы по тегам