使用多个belongs_to关联多次连接到同一模型

时间:2013-03-07 15:22:25

标签: ruby-on-rails associations squeel

我有一个类协议,它与成员类有两个belongs_to关联 - 引用为主要和次要:

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

会员类不知道与协议类的关联 - 它不需要:

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

我想要做的是搜索主要成员或次要成员符合一组标准的所有协议。

从以前的工作(see question #14139609)开始,我已经整理出如何为Member.by_any()构建条件where子句。

在搜索协议时尝试重用该方法让我尝试这样做:

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

在控制台中使用member_attribute_hash = {surname: 'Freud'}运行此命令时,生成的SQL无法履行为第二次成员加入而生成的别名:

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%'

请注意WHERE子句中的重复条件。这将返回协议,其中主要成员具有类似“弗洛伊德”的姓氏,但忽略了辅助成员条件,因为别名没有流经合并。

有什么想法吗?

1 个答案:

答案 0 :(得分:0)

在努力理解这一点后,我最终将Member.by_any范围替换为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

sifterscope之间的唯一区别(代码方式)是替换范围中的where并使用squeel。{ / p>

因此,我现在无法使用merge来访问协议模型中的Member.by_any范围,而是可以从协议模型中引用Member :by_any sifter。看起来像是:

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

这解决了别名问题 - 开始庆祝!

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%'

但我仍然没有得到我预期的结果 - 庆祝活动暂停。哪里错了? where子句中的AND是错误的。经过一段时间的挖掘(和电脑一夜之后),一个精神焕发的大脑决定试试这个:

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

制作:

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%')))

很可爱...... 重新开始庆祝活动 ...现在我得到的协议中有一个主要或次要成员,根据单个筛子中定义的规则进行匹配。

对于他在Squeel所做的所有工作,Ernie Miller大肆宣传。