HasFinder — It’s Now Easier than ever to create complex, re-usable SQL queries

September 2, 2007 Pivotal Labs

HasFinder is an extension to ActiveRecord that makes it easier than ever to create custom find and count queries.

Let’s start with an example. Suppose you have an Article model; some articles are published, some are popular. Let’s declare finders for each of these notions. You may be tempted to write something like the following:

class Article < ActiveRecord::Base
  def self.published
    find(:all, :conditions => {:published => true})
  end

  def self.popular
    ...
  end
  ...
end

But there are serious limitations to this approach. How do you find articles that are BOTH popular and published? How can you easily paginate published articles?

HasFinder Features

Let’s define the equivalent finders using my new plugin, has_finder:

class Article < ActiveRecord::Base
  has_finder :published, :conditions => {:published => true}
  has_finder :popular, :conditions => ...
end

Query Composition

Now, you can elegantly compose queries:

Article.published.popular

This will return all articles that are both popular and published.

Calculations and Nested Finds

You can also easily paginate or call nested finders or do calculations:

Article.published.paginate(:page => 1)
Article.published.popular.count
Article.popular.find(:first)
Article.popular.find(:all, :conditions => {...})

Works with ActiveRecord has_many and has_many :through Associations

Furthermore, without any additional work, these finders will work with ActiveRecord associations.

class User
  has_many :articles
end

user.articles.popular.find(:first)
user.articles.published.popular.average(:view_count)

Finders are extendable just like ActiveRecord Associations

class Article
  has_finder :unpublished :conditions => {:published => false} do
    def published_all
      find(:all).map(&:publish)
    end
  end
end

Alternatively, you can use the :extend options:

class Article
  has_finder :unpublished, :conditions => ..., :extend => MyExtensionModule
end

Finders behave just like ActiveRecord Associations

For example, you can call #reload:

Article.published.popular.reload

Finders can take parameters

class Car
  has_finder :colored, lambda {|color| { :conditions => {:color => color} } }
end

Car.colored('red').paginate(:page => 1)

What makes HasFinder better than alternatives like scope_out and scoped_proxy?

There are already two plugins similar to HasFinder: scope_out and scoped_proxy. Both of them are excellent. In fact, Scoped Proxy was the model for HasFinder. Unfortunately, neither plugin provided all of the features I desired. Scope-Out lacks on-the-fly composition, a nice way to call a nested find, or the ability to do arbitrary calculations. Scoped Proxy is great, but it doesn’t work with regular ActiveRecord Associations, it is not extendable like ActiveRecord associations, and it doesn’t behave exactly like a regular ActiveRecord Association. Neither of them work out of the box with will_paginate. For all of these reasons and more, I rolled my own. It’s now available as a gem.

Installation

% gem install has_finder

Usage

In environment.rb:

gem 'has_finder'
require 'has_finder'

See the examples above for usage.

About the Author

Biography

Previous
Ruby quiz du jour
Ruby quiz du jour

Question: In Ruby, what does the following represent? -1.0/0...0 Post your guesses as comments. Answer t...

Next
Ten Things I Hate about Proxy Objects, Part I
Ten Things I Hate about Proxy Objects, Part I

Has Many Through Has Many Through Has Many Through ... In which the author relates several things he hates...