By Nouran Mahmoud

Tagging from Scratch in Rails

By Nouran Mahmoud

Price tag icon

Tags are sort of like categories, describing a piece of information (content) and allowing to the user to search for it again. In this tutorial, you will see how to build a simple tagging system, from the ground up, in Rails.

This tutorial assumes a basic knowledge with Ruby on Rails. I’ll try to keep it simple.

A common use of tags, which we all already know, is by Twitter to collect tweets around a certain topic related to the (hash)tag.


  • Rails 4.1.4
  • Ruby 2.0.1
  • Foundation 5

Getting Started

The enitre source code of this application can be found in this repository

First, create your Rails project:

rails new TaggingTut

Then, add the foundation-rails gem to the Gemfile, are remove the turbolinks gem. Run bundle install.

We will use sqlite, the default database used by Rails

Remove this line from app/assets/javascript/application.js., since we removed the turbolinks gem.

//= require turbolinks

Creating Tags

Generate the model for tags with a single attribute, name:

rails g model tag name:string

We will index the name attribute (index: true) to speed up our search with these tags. I recommend this tutorial for using indexes in Rails associations.

Tags have many Posts, and Posts can have more than one tag. As such, the relationship will be many-to-many. We can represent this association in Rails in two ways:

  • First: Use a has_and_belongs_to_many association. This will generate the join table in the database, but there wont be a generated model for the join. So, you won’t be able to add validations or any other attributes to the join.

  • Second: Use has_many, through which requires a model to be created for the join table. This way is preferred for most cases, so we will use it.

Create models “Post” and “Tagging”, as well

rails g model post author:string content:text
rails g model tagging post:belongs_to tag:belongs_to

After creating these models, run rake db:migrate.

Now, we will create associations between posts and tags through ActiveRecord as follows:


has_many :taggings
has_many :tags, through: :taggings


has_many :taggings
has_many :posts, through: :taggings

app/models/tagging.rb will be generated like so:

belongs_to :post
belongs_to :tag

We now have a join between posts and tags through the taggings join table.

Now, we will need to handle the creation of tags as part of the post create action. So, we will define a method to take all the entered tags, strip them, and then write each tag to the database.

The Post model had two attributes, author and content, which are also defined in the current form. An attribute for all_tags will be added to the form data, as well. In Rails 4, add the desired (virtual, in this case) attribute using strong parameters. Virtual attributes are very simple, in this case defined as a getter and setter methods. The strip function is for removing whitespace


def all_tags=(names)
  self.tags = names.split(",").map do |name|
      Tag.where(name: name.strip).first_or_create!

def all_tags", ")

The all_tags function will be customized to render all the tags separated by commas.

Before creating the controller and views, install Zurb Foundation:

rails g foundation:install

Now, customize the controller and views for rendering the posts including all the tags. Create app/controllers/posts_controller.rb by typing:

rails g controller posts index create

Specify the strong parameters as follows, including our virtual attribute to hold all the tags entered through the view:

def post_params
  params.require(:post).permit(:author, :content, :all_tags)

The permit method creates a whitelist of parameters to be allowed to pass. Read more about about strong parameters here

Let’s create the form with a text field for tags. We will create the post using AJAX. It’s pretty simple:


<div class="row text-center">
  <%= form_for(, remote: true) do |f| %>
    <div class="large-10 large-centered columns">
      <%= f.text_field :author, placeholder: "Author name" %>
    <div class="large-10 large-centered columns">
      <%= f.text_area :content, placeholder: "Your post", rows: 5 %>
    <div class="large-10 large-centered columns">
      <%= f.text_field :all_tags, placeholder: "Tags separated with comma" %>
    <div class="large-10 large-centered columns">
      <%= f.submit "Post", class: "button"%>
  <% end %>

__remote: true__ is the attribute that tells the form to be submitted via AJAX rather than by the browser’s normal submit mechanism.

After creating our post, create will redirect to the index action and view the existing posts.


def index
  @posts = Post.all


<div class="row">
  <div class="large-8 columns">
    <%= render partial: "posts/new" %>

Don’t forget to handle the routes.

root 'posts#index'
resources :posts, only: [:create]

Add some very simple styling to the view as follows:

