Skip to content

Grails Extended ManyToMany GORM Example

by Jeff

This is a variation on this article. Having never been a big fan of writing XML, I’ll gladly opt for the XML-less version. The first example online I found has some formatting issues and just seems a little hard to follow.

My step by step example follows after the jump. Call it the “Grails extended many-to-many step-by-step guide using the grails console, for dummies”.

Let’s start with a very simple example:

A person has one or more characteristics. A characteristic can be applied to more than one person. A PersonCharacteristic relationship has an assignedValue.

Here’s what I did:


grails create-domain-class person

grails create-domain-class characteristic

grails create-domain-class personCharacteristic

First I fleshed out Person:

class Person {

	static hasMany = [characteristics:PersonCharacteristic]

	String firstName
	String lastName

	List characteristics() {
		return characteristics.collect{it.characteristic}
	}

	List addCharacteristic(Characteristic characteristic) {
		return addCharacteristic(characteristic, new Integer(1))
	}

	List addCharacteristic(Characteristic characteristic, Integer assignedValue) {
		PersonCharacteristic.link(this, characteristic, assignedValue)
		return characteristics()
	}

	List removeFromCharacteristics(Characteristic characteristic) {
		PersonCharacteristic.unlink(this, characteristic)
		return characteristics()
	}

}

and Characteristic is pretty similar

class Characteristic {

	static hasMany = [people:PersonCharacteristic,employers:EmployerCharacteristic]

	String name
	String description
	Integer systemWeight = new Integer(1)

	List people() {
		return people.collect{it.person}
	}

	List employers() {
		return employers.collect{it.employer}
	}

	List addToEmployers(Employer employer) {
		EmployerCharacteristic.link(employer, this)
		return employers()
	}

	List addToPeople(Person person) {
		PersonCharacteristic.link(person, this)
		return people()
	}

	List removeFromEmployers(Employer employer) {
		EmployerCharacteristic.unlink(employer, this)
		return employers()
	}

	List removeFromPeople(Person person) {
		PersonCharacteristic.unlink(person, this)
		return people()
	}
}

and finally, PersonCharacteristic

class PersonCharacteristic {

	def Person person
	def Characteristic characteristic
	def Integer assignedValue

	static PersonCharacteristic link(Person person, Characteristic characteristic) {

		return link(person, characteristic, new Integer(1))
	}

	static PersonCharacteristic link(Person person, Characteristic characteristic, Integer assignedValue ) {
		def pc = PersonCharacteristic.findByPersonAndCharacteristic(person, characteristic)
		if (!pc) {
			pc = new PersonCharacteristic(userWeight:1)
			pc.person = person
			pc.characteristic = characteristic
			pc.assignedValue = assignedValue
			person?.addToCharacteristics(pc)
			characteristic?.addToPeople(pc)
			pc.save()
		}
		return pc
	}

	static void unlink(Person person, Characteristic characteristic) {
		def pc = PersonCharacteristic.findByPersonAndCharacteristic(person, characteristic)
		if (pc) {
			person?.removeFromCharacteristics(pc)
			characteristic?.removeFromPeople(pc)
			pc.delete()
		}
	}
}

Note: if you want an explanation of this class look here and read the section The Many-to-Many map.

Now, with any luck we’ll be able to do this with the console…

