Ruby On Rails: Named Scope and Default Scope.

akpojotor shemi
5 min readJan 9, 2020

--

Scope Images

In Ruby on Rails, named scopes are similar to class methods (“class.method”) as opposed to instance methods (“class#method”). Named scopes are short code defined in a model and used to query Active Record database. In this context, named scope are not the same as global or local scope in function and variable definition.

The use of named scopes encourages and supports separation of concerns in accordance with the MVC model, that is simple queries can be moved from the views and controller to the model.

Like Ruby class methods, to invoke the named scopes, simply call it on the class object. Named scopes takes two arguments; name and lambda.Use an expressive name. Lambda is a block of code. The block of code within lambda is executed when the scope is called and not when it is defined. Named scopes always return an ActiveRecord::Relation object.

A one model application is used to illustrate named scopes concepts. The tea model has the following attributes: name, rating and caffeine. The schema is showed below. The name column refers to tea favors, caffeine column takes true or false value and the rating column is the tea’s average consumer rating. The seed data is also included below.

class CreateTeas < ActiveRecord::Migration[6.0]
def change
create_table :teas do |t|
t.string :name
t.boolean :caffeine
t.integer :rating
t.timestamps
end
end
end
#seed dataTea.create(name:"ginger",rating:5,caffeine:true)
Tea.create(name:"turmeric",rating:4,caffeine:false)
Tea.create(name:"lemon",rating:3,caffeine:true)
Tea.create(name:"pomegranate",rating:2,caffeine:false)
Tea.create(name:"chamomile",rating:nil,caffeine:false)

In the tea model, the named scope caffeinated is used to query the database where the caffeine column or attributes is true. Using the dot notation, call the caffeinated on the Tea class; Tea.caffeinated returns all tea instances (tea flavors) containing caffeine. On the other hand, Tea.decaffeinated returns all caffeine-free tea instances. The named scopes and corresponding class methods is showed below but commented out. The Active Record where finder method is employed here.

class Tea < ApplicationRecord    scope :caffeinated, lambda {where(:caffeine=>true)}
scope :decaffeinated, lambda {where(:caffeine=>false)}

# def self.caffeinated
# self.where(:caffeine=>true)
# end
# def self.decaffeinated
# self.where(:caffeine=>false)
# end

end
## output from Tea.caffeinated
Tea Load (0.3ms) SELECT "teas".* FROM "teas" WHERE "teas"."caffeine" = ? LIMIT ? [["caffeine", 1], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">, #<Tea id: 3, name: "lemon", caffeine: true, rating: 3, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">]>## output from Tea.decaffeinated
Tea Load (0.4ms) SELECT "teas".* FROM "teas" WHERE "teas"."caffeine" = ? LIMIT ? [["caffeine", 0], ["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">, #<Tea id: 4, name: "pomegranate", caffeine: false, rating: 2, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">, #<Tea id: 5, name: "chamomile", caffeine: false, rating: nil, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:39">]

The next two named scope examples employ order, which is another Active Record finder method. Tea.sort_rating will return all tea instances ordered from the highest rating to lowest rating. Tea.sort_name will return all tea instances ordered alphabetically by tea name. The corresponding class methods is shown along side the named scopes below but commented out.

class Tea < ApplicationRecord    scope :sort_rating, lambda {order("rating desc")}
scope :sort_name, lambda {order("name desc")}

#def self.sort_rating
# self.order("rating desc")
#end
#def self.sort_named
# self.order("name desc")
#end
end## output from Tea.sort_rating.limit(2)
Tea Load (0.5ms)
SELECT "teas".* FROM "teas" ORDER BY rating desc LIMIT ? [["LIMIT", 2]]
=> #<ActiveRecord::Relation [#<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">, #<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">]>## output from Tea.sort_name.limit(2)
Tea Load (0.4ms) SELECT "teas".* FROM "teas" ORDER BY name desc LIMIT ? [["LIMIT", 2]]
=> #<ActiveRecord::Relation [#<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">, #<Tea id: 4, name: "pomegranate", caffeine: false, rating: 2, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">]>

Named scope could also be defined to accept arguments. To illustrate, we pass in an argument into name_search as shown below. Active Record then uses the name argument to search the database. The corresponding class methods is shown below but commented out.

class Tea < ApplicationRecord   scope :name_search, lambda {|query| where(["name == ?","#{query}"])}

#def self.name_search(query)
# where("name == ?", query)
#end
end## output from Tea.name_search("turmeric")Tea Load (0.3ms) SELECT "teas".* FROM "teas" WHERE (name == 'turmeric') LIMIT ? [["LIMIT", 11]]=> #<ActiveRecord::Relation [#<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">]>

We can chain Active Record finder methods. We could chain where, order and limit in a single query. For example, the recent_top_two_ratings named scope used multiple finder methods in its definition. Please see below.(side note: calling the touch method on a Tea instance will change the updated_at attribute).

class Tea < ApplicationRecord
scope :recent_top_two_ratings, lambda {where("rating >=2").order("updated_at").limit(2)}
end
## output from Tea.recent_top_two_ratingsTea Load (0.3ms) SELECT "teas".* FROM "teas" WHERE (rating >=2) ORDER BY updated_at LIMIT ? [["LIMIT", 2]]=> #<ActiveRecord::Relation [#<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">, #<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">]>

Because named scope always returns an ActiveRecord::Relation object, they can be also chained together just like the finder methods. See example below

##output from Tea.caffeinated.sort_rating(0.1ms)  SELECT sqlite_version(*)Tea Load (0.4ms)  SELECT "teas".* FROM "teas" WHERE "teas"."caffeine" = ? ORDER BY rating desc LIMIT ?  [["caffeine", 1], ["LIMIT", 11]]=> #<ActiveRecord::Relation [#<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">, #<Tea id: 3, name: "lemon", caffeine: true, rating: 3, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:53:10">]>

Default Scope

With default scopes, we can change/specify how records are retrieved by default from Active Record database. Any subsequent queries to the database will be retrieved/rendered following the default scope definition. For example, call the select Active Record finder method on the Tea class where rating is not exactly 3. The retrieved ActiveRecord::Relation will be arranged according to the default scope we defined. Use “unscoped” to break out of the default scope. Now, the updated_at is not in descending order.

class Tea < ApplicationRecord
default_scope lambda {order("updated_at desc")}
end
##output from Tea.select{|n|n.rating != 3 if n.rating.present?}Tea Load (0.4ms) SELECT "teas".* FROM "teas" ORDER BY updated_at desc=> [#<Tea id: 4, name: "pomegranate", caffeine: false, rating: 2, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:54:08">, #<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">, #<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">]##output from Tea.unscoped.select{|n|n.rating != 3 if n.rating.present?}Tea Load (0.3ms) SELECT "teas".* FROM "teas"=> [#<Tea id: 1, name: "ginger", caffeine: true, rating: 5, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:11:48">, #<Tea id: 2, name: "turmeric", caffeine: false, rating: 4, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:09:12">, #<Tea id: 4, name: "pomegranate", caffeine: false, rating: 2, created_at: "2020-01-09 13:09:12", updated_at: "2020-01-09 13:54:08">]

Summary

Named scope help your code to be more expressive and cleaner.

It always returns an ActiveRecord::Relation object. Therefore, named scopes are chainable .

Named scopes encourages and supports separation of concerns in accordance with the MVC model.

--

--