Baike.dev
All toolsOnline toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
S

squeel

> 编程语言
Open source

Active Record, improved. Live again :)

2.4K stars0 likes2 views
WebsiteGitHub

About

Active Record, improved. Live again :)

Archived

This project is archived.

Squeel

Squeel lets you write your Active Record queries with fewer strings, and more Ruby, by making the Arel awesomeness that lies beneath Active Record more accessible.

Squeel lets you rewrite...

ruby
Article.where ['created_at >= ?', 2.weeks.ago]

...as...

ruby
Article.where{created_at >= 2.weeks.ago}

This is a good thing. If you don't agree, Squeel might not be for you. The above is just a simple example -- Squeel's capable of a whole lot more. Keep reading.

Getting started

In your Gemfile:

ruby
gem "squeel"  # Last officially released gem
# gem "squeel", :git => "git://github.com/activerecord-hackery/squeel.git" # Track git repo

Then bundle as usual.

If you'd like to customize Squeel's functionality by enabling core extensions for hashes or symbols, or aliasing some predicates, you can create a sample initializer with:

bash
$ rails g squeel:initializer

The Squeel Query DSL

Squeel enhances the normal Active Record query methods by enabling them to accept blocks. Inside a block, the Squeel query DSL can be used. Note the use of curly braces in these examples instead of parentheses. {} denotes a Squeel DSL query.

Stubs and keypaths are the two primary building blocks used in a Squeel DSL query, so we'll start by taking a look at them. Most of the other examples that follow will be based on this "symbol-less" block syntax.

An important gotcha, before we begin: The Squeel DSL works its magic using instance_eval. If you've been working with Ruby for a while, you'll know immediately that this means that inside a Squeel DSL block, self isn't the same thing that it is outside the block.

This carries with it an important implication: Instance variables and instance methods inside the block won't refer to your object's variables/methods.

Don't worry, Squeel's got you covered. Use one of the following methods to get access to your object's methods and variables:

  1. Assign the variable locally before the DSL block, and access it as you would normally.
  2. Supply an arity to the DSL block, as in Person.where{|q| q.name == @my_name} Downside: You'll need to prefix stubs, keypaths, and functions (explained below) with the DSL object.
  3. Wrap the method or instance variable inside the block with my{}. Person.where{name == my{some_method_to_return_a_name}}

Stubs

Stubs are, for most intents and purposes, just like Symbols in a normal call to Relation#where (note the need for doubling up on the curly braces here, the first ones start the block, the second are the hash braces):

ruby
Person.where{{name => 'Ernie'}}
# => SELECT "people".* FROM "people"  WHERE "people"."name" = 'Ernie'

You normally wouldn't bother using the DSL in this case, as a simple hash would suffice. However, stubs serve as a building block for keypaths, and keypaths are very handy.

KeyPaths

A Squeel keypath is essentially a more concise and readable alternative to a deeply nested hash. For instance, in standard Active Record, you might join several associations like this to perform a query:

ruby
Person.joins(:articles => {:comments => :person})
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    INNER JOIN "comments" ON "comments"."article_id" = "articles"."id"
#    INNER JOIN "people" "people_comments" ON "people_comments"."id" = "comments"."person_id"

With a keypath, this would look like:

ruby
Person.joins{articles.comments.person}

A keypath can exist in the context of a hash, and is normally interpreted relative to the current level of nesting. It can be forced into an "absolute" path by anchoring it with a ~, like:

ruby
~articles.comments.person

This isn't quite so useful in the typical hash context, but can be very useful when it comes to interpreting functions and the like. We'll cover those later.

Predicates

All of the Arel "predication" methods can be accessed inside the Squeel DSL, via their method name, an alias, or an an operator, to create Arel predicates, which are used in WHERE or HAVING clauses.

SQL Predication Operator Alias
= eq ==
!= not_eq != (1.9 only), ^ (1.8)
LIKE matches =~ like
NOT LIKE does_not_match !~ (1.9 only) not_like
< lt <
<= lteq <= lte
> gt >
>= gteq >= gte
IN in >>
NOT IN not_in <<

Let's say we want to generate this simple query:

SELECT "people".* FROM people WHERE "people"."name" = 'Joe Blow'

All of the following will generate the above SQL:

ruby
Person.where(:name => 'Joe Blow')
Person.where{{name => 'Joe Blow'}}
Person.where{{name.eq => 'Joe Blow'}}
Person.where{name.eq 'Joe Blow'}
Person.where{name == 'Joe Blow'}

Not a very exciting example since equality is handled just fine via the first example in standard Active Record. But consider the following query:

sql
SELECT "people".* FROM people
WHERE ("people"."name" LIKE 'Ernie%' AND "people"."salary" < 50000)
  OR  ("people"."name" LIKE 'Joe%' AND "people"."salary" > 100000)

To do this with standard Active Record, we'd do something like:

ruby
Person.where(
  '(name LIKE ? AND salary < ?) OR (name LIKE ? AND salary > ?)',
  'Ernie%', 50000, 'Joe%', 100000
)

With Squeel:

ruby
Person.where{(name =~ 'Ernie%') & (salary < 50000) | (name =~ 'Joe%') & (salary > 100000)}

Here, we're using & and | to generate AND and OR, respectively.

