Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Stop writing SQL in your ActiveRecord scopes: embrace Ruby!
branch: master

This branch is 64 commits behind samleb:master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.


Gem Version Dependencies Code Climate Build Status Coverage Status

Write beautiful and expressive ActiveRecord scopes without SQL

SexyScopes is a gem that adds syntactic sugar for creating ActiveRecord scopes in Ruby instead of SQL. This allows for more expressive, less error-prone and database independent conditions.

WARNING: This gem requires Ruby >= 1.9.2 and ActiveRecord >= 3.1.

Usage & Examples

Let's define a Product model with this schema:

# price     :integer
# category  :string
# visible   :boolean
# draft     :boolean
class Product < ActiveRecord::Base

Now take a look at the following scope:

scope :visible, where("category IS NOT NULL AND draft = ? AND price > 0", false)

Hum, lots of SQL, not very Ruby-esque...

With SexyScopes

scope :visible, where((category != nil) & (draft == false) & (price > 0))

Much better! Looks like magic? It's not.

category, draft and price in this context are methods representing your model's columns. They respond to Ruby operators (like <, ==, etc.) and can be combined with logical operators (& and |) to express complex predicates.

Let's take a look at another example with these relations:

# rating:  integer
class Post < ActiveRecord::Base
  has_many :comments

# post_id:  integer
# rating:   integer
class Comment < ActiveRecord::Base
  belongs_to :post

Now let's find posts having comments with a rating > 3.

Without SexyScopes

Post.joins(:comments).merge Comment.where("rating > ?", 3)
# ActiveRecord::StatementInvalid: ambiguous column name: rating

Because both Post and Comment have a rating column, you have to give the table name explicitly:

Post.joins(:comments).merge Comment.where("comments.rating > ?", 3)

Not very DRY, isn't it ?

With SexyScopes

Post.joins(:comments).where Comment.rating > 3

Here you have it, clear as day.

How does it work ?

SexyScopes is essentially a wrapper around Arel attribute nodes.

It introduces a ActiveRecord::Base.attribute(name) class method returning an Arel::Attribute object, which represent a table column with the given name, that is extended to support Ruby operators.

For convenience, SexyScopes dynamically resolves methods whose name is an existing table column: i.e. Product.price is actually a shortcut for Product.attribute(:price).

Please note that this mechanism won't override any of the existing ActiveRecord::Base class methods, so if you have a column named name for instance, you'll have to use Product.attribute(:name) instead of (which is the class actual name, i.e. "Product").

Here is a complete list of operators, and their Arel::Attribute equivalents:

  • For predicates:

    • ==: eq
    • =~: matches
    • !~: does_not_match
    • >=: gteq
    • > : gt
    • < : lt
    • <=: lteq
    • !=: not_eq
  • For combination

    • &: and
    • |: or
    • ~: not (unfortunately, unary prefix ! doesn't work with ActiveRecord)

Advanced Examples

# radius:  integer
class Circle < ActiveRecord::Base
  # Attributes can be coerced in arithmetic operations
  def self.perimeter
    2 * Math::PI * radius

  def self.area
    Math::PI * radius * radius

Circle.where Circle.perimeter > 42
# SQL: SELECT `circles`.* FROM `circles`  WHERE (6.283185307179586 * `circles`.`radius` > 42)
Circle.where Circle.area < 42
# SQL: SELECT `circles`.* FROM `circles`  WHERE (3.141592653589793 * `circles`.`radius` * `circles`.`radius` < 42)

class Product < ActiveRecord::Base
  predicate = (attribute(:name) == nil) & shoes shirts ))
  puts predicate.to_sql
  # `products`.`name` IS NULL AND NOT (`products`.`category` IN ('shoes', 'shirts'))

  # SQL: SELECT `products`.* FROM `products` WHERE `products`.`name` IS NULL AND 
  #      NOT (`products`.`category` IN ('shoes', 'shirts'))


Add this line to your application's Gemfile:

gem 'sexy_scopes'

And then execute:


Or install it yourself as:

gem install sexy_scopes

Then require it in your application code:

require 'sexy_scopes'


Report bugs or suggest features using GitHub issues.

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Add some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request


  • Document the sql_literal method and how it can be used to create complex subqueries
  • Handle associations (i.e. Post.comments == Comment.joins(:posts) ?)


SexyScopes is released under the MIT License.

Copyright (c) 2010-2013 Samuel Lebeau, See LICENSE for details.

Something went wrong with that request. Please try again.