CakePHPでRails Tutorialをやってみる〜その５ ユーザー登録〜

# はじめに

* [その１ Cloud9の設定](http://qiita.com/naoki85/items/f4bfabc941381ca5ff35)
* [その２ ほぼ静的なページの作成](http://qiita.com/naoki85/items/0b7e7ea106a1c661ed35)
* [その３ レイアウトの作成](http://qiita.com/naoki85/items/d4bc9e333f2f5b2bcff3)
* [その４ ユーザーのモデルを作成する〜](http://qiita.com/naoki85/items/29e8576b694a5bc89015)

前回の続きです。
今回は[Rails Tutorialの7章](https://railstutorial.jp/chapters/sign_up?version=5.0#cha-sign_up)をやりたいと思います。
デバッグの部分などは省略しております。

[Rails Tutorial](https://railstutorial.jp/chapters/sign_up?version=5.0#cha-sign_up)では、自分でゴリゴリ書いていきますが、ここでは`bake`コマンドを使用して作成した後、修正をしていく方針にします。
# bakeコマンドでコントローラーとテンプレートを生成
今回作成したいものは、[前回](http://qiita.com/naoki85/items/29e8576b694a5bc89015)作成した`User`モデルに対するCRUDです。
まずは、コントローラーです。

```
$ bin/cake bake controller Users
# 以下のファイルが作成されます
/src/Controller/UsersController.php
tests/TestCase/Controller/UsersControllerTest.php
```
次にテンプレートです。

```
$ bin/cake bake template Users
# 以下のファイルが作成されます
src/Template/Users/index.ctp
src/Template/Users/view.ctp
src/Template/Users/add.ctp
src/Template/Users/edit.ctp
```
それぞれ、`index`（一覧）、`view`（詳細）、`add`（追加）、`edit`（編集）になります。
コントローラーには`delete`もありますが、削除したら一覧にリダイレクトするので、テンプレートはありません。

サーバーを起動させた状態で、`/users`にアクセスして下図のような画面が表示されれば、OKです。
![スクリーンショット 2017-09-08 20.34.52.png](https://qiita-image-store.s3.amazonaws.com/0/118817/baf4eaf1-1868-3573-ae03-d172c7d324be.png)
# ビューを修正
ビューの修正は[Bootstrap](http://bootstrap3.cyberlab.info/gettingStarted/)を基本としてスタイルをあてていきます。
Bootstrap以外の部分については、`custom.css`に記載していきますので、後ほど記載する差分をご参照ください。
## 新規登録の入力フォームを修正
ただ、`bake`で生成されたビューファイルでは、新規登録の入力フォームが`password_digest`になってしまっています。
`User`モデルは`password`と確認用の`password_confirmation`を受け取り、一致していたらハッシュ化して`password_digest`に放り込みます。
そのため、`add.php`のフォーム部分を修正します。

```php:src/Template/Users/add.php
<?= $this->Form->create($user) ?>
<fieldset>
    <?php
        echo $this->Form->control('name',
            ['class' => 'form-control', 'require' => true]);
        echo $this->Form->control('email',
            ['type' => 'email', 'class' => 'form-control', 'require' => true]);
        echo $this->Form->control('password',
            ['type' => 'password', 'class' => 'form-control', 'require' => true]);
        echo $this->Form->control('password_confirmation',
            ['type' => 'password', 'class' => 'form-control', 'require' => true]);
    ?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
```
[Form](https://book.cakephp.org/3.0/ja/views/helpers/form.html)
`$this->Form->control`を使用することで、`input`タグを生成してくれますが、第二引数でHTML属性を連想配列で渡すことでお好みで設定できます。
## エレメントを使用して編集画面でも使い回す
実は、`add.php`と`edit.php`はフォーム部分が（見た目上は）ほとんど変わらないので、エレメントにしたいと思います。
幸いなことに、`$this->Form->create`は現在のURLから`action`属性を推測してくれます。
[フォームの開始](https://book.cakephp.org/3.0/ja/views/helpers/form.html#id1)
例えば、

* `users/add`で呼ばれれば、`action="users/add" method="post"`になります。
* `users/edit/2`で呼ばれれば、`action="users/edit/2" method="post"`になります。

つまり、`create`が勝手に判断してくれるので、`add`と`edit`にそれぞれ書く必要ななさそうです。
（もちろん、ケースバイケースだとは思いますが。。。）

```php:src/Template/Element/Users/form.ctp
<?= $this->Form->create($user) ?>
<fieldset>
    <?php
        echo $this->Form->control('name',
            ['class' => 'form-control', 'require' => true]);
        echo $this->Form->control('email',
            ['type' => 'email', 'class' => 'form-control', 'require' => true]);
        echo $this->Form->control('password',
            ['type' => 'password', 'class' => 'form-control', 'require' => true]);
        echo $this->Form->control('password_confirmation',
            ['type' => 'password', 'class' => 'form-control', 'require' => true]);
    ?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
```
```php:src/Template/Users/add.ctp
<div class="users form col-lg-9 col-md-8 columns content">
    <legend><?= __('Add User') ?></legend>
    <?= $this->element('Users/form'); ?>
</div>
```
```php:src/Template/Users/edit.ctp
<div class="users form col-lg-9 col-md-8 columns content">
    <legend><?= __('Edit User') ?></legend>
    <?= $this->element('Users/form'); ?>
</div>
```
# コントローラーの統合テストを書いてみる
基本的には、[コントローラーの統合テスト](https://book.cakephp.org/3.0/ja/development/testing.html#integration-testing)を参考にしながら記載しました。
テストの手順としては、下記の通りです。
## #setup、#index、#view

```php:tests/TestCase/Controller/UsersControllerTest.php
    public function setUp()
    {
        parent::setUp();
        $this->base_title = 'Ruby on Rails Tutorial Sample App';
        // UsersTableを使用することを宣言しておく
        $this->users      = TableRegistry::get('Users');
    }

    /**
     * Test index method
     *
     * @return void
     */
    public function testIndex()
    {
        // GETメソッドで/usersにアクセス
        $this->get('/users');
        // レスポンスコードが200で返ってくる
        $this->assertResponseOk();
        // 正しいテンプレートが選択されている
        $this->assertTemplate('Users/index');
        // タイトルタグの記載内容が正しい
        $this->assertResponseContains("Users Index | {$this->base_title}");
    }

    /**
     * Test view method
     *
     * @return void
     */
    public function testView()
    {
        // GETメソッドで/users/view/1にアクセス
        $this->get('/users/view/1');
        // レスポンスコードが200で返ってくる
        $this->assertResponseOk();
        // 正しいテンプレートが選択されている
        $this->assertTemplate('Users/view');
        // タイトルタグの記載内容が正しい
        $this->assertResponseContains("Users View | {$this->base_title}");
    }
```
## #add
テスト前後のレコード数で判断しています。

```php:tests/TestCase/Controller/UsersControllerTest.php
    /**
     * Test add method
     *
     * @return void
     */
    public function testAdd()
    {
        // GETで/users/addにアクセス
        $this->get('/users/add');
        $this->assertResponseOk();
        $this->assertTemplate('Users/add');
        $this->assertResponseContains("Add User | {$this->base_title}");
        
        // POSTで/users/addにアクセス
        // バリデーションエラーになるようパラメーターをわたした場合
        // まずはあらかじめのレコード数を取得しておく
        $before_count = $this->users->find()->count();

        $data = ['name' => 'hogehoge', 'email' => 'email@example.com',
            'password' => 'hogehoge', 'password_confirmation' => 'fugafuga'];
        $this->post('/users/add', $data);

        $this->assertResponseOk();
        // バリデーションエラーになるとaddに戻ってくるので、テンプレートがaddかどうか
        $this->assertTemplate('Users/add');
        // あらかじめのレコード数と変わらないか
        $this->assertEquals($before_count, $this->users->find()->count());
        
        // POST /users/add
        // 適切なパラメーターを渡した場合
        $before_count = $this->users->find()->count();

        $data = ['name' => 'hogehoge', 'email' => 'email@example.com',
            'password' => 'hogehoge', 'password_confirmation' => 'hogehoge'];
        $this->post('/users/add', $data);
        // あらかじめ取得したレコード数より1つ増えているはず
        $this->assertEquals($before_count + 1, $this->users->find()->count());
    }
```
## #edit
テスト後の`name`プロパティが予想された値になっているかどうかで判断しています。
なお、`id = 1`を指定していますが、これはあらかじめフィクスチャの方で作成したものです。
`tests/Fixture/UsersFixture.php`を見てみてください。

```php:tests/TestCase/Controller/UsersControllerTest.php
    /**
     * Test edit method
     *
     * @return void
     */
    public function testEdit()
    {
        // GETで/users/edit/1にアクセス
        $this->get('/users/edit/1');
        $this->assertResponseOk();
        $this->assertTemplate('Users/edit');
        $this->assertResponseContains("Edit User | {$this->base_title}");
        
        // POSTで/users/edit/1にアクセス
        // パスワードが一致していない場合
        $data = ['name' => 'hogehoge', 'email' => 'email@example.com',
            'password' => 'hogehoge', 'password_confirmation' => 'fugafuga'];
        $this->post('/users/edit/1', $data);
        $this->assertResponseOk();
        $this->assertTemplate('Users/edit');
        
        // UsersTableからid=1を取得
        $user = $this->users->get(1);
        // nameがtest_nameのままであることを確認
        $this->assertEquals('test_name', $user->name);
        
        // POSTで/users/edit/1にアクセス
        // 適切なパラメーターを渡した場合
        $data = ['name' => 'hogehoge', 'email' => 'email@example.com',
            'password' => 'hogehoge', 'password_confirmation' => 'hogehoge'];

        $this->post('/users/edit/1', $data);
        $user = $this->users->get(1);
        // ちゃんと更新していれば、nameが変わっているはず
        $this->assertEquals('hogehoge', $user->name);
    }
```
## #delete
当たり前ですが、`id = 1`を消せば、`id = 1`のレコード数が0になるので、そちらを確認します。

```php:tests/TestCase/Controller/UsersControllerTest.php
    /**
     * Test delete method
     *
     * @return void
     */
    public function testDelete()
    {
        $this->post('/users/delete/1');
        $user = $this->users->find()->where(['id' => 1]);
        $this->assertEquals(0, $user->count());
    }
```
# さいごに
今回の差分はこちらになります。
よろしければご覧ください。
[https://github.com/naoki85/cakephp_de_rails_tutorial/commit/31cd105006f3d882678368323b105809c7b21c8b](https://github.com/naoki85/cakephp_de_rails_tutorial/commit/31cd105006f3d882678368323b105809c7b21c8b)

スタイルもこんな感じになります。
![スクリーンショット 2017-09-09 1.02.53.png](https://qiita-image-store.s3.amazonaws.com/0/118817/8ec21050-7cf1-2d2b-66b6-9732fa23f7b9.png)
![スクリーンショット 2017-09-09 1.03.21.png](https://qiita-image-store.s3.amazonaws.com/0/118817/21099bac-4fb1-f5be-714a-79c2f44d7cb1.png)
![スクリーンショット 2017-09-09 1.03.33.png](https://qiita-image-store.s3.amazonaws.com/0/118817/7c2477e5-c0ae-7094-e198-79e6e247487d.png)