viernes, diciembre 07, 2012

Grails N a M: como diablos va esto??

Las relaciones N a M en grails oficialmente se resuelven simplemente con un doble hasMany una parte que manda a través del belongsTo, sin embargo esto sólo es válido para aquellas relaciones N a M sin atributos adicionales. Lo cierto es que esto casi nunca es así y siempre que hay una N a M en realidad hay una entidad nueva pendiente de ser "descubierta". Un ejemplo:
1 Autor -> N Libros
1 Libro -> N Autores
En realidad hay una entidad intermedia que podría indicar si el autor es principal, los capitulos que ha escrito del libro, un contrato de derechos de autor especifico para cada colaboración.......aja! Vaya! Pues si que habia una entidad en medio, supongamos que la llamamos Autoría y que por simplificar indica si el autor es el principal o no (como Ana Rosa....ejem). Con esto vemos que en realidad tendriamos lo siguiente:
1 Autor -> N Autorias
1 Libro -> N Autorias
1 Autoría -> 1 Libro, 1 Autor
Ahora en virtud de las bondades de GORM (no confundir con la Isla de Gorm) hacer ciertas operaciones como añadir autorias, borrar autores o libros puede resultar un infierno de problemas de hibernate, flushes y demás historias. Así que visto lo visto y basándonos en este articulo(http://grails.org/Many-to-Many+Mapping+without+Hibernate+XML) quedaría el siguiente código fuente: Autor:
package com.nortia.simple
class Autor {

 String nombre

 static hasMany=[autorias:Autoria]
 static constraints = {}
 def libros(){
   return autorias.collect{it.libro}
 }

 def addToLibros(Libro libro, def params=null){
   Autoria.link(this, libro,params)
   return libros()
 }

 def removeFromLibros(Libro libro){
   Autoria.unlink(this, libro)
   return libros()
 }

 def clearLibros(){
   libros().each{
     removeFromLibros(it)
   }
 }

 def safeDelete(){
   this.clearLibros()
   this.delete()
 }
}
Libro:
package com.nortia.simple
class Libro {

 String titulo

 static hasMany=[autorias:Autoria]
static constraints = {
 }

 def autores(){
   return autorias.collect{it.autor}
 }

 def addToAutores(Autor autor, def params=null){
   Autoria.link(autor, this,params)
   return autores()
 }

 def removeFromAutores(Autor autor){
   Autoria.unlink(autor, this)
   return autores()
 }

 def clearAutores(){
   autores().each{
     removeFromAutores(it)
   }
 }

 def safeDelete(){
   this.clearAutores()
   this.delete()
 }
}
Autoría:
package com.nortia.simple
class Autoria {
 Autor autor
 Libro libro
 boolean autorPrincipal=true

 static belongsTo=[autor:Autor, libro:Libro]

 static Autoria link(def autor, def libro,def params=null){
   def autoria=Autoria.findByAutorAndLibro(autor,libro)
   if(!autoria){
     autoria=new Autoria(params)
     autor?.addToAutorias(autoria)
     libro?.addToAutorias(autoria)
     autoria.save()
   }
   return autoria
 }

 static void unlink(def autor, def libro){
   def autoria=Autoria.findByAutorAndLibro(autor,libro)
   if(autoria){
     autor?.removeFromAutorias(autoria)
     libro?.removeFromAutorias(autoria)
     autoria.delete()
   }
 }

}
Test (lanzando la consola como grails console):
import com.nortia.simple.*
println "0.Autorias: ${Autoria.list()}"
println "0.Autores: ${Autor.list()}"
println "0.Libros: ${Libro.list()}"
Autoria.executeUpdate("delete from Autoria")
Autor.executeUpdate("delete from Autor")
Libro.executeUpdate("delete from Libro")
def a=new Autor(nombre:"Carlos Ruiz Zafon")
a.save()
def l=new Libro(titulo:"La sombra del viento")
l.save()
def l2=new Libro(titulo:"La sombra del viento 2")
l2.save()
a.libros()
Autoria.link(a,l,[autorPrincipal:false]) //Opcion con params1
// Opción completa a.addToLibros(l2,[autorPrincipal:false]
a.addToLibros(l2) //Opción básica
a.libros()*.titulo
println "1.Autorias: ${Autoria.list()}"
def autorias=Autoria.list()
autorias.each{
  println "Autor: ${it.autor.nombre} Libro:${it.libro.titulo} \\\
          Es principal: ${it.autorPrincipal}"
}
a.safeDelete()
println "2.Autorias: ${Autoria.list()}"
Fijate que no hay ningún flush por ningún lado, esto es buen síntoma, quiere decir que tienes operaciones atómicas que no requieren del volcado de la sesión. No obstante si ejecutas 2 veces el mismo test se ve que los libros existen al principio y efectivamente

No hay comentarios: