Configuraci贸n de Slick

28/36

Lectura

Configuraci贸n de Slick

Antes de empezar, deber铆as descargar el template Play Scala Seed desde esta p谩gina: https://developer.lightbend.com/start/?group=play&project=play-scala-seed

Recuerda que para este proyecto usaremos IntelliJ IDEA como editor, as铆 que aseg煤rate de tenerlo instalado junto con los plugins recomendados en una lectura anterior. Debes tener abierto el proyecto en el IntelliJ IDEA.

El proceso que seguiremos ser谩 el siguiente:

  • Instalaremos las dependencias de Slick y el Driver de la base de datos.
  • Agregaremos las configuraciones para la conexi贸n con la base de datos.
  • Crearemos un archivo Movie.scala con el modelo y la definici贸n de la tabla.
  • Haremos el esqueleto de funciones para la clase siguiente.
  • Agregaremos una funci贸n de ayuda para crear la base de datos.

Dependencias

Lo primero que hay que agregar son las dependencias para usar PlaySlick (el plugin que integra Play con Slick), y el driver JDBC de la base de datos que vayamos a usar, que en este caso ser谩 SQLite.

SBT buscar谩 los paquetes de sus dependencias en Maven.

Puedes pensar en Maven como algo similar a NPM de JavaScript (un repositorio de paquetes que pueden ser descargados). Aunque la diferencia con NPM es que Maven no constituye una entidad 煤nica si no m煤ltiples repositorios de paquetes administrados por diferentes entidades o empresas.

Por eso para encontrar nuevos paquetes existe una p谩gina que tiene indexados m谩s de mil repositorios de Maven: https://mvnrepository.com/

All铆 es f谩cil hacer b煤squedas no solo de los paquetes que necesitemos, si no tambi茅n de sus versiones disponibles.

Como ejercicio, haz t煤 la b煤squeda de los dos paquetes que queremos instalar. En la siguiente imagen puedes ver c贸mo debe quedar el build.sbt:

01.png

Luego con el comando sbt update desde la consola, haremos que sbt descargue estas dependencias en nuestro proyecto para que est茅n disponibles.

Configuraci贸n

Lo siguiente que debes hacer, es configurar los datos de acceso a una base de datos SQLite. Esto lo haremos en el archivo application.conf que se encuentra en la carpeta conf del proyecto.

02.png

El archivo application.conf est谩 escrito en un formato llamado HOCON. En la imagen estamos definiendo las configuraciones para la base de datos por defecto (Slick permite configurar m煤ltiples conexiones al tiempo tambi茅n). Las configuraciones m铆nimas son las siguientes:

  • Profile: Le dice a Slick el perfil de la base de datos que vamos a usar. Hay que notar el s铆mbolo $ al final de la clase SQLiteProfile. Puedes ver un listado de las bases de datos soportadas con los links a sus perfiles en la documentaci贸n de Slick, https://scala-slick.org/doc/3.3.1/supported-databases.html
  • Driver: Es el driver que Slick usar谩 para comunicarse con la base de datos. JDBC es un est谩ndar para controladores de bases de datos en Java. Todas las bases de datos populares tienen un driver JDBC y en nuestro caso, ser谩 el path al que instalamos como dependencia.
  • Url: Es la direcci贸n de conexi贸n a la base de datos. Para el caso particular de SQLite, MyDataBase.db ser谩 el nombre del archivo donde se alojar谩 la base de datos. No es necesario que exista con antelaci贸n ya que m谩s adelante haremos que play lo cree por nosotros. En otras bases de datos como Postgres, Oracle o MySql, la URL de conexi贸n ser谩 distinta, pero siempre siguiendo su formato para JDBC.
  • User y Password: Para este proyecto, nuestra base de datos en SQLite no tiene ni usuario ni contrase帽a, lo hacemos as铆 por simplicidad pero en proyectos reales tu base de datos debe tener usuario y clave seguros.
  • El 煤ltimo par谩metro (slick.default), hace referencia al paquete de Scala donde Slick buscar谩 las clases cuando hagamos la definici贸n de los modelos.

Modelo

