README.rdoc

MetaSearch

MetaSearch is extensible searching for your form_for enjoyment. It “wraps”
one of your ActiveRecord models, providing methods that allow you to build
up search conditions against that model, and has a few extra form helpers
to simplify sorting and supplying multiple parameters to your condition
methods as well.

NOTE

The successor to MetaSearch is Ransack. It's got features
that MetaSearch doesn't, along with some API changes. I haven't had
the time to dedicate to making it bulletproof yet, so I'm releasing a
1.1.x branch of MetaSearch to help with migrations to Rails 3.1.

This is intended to be a stopgap measure.

t's important to note that the long-term migration path for your apps
should be toward Ransack, which is written in a more sane manner that will
make supporting new versions of Rails much easier going forward.

Options for the search method are documented at
MetaSearch::Searches::ActiveRecord.

“Wheres”, and what they're good for

Wheres are how MetaSearch does its magic. Wheres have a name (and possible
aliases) which are appended to your model and association attributes. When
you instantiate a MetaSearch::Builder against a model (manually or by
calling your model's search method) the builder responds to
methods named for your model's attributes and associations, suffixed by
the name of the Where.

These are the default Wheres, broken down by the types of ActiveRecord
columns they can search against:

…you might end up with attributes like title_contains,
comments_title_starts_with, moderations_value_less_than,
author_name_equals, and so on.

Additionally, all of the above predicate types also have an _any and _all
version, which expects an array of the corresponding parameter type, and
requires any or all of the parameters to be a match, respectively. So:

Article.search :author_name_starts_with_any => ['Jim', 'Bob', 'Fred']

will match articles authored by Jimmy, Bobby, or Freddy, but not Winifred.

Advanced usage

Narrowing the scope of a search

While the most common use case is to simply call Model.search(params), there may be times where
you want to scope your search more tightly. For instance, only allowing
users to search their own projects (assuming a current_user method
returning the current user):

@search = current_user.projects.search(params[:search])

Or, you can build up any relation you like and call the search method on
that object:

ORed conditions

Only one match type is supported. You can't do
title_matches_or_description_starts_with for instance.

If you're matching across associations, remember that the associated
table will be INNER JOINed, therefore limiting results to those that at
least have a corresponding record in the associated table.

Compound conditions (any/all)

All Where types automatically get an “any” and “all” variant. This has the
same name and aliases as the original, but is suffixed with _any and _all,
for an “OR” or “AND” search, respectively. So, if you want to provide the
user with 5 different search boxes to enter possible article titles:

…you can do this in your form to search your companies by developers with
certain notes:

<%= f.text_field :developers_notes_note_contains %>

You can travel forward and back through the associations, so this would
also work (though be entirely pointless in this case):

<%= f.text_field :developers_notes_developer_company_name_contains %>

However, to prevent abuse, this is limited to associations of a total
“depth” of 5 levels. This means that while starting from a Company model,
as above, you could do Company -> :developers -> :notes ->
:developer -> :company, which has gotten you right back where you
started, but “travels” through 5 models total.

In the case of polymorphic belongs_to associations, things work a bit
differently. Let's say you have the following models:

Your first instinct might be to set up a text field for
:commentable_body_contains, but you can't do this. MetaSearch would
have no way to know which class lies on the other side of the polymorphic
association, so it wouldn't be able to join the correct tables.

Instead, you'll follow a convention Searchlogic users are already
familiar with, using the name of the polymorphic association, then the
underscored class name (AwesomeClass becomes awesome_class), then the
delimiter “type”, to tell MetaSearch anything that follows is an attribute
name. For example:

<%= f.text_field :commentable_article_type_body_contains %>

If you'd like to match on multiple types of polymorphic associations,
you can join them with _or_, just like any other conditions:

Accessing custom search methods (and named scopes!)

MetaSearch can be given access to any class method on your model to extend
its search capabilities. The only rule is that the method must return an
ActiveRecord::Relation so that MetaSearch can continue to extend the search
with other attributes. Conveniently, scopes (formerly “named scopes”) do
this already.

MetaSearch needs us to tell it that we don't want to keep the array
supplied to it as-is, but “splat” it when passing it to the model method.
Regarding :types: In this case, ActiveRecord would have been smart
enough to handle the typecasting for us, but I wanted to demonstrate how we
can tell MetaSearch that a given parameter is of a specific database
“column type.” This is just a hint MetaSearch uses in the same way it does
when casting “Where” params based on the DB column being searched. It's
also important so that things like dates get handled properly by
FormBuilder.

multiparameter_field

The example Where above adds support for a “between” search, which requires
an array with two parameters. These can be passed using Rails
multiparameter attributes. To make life easier, MetaSearch adds a helper
for this:

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. See
MetaSearch::Helpers::FormBuilder for more info.

checks and collection_checks

If you need to get an array into your where, and you don't care about
parameter order, you might choose to use a select or collection_select with
multiple selection enabled, but everyone hates multiple selection boxes.
MetaSearch adds a couple of additional helpers, checks and
collection_checks to handle multiple selections in a more visually
appealing manner. They can be called with or without a block. Without a
block, you get an array of MetaSearch::Check objects to do with as you
please.

Sorting columns

If you'd like to sort by a specific column in your results (the
attributes of the base model) or an association column then supply the
meta_sort parameter in your form. The parameter takes the form
column.direction where column is the column name or
underscore-separated association_column combination, and direction
is one of “asc” or “desc” for ascending or descending, respectively.

