Taxonomy Plugin

  • Authors : Marc Palmer
3 votes
Dependency :
compile ":taxonomy:1.2"

Documentation Source Issues

Summary

Adds methods to domain classes to allow you to apply hierarchical "tags" to objects. Sophisticated methods for finding objects with tags or tags that are descendents of another tag.

Description

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.

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 }

Commercial Support

Commercial support is available for this and other Grailsrocks plugins.

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")

getTaxonomies()

Returns the list of Taxon objects that apply to the given object:

def book = Book.get(1)

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

// Taxonomies returns a list of Taxon objects with name, parent and scope properties println "Taxonomies for book are now " + book.taxonomies*.name

hasTaxonomy(taxonOrPath, taxonomy = null)

Returns true if the indicated taxon is applied to the domain object. The optional taxonomy argument lets you scope the taxon to different taxonomy groupings.

def book = Book.get(1)

if (book.hasTaxonomy(['Non-fiction', 'Autobiography'])) { println "It's an autobiography!" }

clearTaxonomies()

Removes all taxonomies for the given domain object. This is how you clear all current taxons assigned to the object without first knowing what they are:

def book = Book.get(1)

book.clearTaxonomies()

println "Book no longer has any genres"

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