Scoped and Scope in Rails

Scoped and scope in rails

In ActiveRecord, all query building methods (like where, order, joins, limit and so forth) return a so called scope. Only when you call a kicker method like all or first the built-up query is executed and the results from the database are returned.

The scoped class method also returns a scope. The scope returned is by default empty meaning the result set would not be restricted in any way meaning all records would be returned if the query was executed.
You can use it to provide an "empty" alternative like in the query_by_date example by MurifoX.
Or you can use it to combine multiple conditions into one method call, like for example:

Model.scoped(:conditions => 'id < 100', :limit => 10, :order => 'title ASC')

# which would be equivalent to
Model.where('id < 100').limit(10).order('title ASC')

The scope class method allows you to define a class method that also returns a scope, like for example:

class Model
scope :colored, lambda {|col|
where(:color => col)
}
end

which can be used like this:

Model.colored

The nice thing with scopes is that you can combine them (almost) as you wish, so the following is absolutely possible:

Model.red.where('id < 100').order('title ASC').scoped(:limit => 10)

I also strongly suggest reading through http://guides.rubyonrails.org/active_record_querying.html

Rails Scoped has many through has many with scope

You need to actually define a second association:

class Project < ApplicationRecord
has_many :issues
has_many :open_issues,
-> { where(status: 'open') },
class_name: 'Issue'

has_many :assignees, through: :open_issues
end

A has_many through: association just takes the name of another association that it joins through. You cannot define an association that goes through an association extension which you are incorrectly referring to as a scope.

A scope is really just a class method which can be called on any relation (since it proxies to the class) while association extensions can only be called on association proxies.

If you want to actually create a scoped association, you need to pass a callable such as a lambda.

has_many :open_issues,
-> { where(status: 'open') },
class_name: 'Issue'

This really just applies a set of filters directly to the association itself.

something.xs.ys.for(z).bs

Is not actually compatible with how associations actually work in Rails. Associations are not callable on relations or association proxy objects - only on records themselves.

What is scope/named_scope in rails?

A scope is a subset of a collection. Sounds complicated? It isn't. Imagine this:

You have Users. Now, some of those Users are subscribed to your newsletter. You marked those who receive a newsletter by adding a field to the Users Database (user.subscribed_to_newsletter = true). Naturally, you sometimes want to get those Users who are subscribed to your newsletter.

You could, of course, always do this:

User.where(subscribed_to_newsletter: true).each do #something

Instead of always writing this you could, however, do something like this.

#File: users.rb
class User < ActiveRecord::Base
scope :newsletter, where(subscribed_to_newsletter: true)
#yada yada
end

If you're using Rails 4 or newer, do this instead:

#File: users.rb
class User < ActiveRecord::Base
scope :newsletter, -> { where(subscribed_to_newsletter: true) }
#yada yada
end

This allows you to access your subscribers by simply doing this:

User.newsletter.each do #something

This is a very simple example but in general scopes can be very powerful tools to easy your work.

Check out this link: API Description

With Rails 4, Model.scoped is deprecated but Model.all can't replace it

It seems that where(nil) is a real replacement of scoped, which works both on Rails 3 and 4. :(

Rails 5 - how to write a scope

You'll need to pass user or user_id in as an argument when you call the scope. You can define it like this:

scope :proponent,   ->(user){ where(user_id: user.id) }

or

def self.proponent(user)
where user_id: user.id
end

These really are the same thing.

Then calling it:

Proposal.proponent(user)
# => returns a list of proposals for the specific user

Note this is the same thing as saying

proposal = Proposal.find_by(...)
proposal.user.proposals

Rails - apply an array of scopes

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 < ApplicationRecord
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 < ApplicationRecord
def self.published
where(published: true)
end
end

So scope is mostly just a fancy way of creating a class method that is supposed to have certain behavior (i.e. return a relation) and any class method method that returns a relation is a scope. scope used to be something special but now they're just class methods and all the class methods are copied to relations to support chaining.

There is no way to know if method Model.m is a "real" scope that will return a relation or some random class method without running it and checking what it returns or manually examining its source code. The scopes method you seek is gone and will never come back.

You could try to blacklist every class method that you know is bad; this way lies bugs and madness.

The only sane option is to whitelist every class method that you know is good and is something that you want users to be able to call. Then you should filter the scopes array up in the controller and inside AssignableLearningObjectives::Collector. I'd check in both places because you could have different criteria for what is allowed depending on what information is available and what path you're taking through the code; slightly less DRY I suppose but efficiency and robustness aren't friends.

You could apply the scope whitelist in the AssignableLearningObjectives::Collector constructor or in available_objectives.


If you want something prettier than:

scopes.each{ |scope| objectives = objectives.send(scope) }
objectives

then you could use inject:

def available_objectives
objectives = assignable_objectives....
scopes.inject(objectives) { |objectives, scope| objectives.send(scope) }
end

Rails: delete on scoped association behaves differently than just delete from the association

When you call the method materials in the options relation it returns an Option relation instance. That's why it won't work.

It might work:

class Item < ActiveRecord::Base
has_many :item_options
has_many :options, through: :item_options
has_many :material_options, -> { materials }, through: :item_options, source: :option

Then:

Item.first.material_options.delete_all

What is the real benefit of scopes

ActiveRecord scopes are really just syntax sugar wrapped in a best practice, as noted already.

In the 2.x days of Rails, when they were called "named_scope", they mattered a bit more. They allowed easy chaining of conditions for generating a query. With the improvements in Rails 3.x with Arel, it is simple to create functions for query relations, as you noted. Scopes just provide a simple and elegant solutions for chainable, predefined queries. Having all the scopes at the top of a model improves the readability and helps shows how the model is used.



Related Topics



Leave a reply



Submit