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
Can't Use Compass After Installing It
Rails Collection_Select VS. Select
Ruby: What Does the Asterisk in "P *1..10" Mean
Rails Activeadmin - Custom Association Select Box
How to Run Ruby Tasks That Use My Rails Models
Strange Activerecord::Associationtypemismatch
How to Pass Arguments from the Parent Task to the Child Task in Rake
How to Return a Value from a Thread in Ruby
Rbenv Build Failed on Ubuntu 14.04
How to Load a Spec_Helper.Rb Automatically in Rspec 2
How to Log the Entire Trace Back of a Ruby Exception Using the Default Rails Logger
Comparing Times Only, Without Dates
$Redis Global Variable with Ruby on Rails