Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use a scope by default on a Rails has_many relationship

Let's say I have the following classes

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 has a scope life_supporting and SolarSystem has_many :planets. I would like to define my has_many relationship so that when I ask a solar_system for all associated planets, the life_supporting scope is automatically applied. Essentially, I would like solar_system.planets == solar_system.planets.life_supporting.

Requirements

  • I do not want to change scope :life_supporting in Planet to

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

  • I'd also like to prevent duplication by not having to add to SolarSystem

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

Goal

I'd like to have something like

has_many :planets, :with_scope => :life_supporting

Edit: Work Arounds

As @phoet said, it may not be possible to achieve a default scope using ActiveRecord. However, I have found two potential work arounds. Both prevent duplication. The first one, while long, maintains obvious readability and transparency, and the second one is a helper type method who's output is explicit.

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 

Another solution which is a lot cleaner is to simply add the following method to SolarSystem

def life_supporting_planets   planets.life_supporting end 

and to use solar_system.life_supporting_planets wherever you'd use solar_system.planets.

Neither answers the question so I just put them here as work arounds should anyone else encounter this situation.

like image 287
Aaron Avatar asked Jul 24 '12 17:07

Aaron


People also ask

How does scope work in Rails?

Scopes are custom queries that you define inside your Rails models with the scope method. Every scope takes two arguments: A name, which you use to call this scope in your code. A lambda, which implements the query.

Why do we use scope in Rails?

Scopes are used to assign complex ActiveRecord queries into customized methods using Ruby on Rails. Inside your models, you can define a scope as a new method that returns a lambda function for calling queries you're probably used to using inside your controllers.

What is ActiveRecord :: Base in Rails?

ActiveRecord::Base indicates that the ActiveRecord class or module has a static inner class called Base that you're extending. Edit: as Mike points out, in this case ActiveRecord is a module... ActiveRecord is defined as a module in Rails, github.com/rails/rails/tree/master/activerecord/lib/…


2 Answers

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 
like image 179
user1003545 Avatar answered Jan 03 '23 02:01

user1003545


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  
like image 39
Martin Streicher Avatar answered Jan 03 '23 00:01

Martin Streicher