When I started blogging, it had everything: links, short posts, long posts, pictures. Web 2.0 has brought new ways of creating that content, but it feels scattered. This site brings my multiple streams of content back to one place, like we did in the good old days.
Tuesday, July 31, 2007
A guy I know from the Rails mailing list called me this morning. He’s working on some kind of forum application, I think, and is thinking hard about how to do search in a RESTful way.
Normally, I just use the index action to search across a single resource, but in his case he wants to search Posts, which are nested inside Topics, which are nested inside Forums.
# routes.rb map.resources :forums do |forum| forum.resources :topics do |topic| topic.resources :posts end end
He wants to search Posts across all Forums and Topics. One option would be to make PostsController serve double-duty
# routes.rb map.resources :forums do |forum| forum.resources :topics do |topic| topic.resources :posts end end map.resources :posts
and make it figure out whether or not it’s called in a scoped way or an unscoped way.
# posts_controller def index if params.include?(:forum_id) # act normal, we're scoped else # we're special and top-level, so ... do something different end end
In this case, I suggested he go a different way. Even though he says he only wants to search Posts, I feel pretty sure he wants to search Topics (by title) and Forums (by name), too.
And even if he doesn’t, I do. I’m about to add a general-purpose, cross-resource search to SOLIS, the registration and conference management system I’m writing for SUUSI and this seemed like an ideal way to help out a friend, make a much-needed blog post, and get some code done in the meantime.
Before we get started, though…this is one way to do RESTful search. It’s not the only way. It is probably not the best way. I’m not sure it’s a good way, since I just thought of it on the porch this morning and haven’t used it yet. I’m pretty sure it’s an OK way, though, and a good place to start thinking about it. YMMV.
OK, that’s out of the way.
I already know what I want: a singleton resource called /search. So let’s add it to the routes
I can never remember whether singleton resources want singular (SearchController) or plural (SearchesController) names. I’m pretty sure this has changed in edge, and possibly changed back. So, I add the resource and load it up, and see what Rails tells me. When I load /search, I get ‘uninitialized constant SearchController’. Cool, it did what I wanted, and used singular. (I just checked, and on edge it uses plural. Bleh. I guess I can see why, but…anyway.) Now we need that controller.
$ script/generate controller -c Search exists app/controllers/ exists app/helpers/ create app/views/search A app/views/search exists test/functional/ create app/controllers/search_controller.rb A app/controllers/search_controller.rb create test/functional/search_controller_test.rb A test/functional/search_controller_test.rb create app/helpers/search_helper.rb A app/helpers/search_helper.rb
Because it’s a singleton resource, the actions are different; requesting /search calls the show action.
Now, showing a search with no query string doesn’t make sense. /search/new is an ugly URL. I want to search as a GET not a POST, so you can bookmark it. So, here’s what I ended up with.
# SearchController def show if params.include?(:q) # we have a query, so call create to actually do the search # don't redirect, though, there's no need, and bookmarkable # search URLs are handy create render :action => "create" else # we don't have a query, so do the new action new render :action => "new" end end def create @search_results = returning  do |results| results << Person.search(params[:q]) results << Event.search(params[:q]) # ... add other models here, or use whatever searching you need end.flatten end
# new.rhtml <h1>Search for stuff (events, workshops, trips, people...)</h1> <% form_tag search_path, :method => :get do %> <%= text_field_tag :q %> <%= submit_tag "Search", :name => nil %> <% end %> # create.rhtml <% @search_results.each do |result| %> <p><%= result %></p> <% end %>
Obviously you’ll format your search results more prettily, with links and such. And you’ll need search() methods on all the classes you’re going to search. Or, make a Searcher model and let it do all the work.
Monday, July 09, 2007
Rails Edge changeset 6158 added automatic eTag support. I needed it in a Rails 1.2.3 project, so I made a little patch to shoehorn 6158 into 1.2.3.
Here’s the patch, just drop it into lib and require it.
Updated: includes 7580 too.
Updated again: I’m having some issues with this, so looks like I blogged it pre-maturely.