Tutorial: Como escrever modelos usando Fluent

Este tutorial mostrará como implementar um modelo de usuário simples, armazená-lo no banco de dados, recuperá-lo e transmiti-lo à visualização.

Você pode encontrar o resultado deste tutorial no github aqui

Este tutorial é um acompanhamento natural de Como usar o Leaf. Você pode seguir esse tutorial primeiro e voltar mais tarde ou ser um rebelde, pular e ler

Índice

1. Crie um novo projeto
2. Gere o projeto Xcode
3. Configure seu projeto para usar o banco de dados SQLite
4. Crie seu primeiro modelo
5. Implemente uma rota GET para listar todos os usuários
6. Explicando o Async
7. Qual é a função
8. Crie uma visualização
9. Implemente uma rota POST para armazenar um usuário
10. Para onde ir a partir daqui

1. Crie um novo projeto

Usaremos o resultado do tutorial mencionado acima como modelo para criar nosso novo projeto:

vapor new projectName --template = vaporberlin / my-first-leaf-template

2. Gere o projeto Xcode

Antes de gerar um projeto Xcode, teríamos que adicionar um provedor de banco de dados. Existem cada um para cada banco de dados. Mas, para se aquecer com o ORM Fluent, usaremos um banco de dados em memória. Portanto, adicionamos o Fluent-SQLite como uma dependência em nosso Package.swift:

// swift-tools-versão: 4.0
import PackageDescription
let package = Package (
  nome: "projectName", // alterado
  dependências: [
    .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") // adicionado
  ],
  metas: [
    .target (nome: "App", dependências: ["Vapor", "Leaf", "FluentSQLite"]), // adicionado
    .target (nome: "Executar", dependências: ["App"]),
    .testTarget (nome: "AppTests", dependências: ["App"]),
  ]
)

Agora no terminal no diretório raiz projectName / execute:

atualização de vapor -y

Pode demorar um pouco para buscar a dependência, gerar o projeto Xcode e abri-lo para você. Quando terminar, você deve ter uma estrutura de projeto como esta:

Nome do Projeto/
Package── Package.swift
├── Fontes /
App ├── App /
│ │ ├── app.swift
Boot │ ├── boot.swift
Configure │ ├── configure.swift
│ │ └── routes.swift
Run └── Executar /
│ └── main.swift
Tests── Testes /
Resources── Recursos /
Public── Público /
├── Dependências /
Products── Produtos /
Se você vir um erro incluindo "CNIOOpenSSL" quando cmd + r, está faltando uma dependência. Basta executar o vapor de atualização de fermentação e gerar novamente o projeto

3. Configure seu projeto para usar um banco de dados SQLite

Nossa primeira etapa é adicionar o FluentSQLiteProvider em nosso configure.swift:

importar Vapor
folha de importação
import FluentSQLite // adicionado
função pública configure (
  _ config: inout Config,
  _ env: inout Meio Ambiente,
  _ serviços: inout Serviços
) lança {
  // Registrar rotas para o roteador
  deixe roteador = EngineRouter.default ()
  tente rotas (roteador)
  services.register (roteador, como: Router.self)
  deixe leafProvider = LeafProvider ()
  tente services.register (leafProvider)
  tente services.register (FluentSQLiteProvider ()) // adicionado
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
}

Em seguida, iniciaremos um serviço de banco de dados, adicionaremos um SQLiteDatabase a ele e registraremos esse serviço:

importar Vapor
folha de importação
importar FluentSQLite
função pública configure (
  _ config: inout Config,
  _ env: inout Meio Ambiente,
  _ serviços: inout Serviços
) lança {
  // Registrar rotas para o roteador
  deixe roteador = EngineRouter.default ()
  tente rotas (roteador)
  services.register (roteador, como: Router.self)
  deixe leafProvider = LeafProvider ()
  tente services.register (leafProvider)
  tente services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  var database = DatabasesConfig ()
  tente database.add (banco de dados: SQLiteDatabase (storage: .memory), como: .sqlite)
  services.register (bancos de dados)
}

