scala playframework-2.0 magic-methods scalaquery slick

Metodo de Scala Slick que no puedo entender hasta ahora



playframework-2.0 magic-methods (2)

Intento entender algunas obras de Slick y lo que requiere.

Aquí hay un ejemplo:

package models case class Bar(id: Option[Int] = None, name: String) object Bars extends Table[Bar]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) // This is the primary key column def name = column[String]("name") // Every table needs a * projection with the same type as the table''s type parameter def * = id.? ~ name <>(Bar, Bar.unapply _) }

¿Podría alguien explicarme cuál es el propósito del * método aquí, qué es <> , por qué no unapply ? y ¿qué es Projection - method ~ ''devuelve la instancia de Projection2 ?


Como nadie más ha respondido, esto podría ayudarlo a comenzar. No sé Slick muy bien.

De la documentación de Slick :

Incrustación levantada:

Cada tabla requiere un método * que contenga una proyección predeterminada. Esto describe lo que obtiene cuando devuelve filas (en forma de un objeto de tabla) de una consulta. La proyección de Slick * no tiene que coincidir con la de la base de datos. Puede agregar nuevas columnas (por ejemplo, con valores calculados) u omitir algunas columnas a su gusto. El tipo no elevado correspondiente a la proyección * se proporciona como un parámetro de tipo a la Tabla. Para tablas simples no mapeadas, este será un tipo de columna simple o una tupla de tipos de columna.

En otras palabras, slick necesita saber cómo lidiar con una fila devuelta desde la base de datos. El método que definió usa sus funciones de combinador de analizador para combinar las definiciones de columna en algo que se puede usar en una fila.


