我有2个具有has_many
关联的模型:
class Log < ActiveRecord::Base
has_many :log_details
end
和
class LogDetail < ActiveRecord::Base
belongs_to :log
end
该Log
表有一个action_type
字符串列。该LogDetail
表有2列:key
和value
,均为字符串,并带有返回对Log
表的引用log_id
。
我想在Log
模型上编写3个作用域,以查询将Log
表LogModel
两次连接的一些细节。这是一个示例:
has_many :payment_gateways, -> {where(key: 'payment_gateway')}, class_name: 'LogDetail', foreign_key: :log_id
has_many :coupon_codes, -> {where(key: 'coupon_code')}, class_name: 'LogDetail', foreign_key: :log_id
scope :initiate_payment, -> {where(action_type: 'INITIATE PAYMENT')}
scope :payment_gateway, -> (pg) {joins(:payment_gateways).where(log_details: {value: pg}) unless pg.blank?}
scope :coupon_code, -> (cc) {joins(:coupon_codes).where(log_details: {value: cc}) unless cc.blank?}
使用上述范围,如果我尝试查询
Log.initiate_payment.payment_gateway('sample_pg').coupon_code('sample_cc')
我得到的SQL查询:
SELECT
`logs`.*
FROM
`logs`
INNER JOIN
`log_details` ON `log_details`.`log_id` = `logs`.`id`
AND `log_details`.`key` = 'payment_gateway'
INNER JOIN
`log_details` `coupon_codes_logs` ON `coupon_codes_logs`.`log_id` = `logs`.`id`
AND `coupon_codes_logs`.`key` = 'coupon_code'
WHERE
`logs`.`action_type` = 'INITIATE PAYMENT'
AND `log_details`.`value` = 'sample_pg'
AND `log_details`.`value` = 'sample_cc'
而不是:(请注意最后一个AND条件的差异)
SELECT
`logs`.*
FROM
`logs`
INNER JOIN
`log_details` ON `log_details`.`log_id` = `logs`.`id`
AND `log_details`.`key` = 'payment_gateway'
INNER JOIN
`log_details` `coupon_codes_logs` ON `coupon_codes_logs`.`log_id` = `logs`.`id`
AND `coupon_codes_logs`.`key` = 'coupon_code'
WHERE
`logs`.`action_type` = 'INITIATE PAYMENT'
AND `log_details`.`value` = 'sample_pg'
AND `coupon_codes_logs`.`value` = 'sample_cc'
第一个查询由于无法正确解析联接表引用而给我零结果。
如何以这种方式修改范围/模型以生成正确的查询?我想我需要在范围的where
子句中引用联接表别名,但是我不确定如何获得该引用。
遗憾的是,ActiveRecord没有内置方法来指定加入关联时使用的别名。使用merge
尝试合并这两个领域也作为条件被覆盖失败。
3个解决方案:
joins
,但这有点难以理解,您仍然需要为payment_gateways
和重复关联定义。coupon_codes
直接加入SQL:
scope :payment_gateway, -> (pg) { joins(<<-SQL INNER JOIN log_details payment_gateways ON payment_gateways.log_id = logs.id AND payment_gateways.key = 'payment_gateway' AND payment_gateways.value = #{connection.quote(pg)} SQL ) if pg.present? }
但是您需要手动添加关联中已经定义的条件
最后,我最喜欢的一个坚持ActiveRecord的解决方案:
scope :payment_gateway, -> (pg) do where(id: unscoped.joins(:payment_gateways).where(log_details: {value: pg})) if pg.present? end
scope :coupon_code, -> (cc) do where(id: unscoped.joins(:coupon_codes).where(log_details: {value: cc})) if cc.present? end
难题#1:如果您使用Rails <5.2,则可能需要使用类方法而不是范围。
难题#2:解决方案#3的性能可能不及问题#2,EXPLAIN ANALYZE
请务必先看看其中的区别。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句