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

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:
#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) - to save it to the database
  # alternatively to "after_validation :generate_slug" on top of model use "" here within the method

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

3.) In the controller

# post_controller.rb

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

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

<% @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 "" 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| } # make sure you don't have validation errors for this to work.


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