Rails scopes and lambdas

06 Oct 2015

Recently I was asked a searching question that left me pondering. The questioner mentioned that he had been following The Rails Way and was puzzled by it’s emphasis on the use of lambdas in conjunction with Rails scopes.

At the time I could only offer a vague answer along the lines of it being a common idiom. I was not satisfied with my reply so committed to following up with a more definitive response.

Background

Ruby Lambdas

Firstly, let’s recap and clarify exactly what a Ruby lambda is.

The lambda method belongs to the Kernel module. Since Ruby 1.9 introduced an alternative syntax, a lambda can also be identified with the characters ->. So, the following two code blocks are equivalent:

  succ = ->(x) { x + 1 }  
  succ = lambda { |x| x + 1 }  

Another thing to note about lambdas is their arity, or how many arguments are required when they are called. Every lambda has a precise arity, which can be tested using the arity method as follows:

  ->(x) { x + 1 }.arity # => 1  

There is much more that can be said about lambdas but I’ll leave that to others. For example, Chapter 6, Methods, Procs, Lambdas, and Closures within _The Ruby Programming Language by David Flanagan and Yukihiro Matsumoto gives a thorough treatment despite the age of the book.

As a taste, let me quote:

Blocks are syntactic structures in Ruby; they are not objects, and cannot be manipulated as objects. It is possible, however, to create an object that represents a block. Depending on how the object is created, it is called a proc or a lambda. Procs have block-like behavior and lambdas have method-like behavior. Both, however, are instances of class Proc.

Rails Scopes

At the time of writing, the latest Rails version is 4.2.4. On the subject of Rails scopes it is worth reading what is said about them in the Rails guides.

It is also worth keeping in mind that Rails has undergone many changes in it’s history. Currently the Rails guides illustrate that this:

  class Article < ActiveRecord::Base  
    scope :published, -> { where(published: true) }  
  end  

is exactly the same as:

  class Article < ActiveRecord::Base  
    def self.published  
      where(published: true)  
    end  
  end  

Furthermore, the Rails Guides section about passing in arguments advises:

Using a class method is the preferred way to accept arguments for scopes.

In other words, this:

  class Article < ActiveRecord::Base  
    def self.created_before(time)  
      where("created_at < ?", time)  
    end  
  end  

is preferable to:

  class Article < ActiveRecord::Base  
    scope :created_before, ->(time) { where("created_at < ?", time) }  
  end  

If you look carefully and test the two styles, you will realise that the important part of the code is the where clause. So long as the scope or class method returns an object that is an ActiveRecord::Relation, it can be chained together with other scopes or class methods.

In essence, with Rails 4.2 there is no reason to use scopes together with lambdas in preference to class methods. It is a matter of personal preference.

So, why the emphasis on using scopes with lambdas?

To answer this we need to consider the history of Rails. It was around the time of version 2 that named scopes were introduced. They allowed the convenience of chaining scope calls together. Later, if my memory serves me correctly, in version 3, Arel was introduced. This reduced the importance of named scopes when it came to specifying a variety of conditions to return a collection of model objects.

Fast forward to today and, as the Rails Guides state, there is no need to use scopes with lambdas instead of their equivalent class methods, which are arguably easier to read. However, given that Rails has been around for over ten years now, there are lots of legacy Rails codebases, many of which contain a plethora of scopes. Also, there are plenty of seasoned Rails programmers who have formed the habit of using Rails scopes.

In Summary

Hopefully this post has done a better job of clarifying the use of lambdas in Rails scopes than my original fumbling reply during a conversation at last week’s Melbourne Ruby meetup.

Other posts

Previous post: Effective Collaboration using GitHub PRs

More recently: Digging deeper into Rails scopes

© 2024 Keith Pitty, all rights reserved.