模型范围内的唯一联接表参考

玛希达·切拉曼尼

我有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列:keyvalue,均为字符串,并带有返回对Log的引用log_id

我想在Log模型上编写3个作用域,以查询将LogLogModel两次连接的一些细节这是一个示例:

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子句中引用联接表别名,但是我不确定如何获得该引用。

马修·里伯(Matthew Libeer)

遗憾的是,ActiveRecord没有内置方法来指定加入关联时使用的别名。使用merge尝试合并这两个领域也作为条件被覆盖失败。

3个解决方案:

  1. 使用Arel为a命名别名joins,但这有点难以理解,您仍然需要为payment_gateways重复关联定义。coupon_codes
  2. 直接加入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? }

    但是您需要手动添加关联中已经定义的条件

  3. 最后,我最喜欢的一个坚持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] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章