Уникальная ссылка на таблицу соединений в области видимости модели - PullRequest
0 голосов
/ 08 сентября 2018

У меня есть 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.

Я хочу написать 3 области на модели Log, чтобы запросить некоторые детали, соединяя таблицу 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, но я не уверен, как получить эту ссылку.

1 Ответ

0 голосов
/ 08 сентября 2018

К сожалению, ActiveRecord не имеет встроенного способа указать псевдоним, используемый при присоединении к ассоциации. Использование merge для объединения двух областей также не выполняется, поскольку условие переопределяется.

3 решения:

  1. Используйте Arel для псевдонима 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, вам может потребоваться использовать методы класса вместо областей. </p>

Принято # 2: Решение № 3 может быть менее производительным, чем # 2, убедитесь, что EXPLAIN ANALYZE, чтобы увидеть разницу.

...