This article explains how you can alter the looks of your URLs in a Rails friendly way. I assume that you know how to use map.resources
and named routes.
Adding .html to all pages
ActionController
has a default_url_options
method that it uses for all URL generation tasks. It returns an empty hash by default, but can be overridden.
class ApplicationController < ActionController::Base
def default_url_options(options = nil)
{:format => "html"}
end
end
Note that the options to your individual route calls gets predesence, posts_path(:format => "xml")
will append .xml, not .html.
Custom route names
For a projects controller’s show action, the default URL looks like this:
/projects/5
What if you want to localize it?
/prosjekter/5
One way is to name the controller “prosjekter” instead of “projects”. That isn’t very Rails friendly, though, you’ll end up fighting with pluralization conventions. Thankfully, there is a Rails friendly way to do this, via routes.rb.
map.resources :projects, :as => "prosjekter", :path_names => {:new => "nytt", :edit => "rediger"}
That will give you these URLs:
/prosjekter/5 /prosjekter/nytt /prosjekter/5/rediger
You can also set :path_names
globally, by adding the following to config/environment.rb.
config.action_controller.resources_path_names = { :new => 'ny', :edit => 'rediger' }
URLs without the ID of the record
Normaly, Rails URLs look like this:
/posts/5 /projects/239 /users/39/favorites/2
Perhaps you want more descriptive URLs, though, such as having the title of the post in the URL instead of just the ID. Here are a few altervatives.
The easy way with compromises
/posts/5-a-post-about-rockets /projects/239-new-website /users/39-augustl/favorites/2-that-book
The compromise is that the ID still is in the URL. It is unlikely that a visible ID is a problem, but if it for some reason is, you can learn how to do it without the ID in the next section.
What makes Rails use the ID alone in the first place? The answer is to_param
. It’s an instance method in all models, and it looks like this:
def to_param
id
end
We can override it to include the title of the post as well as the ID.
class Post < ActiveRecord::Base
def to_param
"#{id}-#{title.parameterize}"
end
end
Rails will now use the id, a dash, and and URL friendly version of the title. We need to make it URL friendly with parameterize
, so that "Foo and bar!"
turns into "foo-and-bar"
. If we don’t, we’ll get invalid URLs, and browsers will blow up.
The reason this is the easy way, is that this is all there is to it. Our finds will still work, because of the way Ruby handles to_i
on strings.
"5-a-post-about-rockets".to_i
# => 5
The find
method that we use in the controllers calls to_i
for us.
Post.find("5-a-post-about-rockets")
# equals
Post.find(5)
The hard way
/posts/a-post-about-rockets /projects/new-website /users/augustl/favorites-that-book
Because the ID is no longer in the URL, we have to change the code a bit.
class Post < ActiveRecord::Base
before_create :create_slug
def to_param
slug
end
def create_slug
self.slug = self.title.parameterize
end
end
When a post is created, the URL friendly version of the title is stored in the database, in the slug column. We also have to update the finds to find records using the slug column instead of using the ID.
class ProjectsController < ApplicationController
def show
@project = Project.find_by_slug!(params[:id])
end
end
Apply the same tactic to all relevant finds, and you’re good to go.
No controller names
Instead of /users/augustl/projects/a-project
, you might prefer this:
/augustl/a-project /some-user/another-project
Just a heads up: Most people don’t care about how the URLs looks. In my opinion, it only makes sense to do this kind of routing if your users are typing in the URLs by hand, or if the URL is commonly linked to. Github projects is a good example of sensible use of short URLs. If not, I advise you to leave the controller names in there.
Anyhow, here’s how you do it:
map.short_user ":id", :controller => "users", :action => "show"
map.short_project ":user_id/:id", :controller => "projects", :action => "show"
This gives you two very handy named routes.
short_user_path(@user)
short_project_path(@user, @project)
The example above only handles those two show actions. I advise using regular map.resources
for create
, edit
etc. Users will never have to link to the URL that creates a new project, so it’s pointless to style those routes.