Skip to content
Browse files

Migration to github

  • Loading branch information...
0 parents commit afc1f6b9468d2489f771e56e6a8eeb618e8e3605 @marcpalmer marcpalmer committed
120 TaxonomyGrailsPlugin.groovy
@@ -0,0 +1,120 @@
+
+import com.grailsrocks.taxonomy.Taxon
+import com.grailsrocks.taxonomy.TaxonomyService
+
+class TaxonomyGrailsPlugin {
+ // the plugin version
+ def version = "1.2"
+ // the version or versions of Grails the plugin is designed for
+ def grailsVersion = "1.1.1 > *"
+ // the other plugins this plugin depends on
+ def dependsOn = [domainClass:'1.1.1 > *']
+ def observe = ['domainClass']
+ def loadAfter = ['hibernate']
+
+ // resources that are excluded from plugin packaging
+ def pluginExcludes = [
+ "grails-app/views/error.gsp",
+ "grails-app/domain/com/grailsrocks/taxonomy/test/Book.groovy"
+ ]
+
+ def author = "Marc Palmer"
+ def authorEmail = "marc@grailsrocks.com"
+ def title = "Taxonomy Plugin"
+ def description = '''\\
+Add hierarichal tags (taxonomies) to any domain classes.
+'''
+
+ // URL to the plugin's documentation
+ def documentation = "http://grails.org/Taxonomy+Plugin"
+
+ def doWithWebDescriptor = { xml ->
+ // TODO Implement additions to web.xml (optional), this event occurs before
+ }
+
+ def doWithSpring = {
+ // TODO Implement runtime spring config (optional)
+ }
+
+ def doWithDynamicMethods = { ctx ->
+ def taxoService = ctx.taxonomyService
+
+ // Make sure global taxo is initialized
+ taxoService.init()
+
+ applyDynamicMethods(application)
+ }
+
+ def applyDynamicMethods(application) {
+ def taxoService = application.mainContext.taxonomyService
+
+ application.domainClasses*.clazz.each { c ->
+ //println "Checking for taxonomy convention on ${c}"
+ if (c.metaClass.hasProperty(c, 'taxonomy') && c.taxonomy) {
+ //println "Adding taxonomy methods to ${c}"
+ // family can include "taxonomy" arg, string/Taxonomy instance
+ c.metaClass.'static'.findByTaxonomyFamily = { nodeOrPath, Map params = null ->
+ if (!params) {
+ params = [max:1]
+ } else {
+ params.max = 1
+ }
+ o = taxoService.findObjectsByFamily(delegate, nodeOrPath, params)
+ return o.size() ? o.get(0) : null
+ }
+ // family can include "taxonomy" arg, string/Taxonomy instance
+ c.metaClass.'static'.findAllByTaxonomyFamily = { nodeOrPath, Map params = null ->
+ taxoService.findObjectsByFamily(delegate, nodeOrPath, params)
+ }
+ // family can include "taxonomy" arg, string/Taxonomy instance
+ c.metaClass.'static'.findByTaxonomyExact = { nodeOrPath, Map params = null ->
+ if (!params) {
+ params = [max:1]
+ } else {
+ params.max = 1
+ }
+ def o = taxoService.findObjectsByTaxon(delegate, nodeOrPath, params)
+ return o.size() ? o.get(0) : null
+ }
+ // family can include "taxonomy" arg, string/Taxonomy instance
+ c.metaClass.'static'.findAllByTaxonomyExact = { nodeOrPath, Map params = null ->
+ taxoService.findObjectsByTaxon(delegate, nodeOrPath, params)
+ }
+ c.metaClass.addToTaxonomy = { nodeOrPath, taxonomy = null ->
+ def link = taxoService.findLink(delegate, nodeOrPath, taxonomy)
+ if (!link) {
+ if (!(nodeOrPath instanceof Taxon)) {
+ nodeOrPath = taxoService.createTaxonomyPath(nodeOrPath, taxonomy)
+ }
+ taxoService.saveNewLink(delegate, nodeOrPath)
+ }
+ }
+ c.metaClass.clearTaxonomies = { ->
+ taxoService.removeAllLinks(delegate)
+ }
+ c.metaClass.getTaxonomies = { ->
+ taxoService.findAllLinks(delegate)*.taxon
+ }
+ c.metaClass.hasTaxonomy = { nodeOrPath, taxonomy = null ->
+ taxoService.hasLink(delegate, nodeOrPath, taxonomy)
+ }
+ c.metaClass.removeTaxonomy = { nodeOrPath, taxonomy = null ->
+ taxoService.removeLink(delegate, nodeOrPath, taxonomy)
+ }
+ }
+ }
+ }
+
+ def doWithApplicationContext = { applicationContext ->
+ // TODO Implement post initialization spring config (optional)
+ }
+
+ def onChange = { event ->
+ applyDynamicMethods(application)
+ }
+
+ def onConfigChange = { event ->
+ // TODO Implement code that is executed when the project configuration changes.
+ // The event is the same as for 'onChange'.
+ }
+}
6 application.properties
@@ -0,0 +1,6 @@
+#Grails Metadata file
+#Fri Sep 09 16:25:10 BST 2011
+app.grails.version=1.3.7
+app.name=Taxonomy
+plugins.hibernate=1.3.7
+plugins.tomcat=1.3.7
32 grails-app/conf/DataSource.groovy
@@ -0,0 +1,32 @@
+dataSource {
+ pooled = true
+ driverClassName = "org.hsqldb.jdbcDriver"
+ username = "sa"
+ password = ""
+}
+hibernate {
+ cache.use_second_level_cache=true
+ cache.use_query_cache=true // Warning, turning this on causes lock contention
+ cache.provider_class='org.hibernate.cache.EhCacheProvider'
+}
+// environment specific settings
+environments {
+ development {
+ dataSource {
+ dbCreate = "create-drop" // one of 'create', 'create-drop','update'
+ url = "jdbc:hsqldb:mem:devDB"
+ }
+ }
+ test {
+ dataSource {
+ dbCreate = "create-drop"
+ url = "jdbc:hsqldb:mem:testDb"
+ }
+ }
+ production {
+ dataSource {
+ dbCreate = "update"
+ url = "jdbc:hsqldb:file:prodDb;shutdown=true"
+ }
+ }
+}
11 grails-app/conf/UrlMappings.groovy
@@ -0,0 +1,11 @@
+class UrlMappings {
+ static mappings = {
+ "/$controller/$action?/$id?"{
+ constraints {
+ // apply constraints here
+ }
+ }
+ "/"(view:"/index")
+ "500"(view:'/error')
+ }
+}
19 grails-app/domain/com/grailsrocks/taxonomy/Taxon.groovy
@@ -0,0 +1,19 @@
+package com.grailsrocks.taxonomy
+
+class Taxon {
+
+ String name
+ Date dateCreated
+ Date lastUpdated
+
+ static mapping = {
+ cache true
+ name index:'taxon_name_idx'
+ }
+
+ static belongsTo = [parent:Taxon, scope:Taxonomy]
+
+ static constraints = {
+ parent(nullable:true)
+ }
+}
13 grails-app/domain/com/grailsrocks/taxonomy/TaxonLink.groovy
@@ -0,0 +1,13 @@
+package com.grailsrocks.taxonomy
+
+class TaxonLink {
+
+ Taxon taxon
+ String className
+ Long objectId
+
+ static constraints = {
+ className(nullable:false, blank:false)
+ objectId(nullable:false, blank:false)
+ }
+}
14 grails-app/domain/com/grailsrocks/taxonomy/Taxonomy.groovy
@@ -0,0 +1,14 @@
+package com.grailsrocks.taxonomy
+
+class Taxonomy {
+
+ String name
+
+ static mapping = {
+ cache true
+ }
+
+ static constraints = {
+ name(nullable:false, size:1..200)
+ }
+}
7 grails-app/domain/com/grailsrocks/taxonomy/test/Book.groovy
@@ -0,0 +1,7 @@
+package com.grailsrocks.taxonomy.test
+
+class Book {
+ static taxonomy = true
+
+ String title
+}
455 grails-app/services/com/grailsrocks/taxonomy/TaxonomyService.groovy
@@ -0,0 +1,455 @@
+package com.grailsrocks.taxonomy
+
+class TaxonomyService {
+
+ static transactional = true
+
+ static DELIMITER = ','
+
+ static GLOBAL_TAXONOMY_NAME = '_global'
+
+ Taxonomy globalTaxonomy
+
+ void init() {
+ globalTaxonomy = Taxonomy.findByName(GLOBAL_TAXONOMY_NAME)
+ if (!globalTaxonomy) {
+ globalTaxonomy = new Taxonomy(name:TaxonomyService.GLOBAL_TAXONOMY_NAME).save()
+ assert globalTaxonomy
+ }
+ }
+
+ Taxonomy resolveTaxonomy( taxonomyNameOrInstance, boolean create = false) {
+ def taxo = taxonomyNameOrInstance
+ if (taxo) {
+ if (!(taxo instanceof Taxonomy)) {
+ taxo = Taxonomy.findByName(taxo.toString())
+ if (!taxo) {
+ if (create) {
+ taxo = new Taxonomy(name:taxonomyNameOrInstance.toString())
+ assert taxo.save()
+ } else {
+ throw new IllegalArgumentException("No taxonomy with name [${taxonomyNameOrInstance}] found")
+ }
+ }
+ }
+ } else {
+ taxo = globalTaxonomy
+ }
+ return taxo
+ }
+
+ /**
+ * Find all OBJECT INSTANCES with the specified taxonomic path or node
+ * @params params Can contain optional "taxonomy" string/Taxonomy instance to scopy the find to a particular graph
+ * If not specified, it will not filter by taxonomy graph
+ */
+ def findObjectsByTaxon(Class domClass, def nodeOrPath, def params = null) {
+ // @todo work out correct base class to use
+ def taxo = resolveTaxonomy(params?.taxonomy)
+ def node = resolveTaxon(nodeOrPath, taxo)
+ if (log.debugEnabled) {
+ log.debug( "findObjectsByTaxon $domClass, $nodeOrPath, $params")
+ }
+ if (node) {
+ return getObjectsForTaxonIds(domClass, [node.id], params)
+ } else {
+ if (log.warnEnabled) {
+ log.warn( "findObjectsByTaxon returning null because there is no Taxon at [$nodeOrPath] in taxonomy [${taxo?.name}]")
+ }
+ return Collections.EMPTY_LIST
+ }
+ }
+
+ protected getObjectsForTaxonIds(objClass, taxonList, params) {
+ if (taxonList) {
+ def ids = TaxonLink.withCriteria {
+ projections {
+ property('objectId')
+ }
+
+ taxon {
+ if (taxonList.size() == 1) {
+ eq('id', taxonList[0])
+ } else {
+ inList('id', taxonList)
+ }
+ }
+
+ eq('className', objClass.name)
+ }
+ // Bug in Grails 1.2M2, inList dies if id list is empty
+ if (log.debugEnabled) {
+ log.debug( "getObjectIdsForTaxons found object ids $ids")
+ }
+ if (ids) {
+ return objClass.findAllByIdInList(ids, params)
+ }
+ }
+ return Collections.EMPTY_LIST
+ }
+
+ /**
+ * Find all OBJECT INSTANCES that have the given taxon node/path or any of its subtypes
+ */
+ def findObjectsByFamily(def objClass, def nodeOrPath, def params = null) {
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ def parent = resolveTaxon(nodeOrPath, taxonomy)
+
+ def familyTaxonIds = findTaxonIdsDescendedFrom(parent, params)+parent.id
+ if (familyTaxonIds) {
+ return getObjectsForTaxonIds(objClass, familyTaxonIds, params)
+ } else {
+ if (log.warnEnabled) {
+ log.warn( "findObjectsByFamily returning null because there are no Taxons in the family [$nodeOrPath] in taxonomy [${taxo?.name}]")
+ }
+ return Collections.EMPTY_LIST
+ }
+ }
+
+ /**
+ * Find all Taxon that are children of the given taxon. Params can contain 'taxonomy' string/Taxonomy object for scoping
+ */
+ def findTaxonsByParent(parent, Map params = null) {
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ parent = resolveTaxon(parent, taxonomy)
+
+ if (parent) {
+ Taxon.findAllByParentAndScope(parent, taxonomy, params)
+ } else {
+ Taxon.findAllByParentIsNullAndScope(taxonomy, params)
+ }
+ }
+
+ /**
+ * Find all Taxon that are children of the given LIST of Taxon objects.
+ */
+ def findTaxonsByParentTaxons(parentTaxonList, Map params = null) {
+ if (log.debugEnabled) {
+ log.debug "findTaxonsByParentTaxons $parentTaxonList, $params"
+ }
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ def parentIds = parentTaxonList*.id
+ if (parentIds) {
+ return Taxon.withCriteria {
+ parent {
+ inList('id', parentIds)
+ }
+ def c = criteriaParams.clone()
+ c.delegate = delegate
+ c.call(params)
+ }
+ }
+ return Collections.EMPTY_LIST
+ }
+
+ /**
+ * Find all Taxon that are children of the given LIST of Taxon objects.
+ */
+ def findTaxonIdsByParentTaxonIds(parentTaxonIdList, Map params = null) {
+ if (log.debugEnabled) {
+ log.debug "findTaxonsByParentTaxonIds $parentTaxonIdList, $params"
+ }
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ if (parentTaxonIdList) {
+ return Taxon.withCriteria {
+ projections {
+ property('id')
+ }
+ parent {
+ inList('id', parentTaxonIdList)
+ }
+ def c = criteriaParams.clone()
+ c.delegate = delegate
+ c.call(params)
+ }
+ }
+ return Collections.EMPTY_LIST
+ }
+
+ protected recursivelyGatherTaxons(parentList, targetList, params = null) {
+ if (log.debugEnabled) {
+ log.debug "recursivelyGatherTaxons $parentList, $targetList"
+ }
+ def interim = findTaxonsByParentTaxons(parentList, params)
+ if (interim) {
+ targetList.addAll(interim)
+ recursivelyGatherTaxons(interim, targetList)
+ }
+ return targetList
+ }
+
+ protected recursivelyGatherTaxonIds(parentIdList, targetList, params = null) {
+ if (log.debugEnabled) {
+ log.debug "recursivelyGatherTaxonIds $parentIdList, $targetList"
+ }
+ def interim = findTaxonIdsByParentTaxonIds(parentIdList, params)
+ if (interim) {
+ targetList.addAll(interim)
+ recursivelyGatherTaxonIds(interim, targetList)
+ }
+ return targetList
+ }
+
+ /**
+ * Find all Taxon that are children of the given taxon. Params can contain 'taxonomy' string/Taxonomy object for scoping
+ */
+ def findTaxonsDescendedFrom(parent, Map params = null) {
+ if (log.debugEnabled) {
+ log.debug "findTaxonsDescendedFrom $parent, $params"
+ }
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ parent = resolveTaxon(parent, taxonomy)
+
+ if (parent) {
+ return recursivelyGatherTaxons([parent], [], params)
+ } else {
+ return Taxon.findAllByScope(taxonomy, params)
+ }
+ }
+
+ /**
+ * Find all Taxon that are children of the given taxon. Params can contain 'taxonomy' string/Taxonomy object for scoping
+ */
+ def findTaxonIdsDescendedFrom(parent, Map params = null) {
+ if (log.debugEnabled) {
+ log.debug "findTaxonIdsDescendedFrom $parent, $params"
+ }
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ parent = resolveTaxon(parent, taxonomy)
+
+ if (parent) {
+ return recursivelyGatherTaxonIds([parent.id], [], params)
+ } else {
+ return Taxon.findAllByScope(taxonomy, params)*.id // @todo use criteria + projection
+ }
+ }
+
+ /**
+ * Convert and apply max, offset and sort/order params within a criteria
+ */
+ protected criteriaParams = { params ->
+ if (params?.offset) {
+ firstResult(params.offset.toString().toInteger())
+ }
+ if (params?.max) {
+ maxResults(params.max.toString().toInteger())
+ }
+ if (params?.sort) {
+ order(params.sort, params?.order ?: 'asc')
+ }
+ }
+
+ /**
+ * Find all Taxon that are children of the given taxon. Params can contain 'taxonomy' string/Taxonomy object for scoping
+ */
+ def findTaxonsByParentAndCriteria(parent, def params, Closure criteria) {
+ def taxonomy = resolveTaxonomy(params?.taxonomy)
+ parent = resolveTaxon(parent, taxonomy)
+
+ if (log.debugEnabled) {
+ log.debug "findTaxonsbyParentAndCriteria resolved parent to ${parent?.dump()} and taxonomy to ${taxonomy.dump()}"
+ }
+
+ def l = Taxon.withCriteria {
+ eq('scope', taxonomy)
+ if (parent) {
+ eq('parent', parent)
+ } else {
+ isNull('parent')
+ }
+
+ def c = criteriaParams.clone()
+ c.delegate = delegate
+ c.call(params)
+
+ criteria.delegate = delegate
+ criteria.call()
+ }
+
+ if (log.debugEnabled) {
+ log.debug "findTaxonsbyParentAndCriteria found: ${l.dump()}"
+ }
+
+ return l
+ }
+
+
+
+ /**
+ * Take a Taxon or path List/string and taxonomy and find the Taxon instance representing it
+ * @return Returns the Taxon indicated by the path, or null if the path cannot be fully resolved
+ */
+ Taxon resolveTaxon(nodeOrPath, taxonomy = null) {
+ // Start searching assuming null parent for first element
+ if (nodeOrPath instanceof Taxon) {
+ return nodeOrPath
+ }
+
+ taxonomy = resolveTaxonomy(taxonomy, true) // Create taxonomies that are searched for
+
+ if (log.debugEnabled) {
+ log.debug( "resolveTaxon ${nodeOrPath?.dump()}, ${taxonomy.dump()}")
+ }
+
+ if (!(nodeOrPath instanceof List)) {
+ nodeOrPath = nodeOrPath.toString().split(TaxonomyService.DELIMITER)
+ }
+
+ def previous
+ def link
+ def n
+ def c
+ def notFound
+ nodeOrPath.find { t ->
+ if (log.debugEnabled) {
+ log.debug "resolveTaxon in loop - [$t], previous is [$previous?.name]"
+ }
+ n = Taxon.createCriteria().get {
+ eq('name', t)
+ eq('scope', taxonomy)
+ if (previous) {
+ eq('parent', previous)
+ } else {
+ isNull('parent')
+ }
+ }
+ previous = n
+ if (!n) {
+ notFound = true
+ return true // Only break out if we don't find one, eg path not valid
+ }
+ return false
+ }
+ if (log.debugEnabled) {
+ log.debug( "resolveTaxon resolved? ${!notFound} (${n?.dump()})")
+ }
+ return notFound ? null : n
+ }
+
+ /**
+ * Find the TaxonLink object for the give taxo node or path, for the given object instance - if any
+ */
+ def findLink(Object obj, taxo, taxonomy = null) {
+ if (taxo instanceof Taxon) {
+ def c = TaxonLink.createCriteria()
+ c.get {
+ eq('objectId', obj.id)
+ eq('taxon', taxo)
+ eq('className', obj.class.name)
+ }
+ } else {
+ // @todo Here we need to check against a cache first to find the last Taxon in the chain
+
+ def n = resolveTaxon(taxo, taxonomy)
+ if (n) {
+ findLink(obj, n) // recurse once!
+ } else {
+ return null
+ }
+ }
+ }
+
+ /**
+ * Remove the link to a taxo node (or path) for the given object instance
+ */
+ void removeLink(obj, taxonOrPath, taxonomy = null) {
+ findLink(obj, taxonOrPath, taxonomy)?.delete()
+ }
+
+
+ /**
+ * Remove all links for the given object instance
+ */
+ void removeAllLinks(obj) {
+ findAllLinks(obj)*.delete()
+ }
+
+ /**
+ * Test if there is a link to this taxon for given object instance
+ */
+ boolean hasLink(obj, taxonOrPath, taxonomy = null) {
+ findLink(obj, taxonOrPath, taxonomy) ? true : false
+ }
+
+
+ /**
+ * Find all the TaxonLink objects for the give taxonomy for the given object instance - if any
+ */
+ List findAllLinks(Object obj) {
+ def c = TaxonLink.createCriteria()
+ return c.list {
+ eq('objectId', obj.id)
+ eq('className', obj.class.name)
+ }
+ }
+
+ TaxonLink saveNewLink(obj, Taxon node) {
+ if (log.debugEnabled) {
+ log.debug "saveNewLink $obj, $node"
+ }
+ def t = new TaxonLink(taxon:node, className:obj.class.name, objectId:obj.id)
+ if (!t.save()) {
+ if (log.errorEnabled) {
+ log.error "Failed to save taxon link: ${t.errors}"
+ }
+ assert !t.errors
+ }
+ return t
+ }
+
+ /**
+ * Force the specified path of taxons to exist in the specified taxonomy
+ * If no taxonomy is specified, uses globalTaxonomy
+ */
+ Taxon createTaxonomyPath(path, taxonomy = null) {
+ taxonomy = resolveTaxonomy(taxonomy, true)
+
+ if (!(path instanceof List)) {
+ path = path.toString().split(TaxonomyService.DELIMITER)
+ }
+
+ def previous
+ def link
+ def n
+ Closure c
+ path.each { t ->
+ n = Taxon.createCriteria().get {
+ eq('name', t)
+ eq('scope', taxonomy)
+ if (previous) {
+ eq('parent', previous)
+ } else {
+ isNull('parent')
+ }
+ }
+ if (!n) {
+ if (log.debugEnabled) {
+ log.debug "Creating new Taxon with name ${t} in scope ${taxonomy?.dump()} with parent ${previous?.name}"
+ }
+ n = new Taxon(name:t, scope:taxonomy, parent:previous).save()
+ assert n
+ }
+ previous = n
+ }
+ if (log.debugEnabled) {
+ log.debug "Returning new Taxon with name ${n}"
+ }
+ return n
+ }
+
+ void dumpTaxonomy(taxonomy = null) {
+ taxonomy = resolveTaxonomy(taxonomy)
+ println "#"*40
+ println "Taxon paths in taxonomy [${taxonomy.name}]"
+ println "#"*40
+ Taxon.listOrderByDateCreated().each { t->
+ def path = new StringBuffer()
+ path.insert(0, t.name)
+ while (t.parent) {
+ path.insert(0, "${t.parent.name} > ")
+ t = t.parent
+ }
+ println path
+ }
+ }
+}
12 license.txt
@@ -0,0 +1,12 @@
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
382 test/integration/com/grailsrocks/taxonomy/tests/TaxonomyServiceTests.groovy
@@ -0,0 +1,382 @@
+package com.grailsrocks.taxonomy.tests
+
+import grails.test.*
+import com.grailsrocks.taxonomy.*
+import com.grailsrocks.taxonomy.test.*
+
+class TaxonomyServiceTests extends GrailsUnitTestCase {
+
+ def svc
+
+ protected void setUp() {
+ super.setUp()
+
+ svc = new TaxonomyService()
+ svc.init()
+ }
+
+ protected void tearDown() {
+ super.tearDown()
+ }
+
+ void testGlobalTaxonomyInit() {
+ def globalTaxonomy = Taxonomy.findByName(TaxonomyService.GLOBAL_TAXONOMY_NAME)
+ assertNotNull globalTaxonomy
+ assertEquals svc.globalTaxonomy, globalTaxonomy
+ }
+
+ void testAddGlobalTaxonomy() {
+ def book = new Book(title:'Reality Check')
+ assert book.save()
+
+ book.addToTaxonomy(['Non-fiction', 'Business', 'Entrepreneurial'])
+
+ // Check the taxon hierarchy was created
+ assertEquals 3, Taxon.count()
+ def nf = Taxon.findByName('Non-fiction')
+ assertNotNull nf
+ assertEquals svc.globalTaxonomy, nf.scope
+ def bi = Taxon.findByName('Business')
+ assertNotNull bi
+ assertEquals svc.globalTaxonomy, bi.scope
+ def en = Taxon.findByName('Entrepreneurial')
+ assertNotNull en
+ assertEquals svc.globalTaxonomy, en.scope
+
+ // Check the parents of the Taxons are correct
+ assertEquals nf, bi.parent
+ assertEquals bi, en.parent
+
+ // Check the taxon link was created between object and taxon
+ def link = TaxonLink.findByObjectIdAndClassName(book.id, book.class.name)
+ assertNotNull link
+ assertEquals en, link.taxon
+
+ // Check that adding the same taxonomy again does not create new Taxon(s) or links
+ book.addToTaxonomy(['Non-fiction', 'Business', 'Entrepreneurial'])
+ assertEquals 3, Taxon.count()
+ def links = TaxonLink.findAllByObjectIdAndClassName(book.id, book.class.name)
+ assertEquals 1, links.size()
+
+ // Check that adding another taxonomy on the same path creates a new link
+ book.addToTaxonomy(['Non-fiction', 'Business'])
+ assertEquals 3, Taxon.count()
+ links = TaxonLink.findAllByObjectIdAndClassName(book.id, book.class.name)
+ assertEquals 2, links.size()
+
+ // One end point should be entrepreneurial
+ assertNotNull links.find { l -> l.taxon == en }
+ // One end point should be business
+ assertNotNull links.find { l -> l.taxon == bi }
+ }
+
+ void testParamsUsageWithCriteria() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+
+ book1.addToTaxonomy(['Non-fiction', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Blogging'])
+ book1.addToTaxonomy(['Blogs', 'Business'])
+
+ // Test the control case
+ def taxons = svc.findTaxonsByParentAndCriteria('Collection', null) {
+ ilike('name', 'b%')
+ }
+ assertEquals 2, taxons.size()
+
+ // Test max
+ taxons = svc.findTaxonsByParentAndCriteria('Collection', [max:1]) {
+ ilike('name', 'b%')
+ }
+ assertEquals 1, taxons.size()
+
+ // Test offset
+ taxons = svc.findTaxonsByParentAndCriteria('Collection', [max:1, offset:0, sort:'name']) {
+ ilike('name', 'b%')
+ }
+ assertEquals 1, taxons.size()
+ assertEquals 'Blogging', taxons[0].name
+
+ taxons = svc.findTaxonsByParentAndCriteria('Collection', [max:1, offset:1, sort:'name']) {
+ ilike('name', 'b%')
+ }
+ assertEquals 1, taxons.size()
+ assertEquals 'Business', taxons[0].name
+ }
+
+ void testFindTaxonsInFamily() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+
+ /* Create this:
+
+ Non-fiction
+ |--- Business
+ Collection
+ |--- Business
+ |--- Blogging
+ |--- Famous
+ Blogs
+ |--- Business
+
+ ... and in "translations" taxonomy....
+
+ Canada
+ |--- French
+ |--- English
+ */
+ book1.addToTaxonomy(['Non-fiction', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Blogging'])
+ book1.addToTaxonomy(['Collection', 'Blogging', 'Famous'])
+ book1.addToTaxonomy(['Blogs', 'Business'])
+
+ book1.addToTaxonomy(['Canada', 'French'], 'translations')
+ book1.addToTaxonomy(['Canada', 'English'], 'translations')
+
+ // Test full graph of specific taxonomies
+ def taxons = svc.findTaxonsDescendedFrom(null)
+ assertEquals 8, taxons.size()
+ taxons = svc.findTaxonsDescendedFrom(null, [taxonomy:'translations'])
+ assertEquals 3, taxons.size()
+
+ def collectionTaxon = svc.resolveTaxon('Collection')
+ taxons = svc.findTaxonsDescendedFrom('Collection')
+ assertEquals 3, taxons.size()
+ def t = taxons.find { it.name == 'Business' }
+ assertNotNull t
+ assertEquals collectionTaxon.id, t.parent.id
+
+ println "Taxons found for 'collection' are: ${taxons*.dump()}"
+
+ def blogt = taxons.find { it.name == 'Blogging' }
+ assertNotNull blogt
+ assertEquals collectionTaxon.id, blogt.parent.id
+
+ def famt = taxons.find { it.name == 'Famous' }
+ assertNotNull famt
+ assertEquals blogt.id, famt.parent.id
+ }
+
+ void testFindObjectsInFamily() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+ def book2 = new Book(title:'Out of Our Minds')
+ assert book2.save()
+
+ /* Create this:
+
+ Non-fiction
+ |--- Business
+ Collection
+ |--- Business
+ |--- Blogging
+ |--- Famous
+ Blogs
+ |--- Business
+
+ ... and in "translations" taxonomy....
+
+ Canada
+ |--- French
+ |--- English
+ */
+
+ book1.addToTaxonomy(['Non-fiction', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Blogging'])
+ book1.addToTaxonomy(['Collection', 'Blogging', 'Famous'])
+ book1.addToTaxonomy(['Blogs', 'Business'])
+
+ book1.addToTaxonomy(['Canada', 'French'], 'translations')
+ book1.addToTaxonomy(['Canada', 'English'], 'translations')
+
+ book2.addToTaxonomy(['Collection', 'Blogging', 'Famous'])
+ book2.addToTaxonomy(['Non-fiction', 'Business'])
+
+ // This should find both books
+ def collObjs = Book.findAllByTaxonomyFamily('Collection')
+ assertEquals 2, collObjs.size()
+ assertNotNull collObjs.contains(book1)
+ assertNotNull collObjs.contains(book2)
+
+ // This should find one book
+ collObjs = Book.findAllByTaxonomyFamily(['Collection', 'Business'])
+ assertEquals 1, collObjs.size()
+ assertNotNull collObjs.contains(book1)
+
+ // This should find both books
+ collObjs = Book.findAllByTaxonomyFamily(['Collection', 'Blogging', 'Famous'])
+ assertEquals 2, collObjs.size()
+ assertNotNull collObjs.contains(book1)
+ assertNotNull collObjs.contains(book2)
+
+ // This should find both books
+ collObjs = Book.findAllByTaxonomyFamily(['Non-fiction', 'Business'])
+ assertEquals 2, collObjs.size()
+ assertNotNull collObjs.contains(book1)
+ assertNotNull collObjs.contains(book2)
+ }
+
+ void testFindTaxonsByParent() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+
+ book1.addToTaxonomy(['Non-fiction', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Business'])
+ book1.addToTaxonomy(['Collection', 'Blogging'])
+ book1.addToTaxonomy(['Blogs', 'Business'])
+
+ book1.addToTaxonomy(['Canada', 'French'], 'translations')
+ book1.addToTaxonomy(['Canada', 'English'], 'translations')
+
+ // Test the null parent case
+ def taxons = svc.findTaxonsByParent(null)
+
+ assertEquals 3, taxons.size() // Only 3 are in the default taxonomy
+ assertNotNull taxons.find { t -> t.name == 'Non-fiction' }
+ assertNotNull taxons.find { t -> t.name == 'Collection' }
+ assertNotNull taxons.find { t -> t.name == 'Blogs' }
+
+ // Test the non-null parent case
+ taxons = svc.findTaxonsByParent('Collection')
+
+ assertEquals 2, taxons.size() // Only 2 are in the default taxonomy
+ assertNotNull taxons.find { t -> t.name == 'Business' }
+ assertNotNull taxons.find { t -> t.name == 'Blogging' }
+
+ // Test the null parent case with criteria
+ taxons = svc.findTaxonsByParentAndCriteria(null, [:]) {
+ ilike('name', '%ion')
+ }
+
+ assertEquals 2, taxons.size()
+ assertNotNull taxons.find { t -> t.name == 'Non-fiction' }
+ assertNotNull taxons.find { t -> t.name == 'Collection' }
+
+ svc.dumpTaxonomy()
+
+ // Test the non-null parent case with criteria
+ taxons = svc.findTaxonsByParentAndCriteria('Collection', [:]) {
+ ilike('name', 'bus%')
+ }
+
+ assertEquals 1, taxons.size()
+ assertNotNull taxons.find { t -> t.name == 'Business' }
+
+ // Test the non-null parent case
+ taxons = svc.findTaxonsByParentAndCriteria('Collection', [:]) {
+ ilike('name', 'b%')
+ }
+
+ assertEquals 2, taxons.size()
+ assertNotNull taxons.find { t -> t.name == 'Business' }
+ assertNotNull taxons.find { t -> t.name == 'Blogging' }
+
+ // Test the non-null parent case with alternative taxonomy
+ taxons = svc.findTaxonsByParentAndCriteria('Canada', [taxonomy:'translations']) {
+ ilike('name', '%ish')
+ }
+
+ assertEquals 1, taxons.size()
+ assertNotNull taxons.find { t -> t.name == 'English' }
+ }
+
+ void testFindByTaxonExact() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+ def book2 = new Book(title:'Tribes')
+ assert book2.save()
+
+ book1.addToTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ book2.addToTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+
+ book1.addToTaxonomy(['Non-fiction', 'Business', 'Internet'])
+
+ book1.addToTaxonomy(['Technology', 'Blogger'], 'author_category')
+
+ def book
+ // This must work
+ book = Book.findByTaxonomyExact(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ assertNotNull book
+
+ // This must result in two
+ def books = Book.findAllByTaxonomyExact(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ assertEquals 2, books.size()
+ assertTrue books.contains(book1)
+ assertTrue books.contains(book2)
+
+ // These must fail
+ book = Book.findByTaxonomyExact(['Non-fiction', 'Web 2.0'])
+ assertNull book
+ book = Book.findByTaxonomyExact(['Non-fiction'])
+ assertNull book
+ book = Book.findByTaxonomyExact(['Nonsense'])
+ assertNull book
+
+ // Now test non-global taxonomy
+ // This must be null
+ book = Book.findByTaxonomyExact(['Non-fiction', 'Web 2.0', 'Entrepreneurial'], [taxonomy:'author_category'])
+ assertNull book
+ book = Book.findByTaxonomyExact(['Technology', 'Blogger']) // searches global taxon, not there
+ assertNull book
+
+ // This must NOT be null
+ book = Book.findByTaxonomyExact(['Technology', 'Blogger'], [taxonomy:'author_category'])
+ assertNotNull book
+
+ // This must NOT be null
+ book = Book.findByTaxonomyExact(['Technology', 'Blogger'], [taxonomy:'author_category'])
+ assertNotNull book
+ }
+
+
+
+ void testGetAndClearTaxonomies() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+ def book2 = new Book(title:'Tribes')
+ assert book2.save()
+
+ book1.addToTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ book2.addToTaxonomy(['Non-fiction', 'Web 2.0'])
+
+ assertEquals 1, book1.getTaxonomies().size()
+ assertEquals 1, book2.getTaxonomies().size()
+
+ // remove all
+ book1.clearTaxonomies()
+ assertEquals 0, book1.getTaxonomies().size()
+ assertEquals 1, book2.getTaxonomies().size()
+
+ // remove all
+ book2.clearTaxonomies()
+ assertEquals 0, book2.getTaxonomies().size()
+ }
+
+ void testAddHasRemoveTaxons() {
+ def book1 = new Book(title:'Reality Check')
+ assert book1.save()
+ def book2 = new Book(title:'Tribes')
+ assert book2.save()
+
+ book1.addToTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ book2.addToTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+
+ assertEquals svc.resolveTaxon(['Non-fiction', 'Web 2.0', 'Entrepreneurial']).ident(),
+ book1.getTaxonomies()[0].ident()
+ assertTrue book1.hasTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+ assertFalse book1.hasTaxonomy(['Non-fiction', 'Web 2.0', 'BLABLABLA'])
+
+ // remove all
+ assertEquals 1, book1.getTaxonomies().size()
+ book1.clearTaxonomies()
+ assertEquals 0, book1.getTaxonomies().size()
+ assertFalse book1.hasTaxonomy(['Non-fiction', 'Web 2.0', 'Entrepreneurial'])
+
+ // but its still on book2 right?
+ assertEquals svc.resolveTaxon(['Non-fiction', 'Web 2.0', 'Entrepreneurial']).ident(),
+ book2.getTaxonomies()[0].ident()
+ }
+}

0 comments on commit afc1f6b

Please sign in to comment.
Something went wrong with that request. Please try again.