views:

62

answers:

1

I am using this named_scope to search for products that have a description matching any word the user inputs.

E.g., Product.description_like_any("choc pret")

Will return products with names like

  • "Chocolate Bar"
  • "Chocolate Covered Pretzels"
  • "Miniature Chocolate Ponies"

Here's the named_scope I've written (which works)

named_scope :description_like_any, (lambda do |query|
  return {} unless query
  conditions = []
  values = []
  for q in query.split(/\s+/)
    conditions << "(`products`.description LIKE ?)"
    values << "%#{q}%"
  end
  { :conditions => [conditions.join(' AND '), *values] }
end)

Is there a better way to write this? Perhaps I'm missing a Rubyism/Railism or two?

Solution

Using scope_procedure in conjunction with Searchlogic, this can be done in an even easier way. Note, the solution before even leverages Searchlogic's _or_ syntax for connecting two scopes together. The :keywords scope_procedure finds products matching product.description, or product.vendor.name; All with one text field!

Model

# app/models/product.rb
class Product < ActiveRecord::Base
  scope_procedure :keywords, lambda |query|
    description_like_any_or_vendor_name_like_any(query.split(/\s+/))
  end
end

Controller

# app/controllers/products_controller.rb
class ProductsController < ApplicationController
  def index
    @search = Product.search(params[:search])
    @products = @search.all
  end
end

Views

# app/views/products/index.html.erb
<% form_for @search do |f| %>
  <%= f.label :keywords, "Quick Search" %>
  <%= f.input :keywords %>
  <%= f.submit, "Go" %>
<% end %>
+3  A: 

The most Railsy thing to do is to not write this yourself. :-) Use the excellent Searchlogic gem which will create the description_like_any scope for you.

Edit: If you want your user to be able to enter search terms in a free text field like this, you can define your own scope:

class Product < ActiveRecord::Base
   # ...
   scope_procedure :description_like_any_term, lambda { |terms| 
     name_like_any(terms.split(/\s+/))
   }
   # ...
end
Dave Pirotte
+1, Searchlogic is definitely the way to go here.
theTRON
I am using searchlogic but if a user enters "choc pret", I want it to search for any of the words. If I simply use `<%= f.input :description_like_any %>`, searchlogic will generate `SELECT * FROM products where description LIKE '%choc pret%'`. I need it to do `SELECT * FROM products where description LIKE '%choc%` OR description LIKE '%pret%'`. Follow?
macek
First, this is starting to sound like a job for a full text search engine. If your descriptions are numerous and/or lengthy, something like Sphinx or Solr or mysql fulltext. Otherwise, you can do this, but you need to parse your user's input. Something like... params[:search][:description_like_any] = params[:search][:description_like_any].split(/\s+/) ... before you actually call Product.search(params[:search])
Dave Pirotte
@Dave Pirotte, this is excellent. Thank you.
macek
@Dave Pirotte, since you are awesome, I'm guessing you might have the answer to this question, too. [How to sanitize form params for use with searchlogic?](http://stackoverflow.com/questions/3653474/how-to-sanitize-form-params-for-use-with-searchlogic-rails) :)
macek
Sure, I just put something up there... Rails 3 / meta_search solves this problem, but AFAIK Searchlogic doesn't have anything baked in to deal w/ that.
Dave Pirotte