Các bài học lập trình ứng dụng RESTful ~ Chuyên đề phát triển Web - [Chương 6] Các thao tác quản lý data và hàm assign trong CakePHP



:warning: Tôi đã thêm một table tên là `users` ở bài 5.

## :large_blue_circle: Chào mừng các bạn trở lại
Chương 6 này được mình dịch từ bài viết [CakePHPでデータ操作〜担当者アサイン機能の実装 - AWS上で構築するRESTfulアプリ勉強会～Web開発ワークショップ～【第6回】マニュアル](http://qiita.com/k_shimoji/items/174e0f2ee9b726a68994) của tác giả @k_shimoji , mình vừa dịch vừa làm theo nên sẽ chỉnh sửa khá nhiều sau khi mình hiểu hơn về bài học, rất mong nhận được sự góp ý của tất cả các bạn.

:warning: Kế hoạch bài học

Ở bài này chúng ta sẽ thêm khả năng assign nhiệm vụ cho cá nhân vào app TODO list.
Với việc đã thêm chức năng login ở bài trước, chúng ta sẽ cho phép ứng dụng này được sử dụng với nhiều user.

Và qua đây bạn sẽ được học về "association" trong CakePHP.


## :large_blue_circle: "association" trong CakePHP

Theo bài viết trước thì chúng ta sẽ xử lý trên 2 table

- users
- todo_lists

Tuy nhiên không phải chỉ chọn 1 table để xử lý trong mỗi lần làm việc.
Tại ứng dụng mà chúng ta đã viết thời điểm này, bạn cần Sellect bằng cách chọn cả users và todo_lists, sẽ hay hơn nếu mình không viết SQL trực tiếp mà xử lý bằng cách của CakePHP, bạn có thể sử dụng các cơ chế "association".
Bạn có thể join các table bằng code SQL.
### Tìm hiểu về association

Bây giờ mình sẽ tìm hiểu về 3 association sau

- belongsTo
- hasOne
- hasMany

:warning: 「hasAndBelongsToMany」Nó đã có sẵn nên tất nhiên sẽ bị bỏ qua

Giải thích mỗi association

### belongsTo

A belongsTo B, hay nói cách khác, A có một mối quan hệ với một table mà table nó thuộc về B, bạn có thể sử dụng belongsTo association.
Ví dụ, thành phần user trong table `users`, sẽ cần có một quốc tịch "nationality", nó sẽ có một table là "nationality master (countries)".

Mình sẽ thêm table countries ( phần mở ngoặc là các field trong table đó )
- countries(id, name)

Bởi vì mỗi user sẽ có một quốc tịch nằm trong danh sách các nationality, table `users` sẽ như sau

- users(id, username, country_id)

Bạn có thể Sellect dữ liệu bằng cách join users.country_id với countries.id bằng cách viết đoạn SQL sau:

```sql
select users.id, users.username, countries.name as country_name
from users inner join countries on users.country_id = countries.id
```

Đây là association "belongsTo".
Code thì chúng ta sẽ xem sau.

### hasOne

hasOne là cái table thứ 2 trong mối quan hệ belongsTo mà mình đã nói ở trên, Ở ví dụ của belongsTo thì phía users phải show giá trị của countries `country_id`, trường hợp hasOne là ngược lại của belongsTo, như kiểu thằng B chứa 1 thành phần của thẳng A thì thằng A belongTo thằng B, còn thằng B hasOne thằng A ấy.
Ví dụ bạn có 1 table mà chứa profile của user tên là `profiles` chẳng hạn.

- profiles(id, user_id, description, hobbies, etc...)

Ở trường hợp này, chúng ta có user_id ở phía profiles, qua đó mình có thể Sellect bằng cách join users.id và profiles.user_id.
Đây là association "hasOne".

:warning: Phía user cũng có thể có một giá trị profile_id, nhưng sẽ có trường hợp có "non-registered profile."

- set cho users.profile_id chấp nhận giá trị null
- users.id = profies.user_id mình không cần quan tâm.
Đây là tùy theo triết lý thiết kế database của mỗi người, tất cả đều ok.

Khi viết lệnh SQL nó sẽ như thế này.
```sql
select users.id, profiles.introduce, profiles.hobbies
from users inner join profiles on users.id = profiles.user_id
```

Về code thì chúng ta sẽ xem sau

### hasMany

hasMany sử dụng để biểu đạt mối quan hệ mà table này chứa n thành phần của một table khác.
Ví dụ, bạn có một table chứa nghề nghiệp của user (careers).

- careers(id, user_id, introduce, name, etc...)

Ở trường hợp này, có thể nhiều user chung nghề nghiệp (career) ,
Bạn có thể Select bằng cách join với điều kiện users.id trùng với careers.user_id.
Sự khác biệt của nó với hasOne chỉ là sẽ lấy nhiều record hơn trong một lần.

Lệnh SQL như sau

```sql
select users.id, careers.introduce, careers.name
from users inner join careers on users.id = careers.user_id
```

:warning: Sự thật là trong CakePHP, một khi select users và select careers, nghĩa là chúng ta đang select tổng cộng tương ứng 2 lần.

Code chúng ta sẽ xem sau


## :large_blue_circle: Đi vào bài học

Sau khi hoàn thành bài học này thì chúng ta có thể hoàn tất các chức năng sau

### Đặc tả chức năng

- Thêm Todo
	- Bây giờ thì khi thêm TODO sẽ có thêm chức năng "Phân công nhiệm vụ ai sẽ phải làm TODO này" trong list tất cả các user
	- Danh sách user, logged-in user trong danh sách lựa chọn mặc định.
- Hiển thị TODO list
	- Hiển thị "owner" tức là người đã thêm TODO này , và hiển thị người được chỉ định sẽ phải làm todo này ở cột "assignee".
        - Ngoài ra chúng ta sẽ thêm vào một vài giới hạn sau
		- Chỉ người `owner` mới được dùng chức năng "Delete". Nút "Delete" sẽ không hiện với những người khác.
                - Hiển thị "details" cho owner và người responsible. Không hiển thị link "Details" cho những người khác.
- TODO details
   - Một vài thay đổi cá nhân


### Màn hình

- Màn hình TODO list ( Giao diện hơi khác vì mình có vọc vạch dùng thêm bootstrap và tự viết thêm vài chức năng mới, ở một bài viết tới sẽ có hướng dẫn về bootstrap, các bạn yên tâm, còn các chức năng ẩn việc đã làm, xóa tất cả... thì các bạn có thể tự làm ở nhà với những kiến thức đã học )

![demo.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/fb872c53-0130-9eb3-4c56-e1a75cf4004a.png)

- Màn hình TODO detail 

![0.1.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/d89a835c-dbfa-7fe8-50b6-e0159255d94e.png)


## :large_blue_circle: Chuẩn bị

Phần chuẩn bị sẽ giống nhau ở mỗi bài, vì vậy tôi đã tổng hợp nó ở một entry riêng. Các bạn vui lòng xem ở link này [All 12 times of study sessions in do I have use of Git - RESTful application workshops will be built on AWS ~ Web development workshop ~ - Qiita](http://qiita.com/k_shimoji/items/6a66dbb93b0553fa7c7f) để chuẩn bị .


- Bây giờ mình sẽ tạo và làm việc trên branch tên `vol/06` trước đã.


***Còn đây là những table và giá trị mà chúng tôi đã add ở bài trước, các bạn chưa xem bài viết trước có thể xem và tạo [Tại đây](http://qiita.com/k_shimoji/items/ae5a02bd0c7e03feae52#%E3%83%A6%E3%83%BC%E3%82%B6%E7%99%BB%E9%8C%B2%E7%94%A8%E3%81%AE%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E4%BD%9C%E6%88%90).***

Bây giờ mình sẽ thêm 2 trường nữa cho bảng todo_lists.

- ownerID(owner)
- assignerID(assignee)


### Thêm cột cho bảng TODO list


Đầu tien chúng ta đăng nhập vào phpMyAdmin

URL như cũ thôi: `http://(PublicIP)/phpMyAdmin/`.
Nếu bạn chưa thay đổi gì thì username và password đăng nhập đều là `study`.


##### Cấu hình cho mỗi collum

| Name | Data type | length / value | default value | collation | attribute | NULL | Index | A_I (AutoIncrement) | Comments |
|:--|:--|:--|:--|:--|:--|:--|:--|:--|:--
| owner |INT|10|no|　|UNSIGNED|　|　| 　|owner
| assignee |INT|10|no|　|UNSIGNED|　|　|　|assigner

#### Chuẩn bị

Bắt đầu chuẩn bị nào!

-:white_check_mark: Như đã nói ở trên mình sẽ tạo branch `vol/06` cho Git.
-:white_check_mark: Thêm một column cho TODO list (todo_lists) table.

Khi đã sẵn sàng, chúng ta bắt đầu vào Bài 1.


## :large_blue_circle: Bài 1: server side

Ở bài 1, chúng ta sẽ thực hiện những thao tác liên quan đến server side của ứng dụng.
Để test chương trình, sử dụng "POSTMAN" mà chúng ta đã cài đặt ở những bài đầu tiên.

### Points

Mặc dù có 3 "association" mà mình đã liệt kê, nhưng thực tế mình chỉ cần sử dụng duy nhất "belongsTo", bạn sẽ nhận ra điều này.
Cơ mà như vậy thì không thú vị lắm, hãy cũng thử với cả  "hasMany" và "hasOne" nữa nhé.
Đầu tiên, mình sử dụng belognsTo, và bắt đầu code.

### Danh sách những file cần chỉnh sửa

|Thao tác|file|Mô tả|
|---|:----------------------------------|:----------------------|
|Sửa| app/Controller/TodoListsController.php |Setting the find parameters
|Sửa| app/Model/TodoList.php |Association settings
|Sửa| app/Controller/UsersController.php   |lấy user-list


#### TodoListsController.php

Mình sẽ set các thông số của hàm find giống như TodoList model.

```diff:TodoListsController.php
〜略〜
 class TodoListsController extends AppController {
 
 	public function index() {
-		$res = $this->TodoList->find('all');
+		$query = array (
+			'fields' => array (
+				'TodoList.id',
+				'TodoList.todo',
+				'TodoList.status',
+				'Owner.id',
+				'Owner.name',
+				'Assignee.id',
+				'Assignee.name'
+			),
+			'order' => "TodoList.id"
+		);
+		$res = $this->TodoList->find('all', $query);
+		// 整形
+		if (count($res) > 0) {
+		    $loginUserId = $this->Auth->user()['id'];
+			foreach ( $res as $key => $row ) {
+			    //truong hop 「Login user là owner」
+				$res[$key]['TodoList']['owned'] = $row['Owner']['id'] === $loginUserId;
+			    //Truong hop「login user la Assignee」
+				$res[$key]['TodoList']['assigned'] = $row['Assignee']['id'] === $loginUserId;
+			}
+		}
 		$this->set(compact('res'));
 		$this->set('_serialize', 'res');
 	}
 
 	public function view($id = null) {
 		$res = $this->TodoList->findById($id);
 		$this->set(compact('res'));
 		$this->set('_serialize', 'res');
 	}
 
 	public function add() {
 		$data = $this->request->data;
+		$data['owner'] = $this->Auth->user()['id'];
 		$res = $this->TodoList->save($data);
 		$this->set(compact('res'));
 		$this->set('_serialize', 'res');
 	}

〜以下略〜
```

#### Thêm argument cho hàm `find`

- Bạn cần thêm 2 argument `$query`. `$Query` là một mảng, bạn cần set như sau.
	- `fields`
		- Để get column của table. Bạn không cần xác định cụ thể nó, vì nó sẽ lưu trữ mật khẩu của user và vài thứ khác, chúng ta chỉ set những thứ cần thiết.
	- `order`
		- Hiện tại data đang được sắp xếp ngẫu nhiên theo thứ tự được xác định, tuy nhiên todo_lists sẽ là thứ tự giảm dần ID trong bảng để hiển thị những TODO mới nhất.

#### Xây dựng data thu được

- Những data mình thu được trong bài 1 gồm
	- Nếu người đăng nhập là owner, set `owned = true`, trường hợp ngược lại set `owned = false`.
	- Nếu người đăng nhập là assigner, set `assigned = true`, nếu không phải thì `assigned = false`.

#### TodoList.php

Association setting.

```diff:TodoList.php
 class TodoList extends AppModel {
+	public $belongsTo = array (
+		'Owner' => array (
+			'className' => 'User',
+			'foreignKey' => 'owner',
+		),
+		'Assignee' => array (
+			'className' => 'User',
+			'foreignKey' => 'assignee'
+		)
+	);
 }
```

#### belongsTo association

- Cho mỗi lần xem TODO,
   - Thông tin của owner User
   - Thông tin của Assignee user
   
Chúng ta sẽ setting như sau.

- Model của biến sẽ tạo nên một mảng biến tên là  `$belongsTo`.
	- Tôi đã đặt tên tương ứng trong table là `Owner`(Owner), `Assignee`(người được phân công) 
	- Thông số chi tiết của (`Owner`) như sau
		- `className` -> Tên model(User)
		- Thêm Foreign key cho users (Cụ thể là `todo_lists.owner`)
	- Thông số chi tiết của (`Assignee`) như sau
		- `className` -> Tên model(User)
		- Thêm Foreign key  cho users (Cụ thể là `todo_lists.assignee`)

#### Chức năng Thêm TODO

- Bạn phải set ID của người đó vào `owner`.
	- Bây giờ, user đã login nào thực hiện lệnh thêm TODO sẽ được set là owner của TODO đó

### UsersController.php

Chứa các chức năng Login, kiểm tra login, log out, đăng ký.

```diff:UsersController.php
 class UsersController extends AppController {
 
+	public function index() {
+		$res = $this->User->find('all', array (
+			'fields' => array (
+				'User.id',
+				'User.username',
+				'User.name'
+			)
+		));
+		$this->set(compact('res'));
+		$this->set('_serialize', 'res');
+	}
+
 	public function login() {
 		$user = $this->Auth->user();
 		$res = array();

〜以下略〜
```

#### Hàm `index`

- Cùng một khả năng với `TodoListController.php`, bạn cần cố định field để hiểu hàm `find`.

###　Kiểm tra kết quả

Sử dụng POSTMAN, Lấy danh sách TODO list bằng URL (`http://(PublicIP)/rest-study/todo_lists.json`).
Nếu được kết quả trả về với cấu trúc tương tự dưới đây là OK.
belongsTo đã làm việc.

```json
[
    {
        "TodoList": {
            "id": "68",
            "todo": "1 ne hihi aaaa",
            "status": "0",
            "owned": true,
            "assigned": true
        },
        "Owner": {
            "id": "1",
            "name": "XuanTai"
        },
        "Assignee": {
            "id": "1",
            "name": "XuanTai"
        }
    },
    {
        "TodoList": {
            "id": "69",
            "todo": "1 ne hihi aaaa b",
            "status": "0",
            "owned": true,
            "assigned": true
        },
        "Owner": {
            "id": "1",
            "name": "XuanTai"
        },
        "Assignee": {
            "id": "1",
            "name": "XuanTai"
        }
    },
    {
        "TodoList": {
            "id": "70",
            "todo": "1 ne hihi aaaa1213",
            "status": "0",
            "owned": true,
            "assigned": true
        },
        "Owner": {
            "id": "1",
            "name": "XuanTai"
        },
        "Assignee": {
            "id": "1",
            "name": "XuanTai"
        }
    }
]
```

Như kết quả của tôi khi làm theo hướng dẫn trên:
![Capture.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/a73356e7-e96a-9ad8-3b63-5717c7ee501e.png)


### Tổng kết

Các bước thao tác như sau

- :white_check_mark: `app/Controller/TodoListsController.php` sửa như trên.
- :white_check_mark: `app/Model/TodoList.php` sửa như trên.
- :white_check_mark: `app/Controller/UsersController.php` sửa như trên.
- :white_check_mark: Kiểm tra kết quả.
- :white_check_mark: Commit lên Gỉt


**Tài liệu tham khảo trên GitHub với hiển thị Diff**
[Chương 6 - Bài 1 · suzukishouten-study/rest-study@d4fe1a4](https://github.com/suzukishouten-study/rest-study/commit/d4fe1a4c8428b6cf9ecf6b29256537e2741210ca)

Sang bài 1.5 nào.

## :large_blue_circle: Bài 1.5. Thử sử dụng các association khác.

Mọi thứ đã ok với cách thực hiện như bài 1.
Tuy nhiên trước khi sang bài 2, các bạn có thể thử sử dụng `hasOne` và `hasMany` xem sao, để nâng cao tay nghề tí thôi mà, hehe.

Sau khi sửa code và kiểm tra xác nhận kết quả, hãy sang bài 2 với code cũ.

Ở phần test, mục tiêu của mình sẽ là chức năng như trong ảnh dưới đây.
![vl.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/58713523-ee17-eb0a-327f-fef2597b6eca.png)



:warning: user là

- XuanTai
- Test5
- Test2

Đây là những user đã được đăng ký, tuy nhiên XuanTai không thể, xem, xóa, sửa trạng thái TODO thứ 4 ( vì anh ta không phải owner cũng không phải người được phân công), anh ta có toàn qyền đối với TODO 1,2,3,7, và chỉ có quyền vào trang detail ( để sửa, xóa, assign người khác ) của TODO 5 và TODO 6 .

### Kiểm tra hasOne 

Chúng ta cần tự đặt điều kiện user nào thêm TODO chính là owner.
:warning: `user : todo` sẽ phải trở thành `1 : n` nếu bạn muốn chuyển thành quan hệ `hasMany`.

#### Chi tiết các thay đổi của source

##### sửa hàm `index` của `app/Controller/UsersController.php`

```php:app/Controller/UsersController.php
	public function index() {
	    $res = $this->User->find('all');	//Xóa những field đã xác định, chuyển nó thành comment như dưới đây.
// 	    $res = $this->User->find('all', array (
// 			'fields' => array (
// 				'User.id',
// 				'User.username',
// 				'User.name'
// 			)
// 		));
		$this->set(compact('res'));
		$this->set('_serialize', 'res');
	}
```



- set association cho `app/Model/User.php `

```php:app/Model/User.php
class User extends AppModel {
	public $hasOne = array (
		'TodoList' => array (
			'className' => 'TodoList',
			'foreignKey' => 'owner'
		)
	);
}
```

Ổn rồi, hãy kiểm tra kết quả ở (`http://(PublicIP)/rest-study/users.json`)

```json:result
[
    {
        "User": {
            "id": "67",
            "username": "yamada",
            "password": "$2a$10$KQTujh9rcjx7/7b83Hw7jOg8brjOAyT/S.H4VgY7Uknjy5jg1.iD.",
            "name": "山田太郎"
        },
        "TodoList": {
            "id": "62",
            "todo": "牛乳を買う",
            "status": "0",
            "owner": "67",
            "assignee": "67"
        }
    },
    {
        "User": {
            "id": "67",
            "username": "yamada",
            "password": "$2a$10$KQTujh9rcjx7/7b83Hw7jOg8brjOAyT/S.H4VgY7Uknjy5jg1.iD.",
            "name": "山田太郎"
        },
        "TodoList": {
            "id": "78",
            "todo": "犬の散歩",
            "status": "0",
            "owner": "67",
            "assignee": "68"
        }
    },
    {
        "User": {
            "id": "68",
            "username": "tanaka",
            "password": "$2a$10$ig3ul1GQlZPbQDODLt6.SuYqJVv94wNlZJwMqSPvK5H/p2qEBih6e",
            "name": "田中花子"
        },
        "TodoList": {
            "id": "83",
            "todo": "家に電話",
            "status": "1",
            "owner": "68",
            "assignee": "68"
        }
    },
    {
        "User": {
            "id": "69",
            "username": "sato",
            "password": "$2a$10$rBdmyhZHwlv4h.EvacI34OSRuE9Q3t7pYx1HysS1zxw99NOGLUVBi",
            "name": "佐藤一郎"
        },
        "TodoList": {
            "id": null,
            "todo": null,
            "status": null,
            "owner": null,
            "assignee": null
        }
    }
]
```

Mỗi todo sẽ đi kèm thông tin cá nhân của một user.
Nếu ai đó không có TODO nào thì mỗi thành phần của mảng có thể là null.

Code SQL như dưới đây, chúng ta chạy 2 lần `hasMany`.

```sql
SELECT
	`User`.`id`,
	`User`.`username`, 
	`User`.`password`, 
	`User`.`name`, 
	`TodoList`.`id`, 
	`TodoList`.`todo`, 
	`TodoList`.`status`, 
	`TodoList`.`owner`, 
	`TodoList`.`assignee` 
FROM `study`.`users` AS `User` 
		LEFT JOIN `study`.`todo_lists` AS `TodoList` ON (`TodoList`.`owner` = `User`.`id`)  
WHERE 1 = 1
```

Giải thích về lý do mình sử dụng `WHERE 1 = 1` thì bạn không cần quan tâm, nó là một kiểu SQL, bạn chỉ cần JOIN mà thôi.


### Test hasMany

`user : todo` sẽ trở thành `1 : n` khi sử dụng `hasMany`.

### Các phần cần sửa trong source

`app/Controller/UsersController.php` mình sẽ thêm một state cố định với hasOne.

set association cho `app/Model/User.php`.

```php:app/Model/User.php
class User extends AppModel {
	public $hasMany = array (
		'TodoList' => array (
			'className' => 'TodoList',
			'foreignKey' => 'owner'
		)
	);
}
```

hasOne tôi sẽ sửa thành hasMany
Kết quả sẽ như sau

```json:result
[
    {
        "User": {
            "id": "67",
            "username": "yamada",
            "password": "$2a$10$KQTujh9rcjx7/7b83Hw7jOg8brjOAyT/S.H4VgY7Uknjy5jg1.iD.",
            "name": "Yamada"
        },
        "TodoList": [
            {
                "id": "62",
                "todo": "walking",
                "status": "0",
                "owner": "67",
                "assignee": "67"
            },
            {
                "id": "78",
                "todo": "studying",
                "status": "0",
                "owner": "67",
                "assignee": "68"
            }
        ]
    },
    {
        "User": {
            "id": "68",
            "username": "tanaka",
            "password": "$2a$10$ig3ul1GQlZPbQDODLt6.SuYqJVv94wNlZJwMqSPvK5H/p2qEBih6e",
            "name": "Tanaka"
        },
        "TodoList": [
            {
                "id": "83",
                "todo": "家に電話",
                "status": "1",
                "owner": "68",
                "assignee": "68"
            }
        ]
    },
    {
        "User": {
            "id": "69",
            "username": "sato",
            "password": "$2a$10$rBdmyhZHwlv4h.EvacI34OSRuE9Q3t7pYx1HysS1zxw99NOGLUVBi",
            "name": "Sato"
        },
        "TodoList": []
    }
]
```

Vậy là bằng cách này, TODO đã hiển thị đúng với nhiều TODO cho mỗi user.
Đây gọi là hasMany.
Thời điểm này thì SQL sẽ chạy 2 lệnh sau. Mỗi lần chạy SQL thì hasMany chạy 2 lần.

```sql:Lan1
SELECT `User`.`id`, `User`.`username`, `User`.`password`, `User`.`name` FROM `study`.`users` AS `User`   WHERE 1 = 1
```

```sql:Lan2
SELECT `TodoList`.`id`, `TodoList`.`todo`, `TodoList`.`status`, `TodoList`.`owner`, `TodoList`.`assignee` FROM `study`.`todo_lists` AS `TodoList`   WHERE `TodoList`.`owner` IN (67, 68, 69)
```

Ở lần 2 bạn phải select TodoList với owner là ID của user mà bạn nhận được ở lần 1 .
Nhưng mình đã chạy xong rồi nên hãy trở lại chỗ cũ nơi bạn đã sửa để đến với bài 2, hãy tạo một trang mới


## :large_blue_circle: Bài 2: Client side

Ở bài 2 này, mình sẽ code phần client side của ứng dụng.

### Chú

Bạn có thể tự làm hầu hết với những kiến thức mà bạn đã có từ bài trước, tuy nhiên ở thời điểm này, để "nhận và hiển thị danh sách user" ở phần sau sẽ cần một vài comment nhỏ.

- Danh sách user ở trang TODO list

![1.png](https://qiita-image-store.s3.amazonaws.com/0/94375/cbfdf727-e871-e616-80ab-3402f276f1bd.png)


- Danh sách user ở trang TODO Detail


![0.1.PNG](https://qiita-image-store.s3.amazonaws.com/0/94375/d89a835c-dbfa-7fe8-50b6-e0159255d94e.png)


#### Bắt đầu thực hiện

Ở Backbone, nếu bạn có thể tận dụng hết thế mạnh của Marionette, đầu tiên thì user list sẽ hiển thị ở View, "Behavior" khá tốt, nhưng tôi sẽ chia sẻ qua ví dụ sử dụng hàm, và đây là thời điểm dùng "CakePHP Associations" là chính, hãy nhớ rằng sẽ rất dễ dàng để implement nó.

Cụ thể

- Nhập URL của API (`http://(PublicIP)/rest-study/users.json`) để lấy collection của user list.
- Collecton này được dùng với vòng lặp, Nó sẽ nằm trong tag `<select> </select>` trong template, Phần thẻ `<option value ="user id"> user's name </ option>` sẽ được lọc bằng cách sử dụng JQuery.

Đây là cách làm

Hiện tại thì tôi đã "copy và paste" để dùng ở cả TODO lists và TODO detail.
(Và nó không DRY vì chúng ta đã vi phạm Don't Repeat Yourself, nhưng thời điểm này mình sẽ làm nhanh để ví dụ thôi !)

Và đây là chú thích về chỉnh sửa.


### Các file cần thay đổi

|Thao tác|file|Mô tả|
|---|:------------------------------------------------|:----------------------|
|Sửa|app/View/Layouts/default.ctp                    |Sửa template
|Sửa|app/webroot/js/collections/user-collection.js   |user collection
|Sửa|app/webroot/js/models/todo-model.js             |parse
|Sửa|app/webroot/js/views/todo-layout-view.js        |Để đọc userlist
|Sửa|app/webroot/js/views/todo-composite-view.js     |User list
|Sửa|app/webroot/js/views/todo-item-view.js          |Hiển thị các nút điều khiển như detail, delete
|Sửa|app/webroot/js/views/todo-detail-layout-view.js |Đọc User List
|Sửa|app/webroot/js/views/todo-detail-item-view.js   |User List


### default.ctp

Sửa template

```diff:default.ctp

〜　略　〜
  <title>シンプルTODOアプリ</title>
 </head>
 <body>
 	<!-- ヘッダ -->
 	<div id="header"></div>
 	<!-- コンテンツ -->
 	<div id="main"></div>

〜　略　〜
 
 	<!-- TODO一覧表示のテンプレート -->
 	<script type="text/template" id="todo-composite-template">
 	<textarea style="width:300px;height:50px"id="new-todo" placeholder="Todo?" autofocus></textarea>
+	<select name="assignee" id="user-list">
 	<input type="button" id="addTodo" value="追加">
 	<hr>
 	<div>
 		<table border="1" width="350px">
+			<thead>
+				<tr>
+					<td>Status</td>
+					<td>ToDo</td>
+					<td>Owner</td>
+					<td>Assignee</td>
+					<td colspan="2"></td>
+				</tr>
+			</thead>
 			<tbody></tbody>
 		</table>
 	</div>
 	</script>
 
 	<!-- TODO一行分のテンプレート（上のtbody部分に挿入される） -->
 	<script type="text/template" id="todo-item-template">
 	<td><input type="checkbox" class="toggle" <%- status === '1' ? 'checked' : '' %>></td>
 	<td style="margin:0px">
 		<span class="todo-edit" style="margin:0px"><%- todo %></span>
 	</td>
 	<td>
+		<span><%- Owner.name %></span>
+	</td>
+	<td>
+		<span><%- Assignee.name %></span>
+	</td>
+	<td>
 		<a class="remove-link" href="#">Delete</a>
+	</td>
+	<td>
 		<a class="detail-link" href="#todo-lists/<%- id %>">Detail</a>
 	</td>
 	</script>
 
 	<!-- 詳細画面のレイアウトテンプレート -->
 	<script type="text/template" id="todo-detail-layout-template">
 	<div id="todo-item"></div>
 	</script>
 
 	<!-- 詳細画面の表示内容テンプレート -->
 	<script type="text/template" id="todo-detail-item-template">
 	<h2>Todo #<%- id %></h2>
 	<div>
 	<textarea style="width:300px;height:50px" id="edit-todo" autofocus placeholder="Todo?"><%- todo %></textarea>
+	<select name="assignee" id="user-list">
+	</select>
 	<input type="button" id="updateTodo" value="Update"></input>
 	<input type="button" id="updateCancel" value="Cancel"></input>
 	</div>
 	</script>
 
〜　略　〜
```

Giải thích chỉnh sửa

- TODO list screen
	- Thêm `<select> </select>` để hiển thị danh sách user
        - Thêm `<thead> </thead>` vào table (vì đã thêm các cột mới )
        - [Delete] và [Details] cần chia thêm `<td> </td>`
- TODO detail screen
	- Thêm `<select> </select>` để hiển thị danh sách user.

### user-collection.js

```js:user-collection.js
//User list collection
define(function(require) {
	var UserModel = require('models/user-model');

	var UserCollection = Backbone.Collection.extend({
		url : '/rest-study/users.json',
		model : UserModel,

		parse : function(response) {
			return response;
		}
	});
	
	return UserCollection;
});
```

Required mình sẽ require tối thiểu một collection.
Mình không cần giải thích gì cả.


### todo-model.js

Chúng ta đang phân tích `Owner` và `Assignee` khi thêm một TODO mới.

```diff:todo-model.js
 //Model representing County Todo data 1
 define(function() {
 	var TodoModel = Backbone.Model.extend({
 		urlRoot : '/rest-study/todo_lists',
 		parse : function(response) {
 			//モデルをパース
 			console.log("モデルをパース");
 			console.log(response);
-			return response.TodoList;
+			var parsed = response.TodoList;
+			if (response.Owner) {
+				parsed.Owner = response.Owner;
+				parsed.Assignee = response.Assignee;
+			}
+			return parsed;
 		},
 		toggle : function() {
 			this.set('status', this.get("status") === '1' ? '0' : '1');
 			this.save();
 		}
 	});
 	return TodoModel;
 });
```

Khi chuyển thành JSON thì TODO1 sẽ theo format sau, trả về từ server bằng CakePHP,

```json:ServerResponseJSON
{
    "TodoList": {
        "id": "62",
        "todo": "Watching TV",
        "status": "0",
        "owned": false,
        "assigned": false
    },
    "Owner": {
        "id": "67",
        "name": "Yamada"
    },
    "Assignee": {
        "id": "67",
        "name": "Yamada
    }
}
```

Nó sẽ thay đổi sang format sau

```json:ServerResponseJSON
{
    "id": "62",
    "todo": "Watching TV",
    "status": "0",
    "owned": false,
    "assigned": false,
    "Owner": {
        "id": "67",
        "name": "Yamada"
    },
    "Assignee": {
        "id": "67",
        "name": "Yamada"
    }
}
```

### todo-layout-view.js

Bạn cần thêm một tiến trình để đọc danh sách user.

```diff:todo-layout-view.js
 define(function(require){
 	var TodoCompositeView = require('views/todo-composite-view');
 	var TodoCollection = require('collections/todo-collection');
+	var UserCollection = require('collections/user-collection');
 
 	var TodoLayoutView = Marionette.LayoutView.extend({
 		//テンプレート
 		template: '#todo-layout-template',
 
 		regions : {
 			listRegion : '#todo-lists',
 		},
 
-		onRender : function(){
+		onRender : function() {
+			this.userCollection = new UserCollection();
+			this.listenTo(this.userCollection, 'reset', this.onLoadUsers, this);
+			this.userCollection.fetch({reset : true});
+		},
+
+		onLoadUsers : function(userCollection) {
 			var todoCollection = new TodoCollection();
 			this.listenTo(todoCollection , 'reset', this.showTodoList, this);
 			todoCollection.fetch({reset : true});
 		},
 
 		showTodoList : function(todoCollection){
 			this.listRegion.show( new TodoCompositeView({
-				collection : todoCollection
+				collection : todoCollection,
+				userList : this.userCollection.models
 			}));
 		},
 
 	});
 	return TodoLayoutView;
 });
```

- Đọc `collections/user-collection` bằng `require`
- Chỉ trong hàm `onRender`, mình sẽ lấy về danh sách user từ server bằng `this.userCollection.fetch()`.
- Khi đã đổ dữ liệu về xong (ListenTo đã hoạt động), Chạy hàm `onLoadUsers`, và kéo theo đó là `showTodoList` cũng được thực thi
- Ở hàm `showTodoList`, trong khi khởi tạo `TodoCompositeView`, chúng ta cần lấy collection của user list.
- Chúng ta không xem user list collection trong tiến trình của `TodoCompositeView`.

### `todo-composite-view.js`

Nó sẽ hiển thị danh sách user

```diff:todo-composite-view.js
 //View của TODO list
 define(function(require) {
 	var TodoItemView = require('views/todo-item-view');
 
 	var TodoCompositeView = Marionette.CompositeView.extend({
 		template: '#todo-composite-template',
 
 		childView : TodoItemView,
 
 		childViewContainer : 'tbody',
 
 		ui : {
 			addTodo : '#addTodo',
-			newTodo : '#new-todo'
+			newTodo : '#new-todo',
+			userList : '#user-list'
 		},
 
 		events : {
 			'click @ui.addTodo' : 'onCreateTodo',
 		},
 
-		initialize: function(){
+		initialize: function(options){
 			_.bindAll( this, 'onCreatedSuccess' );
+			this.userList = options.userList;
 		},
 
+		onRender : function() {
+			//ユーザ一覧を表示
+			this.showUserList(this.ui.userList, this.userList);
+			//ログインユーザをデフォルトで選択状態にする
+			this.ui.userList.val(window.application.loginUser.id);
+		},
+					
+		//ユーザ一覧を表示
+		showUserList : function($list, userList){
+			$.each(userList, function(index, userModel) {
+				$list.append(
+					"<option value='" 
+					+ userModel.attributes.id + "'>"
+					+ userModel.attributes.name + "</option>");
+			});
+		},
+			
 		onCreateTodo : function() {
 			this.collection.create(this.newAttributes(), {
 		          silent:  true ,
 		          success: this.onCreatedSuccess
 			});
 			this.ui.newTodo.val('');
 		},
 
 		newAttributes : function() {
 			return {
 				todo : this.ui.newTodo.val().trim(),
-				status : 0
+				status : 0,
+				assignee : this.ui.userList.val()
 			};
 		},
 
 		onCreatedSuccess : function(){
 			this.collection.fetch({ reset : true });
 		},
 
 	});
 	return TodoCompositeView;
 });
```


- Thêm `userList` vào biến `ui`để xử lý select box của user list.
- Ở trong hàm `initialize`, tại todo-layout-view từ collection danh sách user đã lấy về (đổ vào `options.userList`) của chính nó (và truyền vào this.userList)
- Ở trong hàm `onRender`, hiển thị danh sách user bằng cách chạy hàm `showUserList`.
- Sau khi có user list, để lấy ID từ thông tin user của user đã login thì mình sẽ sử dụng hàm `window.application.loginUser` lúc login, Và người đó (login user) sẽ được chọn mặc định ở user list. 
- Tại hàm `showUserList`, mình sẽ sử dụng jquery, chúng ta sẽ sử dụng tag `<option> </ option>` và tag `<select> </ select>` để chứa những thành phần được gọi trong biến `ui`.
- Khi một TODO được thêm vào  ( Hàm `newAttributes`), mình sẽ set người được chọn trong user list vào (`assignee`).

### todo-item-view.js

Chúng ta cần thêm phần quản lý sự hiển thị các button Delete-detail.

```diff:todo-item-view.js
 //View của mỗi TODO
 define(function(){
 	var TodoItemView = Marionette.ItemView.extend({
 		//Tag name của thành phần trong DOM
 		tagName : 'tr',
 
 		//Tamplate
 		template : '#todo-item-template',
 
 		ui : {
 			checkBox : '.toggle',
-			removeLink : '.remove-link'
+			removeLink : '.remove-link',
+			detailLink : '.detail-link'
 		},
 
 		//Cac su kien o DOM
 		events : {
 			//Khi click vao Checkbox
 			'click @ui.checkBox' : 'onStatusToggleClick',
 			//Khi click button Delete
 			'click @ui.removeLink' : 'onRemoveClick',
 		},
 
 		onStatusToggleClick : function() {
 			this.model.toggle();
 		},
 
 		onRemoveClick : function() {
 			this.model.destroy({
 				wait : true
 			});
 		},
 
+		onRender : function() {
+			if (!this.model.attributes.owned) {
+				//Neu khong phai Owner thi hide nut delete
+				this.ui.removeLink.css({
+					display : 'none' 
+					//hoac dung visibility : hidden cung duoc
+				});
+				if (!this.model.attributes.assigned) {
+					//truong hop khong phải assignee
+					//hide link vao detail
+					this.ui.detailLink.css({
+						display : 'none'
+						//hoac dung visibility : hidden cung duoc
+					});
+					//khong cho thay doi trang thai checkbox
+					this.ui.checkBox.prop('disabled', true);
+				}
+			}
+		}
 	});
 	return TodoItemView;
 }); 
```

- Thêm biến `detailLink` và Delete link vào `ui`.
- Trong hàm `onRender`, mình sẽ quản lý sự hiển thị hay không của các link xóa và detail theo quy tắc sau.
	- Ẩn Delete link nếu đây không phải owner
		- Trong trường hợp đây không phải là người được giao làm TODO này
			- Vẫn hiện link detail
			- Checkbox sẽ bị cố định và không thể thay đổi

※Cách thực hiện việc quản lý ẩn hiện.

- Ản link
	- Thêm css bằng jquery, thêm vào style `display : 'none'`
- Check box cố định
	- Thêm hàm `prop` bằng jquery, `disabled` chuyển thành `true`.

### todo-detail-layout-view.js

Bạn phải thêm một tiến trình đọc user list.
Những thứ bạn đang làm gần như giống với `todo-layout-view.js`.

```diff:todo-detail-layout-view.js
 define(function(require) {
     var TodoDetailItemView = require('views/todo-detail-item-view');
     var TodoModel = require('models/todo-model');
+    var UserCollection = require('collections/user-collection');
 
 	var TodoDetailLayoutView = Marionette.LayoutView.extend({
 		//テンプレート
 		template : '#todo-detail-layout-template',
 
 		regions : {
 			itemRegion : '#todo-item',
 		},
 
 		onRender : function() {
-			var todoModel = new TodoModel({
-				id : this.options.modelId
-			});
-			//モデルのサーバからのデータ取得完了時、描画を行う
-			this.listenTo(todoModel, 'sync', this.showItem, this);
-			//サーバからデータ取得
-			todoModel.fetch({
-				wait : true
+			this.userCollection = new UserCollection();
+			this.listenToOnce(this.userCollection, 'reset', this.onLoadUsers, this);
+			this.userCollection.fetch({
+				reset : true
 			});
 		},
 
-		showItem : function(todoModel) {
-			this.itemRegion.show( new TodoDetailItemView({
-				model : todoModel
-			}));
-		},
+		onLoadUsers : function(userCollection){
+ 			var todoModel = new TodoModel({
+ 				id : this.options.modelId
+ 			});
+ 			//モデルのサーバからのデータ取得完了時、描画を行う
+			this.listenToOnce(todoModel, 'sync', this.showItem, this);
+ 			//サーバからデータ取得
+ 			todoModel.fetch({
+ 				wait : true
+ 			});
+ 		},
+
+ 		showItem : function(todoModel) {
+ 			this.itemRegion.show(new TodoDetailItemView({
+ 				model : todoModel,
+ 				userList : this.userCollection.models
+ 			}));
+ 		},
 
 	});
 	return TodoDetailLayoutView;
 });
```

- Đọc `collections/user-collection` thông qua `require`
- Trong hàm `onRender`, Chạy hàm `this.userCollection.fetch()` để đổ dữ liệu danh sách user về từ server.
- Được thực hiện từ `onRender`, tiến trình nhận dữ liệu ở model thứ 1 từ server, bạn đã được chuyển đến `onLoadUsers`.
- Khi quá trình đổ dữ liệu hoàn tất (ListenTo đã chạy), Chạy hàm `onLoadUsers`,`todoModel.fetch()` để lấy dữ liệu của TODO 1.
- Trong hàm `showItem`, trong phần initialization của `TodoDetailItemView`, mình cần truyền collection của user list
- Hiển thị user list collection trong các tiến trình của `TodoDetailItemView`.

### `todo-detail-item-view.js`

Hiển thị todo list

```diff:todo-detail-item-view.js
 //詳細ビュー
 define(function() {
 	var TodoDetailItemView = Marionette.ItemView.extend({
 
 		//テンプレート
 		template: "#todo-detail-item-template",
 
 		ui : {
 			todoStatus   : '#edit-todo',
 			updateButton : '#updateTodo',
-			cancelButton : '#updateCancel'
+			cancelButton : '#updateCancel',
+			userList     : '#user-list'
 		},
 
 		//DOMイベントハンドラ設定
 		events : {
 			//更新ボタンクリック時
 			'click @ui.updateButton' : 'onUpdateClick',
 			//キャンセルボタンクリック時
 			'click @ui.cancelButton' : 'onCancelClick',
 		},
 
 		//初期化
-		initialize: function(){
+		initialize: function(options){
 			_.bindAll( this, 'onSaveSuccess' );
+			this.userList = options.userList;
+		},
+
+		onRender : function() {
+			//ユーザ一覧を表示
+			this.showUserList(this.ui.userList, this.userList);
+			//担当者を選択状態にする
+			this.ui.userList.val(this.model.attributes.assignee);
+		},
+
+		//ユーザ一覧を表示
+		showUserList : function($list, userList){
+			$.each(userList, function(index, userModel) {
+			$list.append(
+				"<option value='" 
+				+ userModel.attributes.id + "'>"
+				+ userModel.attributes.name + "</option>");
+			});
 		},
 
 		//更新ボタンクリックのイベントハンドラ
 		onUpdateClick : function() {
 			//テキストボックスから文字を取得
-			var todoString = this.ui.todoStatus.val();
+			var todoString = this.ui.todoStatus.val();  // Todo
+			var assigneeId = this.ui.userList.val();	// 担当者
 			this.model.save({
-				todo : todoString
+				todo : todoString,
+				assignee : assigneeId
 			}, {
 				silent : true,
 				success : this.onSaveSuccess,
 			});
 		},
 
 		//キャンセルボタンクリックのイベントハンドラ
 		onCancelClick : function() {
 			this.backTodoLists();
 		},
 
 		//更新成功
 		onSaveSuccess : function() {
 			this.backTodoLists();
 		},
 
 		//TODOリスト画面に戻る
 		backTodoLists : function() {
 			Backbone.history.navigate('#todo-lists', true);
 		}
 
 	});
 	return TodoDetailItemView;
 });
```

Để hiển thị user list, tương tự như đã làm với `todo-composite-view.js`.


- Thêm `userList` vào biến `ui` để xử lý select box của user list.
- Trong hàm `initialize`, tại todo-layout-view được truyền từ user list collection ( vào `options.userList`) của chính nó (nên được lưu ở this.userList)
- Trong hàm `onRender`, hiển thị userlist bằng cách chạy hàm `showUserList`.
- Sau user list mình set vào state được chọn ( lấy từ `assignee` của model) user ID của TODO của mỗi cá nhân sẽ được hiển thị.
- Hàm `ShowUserList` là việc copy và paste của cùng 1 hàm từ `todo-composite-view.js`! ( Các bạn có thể xem giải thích ở trên kia!)
- Trong tiến trình của việc click button Update (`onUpdateClick` function), mình sẽ sẽ cho phép người dùng được chọn ở danh sách user được phân công (`assignee`).



### Tổng kết bài học

Các bước thực hiện của ta như sau.


- :white_check_mark: `app/View/Layouts/default.ctp` sửa như trên.
- :white_check_mark: `app/webroot/js/collections/user-collection.js` sửa như trên.
- :white_check_mark: `app/webroot/js/models/todo-model.js` sửa như trên.
- :white_check_mark: `app/webroot/js/views/todo-layout-view.js` sửa như trên.
- :white_check_mark: `app/webroot/js/views/todo-composite-view.js` sửa như trên.
- :white_check_mark: `app/webroot/js/views/todo-item-view.js` truyền thêm những giá trị mới như trên.
- :white_check_mark: `app/webroot/js/views/todo-detail-layout-view.js` truyền thêm những giá trị mới như trên.
- :white_check_mark: `app/webroot/js/views/todo-detail-item-view.js` truyền thêm những giá trị mới như trên.
- :white_check_mark: Kiểm tra kết quả.
- :white_check_mark: Commit lên Git


**Tham khảo Diff hoàn chỉnh trên Github**

[第6回 Lesson1 クライアントサイド · suzukishouten-study/rest-study@f1623dc](https://github.com/suzukishouten-study/rest-study/commit/f1623dce75cbd2d38a4b971ffcb5b48ca6d4b91b)

Đó là tất cả bài học này.


## Tôi rất mong nhận được sự phản hồi / góp ý của các bạn

Cảm ơn các bạn đã quan tâm theo dõi series, rất cả các bài học tôi sẽ đều chỉnh sửa sau khi hiểu thêm kỹ hơn vấn đề gì, hoặc nhận ra sai sót, rất mong luôn nhận được sự góp ý từ các bạn.