Tagalong

Build Status

Logo

Tagalong is a Rails tagging plugin that is intended to be clean, efficient, and simple. I have tried hard to keep the API slim and intelligable in terms of Object Oriented Programming. I focused heavily on this as I feel most other Rails tagging plugins seriously neglect OO in their APIs.

The other key differentiation between Tagalong and other tagging libraries is the relational database structure behind the scenes. This database structure allows Tagalong to provide not only an Object Oriented API, but also a set of features that help differentiate it from other Rails tagging plugins.

Feature Overview

  • clean Object Oriented API
  • does NOT require saving of the model when tagging/untagging
  • keeps history of tags Taggers have used
  • allows defining multiple Taggers and Taggables
  • tracks the number of times tags are used
  • returns tag lists in alphabetical order (imporant for UI)

Installation

Add this line to your application's Gemfile:

gem 'tagalong'

And then execute:

$ bundle

Or manually install it:

$ gem install tagalong

API Concepts

There are three major concepts that one should understand before reading the documentation for the API, the Tag, Tagger, and Taggable.

Tag

A Tag in the Tagalong API is represented by a string. Conceptually it is simply a textual label that is associated with a Taggable and a Tagger.

Tagger

A Tagger is an object that will be performing the action of applying a tag to a Taggable. In most Rails apps this is the a User object.

Taggable

A Taggable is an object that will be having the tags applied to it. In a Rails app this could be a Task object, Contact object, or any type of object that you want to be able to tag.

Usage

Migration Setup

In order to use Tagalong, generate the proper migrations so that the tags can be stored in the database. This can be done with the following command:

rails generate tagalong:migration

The above will generate the migration and place it appropriately in the db/migrate/ project path.

Declaring Taggers and Taggables

It is necessary to declare at least one Tagger and Taggable in addition to generating and running the migrations. This is done as follows:

class Contact < ActiveRecord::Base
  tagalong_taggable
end

class User < ActiveRecord::Base
  tagalong_tagger
end

Once Taggers and Taggables are declared, they can be used in numerous ways as outlined below.

Tag Usage

Tagging

To tag a taggable, call the tag method on a Tagger object and hand it a persisted Taggable object with the given label that you want to apply.

@user.tag(@contact, "sometag")
Untagging

To untag, call the untag method on a Tagger object and hand it a persisted Taggable object with the given label that you want to untag.

@user.untag(@contact, "sometag")

List Tagger tags

To list Tagger tags, call the tags method on a Tagger object. This will return an array of all tags that Tagger has ever used in ascending alphabetical order.

@user.tags
# => ['another_tag', 'some_tag', 'woot_tag']

List Tagger tags with more information

You can get a list of tags that belong to a Tagger along with some additional information by calling the tags_including method. The result will be a hash that always contains the key :name which is the name of the tag. There can be other keys added to the hash if you choose to pass the option for them to the method. A list of the params you can pass and their explanations are below:

:number_of_references => true

This only takes true as a value. When passed, it will add :number_of_references to the result hash. The value of :number_of_references is the number of times the tag is used by the Tagger.

@user.tags_including(:number_of_references => true)
# => [
       { name: 'another_tag', :number_of_references => 42 },
       { name: 'some_tag', :number_of_references => 23 },
       { name: 'woot_tag', :number_of_references => 2 }
     ]
:has_been_tagged => @contact

Takes a Taggable as a value. When passed, it will add :has_been_tagged to the result hash for each tag returned. The value of :has_been_tagged will contain a boolean representing if the Taggable passed is tagged by the current tag in the iteration.

@user.tags_including(:has_been_tagged => @contact)
# => [
       { name: 'another_tag', :has_been_tagged => true },
       { name: 'some_tag', :has_been_tagged => false },
       { name: 'woot_tag', :has_been_tagged => true }
     ]

Search Tagger tags

A Tagger's tags can be searched for using the matching_tags method and a given search phrase as follows:

@user.matching_tags("cats have")
# => [
       { name: 'cats have 9 lives', :number_of_references => 42 },
       { name: 'what do cats have', :number_of_references => 23 },
     ]

The above returns an array of hashes representing tags ordered in descending order of their number of references. Note: This function by default simply uses the ActiveRecord backend to search for matching tags. However, this method switches to using Sunspot to handle the searching if Sunspot has been enabled for this plugin. For more information please see the Sunspot section below.

List Taggable tags

To list Taggable tags, call the tags method on a Taggable object. This will return an array of all tags that Taggable is currently tagged with in ascending alphabetical order.

@contact.tags
# => ['some_tag', 'woot_tag']

List Taggables that have a tag

To list Taggables that have a tag, call the taggables_with method on a Tagger object as follows. This will return an array of Taggable objects that are currently tagged with the given tag.

@user.taggables_with('some_tag')
# => [Contact Object, Contact Object]

Check if Taggable is tagged with a tag

To check if a Taggable is tagged with a tag, call the tagged_with? method on a Taggable object as follows. This will return true in the case that the Taggable IS currently tagged with the given tag, and false in the case where the Taggable is NOT currently tagged with the given tag.

@contact.tagged_with?('some_tag')
# => true

Check if Tagger has a tag

To check if a Tagger has a specific tag, call the has_tag? method. This will return a boolean representing if the Tagger has used the passed tag (and not deleted it).

@user.has_tag?('sometag')
# => true

Tag Management

Creating

To create a tag on the Tagger object without applying the tag to a Taggable, call the create_tag method.

@user.create_tag("sometag")
Renaming

To rename a tag that exists on a Tagger object, simply call the rename_tag method with the existing tag and new desired name. If the name is already in use, it will raise an ActiveRecord::RecordInvalid error.

@user.rename_tag("sometag", "another_tag_name")
Deleting

To delete a tag, call the delete_tag method on a Tagger object and hand it the label of the tag you want to delete. This will remove the tag from the Tagger, as well as all Taggables that might be using it.

@user.delete_tag("sometag")

Sunspot

Tagalong currently supports Sunspot for searching tags. In order to use Sunspot for searching tags a few things need to happen in the client Rails application.

  1. Add Sunspot to your Rails app as you normally would
  2. Create a Rails initializer file called tagalong.rb that contains Tagalong.enable_sunspot.

Credits

I just wanted to thank all of the other open source Rails tagging plugins out there. Especially, acts-as-taggable-on, is_taggable, and rocket_tag. I learned a lot from you all.

I also want to thank RealPractice, Inc. for donating some developer hours to the project as well as being our initial user.

Beyond that I want to specifically thank @cyoungberg and @russCloak for discussing the API decissions with me. It definitely helped having you guys as a sounding board.

Contributing

If you are interested in contributing code please follow the process below and include tests. Also, please fill out issues on our GitHub issues page if you have discovered a bug or simply want to request a feature.

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Commit your changes (git commit -am 'Added some feature')
  4. Push to the branch (git push origin my-new-feature)
  5. Create new Pull Request