En la carpeta app, crearemos un paquete que se llame models, y dentro crearemos un archivo llamado Movie.scala. Puedes crearlos con clic derecho New, o creando la carpeta y el archivo manualmente. Debe quedarte de la siguiente manera:

03.png

Case Class Movie

Lo primero ser谩 convertir la clase Movie en un case class, y agregarle los atributos correspondientes. Este case class debe quedar de la siguiente manera:

04.png

Los puntos a notar son:

  • Usamos par茅ntesis () en vez de llaves {}.
  • Los atributos id y tags son opcionales, pero el primero crea por defecto un UUID (que debes de importar) cuando el objeto no tenga id.

MovieTable

Teniendo ya el case class, debemos crear una clase que ser谩 el 鈥減uente鈥 entre la informaci贸n que se env铆e/reciba de la base de datos, hacia el objeto de Scala.

Esta clase la llamaremos MovieTable y puede estar en el mismo archivo, junto con la case class Movie (a diferencia de Java, en Scala podemos tener distintas clases en el mismo archivo sin problema). Debe quedar de la siguiente manera:

05.png
  • Se debe importar el contenido del objeto api que est谩 en el profile de nuestra base de datos SQLite (en Scala el gui贸n bajo _ en una importaci贸n, es equivalente al asterisco * de Java o Python).
  • La clase extiende de un trait llamado Table[T] que recibe un tipo de dato T, que en este caso ser谩 el case class Movie. All铆 tambi茅n de le dice el nombre de la tabla de SQL a la que hace referencia.
  • Cada atributo se define con un tipo column, d谩ndole el tipo de dato de Scala, y el nombre de la columna en SQL.
  • Algunos atributos tienen opciones especiales, como el id que tiene O.PrimaryKey, o tags que tiene O.Length(2000, varying = true) . Existen varias opciones que est谩n dentro del objeto O de opciones de columna. Las opciones disponibles siempre depende de la base de datos que est茅s usando.
  • La definici贸n del id en la tabla es directamente un String en vez de un Option[String]. Esto es porque la clave primaria nunca puede ser nula.
  • Hay una definici贸n especial que describe la proyecci贸n entre el objeto tabla y la case class a la que mapea. El nombre que tiene es un asterisco *. Para el atributo id.?, ese ? es necesario porque en la definici贸n de la tabla ese atributo es obligatorio, pero en la definici贸n del objeto es opcional, de esa manera el compilador sabe que lo debe transformar.

Si quieres conocer m谩s detalles sobre esto, puedes verlos en la documentaci贸n de Slick: https://scala-slick.org/doc/3.3.1/schemas.html

MovieRepository

La 煤ltima parte en este archivo ser谩 lo que se conoce como repositorio.

En este contexto, un repositorio es el encargado de comunicar la base de datos con lo que ser谩 m谩s adelante el controlador. Esta ser谩 la clase sobre la que vamos a trabajar en la pr贸xima clase.

El repositorio debe quedar de la siguiente manera:

06.png
  • Hay varias clases que es necesario importarlas, y que luego ser谩n 鈥渋nyectadas鈥 haciendo uso de un patr贸n conocido como Inyecci贸n de dependencias. No es el objetivo explicar eso aqu铆, lo importante es entender que una instancia esas clases va a estar disponible dentro de la clase repositorio luego de ser inyectada.
  • El objeto inyectado dbConfigProvider contiene lo necesario para realizar peticiones a la base de datos, usando los datos que escribiste en el application.conf.
  • El contexto de ejecuci贸n es usado para realizar las peticiones de manera as铆ncrona. M谩s adelante veremos esto en detalle. Se usa la palabra reservada implicit para no tener que pasarle este par谩metro todo el tiempo, si no que el compilador lo ponga por nosotros.
  • El atributo movieQuery, es desde el cual vamos a estar creando las peticiones a la base de datos en la pr贸xima clase.

Funciones de ayuda para la creaci贸n de la base de datos

Esta parte no la explicar茅 en detalle, pero ser谩 煤til para crear el esquema de la base de datos usando lo que acabamos de escribir. Es decir, que no es necesario tener las definiciones de las tablas en SQL, si no que con base en el c贸digo en Scala, podemos decirle a Slick que las cree por nosotros.

En el repositorio