(I would assume you know this but just in case… go to your project base dir and type…

grails console

Now here are some people and characteristics. This is the easy part.

def a = new Person(firstName: "Jeff", lastName: "Revert")
a.save()
def b = new Person(firstName: "Priyatam", lastName: "Console")
b.save()
def c = new Characteristic(name: "Stupid", description: "slow of mind")
c.save()
def d = new Characteristic(name: "Handsome", description: "a pleasing appearance")
d.save()

you should see this output if you’re following along correctly:

Result: Characteristic : 2

But why take my word for it? Test it!

def jeff = Person.findByFirstName('Jeff')
def priyatam = Person.findByFirstName('Priyatam')
def stupid = Characteristic.findByName('Stupid')
def handsome = Characteristic.findByName('Handsome')
if(jeff) {
    println "Found Jeff"
}
if(priyatam) {
    println "Found Priyatam"
}
if(stupid) {
    println "Found Stupid"
}
if(handsome) {
    println "Found Handsome"
}

You should see this output:

Found Jeff
Found Priyatam
Found Stupid
Found Handsome

Next we need to add our characteristics to our people.

def person = Person.findByFirstName('Jeff')
def characteristic = Characteristic.findByName('Stupid')
person.addCharacteristic(characteristic)

and the resulting output:

Result: [Characteristic : 1]

I should be set with my many to many relationship!

Not convinced? I know how you feel…

One problem with Grails default database config is that you can’t easily go there and check out the schema. Well, I don’t know how to anyway, and I’m guessing I’m not alone. In my real world job I always check out the database. It’s like a security blanket, only with a penchant for evil. Anyway, it’s not that HSQLDB can’t provide it, it’s that the config isn’t running an in-memory database capable of connecting to. So change the development datasource url to this:

url = "jdbc:hsqldb:hsql://localhost:9001/devDB"

Open a new shell, go to your grails install directory, and run this:

java -cp ./lib/hsqldb-1.8.0.5.jar org.hsqldb.Server -database.0 mem:devDB -dbname.0 devDB

This will run an in memory database server that you can connect to with your favorite database exploring app. Now fire up the grails console again, and let’s add this stuff again.
After adding the people and characteristics, and adding the PersonCharacteristic, check out the tables. I see PERSON, CHARACTERISTIC, and PERSON_CHARACTERISTIC.

select * from person_characteristic

and this shows I’ve got a PERSON_CHARACTERISTIC table with an ID, VERSION, CHARACTERISTIC_ID, PERSON_ID, and ASSIGNED_VALUE columns with values 1,0,1,1,1. So you get exactly what you’d expect. Not bad!

[Slashdot] [Digg] [Reddit] [del.icio.us] [Facebook] [Technorati] [Google] [StumbleUpon]

6 Comments

  1. Graeme Rocher wrote:

    class Person {

    static hasMany = [characteristics:Characteristic]


    }

    class Characteristic {

    static hasMany = [people:Person]
    static belongsTo = Person

    }

    Cheers
    Graeme

    Saturday, June 7, 2008 at 5:01 am | Permalink
  2. Graeme Rocher wrote:

    Also note the article (http://grails.org/Many-to-Many+Mapping+without+Hibernate+XML) is a tutorial written by a Grails user and not official documentation. You don’t need to setup an OpenSessionInViewFilter, grails does this for you

    Saturday, June 7, 2008 at 5:02 am | Permalink
  3. Jeff wrote:

    I found the official documentation example today, which is very clear. Thanks for the tip Graeme.

    Saturday, June 7, 2008 at 10:40 am | Permalink
  4. andhapp wrote:

    I got stuck in the same thing as well… I had my hibernate mapping files ( *.hbm.xml) and I set the inverse to false on both sides of the relationship for the many to many to work… i noticed that if you do not do that it would not save the object (or the values) in the linking table for some reason.

    Saturday, June 7, 2008 at 12:06 pm | Permalink
  5. AnotherHale wrote:

    I am new to Grails/Groovy and not familiar with much of the syntax yet. I am wondering if I can do the following.

    In the Characteristic class can you replace these two methods:

    List addToPeople(Person person)
    List removeFromPeople(Person person)

    with the following:

    List addPerson(Person person)
    List removePerson(Person person)

    It just seems more intuitive to code charcteristic.addPerson(someperson). I just don’t know if there is something magical about ‘addTo*’ or ‘removeFrom*’

    Tuesday, June 10, 2008 at 12:02 am | Permalink
  6. Jeff wrote:

    hey take a look at the documentation:
    http://grails.org/GORM+-+Defining+relationships

    it explains the magic of addTo* much better than I did.

    Going forward with my example I’m more interested in creating a many to many relationship where the join table can have extra columns.

    Tuesday, June 10, 2008 at 11:50 am | Permalink

One Trackback/Pingback

  1. [...] Revert to Console M-M blog post Possibly related posts: (automatically generated)Our first postCharacter Profile: Re-Edit*SIGH* [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*