Scoping
On this page
Overview
In this guide, you can learn how to implement scopes into your Mongoid models. Scopes provide a convenient way to reuse common filter criteria. To learn more about creating filter criteria, see the Specify a Document Query guide.
You might implement scopes into your application to reduce repeated code if you are applying the same criteria to most queries.
Named Scopes
Named scopes are criteria defined at class load that are referenced by a provided name. Similar to filter criteria, they are lazily loaded and chainable.
This example defines a Band
model that includes the following named
scopes:
japanese
: Matches documents in which the value of thecountry
field is"Japan"
rock
: Matches documents in which the value of thegenre
field includes"rock"
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :japanese, ->{ where(country: "Japan") } scope :rock, ->{ where(:genres.in => [ "rock" ]) } end
Then, you can query by using the named scopes. The following query uses
the named scopes to match documents in which value of the country
field is "Japan"
and value of the genre
field includes
"rock"
:
Band.japanese.rock
Advanced Scoping
You can define Proc
objects and blocks in named scopes so that they
accept parameters and extend functionality.
This example defines a Band
model that includes the based_in
scope,
which matches documents in which the country
field value
is the specified value passed as a parameter:
class Band include Mongoid::Document field :name, type: String field :country, type: String scope :based_in, ->(country){ where(country: country) } end
Then, you can query by using the based_in
scope, as shown in the following
code:
Band.based_in("Spain")
Mongoid allows you to define a scope that shadows an existing class method, as shown in the following example:
class Band include Mongoid::Document def self.on_tour true end scope :on_tour, ->{ where(on_tour: true) } end
You can direct Mongoid to raise an error when a scope overwrites an
existing class method by setting the scope_overwrite_exception
configuration option to true
.
To learn more about this setting, see the Application Configuration guide.
Default Scopes
Default scopes are useful for cases where you apply the same
criteria to most queries. By defining a default scope, you specify these
criteria as the default for any queries that use the model. Default
scopes return Criteria
objects.
To create a default scope, you must define the default_scope
method
on your model class.
The following code defines the default_scope
method on the Band
model to only retrieve documents in which the active
field value is true
:
class Band include Mongoid::Document field :name, type: String field :active, type: Boolean default_scope -> { where(active: true) } end
Then, any queries on the Band
model pre-filter for documents in which the
active
value is true
.
Field Initialization
Specifying a default scope initializes the fields of new models to the values given in the default scope if those values are literals, such as boolean values or integers.
Note
Field and Scope Conflicts
If you provide a default value in a field definition and in the default scope, the value in the default scope takes precedence, as shown in the following example:
class Band include Mongoid::Document field :name, type: String field :on_tour, type: Boolean, default: true default_scope ->{ where(on_tour: false) } end # Creates a new Band instance in which "on_tour" is "false" Band.new
We do not recommend using dot notation to reference nested fields in default scopes. This can direct Mongoid to initialize unexpected fields in new models.
For example, if you define a default scope that references the
tour.year
field, a new model is initialized with the field
tour.year
instead of a tour
field with a nested object that
contains a year
field.
When querying, Mongoid interprets the dot notation correctly and matches documents in which a nested field has the specified value.
Associations
If you define a default scope on a model that is part of an association, you must reload the association to have scoping reapplied. This is necessary for when you change a value of a document in the association that affects its visibility when the scope is applied.
This example uses the following models:
class Label include Mongoid::Document field :name, type: String embeds_many :bands end class Band include Mongoid::Document field :name, type: String field :active, default: true embedded_in :label default_scope ->{ where(active: true) } end
Suppose you create a Label
model that contains an association to a
Band
in which the value of active
is true
. When you update
the active
field to false
, Mongoid still loads it despite the
default scope. To view the documents in the association with the scope
applied, you must call the reload
operator.
The following code demonstrates this sequence:
label = Label.new(name: "Hello World Records") band = Band.new(name: "Ghost Mountain") label.bands.push(band) label.bands # Displays the Band because "active" is "true" band.update_attribute(:active, false) # Updates "active" to "false" # Displays the "Ghost Mountain" band label.bands # => {"_id":"...","name":"Ghost Mountain",...} # Won't display "Ghost Mountain" band after reloading label.reload.bands # => nil
or and nor Query Behavior
Mongoid treats the criteria in a default scope the same way as any other
query conditions. This can lead to surprising behavior when using the
or
and nor
methods.
The following examples demonstrate how Mongoid interprets queries on models with a default scope:
class Band include Mongoid::Document field :name field :touring field :member_count default_scope ->{ where(touring: true) } end # Combines the condition to the default scope with "and" Band.where(name: 'Infected Mushroom') # Interpreted query: # {"touring"=>true, "name"=>"Infected Mushroom"} # Combines the first condition to the default scope with "and" Band.where(name: 'Infected Mushroom').or(member_count: 3) # Interpreted query: # {"$or"=>[{"touring"=>true, "name"=>"Infected Mushroom"}, {"member_count"=>3}]} # Combines the condition to the default scope with "or" Band.or(member_count: 3) # Interpreted query: # {"$or"=>[{"touring"=>true}, {"member_count"=>3}]}
To learn more about logical operations, see Logical Operations in the Specify a Query guide.
Disable Scope When Querying
You can direct Mongoid to not apply the default scope by using the
unscoped
operator, as shown in the following examples:
# Inline example Band.unscoped.where(name: "Depeche Mode") # Block example Band.unscoped do Band.where(name: "Depeche Mode") end
Override Default Scope at Runtime
You can use the with_scope
method to change the default scope in a
block at runtime.
The following model defines the named scope mexican
:
class Band include Mongoid::Document field :country, type: String field :genres, type: Array scope :mexican, ->{ where(country: "Mexico") } end
You can use the with_scope
method to set the mexican
named
scope as the default scope at runtime, as shown in the following code:
Band.with_scope(Band.mexican) do Band.all end
Class Methods
Mongoid treats class methods that return Criteria
objects
as scopes. You can query by using these class methods, as shown in
the following example:
class Band include Mongoid::Document field :name, type: String field :touring, type: Boolean, default: true def self.touring where(touring: true) end end Band.touring
Additional Information
To learn more about customizing your Mongoid models, see the Model Your Data guides.