Thứ Ba, 13 tháng 4, 2021

Lucid ORM - Relationships trong AdonisJS




Relationships là xương sống của ứng dụng theo hướng dữ liệu(data-driven applications).
Liên kết một model với các model khác.
Ví dụ: User thì có thể có liên kết với nhiều Post.
Post thì có thể liên kết với nhiều Comment.

## Cơ bản
Ví dụ liên kết giữa 2 model là User và Profile.

1. Định nghĩa quan hệ:
File app/Models/User.js
<!-->
const Model = use('Model')

class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
}
}

module.exports = User
<-->

Nếu chưa có Profile Model thì tạo bằng lệnh sau:
> adonis make:model Profile

File app/Models/Profile.js
<!-->
const Model = use('Model')

class Profile extends Model {
}

module.exports = Profile
<-->

Chỉ cần định nghĩa quan hệ giữa 2 model ở một file là đủ,
không cần thiết định nghĩa ở cả 2 file.

2. Lấy dữ liệu User Profile
<!-->
const User = use('App/Models/User')

const user = await User.find(1)
const userProfile = await user.profile().fetch()
<-->

## Has One
Quan hệ `Has One` dùng để định nghĩa mối quan hệ một-một(`one to one`)
sử dụng khóa ngoại(foreign key) để liên kết.
> hasOne(relatedModel, primaryKey, foreignKey)

- relatedModel: liên kết đến file Model tương ứng Ví dụ: `App/Models/Profile`
- primaryKey: khóa chính của model hiện tại (nếu không điền thì là field `id`)
- foreignKey: khóa ngoại liên kết (mapping với field `id` của model hiện tại).
Cấu trúc field mặc định là <tableName>_<primaryKey> (Ví dụ user_id)

<!-->
class User extends Model {
profile () {
return this.hasOne('App/Models/Profile')
    // OR this.hasOne('App/Models/Profile', 'id', 'user_id')
}
}
<-->

1. Database Tables
Table users (id, username, password)
Table profiles (id, user_id, profile_name)

## Has Many
Has Many giúp định nghĩa cho quan hệ một-nhiều(`one to many`)
> hasMany(relatedModel, primaryKey, foreignKey)

1. Database Tables
Table users (id, username, password)
Table posts (id, user_id, title, body)

2. Định nghĩa quan hệ
File app/Models/User.js
<!-->
const Model = use('Model')

class User extends Model {
posts () {
return this.hasMany('App/Models/Post')
}
}

module.exports = User
<-->

## Belongs To
belongsTo ngược lại với hasOne.
Nếu như là 1 User có 1 Profile (`hasOne`) thì ta cũng có thể định nghĩa ở Profile Model
rằng 1 profile này là thuộc về 1 user(belongsTo).
> belongsTo(relatedModel, primaryKey, foreignKey)

1. Database Tables
Table users (id, username, password)
Table profiles (id, user_id, profile_name)

2. Định nghĩa quan hệ
File app/Models/Profile.js
<!-->
const Model = use('Model')

class Profile extends Model {
user () {
return this.belongsTo('App/Models/User')
}
}

module.exports = Profile
<-->

## Belongs To Many
Belongs To Many định nghĩa cho quan hệ nhiều nhiều(`many to many`) ở trên cả 2 models.

Ví dụ:
Một User thì có nhiều Car. Và một chiếc xe thì cũng có thể được sử dụng bởi nhiều User.

