【问题标题】:How to compare a joined table with the source dynamically如何动态比较连接表和源表
【发布时间】:2015-12-22 13:12:25
【问题描述】:

背景

我有五个模型:Reward、BrandSubscription、Brand、Tier 和 User。

  • Brand 有很多 Tiers
  • BrandSubscription 属于 TierUser
  • Reward 属于 Tier
  • Tier 有一个名为 order 的属性。如果BrandSubscription 具有更高的层级,则它也将具有所有较低的层级。
  • 一个BrandSubscription 可以从它的所有Tiers 中看到所有Rewards,在这种情况下是它所属的Tier 及其所有较低的Tiers

问题

我的问题是上面列表的最后一项。我正在尝试获得品牌订阅的所有奖励。

理想的方式是BrandSubscription 实体上的has_many :rewards,即through: :tier。在Tier 我可以有一个has_many :rewards。这种方法的问题是奖励不限于当前层,而是必须包括来自较低order层的所有奖励。

为了实现这一点,我在Tier 模型的has_many :rewards 上设置了一个范围:

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_tablethat_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 来完成这项工作,我真的很高兴,因为代码会更简洁。

参考

我正在使用以下链接来尝试解决此问题:

【问题讨论】:

    标签: mysql ruby-on-rails ruby activerecord arel


    【解决方案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
    

    【讨论】:

    • 这可行,但我真正需要的是一个查询中的所有内容。此解决方案将进行 4 次查询,并且无法使用 include 急切加载所有内容。不过还是谢谢。
    • 这4个查询是什么?有一个可以获取所有相关的等级 id,一个可以获取所有奖励。
    • 品牌订阅的负载,然后是品牌订阅层(因为belongs_to),然后是层的品牌(因为层上品牌的belongs_to),然后是品牌的层使用 order where 子句。
    • 你是对的。我将其优化为三个查询:品牌订阅、等级和奖励。如果性能是一个问题并且层级很少更改,您可以非规范化奖励层级关系吗?
    猜你喜欢
    • 1970-01-01
    • 2020-07-20
    • 1970-01-01
    • 2015-12-22
    • 2018-08-21
    • 2019-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多