Este post es fruto de una pregunta sobre el mismo tema que hice y me respondí a mi mismo en Stack Overflow. Aquí voy a reordenar y a poner más clarito lo que aprendí de ella y la solución a la que llegué.
Situación inicial
Voy a explicar el punto de partida sobre el problema que tenía para que podáis entenderlo mejor. Para empezar tenía la siguiente tabla de Categorías:
1 2 3 4 5 6 7 8 9 |
create table "CATEGORIES" ( "id" character varying(50) NOT NULL, "name" character varying(50) NOT NULL, "idParent" character varying(50), CONSTRAINT "CATEGORIES_pkey" PRIMARY KEY (id), CONSTRAINT "CATEGORIES_idParent_fkey" FOREIGN KEY ("idParent") REFERENCES "CATEGORIES" (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION ); |
Una categoría tiene un id y un nombre (name). En caso de que sea una subcategoría, también tendrá el atributo idParent, que es una clave foránea que apunta al id the la categoría padre.
Imaginemos que tengo los siguientes datos:
1 2 3 4 |
id name idParent parent Parent child1 Child1 parent child2 Child2 parent |
Aquí quiero el resultado en un mapa agrupado por la categoría padre, cuya estructura sea la categoría padre como key y sus subcategorías correspondientes en una secuencia como value:
1 2 3 |
Map( (parent,Parent,None) -> Seq[(child1,Child1,parent),(child2,Child2,parent)] ) |
Scala Slick 3 relación a la misma entidad
Ahora veamos como resolví esto con Scala y Slick.
Lo primero de todo, defino el esquema (schema), con una query definition para las subcategorías (subcategories), para ayudarme más tarde con la navegación entre objectos.
1 2 3 4 5 6 7 8 9 10 |
class CategoriesTable(tag: Tag) extends Table[Category](tag, "CATEGORIES") { def id = column[String]("id", O.PrimaryKey) def name = column[String]("name") def idParent = column[Option[String]]("idParent") def * = (id, name, idParent) <> (Category.tupled, Category.unapply) def categoryFK = foreignKey("category_fk", idParent, categories)(_.id.?) def subcategories = TableQuery[CategoriesTable].filter(id === _.idParent) } |
Una vez que tenemos esto, en el DAO definimos una TableQuery para las categorías y el método findChildrenWithParents, que devuelve justo lo que queríamos:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
private val categories = TableQuery[CategoriesTable] def findChildrenWithParents() = { val result = db.run((for { c <- categories s <- c.subcategories } yield (c,s)).sortBy(_._1.name).result) result map { case categoryTuples => categoryTuples.groupBy(_._1).map{ case (k,v) => (k,v.map(_._2)) } } } |
¿Qué estoy haciendo?
- En result, un join de categorías con sus subcategorías ordenado por el nombre de la categoría padre.
- Agrupar result por la categoría padre.
- Como el resultado de hacer groupBy también contiene a la categoría padre en la secuencia, hago un map del mismo para quedarnos simplemente con las subcategorías.
Aclaración
A ver, esta solución no es 100% perfecta para mi. Si te fijas, en result ya le he dicho a Slick que ejecute la query en base de datos con db.run. Para mi sería mejor tener el groupBy en la query de Slick y después ejecutar la query. Pero me estaba dando problemillas, ya que Slick se quejaba de que la proyección de mi esquema no era adecuada al resultado.
¡Ahora te toca a ti!
Como has visto, no estoy del todo contento con la solución. Funciona… sí, lo sé, llámame perfecionista si quieres… pero no me satisface a 100%. Ahora te toca a ti… ¿Me ayudas con ese groupBy de la Slick query? Si tienes una solución o una mejora, escríbeme un comentario. Si te ha gustado este post, por favor ¡suscríbete y compártelo!