Multiple joins to the same model using multiple belongs_to associations

六眼飞鱼酱① 提交于 2019-12-12 01:07:16

问题


I have a class Agreement that has two belongs_to associations to a Member class - referenced as primary and secondary:

class Agreement < ActiveRecord::Base
  belongs_to :primary, class_name: 'Member'
  belongs_to :secondary, class_name: 'Member'
  ...

  def self.by_member(member_attribute_hash)
    # returns any agreements that has a primary OR secondary member that matches any of the values 
    # in the member_attribute_hash
    ...
  end
end

The Member class has no knowledge of the association with the Agreement class - it does not need to:

class Member < ActiveRecord::Base
  # contains surname, given_names, member_number
  ...

  def self.by_any(member_attribute_hash)
    # returns any member where the member matches on surname OR given_names OR member_number
    ...
  end
end

What I would like to do is search for all agreements where the primary or secondary member matches a set of criteria.

From previous work (see question #14139609), I've sorted out how to build the conditional where clause for Member.by_any().

Trying to reuse that method while search for Agreements led me to try this:

class Agreement < ActiveRecord::Base
  ...

  def self.by_member(member_attribute_hash)
    Agreement.joins{primary.outer}.merge(Member.by_any(member_attribute_hash)).joins{secondary.outer}.merge(Member.by_any(member_attribute_hash))
  end
end

On running this in the console, with a member_attribute_hash = {surname: 'Freud'}, the generated SQL fails to honour the alias generated for the second join to member:

SELECT "agreements".* 
FROM "agreements" 
  LEFT OUTER JOIN "members" 
        ON "members"."id" = "agreements"."primary_id" 
  LEFT OUTER JOIN "members" "secondarys_agreements" 
        ON "secondarys_agreements"."id" = "agreements"."secondary_id" 
WHERE "members"."surname" ILIKE 'Freud%' 
  AND "members"."surname" ILIKE 'Freud%'

Notice the duplicate conditions in the WHERE clause. This will return Agreements where the primary Member has a surname like 'Freud', but ignores the secondary Member condition because the alias is not flowing through the merge.

Any ideas?


回答1:


After struggling to understand this, I ended up replacing the Member.by_any scope with a Squeel sifter:

class Member < ActiveRecord::Base
  # contains surname, given_names, member_number
  ...

  def self.by_any(member_attribute_hash)
    # returns any member where the member matches on surname OR given_names OR member_number
    squeel do
      [
        (surname =~ "#{member[:surname]}%" if member[:surname].present?),
        (given_names =~ "#{member[:given_names]}%" if member[:given_names].present?),
        (member_number == member[:member_number] if member[:member_number].present?)
      ].compact.reduce(:|)
      # compact to remove the nils, reduce to combine the cases with |
    end
  end
end

The only difference (code-wise), bewteen the sifter and the scope is the replacement of the where in the scope with squeel in the sifter.

So, instead of using a merge to access the Member.by_any scope from the Agreement model, I was now able to reference the Member :by_any sifter from the Agreement model. It looked like:

class Agreement < ActiveRecord::Base
  ...

  def self.by_member(member_attribute_hash)
    Agreement.joins{primary.outer}.where{primary.sift :by_any, member_attribute_hash}.joins{secondary.outer}.where{secondary.sift :by_any, member_attribute_hash}
  end
end

This fixed the aliasing issue - begin celebrating!:

SELECT "agreements".* 
FROM "agreements" 
  LEFT OUTER JOIN "members" 
        ON "members"."id" = "agreements"."primary_id" 
  LEFT OUTER JOIN "members" "secondarys_agreements" 
        ON "secondarys_agreements"."id" = "agreements"."secondary_id" 
WHERE "members"."surname" ILIKE 'Freud%' 
  AND "secondarys_agreements"."surname" ILIKE 'Freud%'

but I still wasn't getting the results I expected - celebration put on hold. What was wrong? The AND in the where clause was wrong. After a bit more digging (and a night away from the computer), a refreshed mind decided to try this:

class Agreement < ActiveRecord::Base
  ...

  def self.by_member(member_attribute_hash)
    Agreement.joins{primary.outer}.joins{secondary.outer}.where{(primary.sift :by_any, member_attribute_hash) | (secondary.sift :by_any, member_attribute_hash)}
  end
end

Producing this:

SELECT "agreements".* 
FROM "agreements" 
  LEFT OUTER JOIN "members" 
        ON "members"."id" = "agreements"."primary_id" 
  LEFT OUTER JOIN "members" "secondarys_agreements" 
        ON "secondarys_agreements"."id" = "agreements"."secondary_id" 
WHERE ((("members"."surname" ILIKE 'Freud%') 
   OR ("secondarys_agreements"."surname" ILIKE 'Freud%')))

Ah sweet... restart celebration... now I get Agreements that have a primary or secondary Member that matches according to the rules defined in a single sifter.

And for all the work he's done on Squeel, a big shout-out goes to Ernie Miller.



来源:https://stackoverflow.com/questions/15274910/multiple-joins-to-the-same-model-using-multiple-belongs-to-associations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!