Login required
Download

Taxonomy Plugin

(2)
Author(s): Marc Palmer
Current Release: 0.1
Grails Version: 1.1.1 > *
Tags tagging taxonomy

Taxonomies for domain classes

Similar to the excellent Taggable plugin, this plugin allows you to apply arbitrary hierarchical categorisation (Taxons) to domain objects.

Example of basic usage:

def book = Book.get(1)

// Add the book to the Non-fiction > Autobiography category book.addToTaxonomy(['Non-fiction', 'Autobiography'])

def autobiographies = Book.findAllByTaxonomyFamily(['Non-fiction', 'Autobiography'])

There is support for arbitrary nesting of Taxons (categories) within unlimited Taxonomy(s), and taxonomies are domain class independent.

So category "Local" on Author can be used on Book.

However you can have different taxonomy graphs aside from the default one, so you might have a taxonomy graph "Location" storing the names of states and cities, and another taxonomy graph "Company type" - and objects having categories in both graphs.

Release 0.1 is "alpha" grade. It does not support polymorphism. So querying objects by taxonomy requires you to query the specific class type. This will be resolved in the next release. Also, it does no caching so it isn't going to be blisteringly fast. This will come soon.

Installation

Just run:

grails install-plugin taxonomy

Then on any domain classes that you want to support taxonomy, simply define a "taxonomy" property set to true:

class Book {
    static taxonomy = true

String title }

Reference

Several methods are added to domain classes, and TaxonomyService provides access to other Taxonomy manipulation features.

Throughout these methods a convention is used for the "nodeOrPath" argument.

A nodeOrPath is one of:

  • A comma-delimited list of string taxon names eg "Local,Food,Grocers"
  • A List of string taxon names eg ['Local', 'Food', 'Grocers'] - note that this is safe for taxon names that include commas "," in their name
  • A Taxon instance - typically retrieved using TaxonomyService
This affords you some flexibility and scope for optimisation when dealing with taxonomies.

Methods on domain objects

The follow methods are added to domain class object instances.

addToTaxonomy(nodeOrPath, taxonomy = null)

This method adds a taxonomy classification to a domain object instance. The taxonomy is optional. If none is supplied the taxons will be used (and created in, if necessary) the global "special" taxonomy created by default. Otherwise if you want to have multiple classification spaces you can pass in a different taxonomy name (or instance) and it will use the taxons from that taxonomy.

An example

def book = Book.get(1)

// Add the book to the Non-fiction > Autobiography category book.addToTaxonomy(['Non-fiction', 'Autobiography'])

// Add the book to the Popular > Charts > Top-10 category book.addToTaxonomy(['Popular', 'Charts', 'Top-10'])

// Also add a classification that is purely for internal accountancy use book.addToTaxonomy(['Markup', '10-20%'], "accounting")

removeTaxonomy(nodeOrPath, taxonomy = null)

This performs the reverse of addToTaxonomy:

def book = Book.get(1)

// Remove the book from the Non-fiction > Autobiography category book.removeTaxonomy(['Non-fiction', 'Autobiography'])

// Remove the classification that is purely for internal accountancy use book.removeTaxonomy(['Markup', '10-20%'], "accounting")

Static methods added to domain classes

The follow methods are added to domain class object instances.

findAllByTaxonomyFamily(nodeOrPath, params = null)

This will find object instances that have a taxon that is at or below the specified nodeOrPath in the taxonomy.

The nodeOrPath can be null which implies anything at root level in the given taxonomy. The params are standard finder params,with the optional "taxonomy" param to specify a specific taxonomy tree,

def book1 = Book.get(1)
def book2 = Book.get(2)

book1.addToTaxonomy(['Non-fiction', 'Autobiography']) book1.addToTaxonomy(['Discounted', '10%'], 'pricing') // Pricing taxonomy, separate tree book2.addToTaxonomy(['Non-fiction', 'Popular science']) book2.addToTaxonomy(['Discounted', '20%'], 'pricing') // Pricing taxonomy, separate tree

// Find all books under Non-fiction def nonFictionBooks = Book.findAllByTaxonomyFamily('Non-fiction')

// Find all books under Discounted > 10% in alternative taxonomy def tenPercentBooks = Book.findAllByTaxonomyFamily(['Discounted', '10%'], 'pricing')

findByTaxonomyFamily(nodeOrPath, params = null)

Same as findAllByTaxonomyFamily but only returns the first result it finds - which may depend on your params eg sort order.

findAllByTaxonomyExact(nodeOrPath, params = null)

Finds all the objects with the exact category specified in nodeOrPath - objects with sub-categories will not be found.

def book1 = Book.get(1)
def book2 = Book.get(2)
def book3 = Book.get(3)

book1.addToTaxonomy(['Non-fiction', 'Autobiography']) book1.addToTaxonomy(['Discounted', '5%'], 'pricing') // Pricing taxonomy, separate tree

book2.addToTaxonomy(['Non-fiction', 'Popular science']) book2.addToTaxonomy(['Discounted', '5%'], 'pricing') // Pricing taxonomy, separate tree

book3.addToTaxonomy(['Discounted', '5%', 'One week only'], 'pricing')

// There are no purely Non-fiction books def nonFictionBooks = Book.findAllByTaxonomyExact('Non-fiction') assert 0 == nonFictionBooks.size()

// Find all books at Discounted > 5% in alternative taxonomy, not subtypes of it def fivePercentBooks = Book.findAllByTaxonomyExact(['Discounted', '5%'], 'pricing') assert 2 == fivePercentBooks.size() // doesn't include the one week only book

findByTaxonomyExact(nodeOrPath, params = null)

Same as findAllByTaxonomyExact but only ever returns 1 result.

Querying and manipulating the taxonomies - using TaxonomyService

For now, please see the source code of the service here http://fisheye.codehaus.org/browse/grails-plugins/grails-taxonomy/trunk/grails-app/services/com/grailsrocks/taxonomy/TaxonomyService.groovy?r=HEAD

The service API may be subject to change