Last night we capped off a great weekend at RubyNation 2011 with a wrap-up dinner (thanks Dr. Nic!). I'm proud that so many CodeSherpas had a hand in putting together this conference over the years. Anyways, during the dinner a fellow attendee, Robert, asked me for my thoughts on RESTful search. "It's just the query string on the index, right?" That is a very interesting question, and as it turns out there's more than one way to do it...
We are all familiar with search and use Google every day. Perform a Google search and it gives you results. But "search" itself is a verb - an activity (bear with me if this seems a bit pedantic).
When using Google, we are really interested in web pages or sites related to our search term: a set of resources related to that term:
term ("fast cars") -> search (submit to Google)
-> resource (a list of web pages related to fast cars)
We will use this as our guide to develop a RESTful search service. I will make use of some Rails conventions in my examples below, but the concepts here apply to any RESTful ideal.
Let's say I'm building a web site for a fantastic Mini dealership, and my site includes search for all cars on the lot. I will need to answer questions like:
Do you have any red & white Mini Coopers with superchargers?
Option 1: The query string!
Yes this is very obvious.
GET => /mini_cooper_inventory
Returns my entire inventory
GET => /mini_cooper_inventory?color="red"&color="white"
&supercharger="true"
Returns the portion of my inventory matching the given
query parameters
Option 2: A filter resource.
Aha! I have played a trick on you in Option 1. We were
already using a filtered resource to see only those
Coopers in our inventory. We excluded cars that have
been already been sold, or those on order or in transit.
To think of filters RESTfully:
GET => /mini_coopers
Returns all Mini Coopers that have anything to do with
my dealership: ones that I have sold, ones that are in
inventory, and ones on order.
Because these are such commonly used resources, instead
of doing a query every time I wanted to look at my
inventory:
GET => /mini_coopers?status="inventory"
I have a dedicated another resource:
GET => /mini_cooper_inventory
In Rails you would perform the search in the #index
method of this controller, and then narrow by any
other query parameters. A note of interest, the
mini_cooper_inventory is not backed by a model of
it's own. REST is not CRUD, but that is a topic for
another day.
Option 3: A search-results resource
This is probably useful when you have a really
complex search that spans several resources. Let's
say the IRS (yikes!) is askng me for all details on
all sales made by Duzzy Cheetham or his family in
the past two years. RESTfully speaking, I'm potentially
dealing with any of a number of resources in my system:
mini_coopers
customers
sales
I could use one of the previous 2 options, and search
for sales matching certain critera:
GET => /sales?customer_last_name=Cheetham
&customer_last_name=Swindell
&date_range=2009-04to2011-04
&detailed_report=true
But at this point they query string (Option 1) does not
seem to be a good fit. I could create a search object:
POST => /search
But again, this is not really what I am after, unless
I am giving users the ability to save searches. What I
really want is some sort of customer report:
GET => /customers/:duzzy_cheetham_id/customers_report
I want to see all purchases made by Duzzy Cheetham and
his family:
GET => /all_purchases_made_by_duzzy_cheetham_and_his_family
Neither of those requests are really reusable or useful
outside of this one search. Let's try again.
GET => /search_results/new
This is the form for doing a complex search (familiar to
those of you using Rails). Not bad. What's next?
POST => /search_results
This creates a new search results report that matches my
search terms, including date range, customers, and
whatever else I want to throw in. And if I want 2 types
of searches, one that returns a list of results, and
another that summarizes results in some type of report:
POST => /search_results
POST => /search_results_reports
The name of the actual resource is up to you. The search
submitted can even be given a name that allows users to
save and re-run searches:
POST => /search_results/:saved_search_id
Anyways, there are many options when it comes to organizing your applications to work RESTfully - for the way the web was designed. I hope this example gives some food for thought.
Comments