A simple tagging plugin which allows you to use both the association or array list to manage the tags, while also scoping the tags to a particular context.
Switch branches/tags
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.



When it comes to tagging dsls there are three main players, acts-as-taggable-on, acts-as-taggable-on-steriods and is-taggable. Each seem to have one thing is common and thats using an after save callback to sync the tags list (usually an array) with the tags association.

The key difference between scoped-tags and the other players on the field is that scoped-tags syncs the array version with the association version upon each request.

The initial array tag list implementation was half Array half beast. The current version has been updated to use a proxy based implementation for the array list, similar to the ActiveRecord ProxyAssociation which is used for the different relationship associations. This is the heart of the code which syncs the array style listing with the association listing.

Scoped-tags will, in its next release, allow for tags to be strictly or silently scoped, with the default allowing tag creation if the tag does not exist.

Key Features

  • multiple tag contexts per model
  • works with sphinx and thinking-sphinx
  • add and remove tags using array like features
  • association and array list stay in sync
  • tags are available for use in the validations as they are always kept in sync


./script/plugin install git://github.com/joshk/scoped-tags.git


gem install scoped-tags --source http://gemcutter.org

although it is highly recommended that you install the gemcutter gem and set gemcutter as your default gem repository.

If you are using rails you can also use:

config.gem 'scoped-tags', :source => 'http://gemcutter.org' # source not needed if gemcutter is installed

To generate the migration file use:

./script/generate scoped_tags_migration

How can I use it?

class Person
  scoped_tags :interests

me = Person.new(:name => 'Josh')

me.interests << Tag.new(:name => 'scuba', :context => 'interests') 
# it would be nice to leave the context out, but sadly not just yet
# and me.interests.build(:name => 'scuba') does not work at the moment either 
# (has_many through issue)

me.interest_list => ['scuba']

me.interest_list << 'cycling'
# comma seperated strings are excepted, as well as arrays of strings

me.interests => [#<Tag id: nil, name: "scuba", context: 'interests'>,#<Tag id: nil, name: "cycling", context: 'interests'>]

Things to watch out for

updating a scoped tagged model via a controller update

be aware that if no tags are selected then the browser does not pass through a blank array. This means if the model previously had tags saved to it and you remove the tags, the browser will not pass though the changes because the select box is empty meaning the tags will not be removed.

An example of this is the FCBKcomplete javascript plugin, it uses a select box behind the scenes to store the tags and pass them through to the controller, but if none are selected then the browser passes nothing to the controller, not even a blank array. To fix this, add the following method to the controller and add it as a before_filter to the update action.

Add to the controller:

before_filter :check_blank_tag_ids, :only => :update

def check_blank_tag_ids
    params[:person][:interest_ids] = [] if params[:person] && params[:person][:interest_ids].blank?

use a transaction when updating the scoped model

otherwise the tags will save even if the model validations fail. This is due to the standard behavior of has_many :through relationships.


Person.transaction { @person.update_attributes!(params[:person]) }

Future enhancements

  • add strict and silent scoping on setup (scoped_tags :interests, :strict => true), thus any tag added which is not already in the tags table for that context will be rejected
  • get the instance.context.build method to work correctly