«

»

Nov 17

ActiveRecord like searching object in Rails form helpers

Ruby is a wonderful language. And Rails is a wonderful framework.
Why ? Because of Duck typing !!!

Example :

You build a database application for a book store

Books are classified by :

  • Genre
  • Author
  • Country

On a search page, you can search with a title text field, author select box, genre checkboxes.
So we want to create a search form with all the fields described above :

<%= form_tag('/search') do %>
  <p>
    <label for="title">title</label>
    <%= text_field_tag 'title' %>
  </p>
  <p>
    <label for="author">Author</label>
    <%= select_tag 'author',  options_for_select @authors, @selected_author %>
  </p>
  <% @genres.each do |g| %>
    <p>
      <label for="<%= 'genre_' + g.id.to_s %>"><%= g.label %></label>
      <%= check_box_tag 'genre[]', g.id, g.id == @selected_genre, :id => "genre_#{g.id}" %>
    </p>
  <% end %>
<% end %>

When I submit the code, it does not keep the value of the search fields, as it is when passing activerecord object in form helpers (with text_field not text_field_tag.)
Ok!! And what if I construct an object behaving like activerecord object ?
My template will be like this :

<%= form_for @search do |f| %>
  <p>
    <%= f.label :title, 'Title' %>
    <%= f.text_field :title %>
  </p>
  <p>
    <%= f.label :author, 'Author' %>
    <%= f.select :author, @authors %>
  </p>
 
  <% @genres.each do |g| %>
    <p>
      <label for="<%= 'genre_' + g.id.to_s %>"><%= g.label %></label>
      <%= check_box_tag 'search[genre][]', g.id, g.id == @selected_genre, :id => "genre_#{g.id}" %>
    </p>
  <% end %>
  <%= submit_tag "Search" %>
<% end %>

Yes we have a namespace called “search” on all parameters such as “search[title]” but I think it is not ugly, instead the code will be more readable !!!
Ok, but what’s the magic ? How can I have an activerecord like search object ?

Simply like that :

# encoding: utf-8
class Search
  def initialize(params={})
    @params = Hash.new.merge(params['search'] || {})
  end 
 
  def method_missing(method_sym,*args)
    case method_sym.to_s
    when /^\[\]=?$/
      @params.send(method_sym,*args)
    when /^(.*)=$/
      @params.update($1,*args)
    else
      @params[method_sym.to_s]
    end
  end
 
end

In your controller, simply instantiate a @search variable with your parameters :

# app/controllers/search_controller.rb
 
def search
  @search = Search.new(params)
  # ....
end

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">