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
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 unaList[(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 unaQuery
con la proyección(String, Int)
. Cuando se invoca, devuelve unaList
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
defor
comprensión.Ahora sobre
<>
yBar.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 cualfield1 ~ field2
devuelve una proyección deProjection2[A, B]
dondeA
es el tipo defield1
yB
es el tipo defield2
.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ónProjection2[Int, String]
y devuelve una proyección mapeada en el tipoBar
. Los dos argumentosBar, Bar.unapply _
tellBar, 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 unaBar
. Y nounapply
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 deBar
le proporciona unBar.unapply
que es un extractor que se puede utilizar para recuperar elid
y elname
que se creó laBar
: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 unaQuery
con una proyección mapeada aBaz
. Cuando se invoca, devuelve unaList
de objetosBaz
:val result: List[Baz] = q1.list
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]
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étodosflatMap
(en otras palabras, una Mónada ) se puede utilizar en afor
comprensión en lugar dens
. Un buen ejemplo es la mónada Option . Aquí está el ejemplo anterior donde lo mismofor
declaración funciona tanto en laList
como en las mónadas deOption
:// (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 defilter
. 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
yfilter
se utilizan para generar unaQuery
mediante la transformación repetida deQuery(Bars)
con cada invocación defilter
ymap
. 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?