Ruby on Rails Activerecord Scopes VS Class Methods

Ruby on Rails ActiveRecord scopes vs class methods

From the fine guide:

14 Scopes
[...]

To define a simple scope, we use the scope method inside the class, passing the query that we'd like to run when this scope is called:

class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
end

This is exactly the same as defining a class method, and which you use is a matter of personal preference:

class Article < ActiveRecord::Base
def self.published
where(published: true)
end
end

Note in particular:

This is exactly the same as defining a class method, and which you use is a matter of personal preference

And a little further (the Rails3 guide says the same thing here BTW):

14.1 Passing in arguments
[...]

Using a class method is the preferred way to accept arguments for scopes.

So which you use is a matter of preference and it is even recommended that you use class methods for scopes that take arguments.

Using scope is mostly a notational issue. If you say scope :whatever then you're explicitly saying that whatever is meant to be a query builder; if you say def self.whatever then you're not implying anything about the intent of the whatever method, you're just defining some class method that may or may not behave like a scope.

Of course, 14.1 makes a mess of this notational distinction by recommending that you not use scope when your scope takes arguments. Also keep in mind that in Rails3 you could say:

scope :published, where(published: true)

so an argumentless scope was visually "clean" and terse but adding a lambda to handle arguments would make it look messier:

scope :pancakes, ->(x) { where(things: x) }

But Rails4 wants lambdas even for argumentless scopes the distinction makes even less sense now.

I suspect that the difference is historical at this point. Scopes were probably something special back in the before times but became plain old class methods in the Rails3 era to cut down on duplication and to better mesh with the new query interface that came with Rails3.


So you can skip scope and go straight to class methods if you wish. You're even encouraged to do so when your scope takes arguments.

ActiveRecord Rails 3 scope vs class method

There was more of a difference in Rails 2.x, since named_scopes did not execute your queries (so you could chain them), whereas class methods generally did execute the queries (so you could not chain them), unless you manually wrapped your query in a scoped(...) call.

In Rails 3, everything returns an ActiveRecord::Relation until you need the actual results, so scopes can be chained against class methods and vice versa (as long as the class methods return ActiveRecord::Relation objects, not some other object type (like a count)).

Generally, I use scope entries for simple one-liners to filter down my result set. However, if I'm doing anything complicated in a "scope" which may require detailed logic, lambdas, multiple lines, etc., I prefer to use a class method. And as you caught, if I need to return counts or anything like that, I use a class method.

Are scopes in rails class or instance methods?

Class methods on classes which inherit ActiveRecord::Base can be used also as scopes (on ActiveRecord Relation objects).

Since the module Visible was meant to be mixed into a model which inherits ActiveRecord::Base, its class method visible_to can be used also as a scope.

If this did not clear the issue, you can implement a scope which gets all adult users (age > 20) in the following ways:

class User < ActiveRecord::Base
scope :adult, lambda { where("age > ?", 20) } # with a scope

class << self
def adult # with class method
where("age > ?", 20)
end
end
end

And use it exactly the same with User.adult

Rails: What's the difference between lambda, scope and class method? What's best practice?

It's best to avoid using option 2. That code gets run immediately when your Rails app loads which is bad since it will always return the same value for any Time argument you use in it. That's because it isn't reevaluated every time it's called.

Option 1, as pointed out by musicnerd47, are lazy loaded and it is advisable that you pass lambdas to scopes in Rails 4 rather than doing option 2 since they are reevaluated every time called so they will return updated values.

So the only options would be 1 and 3. This is usually a matter of style that your team adheres to. In our company, we use option 1 when the code we pass to it is going to be an ActiveRecord query and we want it to output a query that can be chained. This is to ensure that an ActiveRecord::Relation object is returned every time we do our queries for multiple records. That would mean that they are always going to be chainable with other ActiveRecord::Relation methods and our other defined scopes.

We use option 3 if it's for behavior that doesn't need to be chained with other scopes.

Here's a good read on the matter of scopes and class_methods where he goes into detail and provides examples on the difference between scopes and class methods.
http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/

Scopes vs class methods in Rails 3

For simple use cases, one can see it as just being syntax sugar. There are, however, some differences which go beyond that.

One, for instance, is the ability to define extensions in scopes:

class Flower < ActiveRecord::Base
named_scope :red, :conditions => {:color => "red"} do
def turn_blue
each { |f| f.update_attribute(:color, "blue") }
end
end
end

In this case,turn_blueis only available to red flowers (because it's not defined in the Flower class but in the scope itself).

Scope vs Class Method in Rails 3

I think your invocation is incorrect. You should add so-called query method to execute the scope, such as all, first, last, i.e.:

order.line_items.unshipped.all

I've observed some inconsistencies, especially in rspec, that are avoided by adding the query method.

You didn't post your test code, so it's hard to say precisely, but my exeprience has been that after you modify associated records, you have to force a reload, as the query cache isn't always smart enough to detect a change. By passing true to the association, you can force the association to reload and the query to re-run:

order.line_items(true).unshipped.all

Accessing a class method from within an Active Record scope?

Scopes are same as class methods, so within the scope you are implicitly call another class methods. And your distinct_mechanics_sql is an instance method, to use it inside a scope declare it as:

def self.distinct_mechanics_sql

or

def Mechanics.distinct_mechanics_sql

or

class << self 
def distinct_mechanics_sql
...
end
end


Related Topics



Leave a reply



Submit