Finalmente, iniciamos e registramos um serviço de migração que usaremos posteriormente para apresentar nosso modelo ao nosso banco de dados. Por enquanto, adicione o seguinte:

importar Vapor
folha de importação
importar FluentSQLite
função pública configure (
  _ config: inout Config,
  _ env: inout Meio Ambiente,
  _ serviços: inout Serviços
) lança {
  // Registrar rotas para o roteador
  deixe roteador = EngineRouter.default ()
  tente rotas (roteador)
  services.register (roteador, como: Router.self)
  deixe leafProvider = LeafProvider ()
  tente services.register (leafProvider)
  tente services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  bancos de dados var = DatabaseConfig ()
  tente database.add (banco de dados: SQLiteDatabase (storage: .memory), como: .sqlite)
  services.register (bancos de dados)
  var migrations = MigrationConfig ()
  services.register (migrações)
}

4. Crie seu primeiro modelo

Crie um diretório em Sources / App / e denomine Models / e, dentro desse novo diretório, crie um novo arquivo rápido chamado User.swift

NOTA: usei o terminal executando mkdir Sources / App / Models / e toque em Sources / App / Models / User.swift

Pode ser necessário gerar novamente o seu projeto do Xcode com vapor xcode -y para permitir que o Xcode veja seu novo diretório.

Em Models / User.swift, inclua o seguinte código:

importar FluentSQLite
importar Vapor
usuário de classe final: SQLiteModel {
  ID da var: Int?
  nome de usuário var: String
  init (id: Int? = nulo, nome de usuário: String) {
    self.id = id
    self.username = nome de usuário
  }
}
extensão Usuário: Conteúdo {}
usuário da extensão: migração {}

Mantive tudo super simples para que possamos entender o que está acontecendo aqui. Em conformidade com SQLiteModel, temos que definir uma variável opcional denominada id, do tipo int. É opcional simplesmente porque, se iniciarmos um novo usuário para armazená-lo no banco de dados, não cabe a nós fornecer a ele um ID nesse momento. Ele receberá um ID atribuído após armazená-lo no banco de dados.

A conformidade com o Conteúdo possibilita que nosso Usuário possa converter em, por exemplo, JSON usando Codable, se o devolvermos em uma rota. Ou então, ele pode converter em TemplateData, que é usado dentro de uma visualização Leaf. E devido ao Codable, isso acontece automaticamente. A conformidade com a migração é necessária para que o Fluent possa usar o Codable para criar o melhor esquema de tabela de banco de dados possível e também para adicioná-lo ao nosso serviço de migração em nosso configure.swift:

importar Vapor
folha de importação
importar FluentSQLite
função pública configure (
  _ config: inout Config,
  _ env: inout Meio Ambiente,
  _ serviços: inout Serviços
) lança {
  // Registrar rotas para o roteador
  deixe roteador = EngineRouter.default ()
  tente rotas (roteador)
  services.register (roteador, como: Router.self)
  deixe leafProvider = LeafProvider ()
  tente services.register (leafProvider)
  tente services.register (FluentSQLiteProvider ())
  config.prefer (LeafRenderer.self, para: ViewRenderer.self)
  bancos de dados var = DatabaseConfig ()
  tente database.add (banco de dados: SQLiteDatabase (storage: .memory), como: .sqlite)
  services.register (bancos de dados)
  var migrations = MigrationConfig ()
  migrations.add (modelo: User.self, banco de dados: .sqlite)
  services.register (migrações)
}

Se você agora cmd + r ou executar, tudo deve ficar bem.

Nota: certifique-se de selecionar Executar como um esquema ao lado do botão antes de executar o aplicativo

5. Implemente uma rota GET para listar todos os usuários

Sim, ainda não temos usuários em nosso banco de dados, mas criaremos e armazenaremos usuários usando um formulário que implementaremos por conta própria. Então, por enquanto, vá para routes.swift e exclua tudo nesse arquivo para que fique assim:

