【问题标题】:Use a scope by default on a Rails has_many relationship在 Rails has_many 关系上默认使用范围
【发布时间】:2012-07-24 17:53:20
【问题描述】:

假设我有以下课程

class SolarSystem < ActiveRecord::Base
  has_many :planets
end

class Planet < ActiveRecord::Base
  scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end

Planet 具有范围 life_supportingSolarSystem has_many :planets。我想定义我的 has_many 关系,以便当我为所有关联的planets 询问solar_system 时,会自动应用life_supporting 范围。本质上,我想要solar_system.planets == solar_system.planets.life_supporting

要求

  • 我确实想将scope :life_supporting 中的Planet 更改为

    default_scope where('distance_from_sun &gt; ?', 5).order('diameter ASC')

  • 我还想通过不必添加到 SolarSystem 来防止重复

    has_many :planets, :conditions =&gt; ['distance_from_sun &gt; ?', 5], :order =&gt; 'diameter ASC'

目标

我想要类似的东西

has_many :planets, :with_scope =&gt; :life_supporting

编辑:解决方法

正如@phoet 所说,使用 ActiveRecord 可能无法实现默认范围。但是,我发现了两个潜在的解决方法。两者都防止重复。第一个虽然很长,但保持了明显的可读性和透明度,第二个是输出明确的辅助类型方法。

class SolarSystem < ActiveRecord::Base
  has_many :planets, :conditions => Planet.life_supporting.where_values,
    :order => Planet.life_supporting.order_values
end

class Planet < ActiveRecord::Base
  scope :life_supporting, where('distance_from_sun > ?', 5).order('diameter ASC')
end

另一个更简洁的解决方案是简单地将以下方法添加到SolarSystem

def life_supporting_planets
  planets.life_supporting
end

并在您使用solar_system.planets 的任何地方使用solar_system.life_supporting_planets

都没有回答这个问题,所以我只是把它们放在这里,以防其他人遇到这种情况。

【问题讨论】:

  • 使用 where_vales 的解决方法确实是最好的解决方案,值得接受的答案
  • where_values 可能不适用于散列条件:{:cleared =&gt; false} ...它提供了 ActiveRecord 不喜欢的散列数组。作为 hack,抓取数组中的第一项是有效的:Planet.life_supporting.where_values[0]...
  • 我发现我必须使用 where_ast 而不是 where_valueswhere_values_hash,因为我在其他型号的范围内使用了 AREL。工作了一个款待! +1

标签: ruby-on-rails-3 activerecord has-many


【解决方案1】:

在 Rails 4 中,Associations 有一个可选的 scope 参数,该参数接受应用于 Relation 的 lambda(参见 ActiveRecord::Associations::ClassMethods 的文档)

class SolarSystem < ActiveRecord::Base
  has_many :planets, -> { life_supporting }
end

class Planet < ActiveRecord::Base
  scope :life_supporting, -> { where('distance_from_sun > ?', 5).order('diameter ASC') }
end

在 Rails 3 中,where_values 解决方法有时可以通过使用 where_values_hash 来改进,它可以更好地处理条件由多个 where 或哈希定义的范围(这里不是这种情况)。

has_many :planets, conditions: Planet.life_supporting.where_values_hash

【讨论】:

  • 您能详细介绍一下 rails 3 解决方案吗?铁轨 4 一个太干净了!
  • 对于 Rails 3,我认为应该阅读 has_many :planets, conditions: Planet.life_supporting.where_values_hash 来强制执行范围。这也是急切加载的黄金。
  • 我发现 where_values_hash 不适用于文本 where 子句,例如User.where(name: 'joe').where_values_hash 将返回预期条件的哈希值,而 User.where('name = ?', 'Joe').where_values_hash 不会。因此,行星示例可能会失败。
  • @nerfologist 感谢您指出上一个代码示例中的错误,我编辑了答案。您的第二条评论是有道理的,我认为这就是我在答案的最后一段中所暗示的。如果您能找到更清晰的方法来解释这些限制,请随时编辑我的答案。
  • @GrégoireClermont 这不再在 Rails 5 中工作
【解决方案2】:

在 Rails 5 中,以下代码可以正常工作...

  class Order 
    scope :paid, -> { where status: %w[paid refunded] }
  end 

  class Store 
    has_many :paid_orders, -> { paid }, class_name: 'Order'
  end 

【讨论】:

  • 太棒了! -&gt; { paid } 是我努力工作的结果。谢谢!!
【解决方案3】:

我刚刚深入研究了 ActiveRecord,但目前has_many 的实现似乎无法实现这一点。您可以将一个块传递给:conditions,但这仅限于返回条件哈希,而不是任何类型的东西。

实现您想要的(我认为您正在尝试做的)的一种非常简单且透明的方法是在运行时应用范围:

  # foo.rb
  def bars
    super.baz
  end

这与您的要求相去甚远,但它可能会起作用;)

【讨论】:

  • 谢谢菲特!这起作用,但是在代码审查中它看起来有点奇怪。我认为在实施此操作时我会得到的反馈是在 has_many 声明上进行重复,因为它更清晰。
  • 从我的代码审查的角度来看,我更喜欢这个选项而不是重复条件等。只要你提供一个测试来说明它的用途,这种方法更加干燥和 SRP跨度>
  • 我强烈建议不要在通常使用关联的情况下使用这种方法。相反,我会从关联中删除条件和在关联上显式调用的范围。以后会更易维护、更清晰。
  • 糟糕,刚刚发现这篇文章真的老了。来到这里研究类似的问题。
猜你喜欢
  • 2017-11-11
  • 1970-01-01
  • 2014-02-25
  • 2012-02-13
  • 2011-09-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-08
相关资源
最近更新 更多