Beautiful and meaningful urls

Hi,

I have an app with nested resources - categories and services, so my routes.rb contains the following

  map.resources :categories do |category|     category.resources :services do |service|   end

Obviously this gives me urls like /categories/1/services/2

I would like to modify the app to display more human urls like

/categories/category_name/services/service_name

Or even better - /category_name/service_name

Both models have a name field in their database table.

Can anybody advise if there is a simple way to modify the routes.rb and show actions in the controllers to set this up?

I feel like there must be a way to do this with minimal code and no heavy lifting, but just can't see it.

Thanks,

Dan

The to_param method on your model is what gets called to generate the ID for the URL.

So a simple solution is to just change that. Lets say your "Category" model has a unique "slug" field. You could change it to this:

class Category   def to_param     self.slug   end end

Then instead of finding by ID in your controller like this:

Category.find(params[:id])

You would need to find by slug:

Category.find_by_slug(params[:id])

That doesn't require any changes to routes. I know there are gems that do this (maybe a little more elegantly). But this is a quick way to get it working with minimal changes, and without any extra gems. Just make sure whatever field(s) you use for to_param are unique.

I would like to modify the app to display more human urls like

/categories/category_name/services/service_name

See the relevant railscast.

Seems like a job for FriendlyID.

Luke

Thanks guys,

I have modified things in this way

http://media.railscasts.com/videos/063_model_name_in_url.mov

Cheers,

Dan

Just an update on this thread.

I now have pretty and meaningul urls of the form mysite.com/categories/ permalink

However, there are evidently some old links out there on the web that still point at mysite.com/categories/1

These now throw an error.

Is there any way to catch any urls that still contain an integer id and redirect them to the new permalink in the controller?

Cheers Dan

DanC wrote in post #956986:

Just an update on this thread.

I now have pretty and meaningul urls of the form mysite.com/categories/ permalink

However, there are evidently some old links out there on the web that still point at mysite.com/categories/1

These now throw an error.

Is there any way to catch any urls that still contain an integer id and redirect them to the new permalink in the controller?

Use the routes, Luke!

Cheers Dan

Best,

Hi,

I am confused as to how I would modify the routes to only catch the urls that have an integer id?

For example, I have an articles resource so my routes file contains:

map.resources :articles

And I have modified my model file with

  def to_param     permalink   end

and the controller to contain

@article = Article.find_by_permalink(params[:id])

Therefore, the working url would look like mysite.com/articles/ permalink

But every time someone tries to access mysite.com/articles/1 which was posted as a link on a forum somewhere months ago I get an error notification.

How would I modify the routes to stop this erroring and automatically redirect to the appropriate permalink url?

Cheers Dan

Please quote when replying.

DanC wrote in post #957008:

Hi,

I am confused as to how I would modify the routes to only catch the urls that have an integer id?

Either use a regex to match the parameter (and then send it to a different controller action) or have a single controller action that runs both find_by_permalink and find_by_id.

Best,

Hi Marnen,

Either use a regex to match the parameter (and then send it to a different controller action) or have a single controller action that runs both find_by_permalink and find_by_id.

I have added this to the controller which seems to work well, as my regex writing is weak.

    @article = Article.find_by_permalink(params[:id])     unless @article        @article = Article.find(params[:id])     end

Thanks for the help. I really appreciate it.

Dan

DanC wrote in post #957019:

Hi Marnen,

Either use a regex to match the parameter (and then send it to a different controller action) or have a single controller action that runs both find_by_permalink and find_by_id.

I have added this to the controller which seems to work well, as my regex writing is weak.

Then get stronger! You don't need a very sophisticated regex, and no Ruby programmer can afford to be scared of regexes. http://www.rubular.com is your friend if you need help.

Regexes are an essential tool in anything involving string processing -- and that includes Rails routing. Don't ignore them.

    @article = Article.find_by_permalink(params[:id])     unless @article        @article = Article.find(params[:id])     end

That works, but you might want to move it to the model (perhaps in a find_by_permalink_or_id method).

Thanks for the help. I really appreciate it.

Dan

Best,

Either use a regex to match the parameter (and then send it to a different controller action) or have a single controller action that runs both find_by_permalink and find_by_id.

I have added this to the controller which seems to work well, as my regex writing is weak.

   @article = Article.find_by_permalink(params[:id])    unless @article       @article = Article.find(params[:id])    end

I'd recommend a slightly different approach.

Setup an "old_article" route in routes.rb that explicitly matches those old urls... Something like (for 2.x):

map.old_article '/articles/:id', ..., :requirements => {:id => /\d+-.*/}

Then, in the action for old_article you can find the article and *301* redirect it to the new article URL.

Doing this will get you a little SEO boost from Google. Or rather... they won't punish you for duplicate content...

If you're using Rails 3, the syntax will change a bit and you could probably put it all in the routes file using the new redirect stuff. Maybe.

Hope this helps...

-philip