importar Vapor
folha de importação
rotas de função pública (_ roteador: roteador) lança {
  // nada aqui
}

Defina uma rota get nos usuários da URL que buscam todos os usuários do banco de dados e passe-os para uma exibição:

importar Vapor
folha de importação
rotas de função pública (_ roteador: roteador) lança {
  
  router.get ("users") {req -> Future  in
    retorne User.query (em: req) .all (). flatMap {users in
      deixe dados = ["lista de usuários": usuários]
      retornar tente req.view (). render ("userview", data)
    }
  }
}

Uau. Há muita coisa acontecendo aqui. Mas não se preocupe, é muito mais simples do que parece e você se sentirá bem depois de ler mais e entender essa magia negra

O VAPOR 3 é sobre Futuros. E vem de sua natureza ser Async agora.

6. Explicando o Async

Vamos dar um exemplo de vida. No Vapor 2, se a namorada dissesse a um garoto que lhe comprasse um sorvete e um donut. Ele ia ao vagão de gelo, pedia gelo e esperava até que estivesse pronto. Então ele continuava e ia a uma loja de donuts, comprava uma e voltava para a namorada com as duas.

Com o Vapor 3, aquele garoto iria ao vagão de gelo, pedia gelo e, no momento em que o gelo era produzido, ele ia à loja de donuts e comprava um donut. Ele volta para a carroça de gelo quando o gelo está pronto, pega e volta para a namorada com os dois.

Vapor 2: O garoto foi bloqueado pela ordem do gelo para terminar até que ele possa prosseguir.
Vapor 3: Boy trabalha sem bloqueio e usa seu tempo de espera para outras tarefas.

7. Qual é a função

Vamos entender o que está acontecendo e o porquê. Estamos usando nossa classe User para consultar o banco de dados. E você pode lê-lo como executar essa consulta na parte de trás da nossa solicitação. Pense no pedido como sendo o garoto. Aquele que faz o trabalho para nós. O trabalhador.

Ok, então não temos uma matriz de usuários, mas o futuro de uma matriz de usuários: Futuro <[Usuários]>. E honestamente. É isso aí. E o que quero dizer com isso é: não há nada de especial ou especial nisso. É simplesmente isso. É apenas "embrulhado" por um futuro. A única coisa com a qual nos preocupamos é como, em nome das mães, extraímos nossos dados do futuro, se queremos trabalhar com eles da maneira como estamos acostumados

É aí que o mapa ou flatMap entra em jogo.

Escolhemos o mapa se o corpo da chamada retornar um valor não futuro.

someFuture.map {dados em
  valor de retorno
}

E chamamos flatMap se o corpo retornar um valor futuro.

someFuture.flatMap {dados em
  retornar Futuro 
}

Existe apenas esta regra simples. Porque, como você precisa retornar algo em cada função de mapa, é isso que indica se você usa o flatMap ou o mapa. A regra é: se algo é um Futuro, você usa o flatMap e se são dados "normais", portanto, não um Futuro, você usa o mapa.

Portanto, em nossa rota, queremos acessar a matriz de usuários para transmiti-la à nossa visão. Então, precisamos de uma das duas funções do mapa. E já que retornamos o que a função render () retorna. E se cmd + clicarmos nele, podemos ver que é um futuro, aprendemos: se retornarmos um futuro, use flatMap.

E é tudo o que fazemos aqui. Não se preocupe se parecer estranho e novo e não tão intuitivo. E você não acha que saberá quando usar o que e como. É para isso que estou aqui (espero) . Siga os tutoriais e pergunte a mim ou à comunidade no Discord todos os tipos de perguntas e acredite em mim! Honestamente, eu não tinha certeza de que ele clicaria em mim até isso acontecer. Dê um tempo !

8. Crie uma visualização

Em Recursos / Visualizações / exclua todos os arquivos que você encontrar lá (welcome.leaf e whoami.leaf) e crie um novo arquivo chamado userview.leaf e adicione:



  
     Modelo 
    
  
  
    

