Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
Squirrel Updates for Rails 2.1, now with Named Scopes!
As a little follow up to the gotchas, I updated Squirrel. Its pagination is compatible with the latest will_paginate now. However, more important is the fact that you can use Squirrel blocks to build scopes:
1 |
User.scoped{ name.not =~ "Jon%" }.ordered.find(:all) |
It works just like scoped does normally, but it lets you give Squirrel-style blocks. This lets you keep all your nice named_scope methods and it lets you do all the crazy crap that Squirrel makes look so much nicer at the same time.
A Refresher!
For those of you who may not have seen Squirrel before, it’s a plugin for ActiveRecord that allows for a more Ruby-ish syntax when specifying queries. It’s especially handy for advanced searches and the like, where lots of joins and conditionals are required. It makes those a lot more easy to read, and makes absolutely sure that it uses the right names to refer to the right columns, which isn’t always as straightforward as you might think on a complex join.
Sadly, we can’t actually use named_scope with a Squirrel block; it’s already using blocks for other things. However, we can do the next best thing, which is creating class methods on our models. Since named_scopes proxy all their methods back to where they were created, everything will work out fine.
Let’s say we had an app that lets users put up postings for a length of time. We can also tag those postings. We want to allow people to search for the postings, so we whip up a Squirrel query like so:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class Post named_scope :ordered, :order => "created_on DESC" named_scope :limit, lambda{|x| {:limit => x} } named_scope :after, lambda{|d| {:conditions => ["posts.created_on > ?", d]} } def self.search(params) scoped do any do title.contains? params[:keyword] body.contains? params[:keyword] end expired == false unless params[:all] == "1" created_on > params[:timeframe].to_i.days.ago params[:tag].split(",").each do |tag| tags.name == tag.strip end end end end |
This lets us chain search alongside the limit and ordered scopes (using some params that look like they came in a Rails request):
1 2 3 4 |
Post.search({:all => "1", :tag => "one, two", :keyword => "Something", :timeframe => ""}).ordered.limit(5) |
This performs the following query:
1 2 3 4 5 6 7 |
SELECT DISTINCT "posts".id FROM "posts"
LEFT OUTER JOIN "tags" ON tags.post_id = posts.id
WHERE ((posts.created_on > '2008-06-25 15:13:36'
AND (posts.title LIKE '%Something%' OR posts.body LIKE '%Something%')
AND (tags.name = 'one') AND (tags.name = 'two')))
ORDER BY created_on DESC
LIMIT 5
|
Now let’s say those Posts have Comments (and both of those belong_to :user). We can find all Posts that have Comments by their author like so:
1 2 3 |
def Post.with_author_comments scoped { user.id == comments.user.id } end |
So to find all the Posts in the last week that have a comment by their Author, we can say:
1 |
Post.with_author_comments.after(7.days.ago) |
Which gets us this lovely bit of SQL:
1 2 3 4 5 6 7 8 |
SELECT
... 32 aliases snipped ...
FROM "posts"
LEFT OUTER JOIN "comments" ON comments.post_id = posts.id
LEFT OUTER JOIN "users" ON "users".id = "comments".user_id
LEFT OUTER JOIN "users" users_posts ON "users_posts".id = "posts".user_id
WHERE ((posts.created_at > '2008-06-18 15:35:27')
AND (((users_posts.id = users.id))))
|
And we can even find all the Users who have never made a Comment this way:
1 2 3 |
def User.without_comments scoped { comments.id.nil? } end |
Each of these works exactly like any other named_scope and can be used in any situation where a named_scope could also be used.
Update: After I hit publish on this, I noticed that the Squirrel-style named scopes need to be specified first in the chain or they clobber what came before, but otherwise work fine. Using blocks with scoped not using the “named” methods also works completely fine. I’ll be looking into why this is and I’ll have an update soon. So, you can use them almost exactly the same as regular named scopes, anyway.
You can get the latest on Squirrel’s github page. Like our other plugins, we’re really hoping everyone will use the git repo, but in case you can’t or won’t, we also have the SVN repo available.
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Jon Yurek
- Published:
- June 25th 04:07 PM
- Updated:
- June 25th 04:28 PM
- Sections:
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; factory_girl a replacement for Rails fixtures; Jester, a REST/ActiveResource client written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.

2 comments
Jump to comment form