Normally, you won't supply this parameter yourself, but instead will
use the helper method sort_link in your views, like so:

<%= sort_link @search, :title %>

Or, if in the context of a form_for against a MetaSearch::Builder:

<%= f.sort_link :title %>

The @search object is the instance of MetaSearch::Builder you got
back earlier from your controller. The other required parameter is the
attribute name itself. Optionally, you can provide a string as a 3rd
parameter to override the default link name, and then additional hashed for
the options and html_options hashes for link_to.

By default, the link that is created will sort by the given column in
ascending order when first clicked. If you'd like to reverse this (so
the first click sorts the results in descending order), you can pass
+:default_order => :desc+ in the options hash, like so:

You can then do sort_link @search, :custom_name and it will work
as you expect.

All sort_link-generated links will have the CSS class sort_link,
as well as a directional class (ascending or descending) if the link is for
a currently sorted column, for your styling enjoyment.

This feature should hopefully help out those of you migrating from
Searchlogic, and a thanks goes out to Ben Johnson for the HTML entities
used for the up and down arrows, which provide a nice default look.

Including/excluding attributes and associations

If you'd like to allow only certain associations or attributes to be
searched, you can do so inside your models

Then your call to Article.search will allow
:comments_body_contains but not :comments_user_id_equals
to be passed.

Conditional access to searches

search_methods, attr_searchable,
attr_unsearchable, assoc_searchable, and
assoc_unsearchable all accept an :if option. If present,
it should specify a Proc (or other object responding to call) that
accepts a single parameter. This parameter will be the instance of the
MetaSearch::Builder that gets created by a call to Model.search. Any unused
search options (the second hash param) that get passed to Model.search will
be available via the Builder object's options reader, and can
be used for access control via this proc/object.

First, MetaSearch will use a key found under
meta_search.attributes.model_name.attribute_name, if it exists. As a
fallback, it will use a localization based on the predicate type, along
with the usual ActiveRecord attribute localization (the
activerecord.attributes.model_name keys above). Additionally, a localized
“or” can be specified for multi-column searches.

Contributions

Better yet, if you’re so inclined, fix the issue yourself and submit a
patch! Or you can fork the
project on GitHub and send me a pull request (please include tests!)

If you like MetaSearch, spread the word. More users == more eyes on code ==
more bugs getting found == more bugs getting fixed (hopefully!)

Lastly, if MetaSearch has saved you hours of development time on your
latest Rails gig, and you’re feeling magnanimous, please consider making a donation to the
project. I have spent hours of my personal time coding and supporting
MetaSearch, and your donation would go a great way toward justifying that
time spent to my loving wife. :)