[ACTUALIZACIÓN] - se agregó (otra más) explicación for comprensiones

  1. El * método:

    Esto devuelve la proyección predeterminada , que es cómo describes:

    ''todas las columnas (o valores calculados) en los que normalmente estoy interesado''

    Tu tabla podría tener varios campos; solo necesita un subconjunto para su proyección predeterminada. La proyección predeterminada debe coincidir con los parámetros de tipo de la tabla.

    Tomemos uno a la vez. Sin las <> cosas, solo el * :

    // First take: Only the Table Defintion, no case class: object Bars extends Table[(Int, String)]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def * = id ~ name // Note: Just a simple projection, not using .? etc } // Note that the case class ''Bar'' is not to be found. This is // an example without it (with only the table definition)

    Solo una definición de tabla como esa te permitirá hacer consultas como:

    implicit val session: Session = // ... a db session obtained from somewhere // A simple select-all: val result = Query(Bars).list // result is a List[(Int, String)]

    la proyección predeterminada de (Int, String) lleva a una List[(Int, String)] para consultas simples como estas.

    // SELECT b.name, 1 FROM bars b WHERE b.id = 42; val q = for (b <- Bars if b.id === 42) yield (b.name ~ 1) // yield (b.name, 1) // this is also allowed: // tuples are lifted to the equivalent projection.

    ¿Cuál es el tipo de q ? Es una Query con la proyección (String, Int) . Cuando se invoca, devuelve una List de tuplas (String, Int) según la proyección.

    val result: List[(String, Int)] = q.list

    En este caso, ha definido la proyección que desea en la cláusula de yield de for comprensión.

  2. Ahora sobre <> y Bar.unapply .

    Esto proporciona lo que se denomina proyecciones asignadas .

    Hasta ahora hemos visto cómo slick le permite expresar consultas en Scala que devuelven una proyección de columnas (o valores calculados); Por lo tanto, al ejecutar estas consultas , debe pensar en la fila de resultados de una consulta como una tupla de Scala . El tipo de tupla coincidirá con la Proyección que está definida (por su comprensión como en el ejemplo anterior, por la proyección por defecto * ). Esta es la razón por la cual field1 ~ field2 devuelve una proyección de Projection2[A, B] donde A es el tipo de field1 y B es el tipo de field2 .

    q.list.map { case (name, n) => // do something with name:String and n:Int } Queury(Bars).list.map { case (id, name) => // do something with id:Int and name:String }

    Estamos lidiando con tuplas, que pueden ser engorrosas si tenemos demasiadas columnas. Nos gustaría pensar en los resultados no como TupleN sino como algún objeto con campos con nombre.

    (id ~ name) // A projection // Assuming you have a Bar case class: case class Bar(id: Int, name: String) // For now, using a plain Int instead // of Option[Int] - for simplicity (id ~ name <> (Bar, Bar.unapply _)) // A MAPPED projection // Which lets you do: Query(Bars).list.map ( b.name ) // instead of // Query(Bars).list.map { case (_, name) => name } // Note that I use list.map instead of mapResult just for explanation''s sake.

    ¿Como funciona esto? <> toma una proyección Projection2[Int, String] y devuelve una proyección mapeada en el tipo Bar . Los dos argumentos Bar, Bar.unapply _ tell Bar, Bar.unapply _ cómo se debe mapear esta proyección (Int, String) a una clase de caso.

    Este es un mapeo bidireccional; Bar es el constructor de la clase de caso, por lo que esa es la información necesaria para ir desde (id: Int, name: String) a una Bar . Y no unapply si lo has adivinado, es al revés.

    ¿De unapply viene la aplicación? Este es un método estándar de Scala disponible para cualquier clase de caso común; solo la definición de Bar le proporciona un Bar.unapply que es un extractor que se puede utilizar para recuperar el id y el name que se creó la Bar :

    val bar1 = Bar(1, "one") // later val Bar(id, name) = bar1 // id will be an Int bound to 1, // name a String bound to "one" // Or in pattern matching val bars: List[Bar] = // gotten from somewhere val barNames = bars.map { case Bar(_, name) => name } val x = Bar.unapply(bar1) // x is an Option[(String, Int)]

    Por lo tanto, su proyección predeterminada se puede asignar a la clase de caso que espera usar:

    object Bars extends Table[Bar]("bar") { def id = column[Int]("id", O.PrimaryKey, O.AutoInc) def name = column[String]("name") def * = id ~ name <>(Bar, Bar.unapply _) }

    O incluso puede tenerlo por consulta:

    case class Baz(name: String, num: Int) // SELECT b.name, 1 FROM bars b WHERE b.id = 42; val q1 = for (b <- Bars if b.id === 42) yield (b.name ~ 1 <> (Baz, Baz.unapply _))

    Aquí el tipo de q1 es una Query con una proyección mapeada a Baz . Cuando se invoca, devuelve una List de objetos Baz :

    val result: List[Baz] = q1.list

  3. Finalmente, como un aparte, ¿el .? ofrece Opción de elevación - la forma de Scala de tratar con los valores que pueden no ser.

    (id ~ name) // Projection2[Int, String] // this is just for illustration (id.? ~ name) // Projection2[Option[Int], String]

    Lo cual, al finalizar, funcionará muy bien con tu definición original de Bar :

    case class Bar(id: Option[Int] = None, name: String) // SELECT b.id, b.name FROM bars b WHERE b.id = 42; val q0 = for (b <- Bars if b.id === 42) yield (b.id.? ~ b.name <> (Bar, Bar.unapply _)) q0.list // returns a List[Bar]

  4. En respuesta al comentario sobre cómo Slick utiliza for comprensiones:

    De alguna manera, las mónadas siempre logran aparecer y demandar ser parte de la explicación ...

    Para las comprensiones no son específicas para las colecciones solamente. Se pueden usar en cualquier tipo de Monad , y las colecciones son solo uno de los muchos tipos de tipos de mónada disponibles en Scala.

    Pero como las colecciones son familiares, son un buen punto de partida para una explicación:

    val ns = 1 to 100 toList; // Lists for familiarity val result = for { i <- ns if i*i % 2 == 0 } yield (i*i) // result is a List[Int], List(4, 16, 36, ...)

    En Scala, una para comprensión es azúcar sintáctica para llamadas al método método (posiblemente anidado): el código anterior es (más o menos) equivalente a:

    ns.filter(i => i*i % 2 == 0).map(i => i*i)

    Básicamente, cualquier cosa con filter , map , métodos flatMap (en otras palabras, una Mónada ) se puede utilizar en a for comprensión en lugar de ns . Un buen ejemplo es la mónada Option . Aquí está el ejemplo anterior donde lo mismo for declaración funciona tanto en la List como en las mónadas de Option :

    // (1) val result = for { i <- ns // ns is a List monad i2 <- Some(i*i) // Some(i*i) is Option if i2 % 2 == 0 // filter } yield i2 // Slightly more contrived example: def evenSqr(n: Int) = { // return the square of a number val sqr = n*n // only when the square is even if (sqr % 2 == 0) Some (sqr) else None } // (2) result = for { i <- ns i2 <- evenSqr(i) // i2 may/maynot be defined for i! } yield i2

    En el último ejemplo, la transformación quizás se vería así:

    // 1st example val result = ns.flatMap(i => Some(i*i)).filter(i2 => i2 %2 ==0) // Or for the 2nd example result = ns.flatMap(i => evenSqr(i))

    En Slick, las consultas son monádicas: solo son objetos con el map , flatMap y métodos de filter . Entonces, for comprensión (que se muestra en la explicación del * método) simplemente se traduce a:

    val q = Query(Bars).filter(b => b.id === 42).map(b => b.name ~ 1) // Type of q is Query[(String, Int)] val r: List[(String, Int)] = q.list // Actually run the query

    Como puede ver, flatMap , map y filter se utilizan para generar una Query mediante la transformación repetida de Query(Bars) con cada invocación de filter y map . En el caso de las colecciones, estos métodos realmente iteran y filtran la colección, pero en Slick se usan para generar SQL. Más detalles aquí: ¿Cómo convierte Scala Slick el código de Scala en JDBC?