I have a category model and I'm routing it using the default scaffolding of resources :categories
. I'm wondering if there's a way to change the paths from /category/:id
to /category/:name
. I added:
match "/categories/:name" => "categories#show"
above the resources line in routes.rb
and changed the show
action in the controller to do:
@category = Category.find_by_name(params[:name])
it works, but the 'magic paths' such as link_to some_category
still use the :id
format.
Is there a way to do this? If this is a bad idea (due to some possible way in which rails works internally), is there another way to accomplish this? So that /categories/music
, for example, and /categories/3
both work?
Rails has a nifty model instance method called to_param
, and it's what the paths use. It defaults to id
, but you can override it and produce something like:
class Category < ActiveRecord::Base
def to_param
name
end
end
cat = Category.find_by_name('music')
category_path(cat) # => "/categories/music"
For more info, check the Rails documentation for to_param.
EDIT:
When it comes to category names which aren't ideal for URLs, you have multiple options. One is, as you say, to gsub
whitespaces with hyphens and vice versa when finding the record. However, a safer option would be to create another column on the categories table called name_param
(or similar). Then, you can use it instead of the name for, well, all path and URL related business. Use the parameterize
inflector to create a URL-safe string. Here's how I'd do it:
class Category < ActiveRecord::Base
after_save :create_name_param
def to_param
name_param
end
private
def create_name_param
self.name_param = name.parameterize
end
end
# Hypothetical
cat = Category.create(:name => 'My. Kewl. Category!!!')
category_path(cat) # => "/categories/my-kewl-category"
# Controller
@category = Category.find_by_name_param(param[:id]) # <Category id: 123, name: 'My. Kewl. Category!!!'>
If you don't want to to break existing code that relying on model id you could define your to_param like this:
def to_param
"#{id}-#{name}"
end
so your url will be: http://path/1-some-model and you still can load your model with Model.find(params[:id]) because:
"123-hello-world".to_i
=> 123
Although possibly more than you need, you may also want to look into 'human readable urls' support like friendly_id or one of the others (for instance, if you need unicode support, etc.) that are described here at Ruby Toolbox.