Route Model Binding
O AdonisJS fornece um recurso poderoso de route model binding, que permite que você vincule os parâmetros de rota com modelos Lucid e consulte automaticamente o banco de dados.
O pacote deve ser instalado e configurado separadamente. Você pode instalá-lo executando o seguinte comando.
npm i @adonisjs/route-model-binding@1.0.1
node ace configure @adonisjs/route-model-binding
# UPDATE: tsconfig.json { types += "@adonisjs/route-model-binding/build/adonis-typings" }
# UPDATE: .adonisrc.json { providers += "@adonisjs/route-model-binding/build/providers/RmbProvider" }
/**
* Certifique-se de adicionar o seguinte middleware global dentro
* do arquivo start/kernel.ts
*/
Server.middleware.register([
// ...other middleware
() => import('@ioc:Adonis/Addons/RmbMiddleware'),
])
- Funciona com todos os drivers de banco de dados
- Lógica de pesquisa personalizável
Exemplo
A vinculação do modelo de rota é uma maneira bacana de remover consultas Lucid de uma linha da sua base de código e usar convenções para consultar o banco de dados durante solicitações HTTP.
No exemplo a seguir, conectamos os parâmetros de rota :post
e :comments
com os argumentos aceitos pelo método show
.
- O valor do primeiro parâmetro da URL será usado para consultar o primeiro modelo tipado no método
show
(por exemplo, Post). - Da mesma forma, o valor do segundo parâmetro será usado para consultar o segundo modelo tipado (por exemplo, Comment).
NOTA
Os parâmetros e modelos são conectados usando a ordem em que aparecem e não o nome. Isso ocorre porque os decoradores TypeScript não podem saber os nomes dos argumentos aceitos por um método.
// Arquivo de rotas
Route.get('posts/:post/comments/:comment', 'PostsController.show')
// Controlador
import { bind } from '@adonisjs/route-model-binding'
import Post from 'App/Models/Post'
import Comment from 'App/Models/Comment'
export default class PostsController {
@bind()
public async show({}, post: Post, comment: Comment) {
return { post, comment }
}
}
NOTA
Você é um aprendiz visual? Confira estes screencasts para aprender sobre a vinculação do modelo de rota, sua configuração e uso.
Uso básico
Comece com o exemplo mais básico e ajuste o nível de complexidade para atender a diferentes casos de uso.
No exemplo a seguir, vincularemos o modelo Post
ao primeiro parâmetro na rota posts/:id
.
Route.get('/posts/:id', 'PostsController.show')
import { bind } from '@adonisjs/route-model-binding'
import Post from 'App/Models/Post'
export default class PostsController {
@bind()
public async show({}, post: Post) {
return { post }
}
}
::: aviso NOTA Certifique-se de sempre importar seu modelo com um import
e não um import type
. Caso contrário, o decorador bind
não conseguirá recuperar a classe do seu modelo e não funcionará.
Usuário ESLint? - Dê uma olhada aqui :::
Os parâmetros e modelos são correspondidos na ordem em que são definidos. Portanto, o primeiro parâmetro na URL corresponde ao primeiro modelo com sugestão de tipo no método do controlador.
A correspondência não é realizada usando o nome do argumento do método do controlador porque os decoradores TypeScript não podem lê-los (então a limitação técnica nos deixa apenas com a correspondência baseada na ordem).
Alterando a chave de pesquisa
Por padrão, a chave primária do modelo é usada para encontrar uma linha correspondente no banco de dados. Você pode alterá-la globalmente ou para apenas uma rota específica.
Alterar chave de pesquisa globalmente via modelo
Após a alteração a seguir, a postagem será consultada usando a propriedade slug
, não a chave primária. Em poucas palavras, a consulta Post.findByOrFail('slug', value)
é executada.
class Post extends BaseModel {
public static routeLookupKey = 'slug'
}
Alterar a chave de pesquisa para uma única rota.
O exemplo a seguir define a chave de pesquisa diretamente na rota entre parênteses.
Route.get('/posts/:id(slug)', 'PostsController.show')
Você notou que nossa rota agora parece um pouco estranha?
O parâmetro é escrito como :id(slug)
, o que não traduz bem. Portanto, com a vinculação do modelo de rota, recomendamos usar o nome do modelo como o parâmetro de rota porque não estamos mais lidando com o id
. Em vez disso, estamos buscando instâncias de modelo do banco de dados.
A seguir está a melhor maneira de escrever a mesma rota.
Route.get('/posts/:post(slug)', 'PostsController.show')
Alterar lógica de pesquisa
Você pode alterar a lógica de pesquisa definindo um método estático findForRequest
no próprio modelo. O método recebe os seguintes parâmetros.
ctx
- O contexto HTTP para a solicitação atualparam
- O parâmetro analisado. O parâmetro tem as seguintes propriedades.param.name
- O nome normalizado do parâmetro.param.param
- O nome original do parâmetro definido na rota.param.scoped
- Setrue
, o parâmetro deve ter como escopo seu modelo pai.param.lookupKey
- A chave de pesquisa definida na rota ou no modelo.param.parent
- O nome do parâmetro pai.value
- O valor do parâmetro durante a solicitação atual.
No exemplo a seguir, consultamos apenas postagens publicadas. Além disso, garanta que esse método retorne uma instância do modelo ou gere uma exceção.
class Post extends BaseModel {
public static findForRequest(ctx, param, value) {
const lookupKey = param.lookupKey === '$primaryKey' ? 'id' : param.lookupKey
return this
.query()
.where(lookupKey, value)
.whereNotNull('publishedAt')
.firstOrFail()
}
}
Parâmetros com escopo
Ao trabalhar com recursos de rota aninhados, você pode querer definir o escopo do segundo parâmetro como um relacionamento com o primeiro parâmetro.
Um ótimo exemplo é encontrar um comentário de postagem por id e garantir que ele seja um filho da postagem mencionada dentro do mesmo URL.
O posts/1/comments/2
deve retornar 404 se o id da postagem do comentário não for 1
.
Você pode definir parâmetros de escopo usando o >
maior que um sinal ou conhecido como o sinal de breadcrumb
Route.get('/posts/:post/comments/:>comment', 'PostsController.show')
import { bind } from '@adonisjs/route-model-binding'
import Post from 'App/Models/Post'
import Comment from 'App/Models/Comment'
export default class PostsController {
@bind()
public async show({}, post: Post, comment: Comment) {
return { post, comment }
}
}
Para que o exemplo acima funcione, você deve definir os comentários
como um relacionamento no modelo Post
. O tipo de relacionamento não importa.
class Post extends BaseModel {
@hasMany(() => Comment)
public comments: HasMany<typeof Comment>
}
O nome do relacionamento é pesquisado, convertendo o nome do parâmetro para camelCase
. Usaremos as formas plural e singular para encontrar o relacionamento.
Personalizando a pesquisa de relacionamento
Por padrão, o relacionamento é buscado usando a chave de pesquisa do modelo filho vinculado. Efetivamente, a consulta a seguir é executada.
await parent
.related('relationship')
.query()
.where(lookupKey, value)
.firstOrFail()
No entanto, você pode personalizar a pesquisa definindo o método findRelatedForRequest
no modelo (observe que este não é um método estático).
class Post extends BaseModel {
public findRelatedForRequest(ctx, param, value) {
/**
* Tenho que fazer essa dança estranha por causa disso:
* https://github.com/microsoft/TypeScript/issues/37778
*/
const self = this as unknown as Post
const lookupKey = param.lookupKey === '$primaryKey' ? 'id' : param.lookupKey
if (param.name === 'comment') {
return self
.related('comments')
.query()
.where(lookupKey, value)
.firstOrFail()
}
}
}
Parâmetros não vinculados
Você frequentemente terá parâmetros que são valores brutos e não podem ser vinculados a um modelo. No exemplo a seguir, version
é um valor de string regular e não é respaldado usando o banco de dados.
Route.get(
'/api/:version/posts/:post',
'PostsController.show'
)
Você pode representar version
como uma string no método do controlador, e não realizaremos nenhuma pesquisa no banco de dados. Por exemplo:
import { bind } from '@adonisjs/route-model-binding'
import Post from 'App/Models/Post'
class PostsController {
@bind()
public async show({}, version: string, post: Post) {}
}
Como os parâmetros de rota e os argumentos do método do controlador são correspondidos na mesma ordem em que são definidos, você sempre terá que dar type-hint em todos os parâmetros.
Compatibilidade com ESLint
Se você usar a regra @typescript-eslint/consistent-type-imports
, você notará que ela substituirá automaticamente seu import
por import type
. Infelizmente, isso acabará quebrando a vinculação do modelo de rota, pois os tipos serão removidos em tempo de execução, então o decorador bind
não pode recuperar a classe do seu modelo.
Você precisará habilitar o linting com reconhecimento de tipo. Você pode seguir este documento: