默认在Rails has_many关系上使用范围


81

假设我有以下课程

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关系,以便当我要求solar_system所有关联时planets,将life_supporting自动应用范围。本质上,我想solar_system.planets == solar_system.planets.life_supporting

要求

  • 希望改变scope :life_supportingPlanet

    default_scope where('distance_from_sun > ?', 5).order('diameter ASC')

  • 我还想避免重复,因为不必添加到 SolarSystem

    has_many :planets, :conditions => ['distance_from_sun > ?', 5], :order => 'diameter ASC'

目标

我想吃点类似的东西

has_many :planets, :with_scope => :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.life_supporting_planets任何使用的地方使用solar_system.planets

两者均未回答问题,因此如果有人遇到这种情况,我将它们放在此处作为解决方法。


2
使用where_vales的解决方法实际上是最好的解决方案,值得一个公认的答案
ViktorTrón13年

where_values可能不适用于哈希条件:{:cleared => false}...它提供了ActiveRecord不喜欢的哈希数组。作为一种技巧,抓住数组中的第一项是有效的:Planet.life_supporting.where_values[0]...
Nolan Amy

我发现我不得不使用where_ast而不是where_valueswhere_values_hash因为我已经在其他模型的范围内使用了AREL。工作了请客!+1
br3nt

Answers:


130

在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太干净了!
Augustin Riedinger 2014年

1
对于Rails 3,我认为应该阅读它has_many :planets, conditions: Planet.life_supporting.where_values_hash以加强范围。这也是渴望加载的黄金。
nerfologist

1
我发现了一种困难的方法,该方法where_values_hash不适用于文本where子句,例如User.where(name: 'joe').where_values_hash将返回预期条件的哈希,而User.where('name = ?', 'Joe').where_values_hash不会。因此,行星的例子很可能会失败。
nerfologist

1
@nerfologist感谢您指出上一个代码示例中的错误,我编辑了答案。您的第二条评论是有道理的,我认为这是我在答案的最后一段中提到的内容。如果您可以找到更清晰的方法来解释这些限制,请随时编辑我的答案。
user1003545 2015年

2
@GrégoireClermont,这不再适用于Rails 5
elquimista

16

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

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

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

1

我只是对ActiveRecord进行了深入研究,因此,如果使用的当前实现可以实现这一目标,这似乎并不可行has_many。您可以向传递一个块,:conditions但这仅限于返回条件的散列,而不是任何种类的arel东西。

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

  # foo.rb
  def bars
    super.baz
  end

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


谢谢大家!这起作用,但是在代码审查中看起来会有些奇怪。我认为实现此目标时得到的反馈是改为对has_many声明进行重复,因为这很清楚。
亚伦

从我的代码审查的角度来看,我宁愿选择此选项,也
不愿

我强烈建议您不要使用通常使用关联的此类方法。相反,我将从关联和在关联上显式调用的范围中删除条件。将来它将更加可维护和更加清晰。
BM5k 2013年

糟糕,才意识到这篇文章真的很老。在这里研究了类似的问题。
BM5k
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.