A comparison between Ransack and Searchlight search gems

Some months ago Ernie Miller announced he was no longer maintaining Ransack anymore. He explained he was not retiring from open source but he wanted to experiment with other things.

Before adding a new gem to a project’s Gemfile I always check things like when was the last commit, how may stars does it have, how many open issues, forks and pending pull requests there are, and who the author and it’s main contributors are. This helps me to evaluate the intrinsic long term risk of using other people’s code. If the gem meets my project’s requirements and it seems likely that it will continue evolving, then I proceed to try it out in a new feature branch.

That’s basically how I deal with open source.

When I found out that Ernie Miller was no longer developing Ransack, which I was heavily using in some of my projects, I kind of freaked out (not really, but you understand the feeling).

Fortunately, Ransack is being actively maintained and including new features.

Some months before Ernie made the announcement I read about a new search gem called searchlight which, as the article said, helps you build search mechanisms for ORMs with chainable methods. So while I was spitting the coffee all over the monitor and thinking the world was over I remembered that article and decided to give Searchlight a try.

Searchlight is a slightly more simple gem than Ransack in terms of code complexity, which is great in terms of maintainability. It is also more flexible as it delegates on the developer the responsability to construct the queries. It doesn’t depends on ActiveRecord, ActionPack or ActiveSupport (it is ORM independent), which is also great.

Ransack basically works like this:

class ProductsController < ApplicationController

  def index
    @search = Product.search(params[:search])
    @products = @search.result(distinct: true)
  end
end
  <table>
    <%= @products.each do |product| %>
      ...
    <% end %>
  </table>

  <%= form_for @search do |f| %>
    <%= f.label :name_eq, 'Name' %>
    <%= f.text_field :name_eq %>

    <%= f.label :price_lt, 'Price less than' %>
    <%= f.text_field :price_lt %>

    <%= f.label :price_gt, 'Price greater than' %>
    <%= f.text_field :price_gt %>
  <%= end %>

The fact that the method called over the search object is named #result and not #results always annoyed me, as well as the distinct:true parameter.

Behind the scenes Ransack translates the params keys into sql conditions using arel nodes. Pure magic! Works great and it supports polymorphism and joins.

Searchlight basically works like this:

class ProductsController < ApplicationController

  def index
    @search = ProductSearch.new(params[:search])
    @products = @search.results
  end
end
  # app/searches/product_search.rb
  class ProductSearch < Searchlight::Search

    search_on Product.all # Base query, you can use named scopes.

    searches :name_eq, :price_lt, :price_gt # Search terms

    def search_name_eq
      search.where(name: name_eq) # the name_eq variable is automagially injected
    end

    def search_price_lt
      search.where(Product.arel_table[:price].lt(price_lt))
    end

    def search_price_gt
      search.where(Product.arel_table[:price].gt(price_gt))
    end
  end

You need to define a search object and inherit the Searchlight::Search class which provides a simple DSL search_on and searches methods to construct the query.

One advantage of this approach is that you can reuse the search object across all the application, and not only inside forms. In the case of Ransack you have to write the query inside the controller, which I don’t think represents a problem itself but wrapping your queries into search objects is the way to go in terms of separation of concerns, testability and maintainability.

We’ll see how this gem evolves. So far so good the gem has more than 400 stars on Github and the author is active (although last commit was on April). May be it is time to get myself the hands dirty and start contributing!

Hope you enjoyed the post and find it useful.

ruby

Comments

comments powered by Disqus