SearchableRecord

SearchableRecord is a small Ruby on Rails plugin that makes the parsing of query parameters from URLs easy for resources, allowing the requester to control the items (records) shown in the resource’s representation.

The implementation is a helper module (a mixin) for ActiveRecord models. It is used by including SearchableRecord module in a model.

The mixin provides a class method, SearchableRecord#find_queried, to the class that includes it. The method is a front-end to ActiveRecord::Base#find: it parses query parameters against the given rules and calls find accordingly, returning the results of find.

A usage example

The following example, although a bit contrived, allows the client to

  • limit the number of items as the result of the search (limit parameter),

  • set an offset for the items (offset parameter, intended to be used together with limit),

  • sort the items either in ascending (sort parameter) or descending (rsort parameter) order by items’ type and name,

  • to limit the result by matching only items that were update before (until parameter) or after (since parameter) a certain date, and

  • to limit the result by matching only items with certain kind of types (type parameter) or names (name parameter), or both (for a name, a conversion to the client supplied parameter must be applied before matching the name in the database).

First, we need resource items. Let us presume the application allows its clients to query Item type of resources:

class Item < ActiveRecord::Base
  include SearchableRecord
end

By including SearchableRecord module to Item, the method find_queried becomes available. The method can be called, for example, in ItemController to parse the client’s query parameters:

Item.find_queried(:all, query_params, rules, options)

In the beginning of this example, we stated requirements what the clients are allowed to query. These requirements are expressed as the following rules:

rules = {
  :limit    => nil,                 # key as a flag; the value for the key is not used
  :offset   => nil,                 # key as a flag
  :sort     => { "name" => "items.name", "created" => "items.created_at" },
  :rsort    => nil,                 # rsort is allowed according to rules in :sort (key as a flag)
  :since    => "items.created_at",  # cast parameter value as the default type
  :until    => "items.created_at",  # cast parameter value as the default type
  :patterns => { :type => "items.type", # match the pattern with the default operator and converter
                 :name => { :column    => "items.name",
                            :converter => lambda { |val| "%#{val.gsub('_', '.')}%" } } }
                                    # match the pattern with the default operator
}

These rules are fed to find_queried as the third argument.

In addition, the application may to require options to be passed to find:

options = {
  :include    => [ :owners ],
  :conditions => "items.flag = 'f'"
}

These can be supplied to find_queried as the fourth argument.

The second argument to find_queried is the query parameters ItemController receives. For example, the client uses the URL http://example-site.org/items?limit=5&offset=4&rsort=name&since=2008-02-28&name=foo_bar to fetch a representation of the application’s resource containing the items. The action results to the following parameters:

query_params = params

# => query_params = {
#      'offset' => '4',
#      'limit'  => '5',
#      'rsort'  => 'name',
#      'until'  => '2008-02-28',
#      'name'   => 'foo_bar',
#      ...
#      # plus Rails-specific parameters, such as 'action' and 'controller'
# }

With these query parameters and arguments, find_queried calls find with the following arguments:

Item.find(:all,
          :include => [ :owners ],
          :order   => "items.name desc",
          :offset  => 4,
          :limit   => 5,
          :conditions => [ "(items.flag = 'f') and (items.created_at <= cast(:until as datetime)) and (items.name like :name)",
                           { :until => "2008-02-28", :name => "%foo.bar%" } ])

This particular search results to at most 5 items that are

  • from offset 4 (that is, items from positions 5 to 9),

  • sorted in descending order by items’ names,

  • updated since 2008-02-28, and

  • have foo.bar in their name.

See find_queried method in SearchableRecord::ClassMethods for details.

Installation

In order to install the plugin as a Ruby gem for a Rails application, edit the environment.rb file of the application to contain the following line:

config.gem "searchable_record"

(This requires Rails version 2.1 or above.)

Then install the gem, either using the Rakefile of the Rails application:

$ rake gems:install

…or with the gem tool:

$ gem install searchable_record

Use git to get the source code for modifications and hacks:

$ git clone git://gitorious.org/searchable-rec/mainline.git

Contacting

Please send feedback by email to Tuomas Kareinen < tkareine (at) gmail (dot) com >.

Legal notes

This software is licensed under the terms of the “MIT license”:

Copyright © 2008, 2009 Tuomas Kareinen

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.