iftheshoefritz
iftheshoefritz

Reputation: 6129

Reuse scope in query with has_many relationship to same STI table

Child1 and Child2 have an STI relationship with Entity, and Child2 has_many Child1. Child1 has a status column managed by 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

I would like to create a scope on Child2 for each state on Child1. It should return all Child2 records that have one or more Child1 records that are in that state. Ideally it would reuse the scopes that AASM creates automatically e.g.

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

...which is nice and clean and DRY. Unfortunately the SQL this generates is confused by the join on the same table:

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'

The last part of the where clause should be child1_entities.status = 'owned'.

I could write the entire query in SQL or Arel but I'm hoping to find something that even if I have to go there to specify an alias for the child1 join, I can still reuse the scopes I already have in Child1.

Upvotes: 1

Views: 580

Answers (1)

Tyrone Wilson
Tyrone Wilson

Reputation: 4618

You can do this without using any third party gems with a couple of scoped relationships

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

then to find all Child2 which have owned or sold :child1s respectively you would use the following queries

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

You could make this a scope on Child2 using:

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

The scoped relationships then make the code a bit more readable

Child2.with_owned_child1s.where(....) etc.

Upvotes: 3

Related Questions