Skip to content


Subversion checkout URL

You can clone with
Download ZIP
The annotations plugin currently being used in the BioCatalogue codebase to allow the addition of arbitrary metadata to things
Fetching latest commit...
Cannot retrieve the latest commit at this time.
Failed to load latest commit information.


Annotations Plugin (for Ruby on Rails applications)

Original Author

Jiten Bhagat (


© 2008-2010, the University of Manchester and the European Bioinformatics Institute (EMBL-EBI)






In v0.2.0 this plugin has been modified substantially to support polymorphic annotation values rather than just strings.

This README will be updated in due course to reflect these changes, once everything is stable and integrated into the BioCatalogue codebase.


This plugin allows arbitrary metadata to be stored and retrieved for any model object in your Ruby on Rails (v2.2+) application. This metadata is in the form of attribute/value data pairs that, together with provenance information, make up 'annotations'. These annotations are “attached” to a model object in your application.

The idea is to abstract out and decouple annotations from your application's core data model and allow layering in of metadata easily.

Examples of things you can use the annotations plugin for:

  • Tags

  • Categories

  • Descriptions

  • Examples

  • Notes

  • Comments

The main benefit is that annotations are stored and retrieved in a uniform manner with a common API. Parts of the plugin can also be extended and overridden in your application (see the Usage section for more info).

The annotations plugin is currently being used successfully in the BioCatalogue codebase to allow easy annotation of web services and their deployments/versions/operations/inputs/outputs/etc. Check out


  • A complete set of ActiveRecord models for annotations, annotation attributes and annotation value seeds.

  • acts_as_annotatable and acts_as_annotation source mixin modules for your models, complete with a number of advanced finders.

  • A basic CRUD controller that can be used out of the box to list, create, view, edit and delete annotations.

  • Versioning of annotations - all updates to annotations cause a new version of that annotation to be created.

  • Annotation Value Seeds can be used to set up some default/preliminary seed data for attributes without having to create annotations.

Full Documentation

To generate full documentation (including this readme and full API docs) do the following:

Install RDoc (v2.3.0 or above):

% [sudo] gem install rdoc

Install the darkfish-rdoc gem:

% [sudo] gem install darkfish-rdoc

Run the following command in the plugin's root directory (vendor/plugins/annotations):

% rdoc -SHNU -w 2 --title='Annotations Plugin' -m 'INDEX.rdoc' -x '(/doc|/tasks|/script|/test)'

The generated documentation will be in the doc folder of the annotations plugin.

Conceptual Data Model

At a conceptual level, an Annotation consists of:

  • a link to the thing being annotated - the Annotatable

  • a link to the source of the annotation - the Source

  • a link to the attribute which specifies what kind of annotation this is - the Attribute

  • the value of the annotation (currently only free text and stored as a string) - the Value

  • the data type of the value (defaults to “String”) - the Value Type

  • version information - Version Info

  • timestamps for event time info - Timestamps

This can be represented as:

Annotation = Annotatable + Source + Attribute + Value + Value Type + Version Info + Timestamps

The database model for an Annotation is made up of the following fields:

| id | annotatable_type | annotatable_id | source_type | source_id | attribute_id | value | value_type | version | version_creator_id | created_at | updated_at |

This makes use of ActiveRecord polymorphic relationships to allow any ActiveRecord model to be an Annotatable or Source.

Note: The version_creator_id field is only relevant when there are more than one version of an Annotation. I.e.: for the first version of the annotation the version_creator_id will be nil.

Annotation Attributes currently only consist of a name. This is the attribute or property you are describing for an Annotatable. Examples: 'tag', 'description', etc.

Versioning of Annotations

Whenever an Annotation is updated, a new AnnotationVersion entry is created in the db (corresponding model name = Annotation::Version).

This uses a library from the version_fu plugin. A customised version of this is embedded within the Annotations plugin (which won't conflict if version_fu is installed in the main codebase).

Installation and Setup

Important Requirements

  • Only works for Ruby on Rails v2.2 and above.

  • Your Application Controller MUST define (or have access to) the following methods:

    • login_required - a method that checks if the user is logged in and if not redirects to the login page.

    • logged_in? - a method to check if the user is logged_in.

    • current_user - a method to retrieve the currently logged in user. This MUST return nil if no user is currently logged in.


To install the plugin as a git cloned repo (this allows you to easily update the plugin in the future):

ruby script/plugin install git://

Note: this requires git to be installed.

To install the plugin the regular way:

ruby script/plugin install


  • Generate the migration(s):

    ruby script/generate annotations_migration all
  • Create a config file - config/initializers/annotations.rb - for the plugin specific configuration (see Usage section for the available config options)

  • Modify your application to work with the plugin:

    • Add the following line to the top of your app/controllers/application_controller.rb file (or application.rb in older versions of Rails):

      require_dependency File.join(Rails.root, 'vendor', 'plugins', 'annotations', 'lib', 'app', 'controllers', 'application_controller')
    • Add the following line to the top of your app/helpers/application_helper.rb file:

      require_dependency File.join(Rails.root, 'vendor', 'plugins', 'annotations', 'lib', 'app', 'helpers', 'application_helper')

    Note: the require_dependency line is crucial in making your app work with the files in the plugin and allows for easy extension and overriding of the plugin parts. *However, this does mean that any file with the require_dependency line will not be automagically loaded by Rails in development mode when changes have been made - a server restart is required whenever any changes are made to these files.*

  • Create the following files in your application:

    • Annotation model:

      # app/models/annotation.rb
      # This extends the Annotation model defined in the Annotations plugin.
      require_dependency File.join(Rails.root, 'vendor', 'plugins', 'annotations', 'lib', 'app', 'models', 'annotation')
      class Annotation < ActiveRecord::Base
    • AnnotationAttribute model:

      # app/models/annotation_attribute.rb
      # This extends the AnnotationAttribute model defined in the Annotations plugin.
      require_dependency File.join(Rails.root, 'vendor', 'plugins', 'annotations', 'lib', 'app', 'models', 'annotation_attribute')
      class AnnotationAttribute < ActiveRecord::Base
    • AnnotationsController controller:

      # app/controllers/annotations_controller.rb
      # This extends the AnnotationsController controller defined in the Annotations plugin.
      require_dependency File.join(Rails.root, 'vendor', 'plugins', 'annotations', 'lib', 'app', 'controllers', 'annotations_controller')
      class AnnotationsController < ApplicationController

    These are used to extend/override the models and controller from the plugin with extra actions, filters, validations, processing and so on (see Usage section for more details and examples).

  • Specify which models in your codebase can be annotated by adding acts_as_annotatable in the model's definition. For example:

    class Service < ActiveRecord::Base
  • Specify which models in your codebase can be the source of annotations by adding acts_as_annotation_source in the model's definition. For example:

    class User < ActiveRecord::Base

The Annotations plugin has now been installed and is ready for use. See the Usage section for further details.


Quick Use

  • To add a bunch of Annotations to a model instance:

    book = Book.find(1)
    data = {
      :description => "My bookie wookie",
      :rating => 5,
      :tag => [ "horror", "romance", "indiluted", "true story" ] }
    new_annotations = book.create_annotations(data, current_user)
  • Create an Annotation directly:

    book = Book.find(10)
    ann1 = => "tag", 
                          :value => "hot", 
                          :source_type => "User", 
                          :source_id => 100,
                          :annotatable_type =>,
                          :annotatable_id =>
  • Building a simple form for adding a single Annotation, like 'description', to a specified model object:

    <% form_tag annotations_url do %>
      <%= hidden_field_tag "annotation[annotatable_type]", "Book" -%>
      <%= hidden_field_tag "annotation[annotatable_id]", 100 -%>
      <%= hidden_field_tag "annotation[attribute_name]", "description" -%>
      <%= text_area_tag "annotation[value]" -%>			
      <%= submit_tag "Submit", :disable_with => "Submitting..." -%>
    <% end %>
  • Building a simple form for adding multiple Annotations from one field, like 'tags', to a specified model object:

    <% form_tag create_multiple_annotations_url do %>
      <%= hidden_field_tag "separator", "," -%>
      <%= hidden_field_tag "annotation[annotatable_type]", "Book" -%>
    	<%= hidden_field_tag "annotation[annotatable_id]", 100 -%>
      <%= hidden_field_tag "annotation[attribute_name]", "description" -%>
      <%= text_area_tag "annotation[value]" -%>			
      <%= submit_tag "Submit", :disable_with => "Submitting..." -%>
    <% end %>
  • Find all Annotations for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(6806)
    annotations = book.annotations
  • Find all 'tag' Annotations for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(23)
    tag_annotations = book.annotations_with_attribute("tag")
  • Find all Annotations for a model instance that have a number of different attribute names (that has acts_annotatable specified in the model's class):

    book = Book.find(22124)
    rating_annotations = book.annotations_with_attributes([ "rating-performance", "rating-usefulness", "rating-documentation" ])

See _More Examples_ below.

Important Usage Notes

  • When using the built in Annotations Controller to create annotations (as in the forms examples above), the default is to use the current_user as the Source. However, you can explicitly specify the source by adding fields for “annotations[source_type]” and “annotations[source_id]”.

  • Attributes are NOT case-sensitive. So 'description' and 'Description' will be the same AnnotationAttribute entity and therefore all annotations with that attribute will be the exact same type/class/kind of annotation).

  • When displaying the values of annotations, ALWAYS clean the data using methods like h (html escape), sanitize and white_list.

  • By default, duplicate annotations cannot be created (by “duplicate” we mean: same value for the same attribute, on an annotatable object, regardless of source). For example: a user cannot add a description to a service that matches an existing description for that service. You can override this behaviour for annotations with certain attributes, using the configuration option: attribute_names_to_allow_duplicates. NOTE: this is different to the limits_per_source config_ option, which isn't about duplicate annotations, but rather limiting the quantity of annotations of a specific attribute on a specific annotatable object by a specific source.

Config Options

All the config options can be found in lib/annotations/config.rb, listed below:

  • attribute_names_for_values_to_be_downcased

  • attribute_names_for_values_to_be_upcase

  • strip_text_rules

  • user_model_name

  • limits_per_source

  • attribute_names_to_allow_duplicates

  • value_restrictions

TODO: explain and document these.

More Examples

  • Create multiple 'tag' Annotations from a single string value:

    params = { :annotatable_type => "Book",
               :annotatable_id => 78,
               :attribute_name => "tag",
               :value => "workflow, taverna, brilliant",
               :source_type => "Group",
               :source_id => 4 }
    Annotation.create_multiple(params, ',')
  • Find all 'comment' Annotations, by a specified Source, for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(67)
    user = User.find_by_name("jane")
    annotations = book.annotations_with_attribute_and_by_source("comment", user)
  • Find all Annotations, that don't have the specified attributes, for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(56)
    annotations = book.all_annotations_excluding_attributes([ "tag", "note", "example" ])
  • Find Annotatable objects that have a specific attribute name AND value:

    • Gets all annotatables regardless of type:

      Annotation.find_annotatables_with_attribute_name_and_value("complexity", "O(x^2)")
    • Gets only annotatables that are Books:

      Book.with_annotations_with_attribute_name_and_value("Tag", "Amusing rhetoric")
  • Find Annotatable objects that have a combination of attribute names AND values:

    Annotation.find_annotatables_with_attribute_names_and_values([ "tag" ], [ "fiction", "sci-fi", "fantasy" ])
    Annotation.find_annotatables_with_attribute_names_and_values([ "tag", "keyword", "category" ], [ "fiction", "fantasy" ])
  • Count the number of Annotations, by any Source, for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(90)
    count = book.count_annotations_by("all")
  • Count the number of Annotations, by Users, for a model instance (that has acts_annotatable specified in the model's class):

    book = Book.find(90)
    count = book.count_annotations_by("User")

TODO: add more advanced examples!

Extending the Plugin

Most of the aspects of this plugin can be extended/overridden in your codebase.


  • To restrict the values for certain annotations, you can either use the Annotations::Config::value_restrictions setting and/or extend the Annotation model in your own codebase and use the ActiveRecord validates_inclusion_of validation. For the latter, an example is below:

    require_dependency RAILS_ROOT + '/vendor/plugins/annotations/lib/app/models/annotation'
    class Annotation < ActiveRecord::Base
      validates_inclusion_of :value, 
      	                     :in => [ "fruit", "nut", "fibre" ], 
      	                     :message => "Please select a valid category.",
      	                     :if => { |ann| ann.attribute_name.downcase == "category" }

TODO: add more!

See Also

The BioCatalogue codebase contains many examples of using the Annotations plugin successfully. Some pointers:

The prototype Shims Library also makes use of the Annotations plugin successfully and may provide some useful examples.

Updating the Plugin

Update the plugin code using the appropriate mechanism. Then:

ruby script/generate annotations_migration vX

… where X is the version of the migration you want to generate

Main Plugin Contents

TODO: list files and what the purpose of each is.

Known Issues

  • May not work with sqlite and sqlite3 databases.

  • Not tested with Postgres DB.

  • Doesn't work consistently well with Single Table Inheritance (STI).


  • Whilst running in development mode, if you get an error such as:

    A copy of ApplicationController has been removed from the module tree but is still active!

    … then restart your server. This is due to the way Rails autoloads files in development mode, which affects the plugin and any extensions in the main app.

Future Plans

In no particular order:

  • Allow curation assertions such as “Agree”, “Useful”, “Incomplete” to be made on annotations.

  • Allow values to be any model rather than free text and provide a basic set of annotation value models, such as AnnotationValueFreeText, AnnotationValueNumber, AnnotationValueUri, etc

  • Extend the AnnotationAttribute model to contain controlled vocabulary / ontology term information.

  • Update the versioning to a better version of version_fu. See:

Appendix A: Data Model


Table name: annotations


  • id

  • source_type

  • source_id

  • annotatable_type

  • annotatable_id

  • attribute_id

  • value

  • value_type

  • created_at

  • updated_at

  • version

  • version_creator_id

Annotation Versions

Table name: annotation_versions


  • id

  • annotation_id

  • version

  • version_creator_id

  • source_type

  • source_id

  • annotatable_type

  • annotatable_id

  • attribute_id

  • value

  • value_type

  • created_at

  • updated_at

Annotation Attributes

Table name: annotation_attributes


  • id

  • name

  • created_at

  • updated_at

Annotation Value Seeds

Table name: annotation_value_seeds


  • id

  • attribute_id

  • value

  • created_at

  • updated_at

Something went wrong with that request. Please try again.