Thứ Ba, 13 tháng 4, 2021

Lucid ORM trong AdonisJS




Lucid là các thực thi của AdonisJS của active record pattern
(ghi nhận các hoạt động của bản ghi theo các mẫu)

`Active record models`(ghi nhận các hoạt động của bản ghi theo các models)
được yêu thích hơn việc ghi nhận các bản ghi thông qua
các queries thuần. (insert into ..., update .. set...)

`Lucid models` cung cấp rất nhiều tiện ích:
1. Lấy dữ liệu và ghi dữ liệu dễ nhìn hơn
2. Quản lý các quan hệ giữa các bảng dễ dàng hơn
File app/Models/User.js
<!-->
class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}

posts () {
return this.hasMany('App/Models/Post')
}
}
<-->

3. Lifecycle hooks để giữ code sạch đẹp DRY(Don't repeat yourself).
4. Getters/setters giúp code rõ ràng hơn.
5. Data serialization: https://adonisjs.com/docs/4.1/serializers
6. Date format: https://adonisjs.com/docs/4.1/lucid#_dates

Lucid models được lưu ở folder `app/Models`.


## Basic Use
1. Tạo một Model
> adonis make:model User
==> Output: app/Models/User.js

- Thêm tham số --migration để đồng thời tạo ra migration file
> adonis make:model User --migration

2. Tạo mới 1 user
<!-->
const User = use('App/Models/User')
const user = new User()
user.username = 'virk'
user.password = 'some-password'
await user.save()
<-->

3. Lấy danh sách users
> const User = use('App/Models/User')
> var users = await User.all()


## Một số quy ước
Có một số quy ước được thiết lập sẵn, nhưng cũng có thể chỉnh sửa dễ dàng.

1. `table name`
Tương ứng với một model thì table name sẽ được viết thường và là số nhiều(có s)
Ví dụ: UserModel --> users table

Để overwrite hành vi này thì
<!-->
class User extends Model {
static get table () {
return 'my_users'
}
}
<-->

2. `Chỉnh connection theo từng Model`
Kết nối mặc định là được thiết lập ở `config/database.js`.
Tuy nhiên vẫn có thể thay đổi được thiết lập này theo từng Model.
<!-->
class User extends Model {
static get connection () {
return 'mysql'
}
}
<-->

3. `Chỉnh sửa khóa chính của từng table`
Mặc định khóa chính của một table là field `id`
Tuy nhiên vẫn có thể sửa được trên từng Model.
<!-->
class User extends Model {
static get primaryKey () {
return 'uid'
}
}
<-->

4. Chỉnh sửa lại tên của field `created_at`
<!-->
class User extends Model {
static get createdAtColumn () {
return 'created' // thay cho created_at
}
}
<-->
return null để disable.

5. Chỉnh sửa lại tên của field `updated_at`
<!-->
class User extends Model {
static get updatedAtColumn () {
return 'updated' // thay cho field updated_at
}
}
<-->
return null để disable.

6. Không sử dụng khóa chính có giá trị tăng dần(auto-incrementing primary key)
<!-->
class User extends Model {
static get incrementing () {
return false // tắt việc khóa chính có số tăng dần
}
}
<-->

7. Đọc và ghi khóa chính khi không sử dụng giá trị tăng dần
<!-->
const user = await User.find(1)
console.log(user.primaryKeyValue)

// when incrementing is false
user.primaryKeyValue = uuid.v4()
<-->


## Thiết lập ẩn đi các giá trị trong JSON Output
1. hidden
<!-->
class User extends Model {
static get hidden () {
return ['password']
}
}
<-->

2. visible
<!-->
class Post extends Model {
static get visible () {
return ['title', 'body']
}
}
<-->

3. setVisible/setHidden
Có thể chỉnh riêng cho từng câu query
> User.query().setHidden(['password']).fetch()

// or set visible
> User.query().setVisible(['title', 'body']).fetch()


## Format Dates
1. Chỉnh sửa ở app/Models/User.js
<!-->
class User extends Model {
getUpdateDate() {
var d = new Date(this.updated_at);

var options = {
year: 'numeric', month: 'numeric', day: 'numeric',
hour: 'numeric', minute: 'numeric', second: 'numeric',
hour12: false,
timeZone: 'America/Los_Angeles',
literal: "-"
};
return new Intl.DateTimeFormat('en-US', options).format(d);
}
<-->
2. Chỉnh sửa ở view
File resources/views/user/index.edge
<!-->
@each(user in users)
<tr>
<td style="width:120px;"> {{ user.username }} </td>
<td style="width:200px;"> {{ user.email }} </td>
<td> {{ user.getUpdateDate() }} </td>
</tr>
@endeach
<-->

***Ngoài ra cũng có thể sử dụng thư viện momentjs***
https://momentjs.com/
> npm install moment --save

Cách sử dụng:
<!-->
var moment = require('moment');
class User extends Model {
getCreatedDate() {
return moment(this.created_at).format('MM-DD-YYYY, hh:mm:ss');
}
<-->


## Query Builder
Lucid models sử dụng AdonisJs `Query Builder` để chạy các database queries.
https://adonisjs.com/docs/4.1/query-builder

Ví dụ:
<!-->
const User = use('App/Models/User')

const adults = await User
.query()
.where('age', '>', 18)
.fetch()
<-->
fetch() được yêu cầu để có thể trả về một lớp serializer instance.
Xem chi tiết serializer instance ở : https://adonisjs.com/docs/4.1/serializers


## Static Methods
Lucid models có rất nhiều phương thức tĩnh để xử lý truy vấn nhanh
mà không cần dùng đến `Query Builder`.

1. Tìm một dòng dữ liệu từ primary key với `find()`
> const User = use('App/Models/User')
> await User.find(1)

2. `findOrFail()`
Giống với find(), tuy nhiên nếu không tìm thấy dòng nào tương ứng
sẽ ném một lỗi `ModelNotFoundException`.
> const User = use('App/Models/User')
> await User.findOrFail(1)

3. `findBy / findByOrFail`
Tìm các dòng dữ liệu tương ứng theo một cặp điều kiện key/value
<!-->
const User = use('App/Models/User')
await User.findBy('email', 'foo@bar.com')

// or
await User.findByOrFail('email', 'foo@bar.com')
<-->

4. `first / firstOrFail`
Lấy ra dòng đầu tiên trong table
<!-->
const User = use('App/Models/User')
await User.first()

// or
await User.firstOrFail()
<-->

5. `last()`
Lấy ra dòng cuối cùng của table
> const User = use('App/Models/User')
> await User.last()

6. `findOrCreate (whereAttributes, values)`
Tìm một dòng dữ liệu, nếu không có thì tạo mới
<!-->
const User = use('App/Models/User')
const user = await User.findOrCreate(
{ username: 'virk' },
{ username: 'virk', email: 'virk@adonisjs.com' }
)
<-->

7. `pick(rows = 1)`
Lấy bao nhiêu dòng kể từ dòng đầu tiên
> const User = use('App/Models/User')
> await User.pick(3)

8. `pickInverse(rows = 1)`
Lấy bao nhiêu dòng kể từ dòng cuối cùng
> const User = use('App/Models/User')
> await User.pickInverse(3)

9. `ids()`
Lấy ra array của khóa chính(primary key)
> const User = use('App/Models/User')
> const userIds = await User.ids()

10. Lấy ra một object của một cặp key/value với `pair`
> const User = use('App/Models/User')
> const users = await User.pair('id', 'country')
==>
Output: { 1: 'ind', 2: 'uk' } // {id: country}

11. Lấy ra tất cả các dòng `all()`
> const User = use('App/Models/User')
> const users = await User.all()

12. `truncate()`
> const User = use('App/Models/User')
> const users = await User.truncate()


## Instance Methods
1. reload lại các giá trị trong một đối tượng
<!-->
const User = use('App/Models/User')
const user = await User.create(props)
// user.serviceToken === undefined

await user.reload()
// user.serviceToken === 'E1Fbl3sjH'
<-->


## Aggregate Helpers
1. `getCount(columnName = '*')`
> const User = use('App/Models/User')
> await User.getCount() // return total rows

Hoặc đặt nó vào trong một query builder
await User.query().where('is_active', 1).getCount()


## Query Scopes
Thiết lập các filter mà có thể tái sử dụng cho các query khác

Ví dụ:
<!-->
const Model = use('Model')

class User extends Model {
// khai báo filter "hasProfile"
static scopeHasProfile (query) {
return query.has('profile')
}

profile () {
return this.hasOne('App/Models/Profile')
}
}
<-->
==> Giờ có thể sử dụng filter hasProfile() cho bất kì query nào.
> `const users = await User.query().hasProfile().fetch()`


## Pagination
<!-->
const User = use('App/Models/User')
const page = 1

const users = await User.query().paginate(page)

return view.render('users', { users: users.toJSON() })
<-->


## Inserts & Updates
1. `save()`
<!-->
const User = use('App/Models/User')

const user = new User()
user.username = 'virk'
user.email = 'foo@bar.com'

// Insert new row
await user.save()

user.age = 22

// Update row
await user.save()
<-->

2. `fill / merge`

Ví dụ 1:
<!-->
const User = use('App/Models/User')

const user = new User()
user.username = 'virk'
user.age = 22

user.fill({ age: 23 }) // xóa đi các giá trị khác trong table chỉ update mỗi `age`

await user.save()

// returns { age: 23, username: null }
<-->

Ví dụ 2:
<!-->
const User = use('App/Models/User')

const user = new User()
user.fill({ username: 'virk', age: 22 })

user.merge({ age: 23 }) // chỉ cập nhật giá trị cho field `age`

await user.save()

// returns { age: 23, username: 'virk' }
<-->

3. `create`
Thay thế cho việc phải set dữ liệu cho từng fields, có thể tạo dòng mới từ một object
<!-->
const User = use('App/Models/User')
const userData = request.only(['username', 'email', 'age'])

// save and get instance back
const user = await User.create(userData)
<-->

4. `createMany`
Giống với `create`, có thể đưa vào multiple instances
<!-->
const User = use('App/Models/User')
const usersData = request.collect(['username' 'email', 'age'])

// lưu một tập các users từ collection "usersData"
const users = await User.createMany(usersData)
<-->

5. `Bulk Updates`
Update nhiều dòng với method `update()`
<!-->
const User = use('App/Models/User')

await User
.query()
.where('username', 'virk')
.update({ role: 'admin' })
<--->

Chú ý: Bulk updates không thực thi model hooks.


## Deletes
<!-->
const User = use('App/Models/User')

const { id } = params
const user = await User.find(id)

await user.delete() // xóa user

console.log(user.id) // truy xuất id của dòng vừa xóa thì ok

user.id = 1 // nhưng nếu ghi dữ liệu thì sẽ ngay lập tức nhận một lỗi
<-->

1. `Bulk Deletes`
<!-->
const User = use('App/Models/User')

await User
.query()
.where('role', 'guest')
.delete()
<-->

Bulk deletes không thực thi model hooks.


## Transactions
<!-->
const Database = use('Database')
const trx = await Database.beginTransaction() // khai báo bắt đầu sử dụng transaction

const user = new User()

// đưa trx object vào để lucid sử dụng
await user.save(trx)
....
await User.create({ username: 'virk' }, trx)
...
const user = await User.find(1, trx)
....
const user = await User.query(trx).where('username','virk).first()
....
await User.createMany([{ username: 'virk' }], trx)
...

// commit các dữ liệu đã thay đổi nếu không có lỗi gì
trx.commit()
// trả mọi thứ trả về như cũ như trước khi chạy xử lý
await trx.rollback()
<-->

1. Transactions in Relationships
Khi sử dụng method `attach` and `detach` với các bảng quan hệ cần truyền trx object
như là tham số thứ 3.
<!-->
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)
<-->


## Boot Cycle
Mỗi Model đều có method boot và phương thức này sẽ chỉ một lần duy nhất
khi model được khởi tạo.
<!-->
const Model = use('Model')

class User extends Model {
static boot () {
super.boot()

// có thể viết thêm các xử lý ở đây
// và nó sẽ được gọi 1 lần duy nhất lúc model được khởi tạo
}
}

module.exports = User
<-->



Không có nhận xét nào:

Đăng nhận xét

Học lập trình web căn bản với PHP

Bài 1: Các kiến thức căn bản Part 1:  https://jimmyvan88.blogspot.com/2012/05/can-ban-lap-trinh-web-voi-php-bai-1-cac.html Part 2:  https://...