Introdução
Lucid é a implementação AdonisJS do padrão de registro ativo.
Se você conhece o Laravel ou o Ruby on Rails, encontrará muitas semelhanças entre o Lucid e o Eloquent do Laravel ou o Active Record do Rails.
Introdução
Os modelos de registro ativo geralmente são preferidos em vez de consultas simples de banco de dados por sua facilidade de uso e APIs poderosas para direcionar o fluxo de dados do aplicativo.
Os modelos Lucid fornecem muitos benefícios, incluindo:
- Busca e persistência de dados do modelo de forma transparente.
- Uma API expressiva para gerenciar relacionamentos:js
// .app/Models/User.js class User extends Model { profile () { return this.hasOne('App/Models/Profile') } posts () { return this.hasMany('App/Models/Post') } }
- Ciclo de vida hooks para manter seu código DRY.
- Getters/setters para mutar dados em tempo real.
- Serialização de dados usando serializadores, propriedades computadas, etc.
- Gerenciamento de formato de data.
- ...e muito mais.
NOTA
Os modelos Lucid não estão vinculados ao esquema do seu banco de dados, em vez disso, eles gerenciam tudo por conta própria. Por exemplo, não há necessidade de definir associações em SQL ao usar relacionamentos Lucid.
Modelos Lucid são armazenados no diretório app/Models
, onde cada modelo representa uma tabela de banco de dados.
Exemplos de mapeamentos de modelo/tabela incluem:
Modelo | Tabela de banco de dados |
---|---|
User | users |
Post | posts |
Comment | comments |
Exemplo Básico
Vamos ver como criar um modelo e usá-lo para ler e escrever no banco de dados.
Criando um Modelo
Primeiro, use o comando make:model
para gerar uma classe de modelo User
:
adonis make:model User
# .Output
✔ create app/Models/User.js
// .app/Models/User.js
'use strict'
const Model = use('Model')
class User extends Model {
}
module.exports = User
DICA
Passe o sinalizador --migration
para também gerar um arquivo de migração.
adonis make:model User --migration
# .Output
✔ create app/Models/User.js
✔ create database/migrations/1502691651527_users_schema.js
Criando um usuário
Em seguida, instancie uma instância User
e salve-a no banco de dados:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.password = 'some-password'
await user.save()
Buscando usuários
Finalmente, dentro do arquivo start/routes.js
, busque todas as instâncias User
:
// .start/routes.js
const Route = use('Route')
const User = use('App/Models/User')
Route.get('users', async () => {
return await User.all()
})
Convenção sobre configuração
Modelos Lucid agem com base nas convenções do AdonisJs, mas você tem a liberdade de substituir os padrões por meio das configurações do seu aplicativo.
table
Por padrão, o nome da tabela do banco de dados do modelo é a forma minúscula e plural do nome do modelo (por exemplo, User
→ users
).
Para substituir esse comportamento, defina um getter table
no seu modelo:
class User extends Model {
static get table () {
return 'my_users'
}
}
connection
Por padrão, os modelos usam a conexão padrão definida dentro do arquivo config/database.js
.
Para substituir esse comportamento, defina um getter connection
no seu modelo:
class User extends Model {
static get connection () {
return 'mysql'
}
}
primaryKey
Por padrão, a chave primária de um modelo é definida para a coluna id
.
Para substituir esse comportamento, defina um getter primaryKey
no seu modelo:
class User extends Model {
static get primaryKey () {
return 'uid'
}
}
OBSERVAÇÃO
O valor do campo primaryKey
deve ser sempre exclusivo.
createdAtColumn
O nome do campo usado para definir o carimbo de data/hora creation (retornar null
para desabilitar):
class User extends Model {
static get createdAtColumn () {
return 'created_at'
}
}
updatedAtColumn
O nome do campo usado para definir o carimbo de data/hora modified (retornar null
para desabilitar):
class User extends Model {
static get updatedAtColumn () {
return 'updated_at'
}
}
incrementing
O Lucid assume que cada tabela de banco de dados modelo tem uma chave primária de incremento automático.
Para substituir esse comportamento, defina um getter incrementing
retornando false
:
class User extends Model {
static get incrementing () {
return false
}
}
OBSERVAÇÃO
Quando incrementing
estiver definido como false
, certifique-se de definir o modelo primaryKeyValue
manualmente.
primaryKeyValue
O valor da chave primária (atualizar somente quando incrementing
estiver definido como false
):
const user = await User.find(1)
console.log(user.primaryKeyValue)
// when incrementing is false
user.primaryKeyValue = uuid.v4()
Ocultando campos
Muitas vezes, você precisará omitir campos dos resultados do banco de dados (por exemplo, ocultando senhas de usuários da saída JSON).
O AdonisJs simplifica isso permitindo que você defina atributos hidden
ou visible
em suas classes de modelo.
hidden
class User extends Model {
static get hidden () {
return ['password']
}
}
visible
class Post extends Model {
static get visible () {
return ['title', 'body']
}
}
setVisible/setHidden
Você pode definir campos hidden
ou visible
para uma única consulta como:
User.query().setHidden(['password']).fetch()
// or set visible
User.query().setVisible(['title', 'body']).fetch()
Datas
O gerenciamento de datas pode adicionar complexidade a aplicativos orientados a dados.
Seu aplicativo pode precisar armazenar e exibir datas em formatos diferentes, o que geralmente requer um grau de trabalho manual.
Lucid lida com datas graciosamente, minimizando o trabalho necessário para usá-las.
Definindo campos de data
Por padrão, os timestamps created_at
e updated_at
são marcados como datas.
Defina seus próprios campos concatenando-os em um getter dates
em seu modelo:
class User extends Model {
static get dates () {
return super.dates.concat(['dob'])
}
}
No exemplo acima, extraímos os campos de data padrão da classe pai Model
e enviamos um novo campo dob
para o array super.dates
, retornando todos os três campos de data: created_at
, updated_at
e dob
.
Formatando campos de data
Por padrão, o Lucid formata datas para armazenamento como AAAA-MM-DD HH:mm:ss
.
Para personalizar formatos de data para armazenamento, substitua o método formatDates
:
class User extends Model {
static formatDates (field, value) {
if (field === 'dob') {
return value.format('YYYY-MM-DD')
}
return super.formatDates(field, value)
}
}
No exemplo acima, o parâmetro value
é a data real fornecida ao definir o campo.
OBSERVAÇÃO
O método formatDates
é chamado antes que a instância do modelo seja salva no banco de dados, portanto, certifique-se de que o valor de retorno seja sempre um formato válido para o mecanismo de banco de dados que você está usando.
Datas de conversão
Agora que salvamos as datas no banco de dados, podemos formatá-las de forma diferente ao exibi-las ao usuário.
Para formatar como as datas são exibidas, use o método castDates
:
class User extends Model {
static castDates (field, value) {
if (field === 'dob') {
return `${value.fromNow(true)} old`
}
return super.formatDates(field, value)
}
}
O parâmetro value
é uma instância Moment.js, permitindo que você chame qualquer método Moment para formatar suas datas.
Desserialização
O método castDates
é chamado automaticamente quando uma instância de modelo é desserializada (acionado pela chamada toJSON
):
const users = await User.all()
// converting to JSON array
const usersJSON = users.toJSON()
Query Builder
Os modelos Lucid usam o AdonisJs Query Builder para executar consultas de banco de dados.
Para obter uma instância do Query Builder, chame o método query
do modelo:
const User = use('App/Models/User')
const adults = await User
.query()
.where('age', '>', 18)
.fetch()
- Todos os métodos do Query Builder são totalmente suportados.
- O método
fetch
é necessário para executar a consulta, garantindo que os resultados retornem dentro de uma instânciaserializer
(consulte a documentação Serializers para obter mais informações).
Métodos estáticos
Os modelos Lucid têm vários métodos estáticos para executar operações comuns sem usar a interface do Query Builder.
Não há necessidade de chamar fetch
ao usar os seguintes métodos estáticos.
find
Encontre um registro usando a chave primária (sempre retorna um registro):
const User = use('App/Models/User')
await User.find(1)
findOrFail
Semelhante a find
, mas em vez disso lança uma ModelNotFoundException
quando não consegue encontrar um registro:
const User = use('App/Models/User')
await User.findOrFail(1)
findBy / findByOrFail
Encontre um registro usando um par chave/valor (retorna o primeiro registro correspondente):
const User = use('App/Models/User')
await User.findBy('email', 'foo@bar.com')
// or
await User.findByOrFail('email', 'foo@bar.com')
first / firstOrFail
Encontre a primeira linha do banco de dados:
const User = use('App/Models/User')
await User.first()
// or
await User.firstOrFail()
last
Encontre a última linha do banco de dados:
const User = use('App/Models/User')
await User.last()
findOrCreate(whereAttributes, values)
Encontre um registro, se não for encontrado, um novo registro será criado e retornado:
const User = use('App/Models/User')
const user = await User.findOrCreate(
{ username: 'virk' },
{ username: 'virk', email: 'virk@adonisjs.com' }
)
pick(rows = 1)
Selecione x
número de linhas da tabela do banco de dados (o padrão é 1
linha):
const User = use('App/Models/User')
await User.pick(3)
pickInverse(rows = 1)
Selecione x
número de linhas da tabela do banco de dados a partir do último (o padrão é 1
linha):
const User = use('App/Models/User')
await User.pickInverse(3)
ids
Retorne uma matriz de chaves primárias:
const User = use('App/Models/User')
const userIds = await User.ids()
NOTA
Se a chave primária for uid
, uma matriz de valores uid
será retornada.
pair(lhs, rhs)
Retorna um objeto de pares chave/valor (lhs
é a chave, rhs
é o valor):
const User = use('App/Models/User')
const users = await User.pair('id', 'country')
// returns { 1: 'ind', 2: 'uk' }
all
Selecionar todas as linhas:
const User = use('App/Models/User')
const users = await User.all()
truncate
Excluir todas as linhas (truncar tabela):
const User = use('App/Models/User')
const users = await User.truncate()
Métodos de instância
As instâncias do Lucid têm vários métodos para executar operações comuns sem usar a interface do Query Builder.
reload
Recarregar um modelo do banco de dados:
const User = use('App/Models/User')
const user = await User.create(props)
// user.serviceToken === undefined
await user.reload()
// user.serviceToken === 'E1Fbl3sjH'
OBSERVAÇÃO
Um modelo com propriedades definidas durante um gancho de criação exigirá recarregamento para recuperar os valores definidos durante esse gancho.
Auxiliares de Agregação
Query Builder auxiliares de agregação fornece atalhos para consultas de agregação comuns.
Os seguintes métodos de modelo estático podem ser usados para agregar uma tabela inteira.
OBSERVAÇÃO
Esses métodos encerram a cadeia do Query Builder e retornam um valor, portanto, não há necessidade de chamar [fetch()](/docs/07-Database/02-Query-Builder.md#aggregate-helpers)
ao usá-los.
getCount(columnName = '*')
Retorna uma contagem de registros em um determinado conjunto de resultados:
const User = use('App/Models/User')
// returns number
await User.getCount()
Você pode adicionar restrições de consulta antes de chamar getCount
:
await User
.query()
.where('is_active', 1)
.getCount()
Assim como getCount
, todos os outros métodos de agregação estão disponíveis no Query Builder.
Escopos de consulta
Os escopos de consulta extraem restrições de consulta em métodos poderosos e reutilizáveis.
Por exemplo, buscando todos os usuários que têm um perfil:
const Model = use('Model')
class User extends Model {
static scopeHasProfile (query) {
return query.has('profile')
}
profile () {
return this.hasOne('App/Models/Profile')
}
}
Ao definir scopeHasProfile
, você pode restringir sua consulta assim:
const users = await User.query().hasProfile().fetch()
- Os escopos são definidos com o prefixo
scope
seguido pelo nome do método. - Ao chamar escopos, descarte a palavra-chave
scope
e chame o método no formato camelCase (por exemplo,scopeHasProfile
→hasProfile
). - Você pode chamar todos os métodos padrão do construtor de consultas dentro de um escopo de consulta.
Paginação
O Lucid também suporta o método paginate
do Construtor de Consultas:
const User = use('App/Models/User')
const page = 1
const users = await User.query().paginate(page)
return view.render('users', { users: users.toJSON() })
No exemplo acima, o valor de retorno de paginate
não é uma matriz de usuários, mas sim um objeto com metadados e uma propriedade data
contendo a lista de usuários:
{
total: '',
perPage: '',
lastPage: '',
page: '',
data: [{...}]
}
Inserções e atualizações
save
Com modelos, em vez de inserir valores brutos no banco de dados, você persiste a instância do modelo que, por sua vez, faz a consulta de inserção para você. Por exemplo:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.email = 'foo@bar.com'
await user.save()
O método save
persiste a instância no banco de dados, determinando de forma inteligente se deve criar uma nova linha ou atualizar a linha existente. Por exemplo:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.email = 'foo@bar.com'
// Insert
await user.save()
user.age = 22
// Update
await user.save()
Uma consulta update só ocorre se algo foi alterado.
Chamar save
várias vezes sem atualizar nenhum atributo do modelo não executará nenhuma consulta subsequente.
fill / merge
Em vez de definir atributos manualmente, fill
ou merge
podem ser usados.
O método fill
substitui os valores de chave/par de instância de modelo existentes:
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.age = 22
user.fill({ age: 23 }) // remove existing values, only set age.
await user.save()
// returns { age: 23, username: null }
O método merge
modifica apenas os atributos especificados:
const User = use('App/Models/User')
const user = new User()
user.fill({ username: 'virk', age: 22 })
user.merge({ age: 23 })
await user.save()
// returns { age: 23, username: 'virk' }
create
Você pode passar dados diretamente para o modelo na criação, em vez de definir atributos manualmente após a instanciação:
const User = use('App/Models/User')
const userData = request.only(['username', 'email', 'age'])
// save and get instance back
const user = await User.create(userData)
createMany
Assim como create
, você pode passar dados diretamente para várias instâncias na criação:
const User = use('App/Models/User')
const usersData = request.collect(['username' 'email', 'age'])
const users = await User.createMany(usersData)
OBSERVAÇÃO
O método createMany
faz n número de consultas em vez de fazer uma inserção em massa, onde n é o número de linhas.
Atualizações em massa
Atualizações em massa são realizadas com a ajuda do Query Builder (o Lucid garante que as datas sejam formatadas adequadamente ao atualizar):
const User = use('App/Models/User')
await User
.query()
.where('username', 'virk')
.update({ role: 'admin' })
OBSERVAÇÃO
Atualizações em massa não executam ganchos de modelo.
Exclusões
Uma única instância de modelo pode ser excluída chamando o método delete
:
const User = use('App/Models/User')
const { id } = params
const user = await User.find(id)
await user.delete()
Após chamar delete
, a instância de modelo é proibida de executar qualquer atualização, mas você ainda pode acessar seus dados:
await user.delete()
console.log(user.id) // works fine
user.id = 1 // throws exception
Exclusões em massa
Exclusões em massa são realizadas com a ajuda do Query Builder:
const User = use('App/Models/User')
await User
.query()
.where('role', 'guest')
.delete()
OBSERVAÇÃO
Exclusões em massa não executam ganchos de modelo.
Transações
A maioria dos métodos Lucid oferece suporte a transações.
O primeiro passo é obter o objeto trx
usando o Database Provider:
const Database = use('Database')
const trx = await Database.beginTransaction()
const user = new User()
// pass the trx object and lucid will use it
await user.save(trx)
// once done commit the transaction
trx.commit()
Assim como chamar save
, você pode passar o objeto trx
para o método create
também:
const Database = use('Database')
const trx = await Database.beginTransaction()
await User.create({ username: 'virk' }, trx)
// once done commit the transaction
await trx.commit()
// or rollback the transaction
await trx.rollback()
Você também pode passar o objeto trx
para o método createMany
:
const user = await User.find(1, trx)
const user = await User.query(trx).where('username','virk').first()
await User.createMany([
{ username: 'virk' }
], trx)
Você também pode passar o objeto trx
para o método delete
:
const user = await User.find(1, trx)
await user.delete(trx)
Transações em relacionamentos
Ao usar transações, você precisará passar um Objeto trx
como o terceiro parâmetro dos métodos de relacionamento attach
e detach
:
const Database = use('Database')
const trx = await Database.beginTransaction()
const user = await User.create({email: 'user@example.com', password: 'secret'})
const userRole = await Role.find(1)
await user.roles().attach([userRole.id], null, trx)
await userRole.load('user', null, trx)
await trx.commit()
// if something gone wrong
await trx.rollback()
Ciclo de inicialização
Cada modelo tem um ciclo de inicialização onde seu método boot
é chamado uma vez.
Se você quiser executar algo que deve ocorrer apenas uma vez, considere escrevê-lo dentro do método boot
do modelo:
const Model = use('Model')
class User extends Model {
static boot () {
super.boot()
/**
I will be called only once
*/
}
}
module.exports = User