While working on my upcoming BigAuthor project, I came upon an interesting challenge that turned out to be so simple with MongoMapper that I just have to share it here. Need to create a hash of multiple filter parameters on a MongoMapper document using a form that is cleanly resource-based, using InheritedResources? It’s not as difficult as you might think…

In my MongoMapper::Document model, I have the following keys:

key :title, String
key :filters, Hash, :index => true

Now, I’m expecting to stick a Hash into ‘filters’ in the form:

{'colors' => ['red','green','blue'],
  'people' => ['joe','mary'],
  'tags' => 'dog cat vampire zombie'}

So, the idea is, the object that is saved with the above filters Hash provides a context for searching through another collection of objects (let’s call it OtherThings) using the filters. (think email rules in Outlook). Anyway, don’t worry too much about that. It’ll become more clear once I launch BigAuthor in a few weeks or so.

Using InheritedResources + Formtastic gives you a big advantage in creating resourceful controllers and their associated semantic forms really, really quickly.

The controller:

class ThingsController < InheritedResources::Base
  respond_to :html, :xml
end

Seriously. It’s that empty.

The ‘new’ form page (in HAML, of course):

%h1 Add a New Thing
- semantic_form_for @things do |form|
  - form.inputs do
    = form.input :title
  - form.semantic_fields_for :filters do |sub|
    - sub.inputs do
      = sub.input :tags
      = sub.input :colors, :collection => Thing::COLORS, :as => :check_boxes
      = sub.input :people, :collection => People.for_filters, :as => :check_boxes      
  - form.buttons do
    = form.commit_button :label => "Create Thing"

Again, that’s it. Really. The checkboxes that are checked get put into Arrays within a params['thing']['filters'] Hash and persist naturally to MongoDB through MongoMapper. No extra work needed.

In fact, the only extra thing I did was to write a short override into the create/update methods so that it strips the blanks from the internal filter arrays. (formtastic’s checkboxes post an empty string for every checkbox that is not checked, which in this case wasn’t what I wanted, but it might be fine or even required for your purposes)

BONUS!

Using Sunspot and Solr for your search solution in your MongoMapper app? Here’s a sample of the search method that my OtherThing class has for creating a search result set based on the filters:

def self.search_with_filters(filters)
  Sunspot.search(self) do
    keywords filters['tags']
    with(:color).any_of filters['colors']
    with(:people).any_of filters['people']
    order_by :created_at, :desc
  end
end

Exactly how my Sunspot/Solr is setup, I’ll leave for the reader to imagine, but don’t overthink it… it’s not complicated and the above works fast… really, really fast. So fast, that instead of pre-creating and caching the results sets, I just re-generate them on the fly, thereby ensuring they’re always up-to-the-second with the latest OtherThings that have been added to the database.

Once again, Mongo and MongoMapper blows me away with its simplicity, further supported by the brilliant inherited_resources and formtastic!

  1. ketan says:

    i done all config as in ur post
    it dose
    rake ts:config

    but error for
    rake ts:start
    Failed to start searchd daemon. Check log/searchd.log.
    Failed to start searchd daemon. Check log/searchd.log

    rake ts:in
    FATAL: no indexes found in config file ‘config/development.sphinx.conf’

    what should be the mistake

  2. Justin French says:

    Hi, for what it’s worth, Formtastic does the same as the basic Rails helpers when it comes to unchecked checkboxes.

    • M. E. Patterson says:

      Yeah, that’s true. Good point. Frankly, I usually want that functionality, because in most cases it’s important for working with a relational database to map 1-to-1 even with the ‘not checked’ boxes. In the case of jamming a Hash directly into Mongo, however, my usage just wants a hash with only the things that were checked, since I’m just going to check the whole hash right into the database as-is. It’s an interesting difference, since Rails (and its helpers) were clearly geared towards relational databases. One of those odd things about going the NoSQL route, eh?