Lista de usuários

    
      
                   
                       
      
    
    #for (usuário na lista de usuários) {
      

        # (user.username)       

    }   

Eu marquei as coisas interessantes aqui. Com a tag , apenas adiciono o bootstrap, que é uma estrutura css que torna nossa visão um pouco melhor.

Com a tag

, definimos o que acontecerá quando enviarmos o formulário, que está disparando em / users com a postagem do método. Implementaremos essa rota pós em segundo.

O é o nosso campo de entrada para escrever um novo nome de usuário e aqui o nome = "nome de usuário" é super importante porque "nome de usuário" é a chave à qual nosso texto será conectado. Você entenderá o que quero dizer quando escrevermos nossa rota de postagem.

O loop #for () é uma tag específica da folha, na qual iteramos a lista de usuários que passamos anteriormente para a visualização como [“userlist”:…] e com # (user.username) podemos acessar nossos objetos da mesma maneira que rapidamente.

Agora, se você cmd + r ou executar o projeto e inicializar seu site em / users, verá o cabeçalho, o campo de entrada e um botão. E isso é perfeitamente bom. A lista de usuários aparecerá assim que criarmos alguns. Vamos lá!

9. Implemente uma rota POST para armazenar um usuário

Em nosso routes.swift, adicione o seguinte código:

importar Vapor
folha de importação
rotas de função pública (_ roteador: roteador) lança {
  router.get ("users") {req -> Future  in
    ...
  }
  router.post ("users") {req -> Future  in
    retornar tente req.content.decode (User.self) .flatMap {user in
      retornar user.save (em: req) .map {_ in
        retornar req.redirect (para: "usuários")
      }
    }
  }
}

Quando enviamos nosso formulário na visualização pressionando o botão Enviar, ele envia os dados do campo de entrada codificados por URL para a rota / users como uma postagem como:

nome de usuário = MartinLasek

Como nosso modelo de usuário consiste em apenas um nome de usuário de propriedade e está em conformidade com o protocolo Content, podemos decodificar o conteúdo enviado pelo formulário em uma instância de nosso usuário. Você pode tentar o que acontece se adicionar outra propriedade à nossa classe User, como idade do tipo Int, executar novamente o projeto e tentar enviar o formulário novamente. Ele informará que não foi possível encontrar um Int na idade do caminho. Como nosso formulário não envia idade = 23 ao lado do nome de usuário.

No entanto, como decodificação retorna um futuro de uma instância de usuário, teremos que usar uma das funções map / flatMap para acessá-la. Agora uma pequena nota lateral. Ambas as funções como um todo resultarão em um futuro. Eu não estou falando sobre o corpo dessas funções. Eu estou falando sobre toda a ligação. É importante porque explica por que chamamos flatMap na decodificação.

Novamente, o flatMap é usado quando o corpo nos fornece um futuro. E acabamos de aprender que flatMap e mapear como um todo sempre resultarão em um futuro. Você pode ver que retornamos user.save (on: req) .map {…} agora, já que este como um todo resultará em um futuro que sabemos que precisamos usar o flatMap antecipadamente.

O próximo é fácil, porque o redirecionamento não está retornando um futuro, por isso usamos o mapa aqui antecipadamente.

Para acessar um valor futuro, você precisa de map ou flatMap. Se o seu valor de retorno dentro de uma função de mapa não for futuro, use map, caso contrário flatMap

Agora, se você cmd + r ou executar seu projeto e iniciar o site no navegador, poderá criar novos usuários e ver uma lista crescente ao fazê-lo

NOTA: Como usamos um banco de dados na memória, todos os dados desaparecem após a reexecução-

10. Para onde ir a partir daqui

Você pode encontrar uma lista de todos os tutoriais com exemplos de projetos no Github aqui:
https://github.com/vaporberlin/vaporschool

Estou muito feliz que você leu meu artigo! Se você tiver alguma sugestão ou melhoria de qualquer tipo, me avise! Eu adoraria ouvir você!

Twitter / Github / Instagram