Para ello, primero debes agregar el siguiente c贸digo dentro de la clase MovieRepository:

  // Esto va dentro de la clase MovieRepository
 
  /**
   * Funci贸n de ayuda para crear la tabla si 茅sta
   * a煤n no existe en la base de datos.
   * @return
   */
  def dbInit: Future[Unit] = {
    // Definici贸n de la sentencia SQL de creaci贸n del schema
    val createSchema = movieQuery.schema.createIfNotExists
    // db.run Ejecuta una sentencia SQL, devolviendo un Future
    db.run(createSchema)
  }

  def getAll = ???
  def getOne = ???
  def create = ???
  def update = ???
  def delete = ???

Nota que la 煤ltima parte en donde est谩n los ???, es donde empezaremos en la pr贸xima clase.

En el HomeController

Luego debes agregar lo siguiente en el archivo app/controllers/HomeController.scala

// Importar el repository y el contexto de ejecuci贸n global
import models.MovieRepository
import scala.concurrent.ExecutionContext.Implicits.global

//....

// Hay que agregar el repository para que sea inyectado
@Singleton
class HomeController @Inject()(
    cc: ControllerComponents,
    movieRepository: MovieRepository
) extends AbstractController(cc) {

  // .....

  /*
    Funci贸n de ayuda para crear la tabla si esta a煤n no existe.
   */
  def dbInit() = Action.async { request =>
    movieRepository.dbInit
      .map(_ => Created("Tabla creada"))
      .recover{ex =>
        play.Logger.of("dbInit").debug("Error en dbInit", ex)
        InternalServerError(s"Hubo un error")
      }
  }
}
  • Se importa el repositorio.
  • Se coloca el repositorio en la cabecera de la clase HomeController para que sea inyectado.
  • Se define la acci贸n dbInit.

En el archivo de rutas

Por 煤ltimo se debe agregar en el archivo conf/routes una ruta hacia la acci贸n dbInit que acabamos de agregar:

GET     /dbInit                     controllers.HomeController.dbInit

