Docs Menu

Scoping

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 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 the country field is "Japan"

  • rock: Matches documents in which the value of the genre 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

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 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.

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.

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

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.

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

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

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

To learn more about customizing your Mongoid models, see the Model Your Data guides.