I'm trying to add "tags" to an Article
model in a Rails 3 application.
I'm wondering if there is a gem or plugin that has adds both the "tagging" functionality in the model and also the auto-complete helpers for the views.
I've found acts_as_taggable
but I'm not sure if that's what I should be using. Is there something newer? I'm getting results from 2007 when I google acts_as_taggable
acts_as_taggable_on and rails3-jquery-autocomplete work nicely together to make a SO like tagging system see example below. I don't think a suitable all in one option exists yet for rails.
Follow these steps to get this all installed:
1 . Backup your rails app!
2 . Install jquery-rails
Note: You can install jQuery UI with
jquery-rails
but I chose not to.3 . Download and install jQuery UI
Choose a theme that will compliment your web design (be sure to test the autocomplete demo with the theme you choose, the default theme did not work for me). Download the custom zip and place the
[zipfile]/js/jquery-ui-#.#.#.custom.min.js
file into your app's/public/javascripts/
folder. place the[zipfile]/css/custom-theme/
folder and all files into your app'spublic/stylesheets/custom-theme/
folder.4 . Add the following to your Gemfile and then run "bundle install"
5 . From the console run the following commands:
Make these changes in your app
Include the necessary javascript and css files in your application layout:
Controller Example
EDIT: Made changes based on Seth Pellegrino's comments.
Model Example
Route.rb
View Example
Note: This example assumes that you are only tagging one model in your entire app and you are only using the default tag type :tags. Basically the code above will search all tags and not limit them to "Article" tags.
I wrote a blog post about this recently; for the sake of brevity, my method allows you to have (optional) context-filtered tags (e.g. by model and by attribute on the model), whereas @Tim Santeford's solution will get you all tags for a model (not filtered by field). Below is the verbatim post.
I tried out Tim Santeford's solution, but the problem was with the tag results. In his solution, you get all existing tags returned through autocomplete, and not scoped to your models and taggable fields! So, I came up with a solution which is in my opinion much much better; it's automatically extensible to any model you want to tag, it's efficient, and above all else it's very simple. It uses the acts-as-taggable-on gem and the select2 JavaScript library.
Install the Acts-As-Taggable-On gem
gem 'acts-as-taggable-on', '~> 3.5'
bundle install
to install itrake acts_as_taggable_on_engine:install:migrations
rake db:migrate
Done!
Set up normal tagging in your MVC
Let's say we have a
Film
model (because I do). Simply add the following two lines to your model:That's it for the model. Now onto the controller. You need to accept the tag lists in your parameters. So I have the following in my
FilmsController
:Notice that the parameter isn't
genres
like we specified in the model. Don't ask me why, but acts-as-taggable-on expects singular + _list, and this is what is required in the views.Onto the view layer! I use the SimpleForm gem and the Slim template engine for views, so my form might look a little different than yours if you don't use the gem. But, it's just a normal text input field:
You need this
input_html
attribute with that value set in order to render it as a comma-separated string (which is what acts-as-taggable-on expects in the controller). Tagging should now work when you submit the form! If it doesn't work, I recommend watching (the amazing) Ryan Bates' Railscast episode on tagging.Integrating select2 into your forms
First off, we need to include the select2 library; you can either include it manually, or use (my preference) the select2-rails gem which packages select2 for the Rails asset pipeline.
Add the gem to your Gemfile:
gem 'select2-rails', '~> 4.0'
, then runbundle install
.Include the JavaScript and CSS in your asset pipeline:
In application.js:
//= require select2-full
. In application.css:*= require select2
.Now you need to modify your forms a bit to include what select2 expects for tagging. This can seem a bit confusing, but I'll explain everything. Change your previous form input:
to:
We add a hidden input which will act as the real value sent to the controller. Select2 returns an array, where acts-as-taggable-on expects a comma-separated string. The select2 form input is converted to that string when its value changes, and set into the hidden field. We'll get to that soon.
The
id
andname
attributes for thef.input
actually don't matter. They just can't overlap with yourhidden
input. Thedata
hash is really important here. Thetaggable
field allows us to use JavaScript to initialize all select2 inputs in one go, instead of manually initializing by id for each one. Thetaggable_type
field is used to filter tags for your particular model, and thecontext
field is to filter on tags that have been used before in that field. Finally, thecollection
field simply sets the values appropriately in the input.The next part is JavaScript. We need to initialize all select2 elements throughout the application. To do this, I simply added the following function to my
application.js
file, so that it works for every route:This may look complex, but it's not too difficult. Basically, the
$("*[data-taggable='true']")
selector just gets every HTML element where we havetaggable: true
set in the data. We just added that to the form, and this is why - we want to be able to initialize select2 for all taggable fields.The rest is just AJAX-related code. Essentially, we make an AJAX call to
/tags
with the parametersname
,taggable_type
andcontext
. Sound familiar? Those are the data attributes that we set in our form input. When the results are returned, we simply give select2 the name of the tag.Now you're probably thinking: I dont have a
/tags
route!. You're right! But you're about to :)Adding the /tags route
Go into your
routes.rb
file and add the following:resources :tags
. You don't have to add all the routes for tags, but I did so that I could have an easy way to CRUD tags. You could also simply do:get '/tags' => 'tags#index'
That's really the only route we need at the moment. Now that we have the route, we have to create a controller called
TagsController
:This is fairly simple. We can send a request to
/tags
, with the parametersname
(the tag text),tags_chosen
(the existing selected tags),taggable_type
(the model that is tagged), and optionalcontext
(the field that is tagged). If we have genre tags for "comedy" and "conspiracy", then type co in our form, the JSON rendered should look something like this:Now in the select2 input, you should see "comedy" and "conspiracy" as auto-completed tag options!
My tags won't save!
There's one last step. We need to set the select2 values into our
hidden
field that we created earlier.This code may be different for you depending on how you structure your form, but you essentially want to get the select2 input, convert the array of strings to a CSV string (e.g.
["comedy", "conspiracy"]
-->"comedy, conspiracy"
), and set that CSV string into the hidden field. That's not too difficult, fortunately.You can catch the select2 input changed event, or anything else that suits you. It's your choice, but this step must be done to ensure that the Rails controller receives the value correctly. Again, in application.js:
You should see the following in your controller action once you have successfully converted your values:
"genre_list"=>"comedy,conspiracy"
And that's all you need to do autocomplete tags in Rails using acts-as-taggable-on and select2!
The acts_as_taggable_on_steroids gem is probably your best bet. I've found that many of the tagging gems are more of a "good place to start" but then require a fair amount of customization to get the result you want.