6 October 2009 · About 4 minutes read

Rails: Complex Queries with Named Scopes

I’ve recently come to realise the massive power of ActiveRecord’s named_scopes. The revelation came whilst I was researching how to scope, by their permissions, the records a @current_user can view or edit.

I’d looked at all sorts of plugins and complex authorization configurations, before realising that named scopes have almost limitless potential for building such complex queries.

What are named scopes?

As demonstrated by Ryan Bates’ screencast, named scopes let you declare finder conditions as methods on your model object.

For example, you could get all active users using

```ruby# app/models/user.rb

class User < ActiveRecord::Base

named_scope :active, :conditions => { :active => true }

end

To select all active users

User.active```

The real power of named scopes, though, lies in their ability to be chained. As they are just methods on your model class, they can be combined, and ActiveRecord will take care of building the relevant conditions and joins:

```ruby# app/models/user.rb

class User < ActiveRecord::Base

named_scope :active => :conditions => { :active => true }

named_scope :admin => :conditions => { :admin => true }

named_scope :recently_logged_in,

          lambda { { :conditions => ['logged_in_at >= ?', 7.days.ago]  } }

end

To get all active users who have recently logged in:

User.active.recently_logged_in

To get all active admin users who have recently logged in

User.active.admin.recently_logged_in```

Using Named Scopes to Build Complex Queries

With chaining, you can build some pretty complex queries with ease. However, it would be great if we were able to declare some conditions at run-time, rather than when the model class is loaded.

The lambda function lets you do just that, taking a set of arguments and inserting them into your query:

```ruby# app/models/user.rb

class User < ActiveRecord::Base

named_scope :admin, :conditions => { :admin => true }

named_scope :last_logged_in, lambda { time_ago
time_ago ||= 7.days.ago

{ :conditions => ['logged_in_at >= ?', time_ago] }

}

end

By default, we’ll get users who have logged in in the last 7 days:

User.last_logged_in

To get users who have logged in during the past 4 weeks:

User.last_logged_in(4.weeks.ago)

To get admin users who have logged in in the past 6 months

User.last_logged_in(6.months.ago).admin```

Combining lambda with named_scope lets you build complex attribute queries with runtime arguments. As an example, I’ve reworked my the code from my previous posts to make use of named_scope:

```ruby# app/models/user.rb

class User < ActiveRecord::Base

named_scope :name_like, lambda { terms
# ensure terms is an array

terms = [*terms]

composed_conditions = EZ::Caboose::Condition.new :users

terms.each do |term|

  term = ['%', term, '%'].join

  condition = EZ::Caboose::Condition.new :users

  condition.append ['first_name ILIKE ?', term], :or

  condition.append ['last_name ILIKE ?', term], :or

  composed_conditions << condition

end

{ :conditions => composed_conditions.to_sql }

}

end```

With this small chunk of code (and a helping hand from the Caboose EZ Condition plugin), you can now search for Users by matching their first or last names with the supplied terms, e.g:

```ruby# To search for users with a first or last name like “joe”

User.name_like(“joe”)

To search for users with a first or last name like “joe” and a first or last name like “bloggs”:

User.name_like([“joe”, “bloggs”])```

As we saw above, the greatest advantage of using named_scopes for this type of functionality is their ability to chain. With this in mind, I was able to solve my problem of scoping results within the permissions of @current_user:

```ruby# app/models/user.rb

class User < ActiveRecord

# …

named_scope :visible_to lambda { user
{ :conditions => ['department_id IN ?', user.managed_department_ids] }

}

end

User.last_logged_in(4.weeks.ago).name_like([“joe”, “bloggs”]).visible_to(@current_user).ordered_by(:name)```

Named scopes are undoubtedly one of the most powerful tools available to ActiveRecord. I can’t help but think I’m still just scratching the surface of their potential, but already I’ve been able to refactor pages of code and build powerful, Rails-friendly custom queries.

Are you using named scopes in your code? Feel free to discuss in the comments!

rails activerecord named-scope
Chris Blunt
Chris Blunt @cblunt
Chris is the founder of Plymouth Software. As well as code and business, he enjoys being a Dad, swimming, and the fine art of drinking tea.