![07.png](https://static.platzi.com/media/user_upload/07-a0159219-ea8b-41fa-993d-3eadd97438e7.jpg

Corriendo el servidor y creando la base de datos

Usaremos en comando sbt compile para estar seguros que todo compila correctamente, y despu茅s escribimos el comando sbt run para correr el servidor en modo desarrollo.

Con el servidor de desarrollo encendido, visitaremos la direcci贸n http://localhost:9000/dbInit, y despu茅s de unos segundos, deber铆as ver el mensaje de 鈥淭abla creada鈥.

08.png

Y si revisas la carpeta del proyecto, deber铆as ver el archivo de la base de datos SQLite tambi茅n:

09.png

Para detener el servidor de desarrollo, usa las teclas Ctrl+D en la consola.

Conclusiones

En esta clase escrita aprendimos muchas habilidades 煤tiles para trabajar con Play y Scala.

Aqu铆 fuimos a lo pr谩ctico, pero por supuesto hay temas que se profundizan en la documentaci贸n de Play y que quiz谩s te puedan interesar: https://www.playframework.com/documentation/2.7.x/PlaySlick

Estoy seguro que en el c贸digo de esta clase notaste c贸mo aplicamos varios de los temas que vimos en las clases pasadas. Escribe en los comentarios cuales viste que te llamaron la atenci贸n, y por qu茅 crees que son 煤tiles o in煤tiles en donde fueron aplicados.

Aportes 12

Preguntas 2

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad? Crea una cuenta o inicia sesi贸n.

No me gusto este articulo, pudo haber sido explicado un poco mejor con videos porque algunos pasos se prestan a confusion y por lo menos en el articulo en vez de dejar imagenes, hubiera estado mejor dejar snippets para copiar y pegar porque muchas de estas cosas son demasiado boilerplate y esta muy facil equivocarse por un par de caracteres

Me parece p茅sima esta clase, qu茅 pas贸 铆bamos bien, muy diferente ver codificar y entender cada elemento que leer un documento.

Hay una serie de errores en el codigo mostrado, puede ser por la nueva versi贸n de play, recomiendo se realicen las correcciones necesarias, dado que se invierte mucho tiempo en tratar de ejecutar el ejercicio, me tomo el atrevimiento de compartir este repositorio de Jonathan Valencia que me ayudo con la configuraci贸n de la base de datos con slick https://github.com/jonathanvalenciav/platzi-video.

Ay que bien se siente jajaja al fin, despu茅s de la infinidad de errores

Creo que tambi茅n es importante lo siguiente:

  • mencionar la versi贸n de sbt, porque me sal铆an errores con ello. Al final us茅 el sbt que me suger铆a IntelliJ y reinici茅 el IDE y ah铆 funcion贸.
  • Tambi茅n es importante que si nos saltamos aprender Play Framework al menos nos expliquen que con c贸digo, no tanto con im谩genes. Bueno, o al menos indicar que el IDE coloca cierto texto encima. Soy nuevo en IntelliJ y copi茅 todo el texto manualmente como se muestra :
    entonces, perd铆 mucho tiempo colocando texto que coloca el IDE mismo. Creo que ser铆a bueno que editaran 茅sta clase con un taller o video explicativo de c贸mo resolver los mont贸n errores que nos salieron.

Gracias Jonathan Valencia y Mart铆n Albarrac铆n.

Para los que tienen problemas corriendo el proyecto les dejo mi repositorio, para gu铆a , hay algunas cositas que no est谩n explicitas en el articulo y hay que leer un poco m谩s sobre todo de el framework para que funcione para los que tengan experiencia en spring les sera un poco mas sencillo entender.

Mi principal problema fue identificar correctamente los imports que hay que hacer as铆 como implementar el controller lo necesario para la compilaci贸n.

https://github.com/djaquels/PlayMovies

Funciono! Despu茅s de tantos errores

Obtengo errores en todo 馃槕

Im谩genes:


El error en navegador:

IO error while decoding C:\Users\sistemasfimtt\iCloudDrive\Platzi\Carreras\Fundamentos de Programaci贸n\ProgramacionFuncionalConScala\play-scala-seed\target\scala-2.13\routes\main\controllers\ReverseRoutes.scala with UTF-8: MALFORMED[1]
Please try specifying another one using the -encoding option

Mis archivos:
build.sbt

name := """platzi-video"""
organization := "com.platzi"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.13.1"

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test
libraryDependencies += "com.typesafe.play" %% "play-slick" % "4.0.2"
libraryDependencies += "org.xerial" % "sqlite-jdbc" % "3.28.0"

application.conf

# https://www.playframework.com/documentation/latest/Configuration

slick.dbs.default.profile="slick.jdbc.SQLiteProfiles$"
slick.dbs.default.db.driver="org.sqlite.JDBC"
slick.dbs.default.db.url="jdbc:sqlite:MyDataBase.db"
slick.dbs.default.db.user=""
slick.dbs.default.db.password=""

slick.default="model.*"

Movie.scala

package models
import java.util.UUID

/*
  Objeto Movie.
 */
case class Movie(
  id: Option[String] = Option(UUID.randomUUID.toString),
  title: String,
  year: Int,
  cover: String,
  description: String,
  duration: Int,
  contentRating: String,
  source: String,
  tags: Option[String]
)

MovieTable.scala

package models
import slick.jdbc.SQLiteProfile.api_
/*
  Clase que mapea el objeto a la tabla movie.

  https://scala-slick.org/doc/3.3.1/schemas.html
 */
class MovieTable(tag: Tag) extends Table[Movie](tag, _tableName = "movie"){
  def id: Rep[String] = column[String]("id", 0.PrimaryKey)
  def title: Rep[String] = column[String]("title")
  def year: Rep[Int] = column[Int]("year")
  def cover: Rep[String] = column[String]("cover")
  def description: Rep[String] = column[String]("description")
  def duration: Rep[Int] = column[Int]("duration")
  def contentRating: Rep[String] = column[String]("content_rating")
  def source: Rep[String] = column[String]("source")
  def tags: Rep[Option[String]] = column[Option[String]]("tags", 0.Lenght(2000, varying = true))

  def *: ProvenShape[Movie] = (id.?, title, year, cover, duration, contentRating, source, tags) <> (Movie.tupled, Movie.unapply)
}

MovieRepository.scala

package models
import javax.inject.Inject
import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.mvc.{AbstractController, ControllerComponents}
import scala.concurrent.{ExecutionContext, Future}
/*
  Clase repository, donde ir谩n las consultas.
 */
class MovieRepository @Inject()(
  protected val dbConfigProvider: DatabaseConfigProvider, cc: ControllerComponents
)(implicit ec: ExecutionContext)
  extends AbstractController(cc)
  with HasDatabaseConfigProvider[JdbcProfile]{
  private lazy val movieQuery = TableQuery[MovieTable]
}
/*
  * Funci贸n de ayuda para crear la tabla si esta
  * a煤n no existe en la base de datos.
  * @return
 */
def dbInit: Future[Unit] = {
  // Definici贸n de la sentencia SQL de creaci贸n del schema
  val createSchema = movieQuery.schema.createIfNotExists
  // db.run ejecuta una secuencia SQL, devolviendo un Future
  db.run(createSchema)
}

def getAll = ???
def getOne = ???
def create = ???
def update = ???
def delete = ???

HomeController.scala

package controllers

import javax.inject._
import play.api._
import play.api.mvc._
// Importar el repository y el contexto de ejecuci贸n global
import models.MovieRepository
import scala.concurrent.ExecutionContext.Implicits.global

/**
 Hay quea gregar el repository para que sea inyectado
 */
@Singleton
class HomeController @Inject()(
  cc: ControllerComponents,
  movieRepository: MovieRepository
) extends AbstractController(cc){
  // ...
  /*
    Funci贸n de ayuda para crear la tabla si esta a煤n no existe
   */
  def dbInit() = Action.async{ request =>
    movieRepository.dbInit
      .map(_ => Created("Tabla creada"))
      .recover{ex =>
        play.Logger.of("dbInit").debug("Error en dbInit", ex)
        InternalServerError(s"Hubo un error")
      }
  }
}

routes

# Routes
# This file defines all application routes (Higher priority routes first)
# https://www.playframework.com/documentation/latest/ScalaRouting
# ~~~~

# An example controller showing a sample home page
GET     /                           controllers.HomeController.index

# Map static resources from the /public folder to the /assets URL path
GET     /assets/*file               controllers.Assets.versioned(path="/public", file: Asset)

GET     /dbInit                     controllers.HomeController.dbInit

驴Alg煤n apoyo con el tema?

este es la estructura de archivos y como debe quedar.
tuve que importar muchas cosas por defecto seg煤n me indicaba IntelliJ porque en el post creo que hay muchos pasos que no se menciona. estar[e subiendo el c贸digo a Github

Terrible art铆culo. Una explicaci贸n bastante insuficiente y pobre

para las personas que reci茅n lleguen como yo y no quieran ponerse a transcribir imagen por imagen tengo este hackLife:
descarguen cada imagen recortenla y carguenla aqu铆, es un OCR para pasar de imagen a texto https://www.onlineocr.net/es/

Buenas, alguien por favor que me hace falta. Cuando lanzo el comando sbt update muestra el siguiente error:

download error: Caught javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path building fail
ed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target (sun.security.validator.ValidatorExceptio
n: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target) while download
ing https://repo.typesafe.com

He buscado pero no se que me falta.
tengo jdk1.8.0_281 de 64 bits y javac 1.8.0_281
Mi build.sbt es

name := "mi-primer-scala"
organization := "com.scala"

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.13.3"

libraryDependencies += guice
libraryDependencies += "org.scalatestplus.play" %% "scalatestplus-play" % "5.0.0" % Test
libraryDependencies += "com.typesafe.play" %% "play-slick" % "4.0.2"
libraryDependencies += "org.xerial" %% "sqlite-jdbc" % "3.28.0"

// Adds additional packages into Twirl
//TwirlKeys.templateImports += "com.example.controllers._"

// Adds additional packages into conf/routes
// play.sbt.routes.RoutesKeys.routesImport += "com.example.binders._"```

Nota: tengo el cacerts en la ruta: C:\Program Files (x86)\Java\jre1.8.0_281\lib\security pero entonces no entiendo que me hace falta. Gracias