Recipe 8.11. Implementing a Live Search


Problem

You want to add a real-time search feature to your site. Instead of rendering the results of each query in a new page, you want to display continually updating results within the current page, as users enter their query terms.

Solution

Use Rails Ajax helpers to create a live search.

Your site allows users to search for books. The first thing you'll need for this is a Book model. Create it with:

$ Ruby script/generate model Book             

Then in the generated migration, create the books table and populate it with a few titles:

db/migrate/001_create_books.rb:

class CreateBooks < ActiveRecord::Migration   def self.up     create_table :books do |t|       t.column :title, :string     end     Book.create :title => 'Perl Best Practices'     Book.create :title => 'Learning Python'     Book.create :title => 'Unix in a Nutshell'     Book.create :title => 'Classic Shell Scripting'     Book.create :title => 'Photoshop Elements 3: The Missing Manual'     Book.create :title => 'Linux Network Administrator's Guide'     Book.create :title => 'C++ Cookbook'     Book.create :title => 'UML 2.0 in a Nutshell'     Book.create :title => 'Home Networking: The Missing Manual'     Book.create :title => 'AI for Game Developers'     Book.create :title => 'JavaServer Faces'     Book.create :title => 'Astronomy Hacks'     Book.create :title => 'Understanding the Linux Kernel'     Book.create :title => 'XML Pocket Reference'     Book.create :title => 'Understanding Linux Network Internals'   end   def self.down     drop_table :books   end end

Next, include the script.aculo.us and Prototype libraries in your layout using javascript_include_tag:

app/views/layouts/books.rhtml:

<html>   <head>     <title>Books</title>     <%= javascript_include_tag :defaults %>   </head>   <body>     <%= yield  %>   </body> </html>

Create a Books controller that defines index and search methods. The search method responds to Ajax calls from the index view:

app/controllers/books_controller.rb:

class BooksController < ApplicationController   def index   end   def get_results     if request.xhr?       if params['search_text'].strip.length > 0         terms = params['search_text'].split.collect do |word|             "%#{word.downcase}%"          end              @books = Book.find(           :all,              :conditions => [             ( ["(LOWER(title) LIKE ?)"] * terms.size ).join(" AND "),             * terms.flatten           ]                )              end            render :partial => "search"      else           redirect_to :action => "index"      end   end end

The index.rhtml view displays the search field and defines an observer on that field with the observe_field JavaScript helper. An image tag is defined as well, with its CSS display property set to none.

app/views/books/index.rhtml:

<h1>Books</h1> Search: <input type="text"  name="search" /> <img  src="/books/4/185/1/html/2//images/indicator.gif" style="display: none;" />   <div ></div> <%= observe_field 'search_form',   :frequency => 0.5,    :update => 'results',   :url => { :controller => 'books', :action=> 'get_results' },   :with => "'search_text=' + escape(value)",   :loading => "document.getElementById('spinner').style.display='inline'",   :loaded => "document.getElementById('spinner').style.display='none'" %>

Finally, create a partial to display search results as a bulleted list of book titles:

app/views/books/_search.rhtml:

<% if @books %>   <ul>     <% for book in @books %>       <li>             <%= h(book.title) %>       </li>        <% end %>   </ul> <% end %>

Discussion

When new users first arrive at your site, you don't have much time to make a first impression. You need to show them quickly that your site has what they're looking for. One way to make a good impression quickly is to provide a live search that displays query results while the search terms are being entered.

The solution defines an observer that periodically responds to text as it's entered into the search field. The call to observe_field takes the id of the element being observedthe search field in this case. The :frequency option defines how often the contents of the field are checked for changes.

When changes in the value of the search field are detected, the :url option specifies that the get_results is called with the search_text parameter specified by the :with option. The final two options handle the display of the "spinner" images, which indicate that a search is in progress. The image used in this context is typically an animated GIF. Any results returned are displayed in the element specified by the :update option.

The get_results method in the Book controller handles the XMLHttpRequests generated by the observer. This method first checks that the request is an Ajax call. If it isn't, a redirect is issued. If the request.xhr? test succeeds, then the search_text value of the params hash is checked for nonzero length after any leading or trailing whitespace is removed.

If params['search_text'] contains text, it's split on spaces, and the resulting array of words is stored in the terms variable. collect is also called on the array of words to ensure that each word is in lowercase.

The find method of the Book class does the actual search. The conditions option creates a number of SQL LIKE clauses, one for each word in the terms array. These SQL fragments are then joined together with AND to form a valid statement.

The array passed to the :conditions option has two elements. The first being the SQL with bind variable place holders (i.e., ?). The asterisk operator before terms.flatten expands the array returned by the flatten method into individual arguments. This is required because the number of bind parameters must match the number bind positions in the SQL string.

Finally, the _search.rhtml partial is rendered, displaying any contents in the @books array as an unordered list within the results div element in the index view.

Figure 8-11 demonstrates that multiple terms can produce a match regardless of their order.

Figure 8-11. A live search of books returning a list of matching titles


See Also

  • Section 8.9"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net