New Blog Features

Posted August 18, 2007

I promise I’ll stop talking about the blog pretty soon. But I just added some new features I wanted to talk about.

First of all, the post URLs are now pretty. For example, instead of /posts/23, this is now /posts/23-new-blog-features. This is mostly because I was getting annoyed looking at Google Analytics and being unable to remember which links went to which posts.

Actually, the pretty-URL thing has been around since the big major update, but it wasn’t as visible as it is now. Links from the front page used the pretty URLs, but links from the feeds didn’t. That’s fixed, now.

Luckily, the old URLs didn’t cause any compatibility issues. /posts/23 goes to the same page as /posts/23-new-blog-features. In fact, it doesn’t matter what you put after the number; /posts/23-the-way-you-can-go-isnt-the-real-way goes to the same place as well. This is so that, in the event that I change the title of one of the posts, the old URLs don’t break.

Second, you may have noticed the search field on the sidebar. This is also mostly to get rid of a pet peeve of mine; I’d occasionally want to site old posts, and it was a pain to look them up in the archives.

The search works in two different ways; if you just type into the field, it’ll auto-update the list below with matching titles in real time. If you click “Go” or press Enter/Return, though, it’ll send you to an actual results page, much like the main page.

This is all structured very RESTfully, too. I’m kinda proud of myself for that. The normal search page is just /posts?query=whatever, and to do the live update, it just grabs /posts.js?query=whatever which has the code to do the update.

Thanks to make_resourceful, I only had to add six lines of code to my controller to make this work. Moreover, two of those were negligible. I changed the current_objects method from

def current_objects
  @current_objects ||= Post.find(:all, :order => 'created_at DESC', :limit => 6)
end

to

def current_objects
  @current_objects ||= if params[:query]
      term = "<span>#{params[:query]}</span>" 
      Post.find(:all, :order => 'created_at DESC',
                :conditions => ['content LIKE ? OR title LIKE ?', term, term])
    else
      Post.find(:all, :order => 'created_at DESC', :limit => 6)
    end
end

From there, it all just worked. But enough tooting my own horn1; there’s certainly plenty wrong with my search. For instance, it’s really not very flexible, and probably wouldn’t be very fast if I had a lot of posts or requests.

I thought about using something more powerful like acts_as_ferret or MySQL’s built-in FULLTEXT search. I decided not to mostly because I wanted to get searching working immediately. I’ll probably make it better later, likely when I get annoyed by some limitation of content LIKE %foo%. For now, this works fine.

1 I really love that metaphor. I like thinking of my ego as some sort of noisemaker that makes a buoyant tooting sound when I compliment myself.

Evgeny said August 18, 2007:

For search, I use google. In google adsense they have this search field that shows the results inside your own blog. You can see it on mine ….

Nathan said August 18, 2007:

But then I couldn’t do that snazzy AJAXy live update! At least not without scraping the Google results with HPricot. Or using their API, I suppose.

Eli said September 17, 2007:

Hey Nathan—

I really liked the idea of the extra URL candy. Those are some cute URLs!!! I looks at the code you so graciously placed in our hands via your SVN repo, but frankly, I couldn’t figure out what you did, what with all that make_resourceful business.

I’m frantically redoing my site (moving from PHP to Rails) and I’d really like to have some nice URLs of my own. I’m so frantic, in fact, that I can’t REST even for a moment.

Can you (or someone) please help me out? Is there some kind of routes.rb magic I need to take care of? Or is it in the controller?

Thanks, and good work on the awesome site!

Nathan said September 17, 2007:

Eli: Sure! The routing is actually easy; it’s set up just like it is by default:

# If you're using RESTful routing
map.resources :posts

# If you're using the old-fashioned controller/action setup:
map.connect ":controller/:action/:id.:format"

The :id parameter gets passed along whether or not it’s actually a number. Thus, if I someone accesses /posts/23-new-blog-features, my params has looks like this: {:controller => 'posts', :action => 'show', :id => '23-new-blog-features'}.

At this point, all you need to do is extract the real id from the string with the pretty stuff stuck on. The relevant method in my controller is current_param:

def current_param
  params[:id].scan(/^(\d+)/)<a href="0">0</a>
end

This takes the :id parameter and uses a regex to return just the digits at the beginning.

If you’re using make_resourceful, all you need to do is drop that method into your controller and you’re good to go. If you’re new to Rails, though, I imagine you aren’t (this is a good thing – m_r is really designed for people who already get the fundamental ideas of REST).

If you aren’t, you’ll want to update the places in your controller where you assign @post so that it uses this method. Change stuff like

@post = Post.find(params[:id])

with

@post = Post.find(current_param)

And you should be good to go.

Let me know if this helps!

Eli said September 18, 2007:

Alright! Thanks, that’s a big help.

It seems to work fine.

I just added your id_with_slug method to the relevant models, and then call that in my link_to hashes.

Make your comments snazzy with Textile!