RESTful searching in Rails

26 Jun 2008

Over and over again I see people asking about how to do a search the “RESTful way” with Rails, and every time I do I just shake my head and wonder why we sometimes get so caught up in how we do something that it keeps from actually doing it. So, with that introduction, I want to present to you a really simple implementation of the most basic type of searching you can do in a Rails application. It just so happens that this method will also handle the bulk of your searching needs.

Let’s say you want to search through orders for a shopping cart. Instead of doing something fancy, let’s just assume you want to search on the customer’s name, the customer’s email, and also optionally restrict the search to a date range. First, here’s a snippet from the view, index.html.erb:


...
< % form_tag({:action => 'index'}, :method => 'get') do %>
< label>Name/Email< /label>
< %= text_field_tag :q, h(params[:q]) %>
< label>Date Range< /label>
< %= text_field_tag :start_on, h(params[:start_on]) %>
< %= text_field_tag :end_on, h(params[:end_on]) %>

< %= submit_tag 'Search' %>
< % end %>
...

What, throwing search right into the index action? You betcha. Oh, you want pagination with that? No problem:


...
< %= will_paginate @orders, :params => params %>
...

Bam. Ok, on to the controller:


class OrdersController < ApplicationController

def index
@orders = Order.paginate(:all,
:order => 'orders.created_at',
:page => params[:page],
:conditions => search_conditions,
:joins => :user)
end

...

protected

def search_conditions
cond_params = {
:q => "%#{params[:q]}%",
:start_on => Chronic.parse(params[:start_on]),
:end_on => Chronic.parse(params[:end_on])
}

cond_strings = returning([]) do |strings|
strings < < "(users.name like :q or users.email like :q)" unless params[:q].blank?
if cond_params[:start_on] && cond_params[:end_on]
strings << "orders.created_at between :start_on and :end_on"
elsif cond_params[:start_on]
strings << "orders.created_at >= :start_on"
elsif cond_params[:end_on]
strings < < "orders.created_at <= :end_on"
end
end

cond_strings.any? ? [ cond_strings.join(' and '), cond_params ] : nil
end
end

It just doesn't get much easier than this. If there was no search, then the :conditions arg gets passed in as nil, which gets silently dropped. If there was one, then we build some conditions based on which parameters were specified. With a little bit of help from the chronic gem, we get easy date entry, to boot.

We're done. And I just don't care whether it's RESTful or not. ;)



Gitosis for protected git repositories

12 Jun 2008

A number of Rails Kits customers asked for access to the git repository for the Kit they purchased, and I considered using GitHub for that, but this kind of usage doesn’t line up well with GitHub, since I have lots of users who need only read-only access to a few repositories. Since I already have the VPS set up anyway, I thought I’d give gitosis a shot.

Following this fantastic gitosis guide, I was ready to serve my git repository for the SaaS Rails Kit to my customers — and only to my customers :) — in no time. Now when I have a new customer wanting git access, I just add a public key to my local gitosis-admin clone, push it to my VPS, and I’m done.



Rails 2.1 gotcha with has_one

9 Jun 2008

I just updated the SaaS Rails Kit for Rails 2.1, and added a couple new payment gateways, and I thought I’d mention a behavior change in Rails 2.1 that might catch you unawares.

The new behavior in Rails 2.1 for has_one is that Rails will now automatically validate the association for you, which can cause you some problems if you already were validating the association yourself, as I do with the SaaS Rails Kit. In my case, I validate the subscription associated with a new account, but only if everything is valid with the account first, as I don’t need to hit the gateway with your credit card info if your account info is invalid. When I started to upgrade the Kit to Rails 2.1, my spec that checks that behavior started failing, as it got back the generic “is invalid” error message, rather than the error message I was generating inside the Subscription model.

So, I thought I’d put this blog post out there to hopefully save some people some time wondering what’s going on, and also to give you a good example of why thoroughly testing your code is a good idea. :)



Rails developers needed in Seattle

9 Jun 2008

Are you doing Ruby on Rails work in Seattle? Do you want more work? I have a number of clients, large and small, who are looking for both contractors and full-time developers who know Rails. I’d be happy to make a connection for you to get more work. If you’re interested, submit your info and I’ll be in touch if there’s a good fit.