Как динамически сравнивать объединенную таблицу с исходной

Контекст

У меня есть пять моделей: Reward, BrandSubscription, Brand, Tier и User.

  • Brand имеет много Tiers.
  • BrandSubscription принадлежит Tier и User.
  • Reward принадлежит Tier.
  • Tier имеет атрибут с именем order. Если у BrandSubscription есть уровень более высокого порядка, у него также будут все более низкие уровни.
  • BrandSubscription может видеть все Rewards из всех своих Tiers, которые в данном случае являются Tier, которым он принадлежит, и все его младшие Tiers.

Проблема

Моя проблема связана с последним пунктом списка выше. Я пытаюсь получить все награды за подписку на бренд.

В идеале было бы has_many :rewards на объекте BrandSubscription, это было бы through: :tier. Tier у меня может быть has_many :rewards. Проблема с этим подходом заключается в том, что награды не ограничиваются текущим уровнем, а должны включать все награды более низких order уровней.

Для этого я поставил прицел на has_many :rewards модели Tier:

class Tier < ActiveRecord::Base
  belongs_to :brand
  has_many :rewards

  has_many :with_lower_tiers, lambda {
    where(this_table[:order].gteq(that_table[:order]))
  }, through: :brand, source: :tiers

  has_many :achievable_rewards, through: :with_lower_tiers, source: rewards
end

Проблема здесь с this_table и that_table. Мне нужно сделать здесь какое-то соединение, чтобы я мог сравнить таблицы. Один подход, который я мог заставить работать, был следующим:

class Tier < ActiveRecord::Base
  belongs_to :brand
  has_many :rewards

  has_many :with_lower_tiers, lambda { |tier|
    where(current_scope.table[:order].lteq(tier.order))
  }, through: :brand, source: :tiers

  has_many :achievable_rewards, through: :with_lower_tiers, source: rewards
end

Здесь я использую объект владельца уровня и получаю его порядок. Проблема здесь в том, что я не могу полагаться на аргумент tier. Следующий запрос уже прерывается, потому что в качестве аргумента функции области действия действительно передается сущность-"владелец" запроса, в данном случае BrandSubscription:

BrandSubscription.joins(:with_lower_tiers)

SQL-запрос, который я хочу получить, следующий: я могу получить все доступные вознаграждения от пользователя. Обратите внимание, что я присоединяюсь к таблице tiers дважды, и именно здесь у меня возникают проблемы:

SELECT DISTINCT rewards.*
  FROM tiers
  INNER JOIN brand_subscriptions ON tiers.id = brand_subscriptions.tier_id
  INNER JOIN tiers tiers_reward ON tiers_reward.brand_id = tiers.brand_id
  INNER JOIN rewards ON tiers_reward.id = rewards.tier_id
  WHERE tiers_reward.order <= tiers.order
    AND brand_subscriptions.user_id = 1234

Я считаю, что некоторые Arel могут помочь, но мне бы очень хотелось, чтобы я мог полностью полагаться на ActiveRecord для этого, так как код был бы намного чище.

использованная литература

Я использую следующие ссылки, чтобы попытаться решить эту проблему:


person Jaison Erick    schedule 22.12.2015    source источник


Ответы (1)


Вы можете построить запрос самостоятельно, вместо того, чтобы полагаться на сложные ассоциации has_many-through

class Reward < ActiveRecord::Base
  belongs_to :tier
end

class Brand < ActiveRecord::Base
  has_many :tiers
end

class Tier < ActiveRecord::Base
  belongs_to :brand
  has_many :rewards

  # has_many :lower_tiers, ->(tier){ where('tiers.order < ?', tier.order) }, through: :brand, source: :tiers

  def lower_tiers
    Tier.where('brand_id = ? AND order < ?', brand_id, order)
  end

  def achievable_rewards
    Rewards.where('tier_id IN (?) OR tier_id = ?', lower_tiers.select(:id), id)
  end
end


class BrandSubscription < ActiveRecord::Base
  belongs_to :user
  belongs_to :tier

  def rewards
    tier ? tier.achievable_rewards : Reward.none
  end
end
person Plamena Gancheva    schedule 22.12.2015
comment
Это может сработать, но мне действительно нужно все в одном запросе. Это решение сделает 4 запроса, и нет возможности использовать include для быстрой загрузки всего. Спасибо хоть. - person Jaison Erick; 22.12.2015
comment
Какие 4 запроса? Есть один, чтобы получить все связанные идентификаторы уровней, и один, чтобы получить все награды. - person Plamena Gancheva; 23.12.2015
comment
Загрузка подписки на бренд, затем уровень подписки на бренд (из-за принадлежности), затем бренд уровня (из-за принадлежности бренда на уровне), затем уровни бренда с предложением order where. - person Jaison Erick; 23.12.2015
comment
Ты прав. Я оптимизировал его до трех запросов: подписка на бренд, уровень и, наконец, награды. Если производительность является проблемой, а уровни редко меняются, вы могли бы денормализовать отношение вознаграждений к уровням? - person Plamena Gancheva; 23.12.2015