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 treebook2.addToTaxonomy(['Non-fiction', 'Popular science'])
book2.addToTaxonomy(['Discounted', '5%'], 'pricing') // Pricing taxonomy, separate treebook3.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 bookfindByTaxonomyExact(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=HEADThe service API may be subject to change