Just this July, I decided it was time to graduate from PHP to Ruby. I had to see what all the hype was about. Rails was everywhere. Getting started with the Ruby on Rails tutorial by Michael Hartl couldn’t be easier. Thirteen chapters later, and I’m working on my new startup’s website, http://thegeno.me.
While I have my complaints about Ruby (this will definitely be a future post!), the Rails framework makes many things just work. It’s like magic. Despite the ease in accomplishing common, regular tasks, achieving more custom effects often requires a bit of…tinkering. The Rails community is pretty big, but Googling and reading a few StackOverflow posts doesn’t provide a quick answer to every question like PHP. Here’s one of the first items I spent some time figuring out and hunting down – changing the URL for the show view of a model.
By default, each item entered into your application’s database is assigned a unique id (the primary key). Rails will use this to identify a particular object from your database. Moreover, the default link for showing this item is identified by this id. For example, in your Users model, you might have a user with the email ‘user@example.com’, whose id is ’2′, and username is ‘widgetman.’ By default, this user’s ‘show’ page is found under the url: http://yourapplication.com/users/2.
While it’s great that Rails generates all of this very easily for us, we might want better permalinks for a more personal feel or better SEO. Assuming that usernames are unique in this example, we would want to make it so that the user’s profile would be routed to the url: http://yourapplication.com/users/widgetman. Doing this is not as straightforward as you’d think.
By default, I allow my users to register with usernames that have no spaces but may include both upper and lowercase letters. Since I want my permalinks to be all lowercase letters, I’m going to add a new column calls ‘permalink.’
$ rails generate migration AddPermalinkToUsers permalink:string
$ bundle exec rake db:migrate
Once the migration is complete, let’s open our User Model file to add the necessary code to create the permalink upon user registration:
../app/models/user.rb
.
.
before_save :create_permalink
.
.
This will execute the function (we are about to write) to create the permalink during our user registration process.
../app/models/user.rb
.
.
private
def create_permalink
self.permalink = username.downcase
end
.
.
Now by default, when a user registers for the site, the data they enter for ‘username’ (or whatever field you want to use for the permalink) will be saved in all lowercase in the permalink column.
By default, to_param, the function that defines the column in your model’s table that is passed as the :id whenever a request to the associated controller is made, is set as the primary key in your table. By specifying a to_param we can help to control which column in your model is used for the permalink (note: this must be a unique value or bad things will happen!).
../app/models/user.rb
.
.
def to_param
permalink
end
.
.
Now, the permalink column from our user model will be used to generate links for individual records. We’re almost there.
Now that we changed the default parameter from :id to :permalink. Normally, our user controller would look something like this for the show action:
../app/controllers/users_controller.rb
.
.
def show
@user = User.find(param[:id])
@title = @user.name
end
.
.
By default, Rails will pass the id from the User table as the param[:id] upon requesting the show action. However, we have just updated our user model to pass the permalink from the User table as the parameter that is returned in param[:id]. Once we realize that User.find(param[:id]) is a shortcut for User.find_by_id(param[:id]) the change we need to make might become more apparent:
../app/controllers/users_controller.rb
.
.
def show
@user = User.find_by_permalink(param[:id])
@title = @user.name
end
.
.
This will make sure that the proper @user is retrieved from your table based on the permalink that was requested. Keep in mind that all instances where you defined @user as @user = User.find(param[:id]) need to be changed to @user = User.find_by_permalink(param[:id]).
This is how I was able to change the permalink structure of my user (and other models). Always make sure that you're using a unique column for defining your permalinks! I hope to write more posts as I learn more Rails.
I think the method you mention above is to_param, not to_params. Great post, I went through a similar process generating friendly URLs for users by full name.
You are 100% correct on that one…I’ve updated the post to the proper function.
Thanks for reading!
Hi Alex!
Works like a charm. Thanks. But I have a problem.
Sorry, I’m new in the rails world.
I need get the ID to pass into my variable. I can’t do that because the ID param was converted into USERNAME in url.
For exemple:
@chef_list = Recipe.find(params[:chef_id])
@recipes = @search.where({:status_id => 1, :chef_id => @chef_list }).order(“id desc”).page(params[:page]).per(9)
So, I need to pass the “chef_id” to list recipes from a especific user.
Works perfectly: /chefs/54/recipes/
Doesn’t work: /chefs/sanson/recipes
Sorry about my english, I Hope that you understand my problem.
What should I do? Thank you again.
Hi Sanson,
I think I’m understanding. Make sure your models and routes are properly set up:
Chef Model:
has_many :recipes
Recipe Model:
belongs_to :chef
Routes:
resources :chefs do
resources :recipes
end
Now the language in the controller:
RecipesController
def index //This will be http://application.com/chefs/sanson/recipes
@chef = Chef.find_by_permalink(params[:chef_id])
@recipes = @chef.recipes
end
def show #This will be http://application.com/chefs/sanson/recipes/42
@chef = Chef.find_by_permalink(params[:chef_id])
@recipe = Recipe.find(params[:id])
end
Not sure if this is exactly what you’re looking for, but hopefully it get’s you to where you want to be!
Disclaimer: I wrote this sort of quickly, so please excuse any errors!