Home arrow Ruby-on-Rails arrow Page 3 - Browsing and Searching an Online Book Catalog

Browsing and Searching an Online Book Catalog

In this second part of a three-part series on building an online book catalog in Ruby-on-Rails, you'll learn how to set up the application to allow users to view book details and search for books. This article is excerpted from chapter four of the book Practical Rails Projects, written by Eldon Alameda (Apress; ISBN: 1590597818).

Author Info:
By: Apress Publishing
Rating: 5 stars5 stars5 stars5 stars5 stars / 1
May 18, 2010
  1. · Browsing and Searching an Online Book Catalog
  2. · Adding Links
  3. · Implementing the Search Books User Story
  4. · Updating the Integration Test

print this article

Browsing and Searching an Online Book Catalog - Implementing the Search Books User Story
(Page 3 of 4 )

An online bookstore, or any other e-commerce site for that matter, would be nothing without search functionality. For simple cases and low loads, it would be enough to just create SQL SELECTqueries from the search terms to find matching items. However, when the load gets higher and there is more than one table involved in the search, it is worthwhile to use a real full-text search engine for the search. In this chapter, we will use a full-text engine written in Ruby called Ferret (http://ferret.davebalmain.com/trac).

Using the Ferret Search Engine

Ferret is open source and uses the MIT license, so it should be a safe choice for any kind of Rails project. There are a couple of other engines available (notably Hyper Estraier and theacts_as_searchableRails plugin that uses it), but we’ll use Ferret in this chapter for several reasons:

  1. Using theacts_as_ferretRails plugin makes integrating Ferret with Rails applications really simple.
  2. Ferret is a full port of the more famous Java search engine Apache Lucene (http:// lucene.apache.org/), supporting its whole API. That makes Ferret an easy choice for former Java developers. 
  3. Ferret is reasonably fast, even though it’s written in a scripting language. Also, there are versions of Ferret where parts or all of the code are written in C, making it suitable for even the most challenging situations.

Installing Ferret is as easy as a single command:

$ sudo gem install ferret

The next step is to install theacts_as_ferretplugin. We could use Ferret directly, but why duplicate proven and tested code, especially since using the plugin also makes our own code a lot cleaner and less error-prone? You can install the plugin with the normal Railsplugincommand:

$ script/plugin install➥svn://projects.jkraemer.net/acts_as_ferret/ trunk/plugin/acts_as_ferret

A /home/george/projects/emporium/vendor/plugins/acts_as_ferret
A /home/george/projects/emporium/vendor/plugins/acts_as_ferret/LICENSE
A /home/george/projects/emporium/vendor/plugins/acts_as_ferret/rakefile
A /home/george/projects/emporium/vendor/plugins/acts_as_ferret/init.rb
A /home/george/projects/emporium/vendor/plugins/acts_as_ferret/lib
A /home/george/projects/emporium/vendor/plugins/➥




Exported revision 59.







Now that both Ferret andacts_as_ferretare installed, the only thing we need to make our books searchable is one line inapp/models/book.rb:

class Book < ActiveRecord::Base
  has_and_belongs_to_many :authors
  belongs_to :publisher

  acts_as_ferret :fields => [:title, :author_names]
# lots of omitted code


With that single line, we have made it possible to do fast searches on books according to their titles and authors.acts_as_ferretnow intercepts all create, update, and delete operations of theBookclass and updates its full-text index accordingly.

But wait a minute! There is no attributeauthor_namesin thebookstable. That is correct. Fortunately,acts_as_ferretcan index even objects’ instance method values, so we’ll add a method calledauthor_namesto theBook model class. Changeapp/models/book.rbas shown here:

class Book < ActiveRecord::Base
  has_and_belongs_to_many :authors
  belongs_to :publisher

  acts_as_ferret :fields => [:title, :author_names]
  file_column :cover_image

  validates_length_of :title, :in => 1..255
  validates_presence_of :publisher
  validates_presence_of :authors
  validates_presence_of :published_at
  validates_numericality_of :page_count, :only_integer => true
  validates_numericality_of :price
  validates_format_of :isbn, :with => /[0-9\-xX]{13}/
  validates_uniqueness_of :isbn

  def author_names
    self.authors.map do |a|
    end.join(", ") rescue ""


Theauthor_namesmethod iterates over all of the authors for a given book and returns their names separated by a comma. If there are no authors, it returns an empty string to avoid data type problems in the indexing code.

acts_as_ferretstores its indices in
index/[environment]inside your Rails application directory, so your tests won’t affect the indices used in development and production. That said, let’s create a unit test for theBookclass to make sure that the search works correctly. Opentest/unit/book_test.rband paste the following code after the existing tests:

def test_ferret

  assert Book.find_by_contents("Pride and Prejudice")

  assert_difference Book, :count do
book = Book.new(:title => 'The Success of Open Source',
              :published_at => Time.now, :page_count => 500,
              :price => 59.99, :isbn => '0-674-01292-5')
    book.authors << Author.create(:first_name => "Steven", :last_name => "Weber")
    book.publisher = Publisher.find(1)
    assert book.valid?

    assert_equal 1, Book.find_by_contents("Open Source").size
    assert_equal 1, Book.find_by_contents("Steven Weber").size

In the beginning of the test, we make sure that the Ferret index is up-to-date. Rails unit tests empty the test database before each test run, but the same doesn’t hold true for the index. Therefore it’s better to rebuild it so that we can be sure that we always have a similar index before we start running the tests.

Next, we use the class methodBook.find_by_contentsto search for a book that has “Pride and Prejudice” in either its title or authors. The result should be positive because there is a book with that name in the fixtures we created at the beginning of this chapter.

find_by_contentsis a class method created automatically byacts_as_ferret. It is the workhorse of the plugin, taking as its parameters a string of search terms, and returning an array of zero or more objects, just like the normalActiveRecord find(:all)andfind_all_by_*methods.

The last part of the test case tests that a new book is correctly added to the index and is found when searched. We have put this code inside anassert_differenceblock, just as we did in Chapter 2, to make sure that the book is also saved to the database. We run the test and see that our search engine is working like a dream.

Now that ourBookmodel supports fast search, it’s time to implement a search interface for our bookstore.

blog comments powered by Disqus

- Ruby-on-Rails Faces Second Security Flaw in ...
- Ruby 2.0 Prepped for February 2013 Release
- Why LinkedIn Switched from Ruby on Rails
- Adding Style with Action Pack
- Handling HTML in Templates with Action Pack
- Filters, Controllers and Helpers in Action P...
- Action Pack and Controller Filters
- Action Pack Categories and Events
- Logging Out, Events and Templates with Actio...
- Action Pack Sessions and Architecture
- More on Action Pack Partial Templates
- Action Pack Partial Templates
- Displaying Error Messages with the Action Pa...
- Action Pack Request Parameters
- Creating an Action Pack Registration Form

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 

Developer Shed Affiliates


© 2003-2018 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials