Tutorial: Cómo escribir modelos con fluidez

Este tutorial le mostrará cómo implementar un modelo de usuario simple, almacenarlo en la base de datos, recuperarlo de la base de datos y pasarlo a la vista.

Puedes encontrar el resultado de este tutorial en github aquí

Este tutorial es un seguimiento natural de Cómo usar Leaf. Puedes ir a ese tutorial primero y volver más tarde o ser un rebelde, omitirlo y seguir leyendo

Índice

1. Crear un nuevo proyecto
2. Generar proyecto Xcode
3. Configure su proyecto para usar la base de datos SQLite
4. Crea tu primer modelo
5. Implemente una ruta GET para enumerar todos los usuarios
6. Explicando Async
7. Lo que el futuro ...
8. Crear una vista
9. Implemente una ruta POST para almacenar un usuario
10. A dónde ir desde aquí

1. Crear un nuevo proyecto

Utilizaremos el resultado del tutorial antes mencionado como plantilla para crear nuestro nuevo proyecto:

nombre de proyecto nuevo de vapor --template = vaporberlin / my-first-leaf-template

2. Generar proyecto Xcode

Antes de generar un proyecto Xcode, tendríamos que agregar un proveedor de base de datos. Hay cada uno para cada base de datos. Pero en aras de calentarse con ORM Fluent, utilizaremos una base de datos en memoria. Por lo tanto, agregamos Fluent-SQLite como una dependencia dentro de nuestro Package.swift:

// swift-tools-version: 4.0
importar PackageDescription
let package = Paquete (
  nombre: "projectName", // cambiado
  dependencias: [
    .package (url: "https://github.com/vapor/vapor.git", de: "3.0.0"),
    .package (url: "https://github.com/vapor/leaf.git", de: "3.0.0-rc"),
   .package (url: "https://github.com/vapor/fluent-sqlite.git", de: "3.0.0-rc") // agregado
  ],
  objetivos: [
    .target (nombre: "Aplicación", dependencias: ["Vapor", "Hoja", "FluentSQLite"]), // agregado
    .target (nombre: "Ejecutar", dependencias: ["Aplicación"]),
    .testTarget (nombre: "AppTests", dependencias: ["App"]),
  ]
)

Ahora en la terminal en el directorio raíz projectName / execute:

actualización de vapor -y

Puede tomar un poco recuperar la dependencia, generar el proyecto Xcode y abrirlo para usted. Cuando termine, debe tener una estructura de proyecto como esta:

nombre del proyecto/
├── Package.swift
├── Fuentes /
│ ├── Aplicación /
│ │ ├── app.swift
│ │ ├── boot.swift
│ │ ├── configure.swift
│ │ └── routes.swift
│ └── Ejecutar /
│ └── main.swift
├── Pruebas /
├── Recursos /
├── Público /
├── Dependencias /
└── Productos /
Si ve un error que incluye "CNIOOpenSSL" cuando cmd + r le falta una dependencia. Simplemente ejecute brew upgrade vapor y vuelva a generar el proyecto

3. Configure su proyecto para usar una base de datos SQLite

Nuestro primer paso es agregar FluentSQLiteProvider dentro de nuestro configure.swift:

importar vapor
hoja de importación
import FluentSQLite // agregado
configuración de funciones públicas (
  _ config: inout Config,
  _ env: inout Environment,
  _ servicios: inout Services
) arroja {
  // Registrar rutas al enrutador
  let router = EngineRouter.default ()
  probar rutas (enrutador)
  services.register (enrutador, como: Router.self)
  let leafProvider = LeafProvider ()
  pruebe services.register (leafProvider)
  pruebe services.register (FluentSQLiteProvider ()) // agregado
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
}

A continuación, iniciaremos un servicio de base de datos, le agregaremos una base de datos SQLite y registraremos ese servicio de base de datos:

importar vapor
hoja de importación
importar FluentSQLite
configuración de funciones públicas (
  _ config: inout Config,
  _ env: inout Environment,
  _ servicios: inout Services
) arroja {
  // Registrar rutas al enrutador
  let router = EngineRouter.default ()
  probar rutas (enrutador)
  services.register (enrutador, como: Router.self)
  let leafProvider = LeafProvider ()
  pruebe services.register (leafProvider)
  pruebe services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  bases de datos var = Bases de datosConfig ()
  intente bases de datos.add (base de datos: SQLiteDatabase (almacenamiento: .memory), como: .sqlite)
  services.register (bases de datos)
}

Finalmente, iniciamos y registramos un servicio de migración que usaremos más adelante para introducir nuestro Modelo en nuestra base de datos. Por ahora agregue lo siguiente:

importar vapor
hoja de importación
importar FluentSQLite
configuración de funciones públicas (
  _ config: inout Config,
  _ env: inout Environment,
  _ servicios: inout Services
) arroja {
  // Registrar rutas al enrutador
  let router = EngineRouter.default ()
  probar rutas (enrutador)
  services.register (enrutador, como: Router.self)
  let leafProvider = LeafProvider ()
  pruebe services.register (leafProvider)
  pruebe services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  bases de datos var = DatabaseConfig ()
  intente bases de datos.add (base de datos: SQLiteDatabase (almacenamiento: .memory), como: .sqlite)
  services.register (bases de datos)
  var migraciones = MigrationConfig ()
  services.register (migraciones)
}

4. Crea tu primer modelo

Cree un directorio dentro de Fuentes / Aplicación / y asígnele el nombre Modelos / y dentro de ese nuevo directorio cree un nuevo archivo rápido llamado User.swift

NOTA: utilicé el terminal ejecutando mkdir Sources / App / Models / y toque Sources / App / Models / User.swift

Es posible que deba volver a generar su proyecto Xcode con vapor xcode -y para permitir que Xcode vea su nuevo directorio.

En Models / User.swift incluye el siguiente código:

importar FluentSQLite
importar vapor
Usuario de clase final: SQLiteModel {
  ID de var: Int?
  nombre de usuario var: String
  init (id: Int? = nil, username: String) {
    self.id = id
    self.username = nombre de usuario
  }
}
Usuario de extensión: Contenido {}
Usuario de extensión: migración {}

Lo mantuve súper simple para que podamos entender lo que está sucediendo aquí. Al cumplir con SQLiteModel, debemos definir una variable opcional llamada id que sea de tipo int. Es opcional simplemente porque si iniciamos un nuevo usuario para almacenarlo en la base de datos, no depende de nosotros darle una identificación en ese momento. Obtendrá una identificación asignada después de almacenarlo en la base de datos.

La conformidad con el Contenido lo hace posible para que nuestro Usuario pueda convertir, por ejemplo, JSON usando Codificable si lo devolviéramos en una ruta. O así puede convertirlo en TemplateData, que se usa dentro de una vista de hoja. Y debido a Codable, eso sucede de forma automática. Es necesario ajustarse a la migración para que Fluent pueda usar Codable para crear el mejor esquema de tabla de base de datos posible y también para que podamos agregarlo a nuestro servicio de migración en nuestro configure.swift:

importar vapor
hoja de importación
importar FluentSQLite
configuración de funciones públicas (
  _ config: inout Config,
  _ env: inout Environment,
  _ servicios: inout Services
) arroja {
  // Registrar rutas al enrutador
  let router = EngineRouter.default ()
  probar rutas (enrutador)
  services.register (enrutador, como: Router.self)
  let leafProvider = LeafProvider ()
  pruebe services.register (leafProvider)
  pruebe services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  bases de datos var = DatabaseConfig ()
  intente bases de datos.add (base de datos: SQLiteDatabase (almacenamiento: .memory), como: .sqlite)
  services.register (bases de datos)
  var migraciones = MigrationConfig ()
  migrations.add (modelo: User.self, base de datos: .sqlite)
  services.register (migraciones)
}

Si ahora cmd + r o ejecuta todo debería estar bien.

Nota: asegúrese de seleccionar Ejecutar como un esquema junto a su botón antes de ejecutar la aplicación

5. Implemente una ruta GET para enumerar todos los usuarios

Sí, todavía no tenemos usuarios en nuestra base de datos, pero crearemos y almacenaremos usuarios utilizando un formulario que implementaremos. Entonces, por ahora, vaya a routes.swift y elimine todo en ese archivo para que termine así:

importar vapor
hoja de importación
las rutas de funciones públicas (_ router: Router) arrojan {
  // nada aquí
}

Defina una ruta get en la url de los usuarios que busque todos los usuarios de la base de datos y páselos a una vista:

importar vapor
hoja de importación
las rutas de funciones públicas (_ router: Router) arrojan {
  
  router.get ("usuarios") {req -> Futuro  en
    return User.query (on: req) .all (). flatMap {usuarios en
      let data = ["lista de usuarios": usuarios]
      return intenta req.view (). render ("userview", datos)
    }
  }
}

Guau. Están pasando muchas cosas aquí. Pero no te preocupes, es mucho más simple de lo que parece y te sentirás genial una vez que leas más y entiendas esta magia negra

VAPOR 3 se trata de futuros. Y proviene de su naturaleza siendo Async ahora.

6. Explicando Async

Tengamos un ejemplo de vida. En Vapor 2, si su novia le dijo a un niño que le comprara un hielo blando y una rosquilla. Iría al carro de hielo, pediría hielo y esperaría hasta que esté listo. Luego continuaría e iría a una tienda de donas, compraría uno y volvería con su novia con ambos.

Con Vapor 3, ese niño iría al carro de hielo, pediría hielo y, en el momento en que se haga el hielo, iría a la tienda de donas y compraría una dona. Regresa al carro de hielo cuando el hielo está listo, lo toma y vuelve con su novia con ambos.

Vapor 2: la orden de hielo bloqueó al niño para que terminara hasta que pueda continuar.
Vapor 3: Boy trabaja sin bloqueo y usa su tiempo de espera para otras tareas.

7. Lo que el futuro ...

Comprendamos qué está sucediendo y por qué. Estamos utilizando nuestra clase de usuario para consultar la base de datos. Y puede leerlo como ejecutar esa consulta en el reverso de nuestra solicitud. Piense en la solicitud como si fuera el niño. El que hace el trabajo por nosotros. El trabajador.

Bien, entonces no tenemos una variedad de usuarios sino un futuro de una variedad de usuarios: Futuro <[Usuarios]>. Y honestamente Eso es. Y lo que quiero decir con eso es que no hay nada especial o especial. Eso es simplemente eso. Simplemente está "envuelto" por un futuro. Lo único que nos importa es cómo, en nombre de las madres, sacamos nuestros datos del futuro si queremos trabajar con ellos de la forma en que estamos acostumbrados

Ahí es donde entra en juego map o flatMap.

Elegimos mapa si el cuerpo de la llamada devuelve un valor no futuro.

someFuture.map {datos en
  valor de retorno
}

Y llamamos flatMap si el cuerpo devuelve un valor futuro.

someFuture.flatMap {datos en
  return Future 
}

Solo existe esta regla simple. Debido a que necesita devolver algo en cada función de mapa, ese algo es lo que le indica si usa flatMap o map. La regla es: si ese algo es un Futuro, usa flatMap y si son datos "normales", entonces no es un Futuro, usa un mapa.

Entonces, en nuestra ruta, queremos acceder a la matriz de usuarios para pasarla a nuestra vista. Entonces necesitamos una de las dos funciones del mapa. Y dado que devolvemos lo que devuelve la función render (). Y si hacemos cmd + clic en él, podemos ver que es un Future , hemos aprendido: si devolvemos un Future, use flatMap.

Y eso es todo lo que hacemos aquí. No se preocupe si se siente extraño y nuevo y no tan intuitivo. Y no sientes que sabrías cuándo usar qué y cómo. Para eso estoy aquí (con suerte) . Siga los tutoriales y pregúnteme a mí oa la comunidad en Discord todo tipo de preguntas y créanme que hará clic. Honestamente, no estaba seguro de que haría clic para mí hasta que lo hiciera. ¡Dale tiempo !

8. Crear una vista

Dentro de Recursos / Vistas / elimine todos los archivos que encuentre allí (welcome.leaf y whoami.leaf) y cree un nuevo archivo llamado userview.leaf y agregue:



  
     Modelo 
    
  
  
    

Lista de usuarios

    
      
                   
                       
      
    
    #for (usuario en la lista de usuarios) {
      

        # (user.username)       

    }   

He marcado las cosas interesantes aquí. Con la etiqueta solo agrego bootstrap, que es un marco CSS que hace que nuestra vista se vea un poco mejor.

Con la etiqueta

definimos lo que sucederá cuando enviemos el formulario, que está disparando a / users con la publicación del método. Implementaremos esa ruta posterior en el segundo.

El es nuestro campo de entrada para escribir un nuevo nombre de usuario y aquí el name = ”username” es súper importante porque “username” es la clave a la que se conectará nuestro texto. Comprenderá lo que quiero decir cuando escribamos nuestra ruta de publicación.

El bucle #for () es una etiqueta específica de hoja en la que iteramos sobre la lista de usuarios que pasamos anteriormente a la vista como ["lista de usuarios": ...] y con # (user.username) podemos acceder a nuestros objetos de la misma manera que lo hacemos rápidamente.

Si ahora ejecuta cmd + r o ejecuta el proyecto y activa su sitio en / users, verá el encabezado, el campo de entrada y un botón. Y eso está perfectamente bien. La lista de usuarios aparecerá tan pronto como hayamos creado algunos. ¡Hagámoslo !

9. Implemente una ruta POST para almacenar un usuario

En nuestro route.swift agregue el siguiente código:

importar vapor
hoja de importación
las rutas de funciones públicas (_ router: Router) arrojan {
  router.get ("usuarios") {req -> Futuro  en
    ...
  }
  router.post ("usuarios") {req -> Future  en
    volver intente req.content.decode (User.self) .flatMap {usuario en
      return user.save (on: req) .map {_ in
        return req.redirect (a: "usuarios")
      }
    }
  }
}

Cuando enviamos nuestro formulario en la vista presionando el botón Enviar, enviará los datos del campo de entrada codificados en url a la ruta / users como una publicación como:

username = MartinLasek

Dado que nuestro modelo de usuario consta de un solo nombre de usuario de propiedad y se ajusta al contenido del protocolo, podemos decodificar el contenido que envía el formulario en una instancia de nuestro usuario. Puede probar lo que sucede si agrega otra propiedad a nuestra clase de usuario, como la edad de tipo Int, vuelva a ejecutar el proyecto e intente enviar el formulario nuevamente. Le dirá que no pudo encontrar un Int a la edad del camino. Porque nuestro formulario no envía edad = 23 junto con el nombre de usuario.

Sin embargo, dado que la decodificación devuelve el Futuro de una instancia de usuario, tendremos que usar una de las funciones map / flatMap para acceder a ella. Ahora una pequeña nota al margen. Ambas funciones en su conjunto darán lugar a un futuro. No estoy hablando del cuerpo de estas funciones. Estoy hablando de toda la llamada. Es importante porque explica por qué llamamos flatMap en decodificación.

De nuevo, flatMap se usa cuando el cuerpo nos dará un futuro. Y acabamos de enterarnos de que flatMap y map en su conjunto siempre darán lugar a un futuro. Puede ver que devolvemos user.save (on: req) .map {...} ahora ya que este en su conjunto dará como resultado un futuro que sabemos que tenemos que usar flatMap por adelantado.

El siguiente es fácil porque la redirección no está devolviendo un futuro, por eso usamos el mapa por adelantado aquí

Para acceder a un valor futuro, necesita map o flatMap. Si su valor de retorno dentro de una función de mapa no es un futuro, use map; de lo contrario, flatMap.

Si ahora ejecuta cmd + r o ejecuta su proyecto y activa su sitio en su navegador, podrá crear nuevos usuarios y ver una lista creciente al hacerlo

NOTA: Como usamos una base de datos en memoria, todos los datos desaparecerán después de volver a ejecutar

10. A dónde ir desde aquí

Puede encontrar una lista de todos los tutoriales con proyectos de ejemplo en Github aquí:
https://github.com/vaporberlin/vaporschool

Estoy muy feliz de que leas mi artículo! Si tiene alguna sugerencia o mejora de algún tipo, ¡hágamelo saber! ¡Me encantaría saber de ti!

Twitter / Github / Instagram