Adam O'Grady

Rails Blog: Part 4

By now you should have a blog with users, administrators and even a tag system as of part 3 in this series, so now we’re onto something for greater aesthetics. In this case, custom URLs for posts and tags.

We’ll start by running rails g migration add_url_to_tags_and_users, this generates a migration that we can open up (usually in db/migrate/YYYYMMDDHHMMSS_add_url_to_tags_and_users.rb) and add the following inside the change method:

add_column :tags, :url, :string

add_column :posts, :url, :string

This will add columns for the url component to both the Tag and Post model and we can send these changes to the database with rake db:migrate. Next add the following to both app/models/post.rb and app/models/tag.rb before the end keyword:

validates :url, uniqueness: true
def to_param
  url
end

def self.find_by_param(input)
  find_by_url(input)
end

These tell Rails to use the :url parameter when making and using URLs such as localhost:3000/post/welcome instead of localhost:3000/post/1 and also makes sure that the URL parameter is unique in the table. Lastly it also creates a method usable by the Tag and Post classes that allows a user to find by the url parameter with find_by_param which will be used later.. Next edit the create and update methods in app/controllers/posts_controller.rb and put @post.url = post_params[:title].squish.downcase.tr(" ","_").gsub(/[^0-9A-Za-z_]/, '') near the top , directly above @post.tags = ready_tags. Now we need to find every instance of .find(params[:id]) and change it to .find_by_param(params[:id]) allowing us to use the new :id parameter (which will be the :url variable) as the identifier. Do that in both app/controllers/posts_controller.rb and app/controllers/tags_controller.rb.

Next we’ll want to modify the ready_tags private function to look like this:

def ready_tags
  tags = tag_params[:tag_ids].split(/,\s*/)
  tags_ready = []
  tags.each do |tag|
    temp = Tag.find_by_url(tag.squish.downcase.tr(" ","_").gsub(/[^0-9A-Za-z_]/, ''))
    if temp == nil
      temp = Tag.create(name: tag, url: tag.squish.downcase.tr(" ","_").gsub(/[^0-9A-Za-z_]/, ''))
    end
    tags_ready.push(temp)
  end
  tags_ready
end

This makes sure it’s searching for tags by unique URL and also creates the tag with a unique URL as well.

You will also need to modify your app/controllers/posts_controller.rb create method, wrapping everything in the method in the following block:

if post_params[:title].squish.downcase.tr(" ","_").gsub(/[^0-9A-Za-z_]/, '') == 'new'
  redirect_to posts_path, notice: 'Post cannot be titled "new".'
else
  [EXISTING CODE]
end

Now do similar with the update method, wrapping it all in the following:

if post_params[:title].squish.downcase.tr(" ","_").gsub(/[^0-9A-Za-z_]/, '') == 'new'
  render :edit
else
  [EXISTING CODE]
end

This prevents someone creating a post that would have the URL ‘new’, which would break the resourceful routing rails uses. You’ll also need to modify config/routes.rb, removing the line resources :tags and replacing it with:

get '/tags', to: 'tags#index', as 'tags'
get '/tags/:id', to: 'tags#show'
delete '/tags/:id', to: 'tags#destroy'

This non-resourcefully defines the routes used for tags (index, show, destroy) and also allows us to create a tag labelled “new” without breaking anything.

You should now have a working, aesthetically pleasing URL system for your blog. This will likely be the last of the tutorials in this series on building a blog in Rails, the implementation you’ve created up till now should have a number of standard and useful features for blogs and you should have enough expertise to start developing your own features and branching out further.