There are two obvious but important differences between these two code samples, and both of them have to do with context.

  1. To read code with SQL interpolation, the structure of the SQL query must first be considered, then we must cross-reference the values to be substituted with their placeholders. This carries with it a small but perceptible (and annoying!) context shift during which we stop thinking about the comparison being performed, and instead play "count the arguments", or, in the case of named/hash interpolations, "find the word". The Squeel syntax places both sides of each comparison in proximity to one another, allowing us to focus on what our code is doing.

  2. In the first example, we're starting off with Ruby, switching context to SQL, and then back to Ruby, and while we spend time in SQL-land, we're stuck with SQL syntax, whether or not it's the best way to express what we're trying to do. With Squeel, we're writing Ruby from start to finish. And with Ruby syntax comes flexibility to express the query in the way we see fit.

Predicate aliases

That last bit is important. We can mix and match predicate methods with operators and take advantage of Ruby's operator precedence or parenthetical grouping to make our intentions more clear, on the first read-through. And if we don't like the way that the existing predications read, we can create our own aliases in a Squeel configure block:

ruby
Squeel.configure do |config|
  config.alias_predicate :is_less_than, :lt
end
ruby
Person.where{salary.is_less_than 50000}.to_sql
# => SELECT "people".* FROM "people"  WHERE "people"."salary" < 50000

And while we're on the topic of helping you make your code more expressive...

Compound conditions

Let's say you want to check if a Person has a name like one of several possibilities.

ruby
names = ['Ernie%', 'Joe%', 'Mary%']
Person.where('name LIKE ? OR name LIKE ? OR name LIKE ?', *names)

But you're smart, and you know that you might want to check more or less than 3 names, so you make your query flexible:

ruby
Person.where((['name LIKE ?'] * names.size).join(' OR '), *names)

Yeah... that's readable, all right. How about:

ruby
Person.where{name.like_any names}
# => SELECT "people".* FROM "people"
#    WHERE (("people"."name" LIKE 'Ernie%' OR "people"."name" LIKE 'Joe%' OR "people"."name" LIKE 'Mary%'))

I'm not sure about you, but I much prefer the latter. In short, you can add _any or _all to any predicate method, and it would do what you expect, when given an array of possibilities to compare against.

Sifters

Sifters are like little snippets of conditions that take parameters. Let's say that you have a model called Article, and you often want to query for articles that contain a string in the title or body. So you write a scope:

ruby
def self.title_or_body_contains(string)
  where{title.matches("%#{string}%") | body.matches("%#{string}%")}
end

But then you want to query for people who wrote an article that matches these conditions, but the scope only works against the model where it was defined. So instead, you write a sifter:

ruby
class Article < ActiveRecord::Base
  sifter :title_or_body_contains do |string|
    title.matches("%#{string}%") | body.matches("%#{string}%")
  end
end

Now you can write...

ruby
Article.where{sift :title_or_body_contains, 'awesome'}
# => SELECT "articles".* FROM "articles"
#    WHERE ((
#      "articles"."title" LIKE '%awesome%'
#      OR "articles"."body" LIKE '%awesome%'
#    ))

... or ...

ruby
Person.joins(:articles).
       where{
         {articles => sift(:title_or_body_contains, 'awesome')}
       }
# => SELECT "people".* FROM "people"
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"
#    WHERE ((
#      "articles"."title" LIKE '%awesome%'
#      OR "articles"."body" LIKE '%awesome%'
#    ))

Or, you can just modify your previous scope, changing where to squeel:

ruby
def self.title_or_body_contains(string)
  squeel{title.matches("%#{string}%") | body.matches("%#{string}%")}
end

Subqueries

You can supply an ActiveRecord::Relation as a value for a predicate in order to use a subquery. So, for example:

ruby
awesome_people = Person.where{awesome == true}
Article.where{author_id.in(awesome_people.select{id})}
# => SELECT "articles".* FROM "articles"
#    WHERE "articles"."author_id" IN (SELECT "people"."id" FROM "people"  WHERE "people"."awesome" = 't')

Joins

Squeel adds a couple of enhancements to joins. First, keypaths can be used as shorthand for nested association joins. Second, you can specify join types (inner and outer), and a class in the case of a polymorphic belongs_to relationship.

ruby
Person.joins{articles.outer}
# => SELECT "people".* FROM "people"
#    LEFT OUTER JOIN "articles" ON "articles"."person_id" = "people"."id"
Note.joins{notable(Person).outer}
# => SELECT "notes".* FROM "notes"
#    LEFT OUTER JOIN "people"
#      ON "people"."id" = "notes"."notable_id"
#      AND "notes"."notable_type" = 'Person'

These can also be used inside keypaths:

ruby
Note.joins{notable(Person).articles}
# => SELECT "notes".* FROM "notes"
#    INNER JOIN "people" ON "people"."id" = "notes"."notable_id"
#      AND "notes"."notable_type" = 'Person'
#    INNER JOIN "articles" ON "articles"."person_id" = "people"."id"

You can refer to these associations when constructing other parts of your query, and they'll be automatically mapped to the proper table or table alias This is most noticeable when using self-referenti

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

Ruby

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category编程语言
PricingOpen source

> Related tools

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言