默认情况下在 Rails has_many 关系上使用作用域

假设我有以下课程

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

规定

  • 我做 没有想改变 Planet中的 scope :life_supporting

    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.planets的任何地方使用 solar_system.life_supporting_planets

两者都没有回答这个问题,所以我只是把它们放在这里,作为其他人遇到这种情况时的解决办法。

62234 次浏览

i just had a deep dive into ActiveRecord and it does not look like if this can be achieved with the current implementation of has_many. you can pass a block to :conditions but this is limited to returning a hash of conditions, not any kind of arel stuff.

a really simple and transparent way to achieve what you want (what i think you are trying to do) is to apply the scope at runtime:

  # foo.rb
def bars
super.baz
end

this is far from what you are asking for, but it might just work ;)

In Rails 4, Associations have an optional scope parameter that accepts a lambda that is applied to the Relation (cf. the doc for 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

In Rails 3, the where_values workaround can sometimes be improved by using where_values_hash that handles better scopes where conditions are defined by multiple where or by a hash (not the case here).

has_many :planets, conditions: Planet.life_supporting.where_values_hash

In Rails 5, the following code works fine...

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


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