Pretty Paths in Rails

2020-07-26 14:37发布

问题:

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?

回答1:

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!!!'>


回答2:

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



回答3:

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.