Skip to content
kaneru edited this page Apr 18, 2017 · 4 revisions

Tutorial is based on https://www.reddit.com/r/rails/comments/2chtgw/tagging_in_rails_4/#form-t1_cjfszawia1

This assumes that you have already created posts. You can quickly generate posts by running rails generate scaffold Post title:string content:text

Installation for Rails 4

Gemfile

gem 'acts-as-taggable-on'

terminal

bundle install

terminal

rake acts_as_taggable_on_engine:install:migrations
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) live inside the gem.

Model integration

app/models/post.rb

class Post < ActiveRecord::Base
   acts_as_taggable_on :tags
end

Note* use the syntax acts_as_taggable_on :tags instead of acts_as_taggable because the default implementation seems to be broken.

Now in a rails console you can:

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

class PostsController < ApplicationController
  # ...      

  private

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

The important machinery here is whitelisting tag_list from the params.

Form integration

# app/views/posts/_form.html.erb

<%= form_for post do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :body %>
  <%= f.text_field :tag_list, value: f.object.tag_list.to_s %>
  <%= 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

Rails.application.routes.draw do
  resources :posts
  resources :tags, only: [:index, :show]
end

# app/controllers/tags_controller.rb
    
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
    
<%= link_to tag.name, tag_path(tag) %>

# app/views/tags/index.html.erb

<h1>Tags</h1>
<%= render @tags %>

# app/views/tags/show.html.erb

<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

<h2><%= link_to post.title, post_path(post) %></h2>

# app/views/posts/index.html.erb

<h1>Posts</h1>
<%= render @posts %>

# or 

<%= post.title %>
<%= post.content %>
<h4>Tags</h4>
<%= render post.tags %>

# app/views/posts/show.html.erb

<h1><%= @post.title %></h1>
<div><%= @post.body %></div>
<div><%= render @post.tags %></div>