MetaSearch, Object-based searching for Rails 3

Comments

This weekend, I spent some time playing around with Rails 3’s new Arel-based finder code, and put together a new object-based search gem for use with form_for. I’m calling it MetaSearch, both in reference to this domain, and all the metaprogramming fun in writing it. Here are the basics…

Getting Started

Add a line to your Gemfile:

      gem "meta_search"

In your controller, do something like:

      def index
        @search = Article.search(params[:search])
        @articles = @search.all
      end

And in your view, something like this:

      <% form_for :search, @search, :html => {:method => :get} do |f| %>
        <%= f.label :title_contains %>
        <%= f.text_field :title_contains %><br />
        <%= f.label :comments_created_at_greater_than, 'With comments after' %>
        <%= f.datetime_select :comments_created_at_greater_than,
            :include_blank => true %><br />

        <%= f.submit %>
      <% end %>

The search method your models gain is returning an instance of MetaSearch::Builder, which wraps your model and intelligently builds up an ActiveRecord::Relation based on your search parameters. It delegates to this relation for methods like all, count and so on, so for the most part, it will quack like a relation in terms of the typical things you might do in a controller, and won’t execute the query against the database until the data is needed.

By default, you will be able to search all persisted attributes (columns) in your model, and all of its associations, named by association (where applicable), attribute, and condition, joined by an underscore. The bundled conditions include the most common use cases (equals, contains, starts_with, ends_with, less_than, greater_than, and so on) but you can add your own custom “wheres” as well.

Adding a new Where

Create an initializer (/config/initializers/meta_search.rb, for instance), and place lines like this in it:

      MetaSearch::Where.add :between, :btw,
        {:condition => 'BETWEEN', :substitutions => '? AND ?'}

You can also set custom formatters for the parameters which are evaluated at runtime (to get things like ‘%param% and so on in your substitution params). Check out the rdoc for more info.

If your Where has multiple substitutions (like the one above), it will expect an array to be passed to it in the search, which means you’ll be using Rails multiparameter attributes. To make this a little bit easier, MetaSearch adds a FormBuilder method, multiparameter_field.

multiparameter_field

<%= f.multiparameter_field :moderations_value_between,
    {:field_type => :text_field}, {:field_type => :text_field}, :size => 5 %>

multiparameter_field works pretty much like the other FormBuilder helpers, but it lets you sandwich a list of fields, each in hash format, between the attribute and the usual options hash, which gets applied as a default to each field. You can set up type casting per attribute, as well. Check out the rdoc for more info.

The last little thing you might find useful is the ability to exclude certain attributes or associations from being searchable. Since searches are usually done via an HTTP get, it makes it really easy for people to experiment with creating custom queries using attributes that aren’t in your form. Ryan Bates outlined this in his Railscast on Searchlogic some time ago, and MetaSearch makes filtering these parameters very easy.

Excluding attributes and associations

If you’d like to prevent certain associations or attributes from being searchable, just merge the relevant options into your search:

      @search = Article.search(
        params[:search].merge(
          :search_options => {
            :exclude_associations => :comments,
            :exclude_attributes => [:created_at, :updated_at]
          }
        )
      )

Excluding certain attributes of an association is not (yet) supported. (Update)

That’s about it for now. Go do a gem install meta_search, read the docs, and try it out!

comments powered by Disqus