.tags-cloud {
  margin-top: 16px;
  padding: 14px;

.top-pad {
  padding: 25px;
  box-shadow: 0px 3px 8px -4px rgba(0,0,0,0.15);
  background: white;
  border-radius: 4px;
  padding-bottom: 12px;

  margin-top: 10px;
  margin-bottom: 10px;

  padding-top: 10px;
  padding-bottom: 10px;

Run rails s and let’s see what we have.
Form for post

Oops, there are no posts!. We never wrote the create action.


def create
  @post =
  respond_to do |format|
      format.js # Will search for create.js.erb
      format.html { render root_path }

This snippet creates a new Post with the parameters specified by the user, checking whether it’s valid and returning the result. Since the form is submitted with AJAX, the respond format is js.

Now, we need to create the create.js.erb file to hold the javascript that will run after creating the post:

var new_post = $("<%= escape_javascript(render(partial: @post))%>").hide();
$('#post_<%= %>').fadeIn('slow');

This code renders a partial view of the newly created post, the prepend function allows it to be rendered on top of the old posts with a fadeIn effect.

Create a partial that will render each post:


<%= div_for post do %>
  <div class="large-12 columns border border-box glassy-bg mt pt">
    <strong><%= h( %></strong><br />
    <sup class="text-muted">From <%= time_ago_in_words(post.created_at)%></sup><br />
    <div class="mb pb">
      <%= h(post.content) %>
    <div class="tags">
      <%=raw post.all_tags %>
<% end %>

Before we check the output, modify the index view to hold the partial for posts:


<div class="row mt pt">
  <div class="large-5 columns">
    <div class="top-pad glassy-bg">
      <%= render partial: "posts/new" %>
  <div class= "large-7 columns" id="posts">
    <%= render partial: @posts.reverse %>

Posts will be in reverse order from top to bottom, meaning, the most recenlty entered post will be first.

At this stage, we have posts with tags stored in the database using the two tables, tags and taggings . The taggings table saves the association between posts and tags. Here’s what our posts look like:


Tag-based Search

In this section, we will create scope-based searches on tag name.

Create a class method called tagged_with(name) which will take the name of the specified tag and search for posts associated with it.


def self.tagged_with(name)

Create an instance variable holding the results on the controller.


def index
  if params[:tag]
    @posts = Post.tagged_with(params[:tag])
    @posts = Post.all

Add a get route to hold the tag name and point to the posts_controller#index method:


get 'tags/:tag', to: 'posts#index', as: "tag"

After that, change the tags of each post to be links to the ‘index’ method, as follows:


<%=raw tag_links(post.all_tags)%>

tag_links(tags) is a helper method which will hold the logic of converting the tags to links.


def tag_links(tags)
  tags.split(",").map{|tag| link_to tag.strip, tag_path(tag.strip) }.join(", ") 

Yay! Now, we have tag-based search for our posts!

tag-based search


Tag Cloud

Let’s generate one of those cool tag clouds based on counting the number of occurrences for each tag across all posts.

First, create a method to count all tags associated with posts:


def self.counts"name, count(taggings.tag_id) as count").joins(:taggings).group("taggings.tag_id")

This query groups the matched tag_ids from the taggings join table and counts them.

We will style them according to their counts by creating a helper method called tag_cloud which take the result of calling the counts function and CSS classes.


def tag_cloud(tags, classes)
  max = tags.sort_by(&:count).last
  tags.each do |tag|
    index = tag.count.to_f / max.count * (classes.size-1)
    yield(tag, classes[index.round])

This helper method will get the tag with the max count. Then, it loops on each tag to calculate the index which will choose the CSS class based on rounded value. Then, the passed block will be executed.

We need to add styles for different sizes as follows:


.css1 { font-size: 1.0em;}
.css2 { font-size: 1.2em;}
.css3 { font-size: 1.4em;}
.css4 { font-size: 1.6em;}

Don’t forget to add *= require tags to application.css.

Finally, add the code to display the tags in the view and apply the CSS classes to them.


<div class="tags-cloud glassy-bg">
  <h4>Tags Cloud</h4>
  <% tag_cloud Tag.counts, %w{css1 css2 css3 css4} do |tag, css_class| %>
    <%= link_to, tag_path(, class: css_class %>
  <% end %>

Check it out, our tags are in a cloud!
tag cloud


After this article, you should be able to handle the act_as_taggable_on gem without issue. You can read more about it on its github repo.


I hope this tutorial helps you understand what goes into creating a basic tagging system. Tag, you’re it! :)

The most important and interesting stories in tech. Straight to your inbox, daily. Get Versioning.
Login or Create Account to Comment
Login Create Account