The idea is to generate pretty URLS i.e. /username/post-title instead of /1/post/2


1.) Add slug field to your table (as string) through a migration, for each table that has content in its columns you want to use as slugs (posts, categories, comments etc.)


class CreateSlugs < ActiveRecord::Migration
  def change
      add_column :posts, :slug, :string
      add_column :categories, :slug, :string
      add_column :users, :slug, :string
  end
end




2.) In your model add a method

# /models/post.rb

#...
after_validation :generate_slug # keep business logic in mind (this changes the url when a title of a post is changed. to keep the sluggish url the same use "before_create" which is a different Active Record Callback, see them listed here: http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html)
#Which Active Record callback to use is a business, not a development decision.

#...

def generate_slug
  self.slug = self.title.gsub(' ', '-').downcase # use regular expressions to take care of edge cases, like question marks etc. that can't be in urls (replace all non-alpha numerical characters with a dash)
  #self.save - to save it to the database
  # alternatively to "after_validation :generate_slug" on top of model use "self.save" here within the method
end

def to_param
  self.slug # See HTML comment above for why we write this method
end

3.) In the controller

# post_controller.rb

def set_post
  @post = Post.find_by slug: params[:id]
end

4.) Add reload to show.html.erb (it worked without this in my case) for validation errors.

<h5>Comments:</h5>
<% @post.reload.comments.each do |comment| %> <!-- it was @post.comments.each do before -->
  <%= render 'comments/comment', comment: comment %>
<% end %>

5.) Wherever I reference the id of a post/comment previously I now have to reference the slug, so


<span id="post_<%= post.slug %>_votes"> <%= post.total_votes %> votes </span>
<!-- this said "post.id" instead of "post.slug" before -->

6.) To generate slugs for entries done prior to slug set up, go into $ rails console

# $ rails console
Post.all.each { |post| post.save } # make sure you don't have validation errors for this to work.


Note

In your view you can add .to_param to your link (not necesary, since .to_param is called indirectly)

<%= link_to ("#{post.comments.size} comments", post_path(post.to_param)) %>
<!-- We call .to_param explicitly here and create the to_param method in the model (see below), because normally calling .to_param would return the id of the object. In our case we want it to return the slug-->