Rails Tagging

Rails Tagging

For instance, in a social network, a user might have tags that are called skills, interests, sports, and more. This article will show you how to implement tagging in your Rails application. Let’s run through this with me.

Installation
Add gem acts-as-taggable-on to your Gemfile:

Gemfile
1
gem 'acts-as-taggable-on'

and run bundle:

1
bundle

install migrations:

1
2
rails generate acts_as_taggable_on:migration
rake db:migrate

This creates some tables and doesn’t need to know anything specific about your models. The models and concerns for working with these tables (Tag, Tagging, etc).

Model Integration

app/models/post.rb
1
2
3
class Post
  acts_as_taggable
end

Now you can do, eg:

1
2
3
4
post = Post.create
post.tag_list = "programming, ruby, rails"
post.tag_list
# => ['programming', 'ruby', 'rails']

#tag_list= takes a string and splits it up, using the resulting substrings to find_or_create Tags, and associate them with the taggable thing (eg, Post), through Taggings. You don’t need to know this.

The important thing is that, with #tag_list= we can manage tags via a comma-separated-list in a text field in a form.

Controller Integration

app/controllers/posts_controller.rb
1
2
3
4
5
6
7
8
9
10
class PostsController < ApplicationController
  def create
    @post = Post.create(post_params)
    redirect_to @post
  end

  def post_params
    params.require(:post).permit(:title, :body, :tag_list)
  end
end

This is assuming you’re trying to tag an existing Post model with title and body fields, web-accessible through an existing PostsController, with index, show, new, etc. methods.

Substitute your own, the important machinery here is whitelisting tag_list from the params.

Form Integration

app/views/posts/_form.html.erb
1
2
3
4
5
6
<%= form_for post do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body %>
  <%= f.text_field :tag_list %>
  <%= f.submit %>
<% end %>

Now you can create tags for stuff. How about displaying them? Couple options, I’ll go through the most explicit (a TagsController with index and show actions), but they can be rolled up into other controllers/actions.

Controller

config/routes.rb
1
2
3
4
Rails.application.routes.draw do
  resources :posts
  resources :tags, only: [:index, :show]
end
app/controllers/tags_controller.rb
1
2
3
4
5
6
7
8
9
10
class TagsController < ApplicationController
  def index
    @tags = ActsAsTaggableOn::Tag.all
  end

  def show
    @tag =  ActsAsTaggableOn::Tag.find(params[:id])
    @posts = Post.tagged_with(@tag.name)
  end
end

It’s unfortunate we have to do this slightly awkward workaround with Post.tagged_with(@tag.name) in tags#show. The ActsAsTaggableOn::Tag model does not have a built-in relationship with its taggable types (this is a necessary consequence of some polymorphism which we’re not using here). We could add one for Post, but this way is easier to demonstrate.

Tags Views

app/views/acts_as_taggable_on/tags/_tag.html.erb
1
<%= link_to tag.name, tag_path(tag) %>
app/views/tags/index.html.erb
1
2
<h1>Tags</h1>
<%= render @tags %>
app/views/tags/show.html.erb
1
2
<h1><%= @tag.name %></h1>
<div><%= render @posts %></div>

Note the partial path is acts_as_taggable_on/tags/tag. This is so we can just say render @tags and let rails do its implicit magic. There are other ways to organize everything, but this is the simplest.

View Integration

app/views/posts/_post.html.erb
1
2
<h2><%= link_to post.title, post_path(post) %></h2>
<div><%= render post.tags %></div>
app/views/posts/index.html.erb
1
2
<h1>Posts</h1>
<%= render @posts %>
app/views/posts/show.html.erb
1
2
3
<h1><%= @post.title %></h1>
<div><%= @post.body %></div>
<div><%= render @post.tags %></div>

And that should be it. It’ll look shitty, but I hope you can figure out how to elaborate on this once you have it working. If anything here is assuming familiarity with something you don’t have, ask and I will gladly elaborate.

So far so good, That’s it!!! See ya!!! :)