Với quan hệ nhiều nhiều, chúng ta sẽ sử dụng 1 bảng trung gian
để lưu trữ 2 khóa ngoại để liên kết (`pivot table`)
Có thể tạo bảng trung gian(pivot table) thông qua migration files
(https://adonisjs.com/docs/4.1/migrations)

> belongsToMany(relatedModel, foreignKey, relatedForeignKey, primaryKey, relatedPrimaryKey)

1. Database Tables
Table users (id, username, password)
Table cars (id, name, model)
Table car_user (id, user_id, car_id)

2. Định nghĩa quan hệ
File app/Models/Car.js
<!-->
const Model = use('Model')

class User extends Model {
cars () {
return this.belongsToMany('App/Models/Car')
    // OR this.belongsToMany('App/Models/Car', 'user_id', 'car_id', 'id' ,'id')
}
}

module.exports = User
<-->

3. pivotTable
Mặc định tên table của pivot sẽ là <TableName1>_<TableName2>. Ví dụ: car_user
Tuy nhiên có thể sửa lại tên pivot ở Model.
<!-->
cars () {
return this
.belongsToMany('App/Models/Car')
.pivotTable('user_cars') // chỉ định tên của pivot table
}
<-->

4. withTimestamps
Mặc định thì pivot table sẽ không có các field created_at và updated_at.
Tuy nhiên có thể bật nó lên như bên dưới:
<!-->
cars () {
return this
.belongsToMany('App/Models/Car')
.withTimestamps()
}
<-->

5. withPivot
Mặc định thì chỉ có các khóa ngoại trong pivot table là được trả về khi nó được gọi,
tuy nhiên vẫn có thể trả về thêm các field
khác với `withPivot`
<!-->
cars () {
return this
.belongsToMany('App/Models/Car')
.withPivot(['is_current_owner']) // trả về thêm field `is_current_owner`.
}
<-->

6. pivotModel
Nếu bạn muốn kiểm soát nhiều hơn liên quan đến pivot table,
bạn có thể tạo Model riêng cho nó.
<!-->
class User extends Model {
cars () {
return this
.belongsToMany('App/Models/Car')
.pivotModel('App/Models/UserCar') // đăng ký sẽ sử dụng UserCar Model
}
}
<-->

File app/Models/UserCar.js
<!-->
const Model = use('Model')

class UserCar extends Model {
static boot () {
super.boot()
this.addHook('beforeCreate', (userCar) => {
userCar.is_current_owner = true
})
}
}

module.exports = UserCar
<-->

## Many Through
Many Through là cách tiện lợi để lấy dữ liệu của các bảng quan hệ một cách gián tiếp.

Ví dụ:
1 User thì thuộc về một Country.
1 User thì có nhiều Post.
==>
Từ 1 Country có thể lấy tất cả các Post cho country này.

> manyThrough(relatedModel, relatedMethod, primaryKey, foreignKey)

1. Database Tables
Table countries (id, name, locate)
Table users (id, country_id, username, password)
Table posts (id, user_id, title, body)

2. Định nghĩa quan hệ
File app/Models/User.js
<!-->
const Model = use('Model')

class User extends Model {
posts () {
return this.hasMany('App/Models/Post')
}
}
<-->
File app/Models/Country.js
<!-->
const Model = use('Model')

class Country extends Model {
posts () {
return this.manyThrough('App/Models/User', 'posts')
}
}
<-->

## Querying Data
Querying Data cung cấp cách lấy dữ liệu từ các bảng quan hệ.

1. Lấy tất cả từ bảng quan hệ
```js
const User = use('App/Models/User')
const user = await User.find(1)
const posts = await user.posts().fetch()
```

2. Sử dụng Query Builder để filter dữ liệu
```js
const user = await User.find(1)

// published posts
const posts = await user.posts().where('is_published', true).fetch()
```

3. Querying ở Pivot Table
const user = await User.find(1)
const cars = await user.cars().wherePivot('is_current_owner', true).fetch()
// The methods whereInPivot and orWherePivot are also available.

## Eager Loading
Load kèm dữ liệu các bảng con
<!-->
const User = use('App/Models/User')

const users = await User
.query()
.with('posts') // lấy thêm danh sách các posts của user này
.fetch()
<-->

1. Thêm các điều kiện ở bảng con cần lấy
```js
const users = await User
.query()
.with('posts', (builder) => {
builder.where('is_published', true)
})
.fetch()
```

2. Load nhiều bảng con liên quan
<!-->
const users = await User
.query()
.with('posts')
.with('profile')
.fetch()
<-->

3. Load các bảng con lồng nhau
Ví dụ: Lấy tất cả các posts của user và kèm theo các comment của các post đó.
<!-->
const users = await User
.query()
.with('posts.comments')
.fetch()
<-->

- Kèm theo filter
<!-->
const users = await User
.query()
.with('posts.comments', (builder) => {
builder.where('approved', true) // lấy ra các comment đã được duyệt (approved)
})
.fetch()
<-->

- Sử dụng nhiều filter
<!-->
const users = await User
.query()
.with('posts', (builder) => {
builder.where('is_published', true)
    // Chỉ lấy các bài viết(post) mà đã được xuất bản(publish)
.with('comments')
})
.fetch()
<-->

- Chỉ trả về thông tin bảng con
<!-->
const user = await User
.query()
.with('posts') // lấy tất cả các bài viết của users
.fetch()
<-->
==> Lấy ra danh sách các posts(trích xuất): `const posts = user.getRelated('posts')`

## Lazy Eager Loading
Lấy riêng dữ liệu bảng chính và dữ liệu bảng con trong 2 lần xử lý.
> const user = await User.find(1) // lấy ra thông tin user 1
> await user.load('posts') // lấy ra danh sách post của user 1

1. lazy load nhiều liên kết
> const user = await User.find(1)
> await user.loadMany(['posts', 'profiles'])

1. b) Load nhiều liên kết kèm filter
<!-->
const user = await User.find(1)
await user.loadMany({
posts: (builder) => builder.where('is_published', true),
profiles: null
})
<-->

Trích xuất dữ liệu:
<!-->
const user = await User.find(1)
await user.loadMany(['posts', 'profiles'])

const posts = user.getRelated('posts')
const profiles = user.getRelated('profiles')
<-->

## Filtering Data
app/Models/Post.js
<!-->
const Model = use('Model')

class Post extends Model {
comments () {
return this.hasMany('App/Models/Comments')
}
}
<-->

1. Lấy chỉ các post mà có comments (`sử dụng .has()`)
> const posts = await Post.query().has('comments').fetch()

Hoặc lấy ra các post có số comment > 2:
`const posts = await Post.query().has('comments', '>', 2).fetch()`

2. `whereHas`
Giống has, nhưng mà có thể thêm filter
<!-->
const posts = await Post
.query()
.whereHas('comments', (builder) => {
builder.where('is_published', true)
}, '>', 2)
.fetch()
<-->

3. `doesntHave`
Ví dụ: Lấy ra các post mà không có comment
> const posts = await Post.query().doesntHave('comments').fetch()

4. `whereDoesntHave`
ngược lại với whereHas
<!-->
const posts = await Post
.query()
.whereDoesntHave('comments', (builder) => {
builder.where('is_published', false)
})
.fetch()
<-->

Ngoài ra cũng có thể sử dụng `orHas`, `orWhereHas`, `orDoesntHave` and `orWhereDoesntHave`.

## Counts
const posts = await Post.query().withCount('comments').fetch()

posts.toJSON()
==> JSON Output
<!-->
{
title: 'Adonis 101',
__meta__: {
comments_count: 2
}
}
<-->

1. Sử dụng Alias field name
> `const posts = await Post.query().withCount('comments as total_comments').fetch()`

2. Count với filter
<!-->
const posts = await Post
.query()
.withCount('comments', (builder) => {
builder.where('is_approved', true)
})
.fetch()
<-->

## Inserts, Updates & Deletes
1. `save()` khi dữ liệu cần lưu ở dạng 1 model
<!-->
const User = use('App/Models/User')
const Post = use('App/Models/Post')

const user = await User.find(1)

const post = new Post()
post.title = 'Adonis 101'

await user.posts().save(post)
<-->

2. `create()` khi dữ liệu ở dạng object
<!-->
const User = use('App/Models/User')

const user = await User.find(1)

const post = await user
.posts()
.create({ title: 'Adonis 101' })
<-->

3. `createMany()`
Sử dụng cho các model có quan hệ nhiều nhiều. hỗ trợ relationship types: `hasMany`
`belongsToMany`
<!-->
const User = use('App/Models/User')

const user = await User.find(1)

const post = await user
.posts()
.createMany([
{ title: 'Adonis 101' },
{ title: 'Lucid 101' }
])
<-->

4. `saveMany()`
Sử dụng cho các model có quan hệ nhiều nhiều. hỗ trợ relationship types: `hasMany`
`belongsToMany`
<!-->
const User = use('App/Models/User')
const Post = use('App/Models/Post')

const user = await User.find(1)

const adonisPost = new Post()
adonisPost.title = 'Adonis 101'

const lucidPost = new Post()
lucidPost.title = 'Lucid 101'

await user
.posts()
.saveMany([adonisPost, lucidPost])
<-->

5. `associate()`
Tạo liên kết giữa 2 model sau khi đã lấy dữ liệu riêng của 2 model
<!-->
const Profile = use('App/Models/Profile')
const User = use('App/Models/User')

const user = await User.find(1)
const profile = await Profile.find(1)

await profile.user().associate(user)
<-->

6. `dissociate()`
Hủy đi liên kết giữa 2 model
<!-->
const Profile = use('App/Models/Profile')
const profile = await Profile.find(1)

await profile.user().dissociate()
<-->

7. `attach()`
Bổ sung thêm dữ liệu giữa 2 khối dữ liệu của 2 model
<!-->
const User = use('App/Models/User')
const Car = use('App/Models/Car')

const mercedes = await Car.findBy('reg_no', '39020103')
const user = await User.find(1)

await user.cars().attach([mercedes.id])
<-->

8. `detach()`
Ngược lại của attach.
<!-->
const user = await User.find(1)
await user.cars().detach()
<-->

Chỉ bỏ đi các chỉ định
<!-->
const user = await User.find(1)
const mercedes = await Car.findBy('reg_no', '39020103')
await user.cars().detach([mercedes.id])
<-->

9. `sync()`
Trong trường hợp mà cần detach all sau đó lại attach thì có thể dùng sync để thay thế.
<!-->
const mercedes = await Car.findBy('reg_no', '39020103')
const user = await User.find(1)

// Behave the same way as:
// await user.cars().detach()
// await user.cars().attach([mercedes.id])

await user.cars().sync([mercedes.id])
<-->

10. `update()`
<!-->
const user = await User.find(1)

await user
.posts()
.where('title', 'Adonis 101')
.update({ is_published: true })
<-->

Update một pivot table
<!-->
const user = await User.find(1)

await user
.cars()
.pivotQuery()
.where('name', 'mercedes')
.update({ is_current_owner: true })
<-->

11. `delete()`
<!-->
const user = await User.find(1)

await user
.cars()
.where('name', 'mercedes')
.delete()
<-->



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://...