Rails - Tagging with autocomplete

How to integrate acts_as_taggable gem with Chosen jQuery library for autocomplete

For a project I was recently working on I had to set up a simple tagging system along with autocompletion. The idea was to guide the user towards a pre-defined list of tags that could be easily filtered. Amongst many options I opted for a great gem called act_as_taggable and the chosen jQuery library.

For this example will be creating a simple application that manages products, this could be the base for a store for example. You can find this example in my github.

Let's start by creating our new application

rails new tagging_autocomplete -T

-T will omit all the test framework.

Then let's add our gems

gem 'bootstrap-sass'
gem 'acts-as-taggable-on'

One way to add external JQuery plugins to your Rails app is to use Rails Assets. It automatically converts the packaged components into gems that are easily droppable into your asset pipeline and stay up to date. I came across Rails assets in this Go Rails episode an I would recommend to use it every time you need to include external JS libraries into your rails application.

Beside adding in your Gemfile

source 'https://rails-assets.org' do
  gem 'rails-assets-chosen'
end

you also need to:

Include following in application.js:
//= require chosen
Include following in application.css:
*= require chosen

And run bundle install.

Next we'll need to create our CRUD to manage products and tags

rails g scaffold Product name:string description:text
rails g scaffold Tag name:string

To create a leaner Scaffold without helpers, test, or assets you can:

  • add --no-helper --no-assets --no-controller-specs --no-view-specs --no-test-framework after your scaffold command
  • or include in config/application.rb
config.generators do |g|
  g.assets            false
  g.helper            false
  g.test_framework    nil
  g.jbuilder          false
end

Set up act_as_taggable gem

rake acts_as_taggable_on_engine:install:migrations

Review the generated migrations, look for the ActsAsTaggableOnMigration and remove

create_table :tags do |t|
  t.string :name
end

As we already have created this table.

then run the migration :

rake db:migrate

Now we need to change in our app/controllers/product_controller.rb

The index action:

def index
  if params[:tag]
    @products = Product.tagged_with(params[:tag])
  else
    @products = Product.all
  end
end

As well as the product_params:

def product_params
  params.require(:product).permit(:name, :description, :tag_list)
end

In our app/models/product.rb we have to include

acts_as_taggable

And in app/views/products/_form.html.erb add the tag_list field:

<%= f.label :tag_list, 'Tags (separated with comas)' %>
<%= f.text_field :tag_list, class: 'form-control' %>

To see your tag list add <%= product.tag_list %> in /views/products/index.html.erb and /views/products/show.html.erb. At this stage we have built a simple tagging system but let's improve it a little with a way for the user to filter results.

Filtering results by tag

As mentioned in this Railscast acts_as_taggable documentation tells us that tag_list returns an array.

@product.tag_list # => ["north", "east", "south", "west"]

So to create the filter link we just have to transform our /views/products/index.html.erb mapping each element of the array like this:

<%= raw product.tag_list.map { |t| link_to t, tag_path(t) }.join(', ') %>

To finish off the filtering functionality we need to tweak a little our routes to show the corresponding products when clicking on a tag and not trigger the show action.

Rails.application.routes.draw do
  resources :tags, except: :show
  get 'tags/:tag', to: 'products#index'
  resources :products
  root 'products#index'
end

Now when you click on an individual tag you get a filtered result!

Let's begin autocompletion

We are just a few steps away to finish with our application. For the autocompletion the Chosen library will do the heavy lifting, we just have to implement it.

One important thing we forgot when creating our migrations for the tagging was to set up our relations within the tag model so let's fix that in app/model/tag.rb:

class Tag < ActiveRecord::Base
  has_many :taggings
  has_many :tags, through: :taggings
end

We also need to permit the tag_ids parameters in our product_params:

def product_params
  params.require(:product).permit(:name, :description, :tag_list, :tag, { tag_ids: [] }, :tag_ids)
end

Change our text_field for a collection_select in our views/products/_form.html.erb:

<%= f.label :tag_list, "Tags" %>
<%= f.collection_select :tag_ids, Tag.order(:name), :id, :name, {}, {multiple: true} %>

And finally initiate chosen.js in app/views/layouts/application.html.erb. If we inspect the select attribute in the browser we'll see that rails create id="product_tag_ids":

  $(document).on('ready page:load', function () {
    $('#product_tag_ids').chosen({
      allow_single_deselect: true,
      width: '100%'
    })
  });

That's all, we made it and we have now created a tagging system with autocompletion. The source code for this application is in my github repository.


comments powered by Disqus