LaravelでRailsチュートリアル【第三章】

## これは何？
僕自身、Ruby on Railsを使ってWebアプリ開発をしてきましたが、転職をきっかけにLaravelを使うことになりました。
そこで、Railsエンジニアの間では登竜門のような扱いになっている[Railsチュートリアル](https://railstutorial.jp/)を、Laravelでやってみたら、Laravelにも慣れるし、Railsとの違いが分かって面白いんじゃないかと思って始めたのがこの企画です。

Railsチュートリアルの第一章、二章は簡単なアプリを作り切って終わってしまうので、本格的に作り始める第三章からやっていきたいと思います。
また、本番環境扱いとしてAWSのEC2にデプロイしていきたいと思います。

## 新規プロジェクトの作成
[Railsチュートリアル](https://railstutorial.jp/chapters/static_pages?version=7.0#sec-sample_app_setup)では、新規プロジェクトの作成から行なっています。

```zsh:Rails
rails _7.0.4.3_ new sample_app
```

```zsh:Laravel
composer create-project laravel/laravel laravel_app
```

使っているバージョン情報は、以下の通りとなります。

:::note info
使用バージョン
Laravel Framework 11.32.0
PHP 8.3.12 (cli) (built: Oct  7 2024 10:53:51) (NTS)
:::

## 作業ブランチの作成
[Railsチュートリアル](https://railstutorial.jp/chapters/static_pages?version=7.0#sec-static_pages)に倣って、作業ブランチを作成していきます。

まずは、Gitを導入してmainブランチをGitHubにプッシュ
```zsh:Terminal
git init
git add .
git commit -m "プロジェクト新規作成"
git remote add origin https://github.com/daigo175/Laravel-tutorial.git
git push origin main
```

次に、作業用ブランチを作っていきます。
`git switch -c static-pages`

:::note info
Gitのバージョン
git version 2.39.5 (Apple Git-154)
:::

## 3.2.1静的なページの生成
静的なページを表示するためのコントローラを作成していきます。

```zsh:Rails
rails generate controller StaticPages home help
```

```zsh:Laravel
php artisan make:controller StaticPagesController
```

この結果、Railsは`static_pages_controller.rb`や`home.html.erb`といったビューファイル、テストユニットやヘルパーなども作成されますが、Laravelにはそのようなジェネレータは無さそうです（私が知らないだけかも）

Laravelでは`StaticPagesController.php`が作成されます。
Railsはファイル名がスネークケース、 Laravelはアッパーキャメルケースとなっているのですね。

### コラム 3.1.元に戻す方法
Railsには、コントローラーの生成を取り消すコマンドがあります。
`rails destroy controller StaticPages home help`
Railsのジェネレータは複数のファイルを一気に作成してくれる分、削除コマンドも用意されているのだと思います。

これはLaravelにはありませんが、Laravelは最低限のファイルが生成される分、取消よりも物理的に削除した方が早いのでしょうね。
逆にRailsは不要なファイルも一緒に作ってしまうし、それを防ぐには設定を書かないといけなかったりで、一長一短な気がします。

### ルーティングの追加
コントローラを作成すると、Railsはオプションで渡したルーティングが自動的に追加されます。

```ruby:config/routes.rb
Rails.application.routes.draw do
  get  "static_pages/home"
  get  "static_pages/help"
end
```

Laravelにも同等の定義を追加することにします。

```php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StaticPagesController;
 
Route::controller(StaticPagesController::class)->group(function () {
    Route::get('/static_pages/home', 'home');
    Route::get('/static_pages/help', 'help');
});
```

### コントローラの編集
同様に、Railsは既にアクションが作成済となります。

```ruby:app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
  def home
  end

  def help
  end
end
```

Laravelにも同様のアクションを作成することにします。

```php:app/Http/Controllers/StaticPagesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\View\View;

class StaticPagesController extends Controller
{
    // homeページを返却
    public function home(): View
    {
        return view('static_pages.home');
    }

    // helpページを返却
    public function help(): View
    {
        return view('static_pages.help');
    }

}
```

### ビューの追加
こちらも同様、Railsではビューファイルが作成されています。

```ruby:app/views/static_pages/home.html.erb
<h1>StaticPages#home</h1>
<p>Find me in app/views/static_pages/home.html.erb</p>
```

```ruby:app/views/static_pages/help.html.erb
<h1>StaticPages#help</h1>
<p>Find me in app/views/static_pages/help.html.erb</p>
```

Laravelにも追加を行います。

```zsh:Laravel
mkdir resources/views/static_pages
touch resources/views/static_pages/home.blade.php
touch resources/views/static_pages/help.blade.php
```

ビューの中身はRailsのそれと同様にしておきます。
そして、一度確認してみます。
`php artisan serve`

![スクリーンショット 2024-11-20 16.52.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821993/aebd2a94-88ae-7c0f-df6d-4bcda551f6af.png)
![スクリーンショット 2024-11-20 16.53.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821993/2dcf601e-c4a6-a27e-5602-c9b1e201d433.png)

## 3.3テストから始める
(Railsチュートリアル)[https://railstutorial.jp/chapters/static_pages?version=7.0#sec-getting_started_with_testing]には、まず要件通りにテストコードを書いて、その後にテストが成功するよう作り上げていくというテスト駆動開発手法が載っています（但しTDDを推しているわけではない）

### 3.3.1最初のテスト
コントローラを作成した段階で、Railsはテストユニットも作成しています。

```ruby:test/controllers/static_pages_controller_test.rb
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  test "should get home" do
    get static_pages_home_url
    assert_response :success
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
  end
end
```

今までと同様、Laravelでもテストユニットを新規作成していきます。
`php artisan make:test StaticPageTest`

```php:tests/Feature/StaticPageTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class StaticPageTest extends TestCase
{
    /**
     * homeページへのアクセスが正常であること
     */
    public function test_the_home_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/home');

        $response->assertStatus(200);
    }

    /**
     * helpページへのアクセスが正常であること
     */
    public function test_the_help_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/help');

        $response->assertStatus(200);
    }

}
```

テストユニットができたら、テストを実行して、問題なくパスすることを確認します。

```zsh:Rails
rails test
```

```zsh:Laravel
php artisan test
```

### 3.3.2Red
[Railsチュートリアル](https://railstutorial.jp/chapters/static_pages?version=7.0#sec-red)では、今後`about`ページを追加するにあたり、まず要件（失敗するテスト）から書いていきます。

```diff_ruby:test/controllers/static_pages_controller_test.rb
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  test "should get home" do
    get static_pages_home_url
    assert_response :success
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
  end

+ test "should get about" do
+   get static_pages_about_url
+   assert_response :success
+ end
end
```

Laravelにも同様に、失敗するテストを追加します。

```diff_php:tests/Feature/StaticPageTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class StaticPageTest extends TestCase
{
    /**
     * homeページへのアクセスが正常であること
     */
    public function test_home_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/home');

        $response->assertStatus(200);
    }

    /**
     * helpページへのアクセスが正常であること
     */
    public function test_help_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/help');

        $response->assertStatus(200);
    }

+   /**
+    * aboutページへのアクセスが正常であること
+    */
+   public function test_about_page_returns_a_successful_response(): void
+   {
+       $response = $this->get('/static_pages/about');
+
+       $response->assertStatus(200);
+   }

}
```

テストを実行してみると、想定通りにエラーとなります。
![スクリーンショット 2024-11-21 8.42.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821993/a5344e6f-c2ff-9545-27f3-9a07904e74bc.png)

### 3.3.3Green
Laravelでのテスト実行結果を見ると`Expected response status code [200] but received 404.`とあります。
[Railsチュートリアル](https://railstutorial.jp/chapters/static_pages?version=7.0#sec-green)同様、ルーティングから追加していくことにします。

```diff_ruby:config/routes.rb
Rails.application.routes.draw do
  get  "static_pages/home"
  get  "static_pages/help"
+ get  "static_pages/about"
  root "application#hello"
end
```

```diff_php:routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\StaticPagesController;
 
Route::controller(StaticPagesController::class)->group(function () {
    Route::get('/static_pages/home', 'home');
    Route::get('/static_pages/help', 'help');
+   Route::get('/static_pages/about', 'about');
});
```

この状態でテストを実行すると`Error: Call to undefined method App\Http\Controllers\StaticPagesController::about()`となり、StaticPagesControllerにaboutアクションが存在しないことがわかります。

こちらも追加していきます。

```diff_ruby:app/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

+ def about
+ end
end
```

```diff_php:app/Http/Controllers/StaticPagesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\View\View;

class StaticPagesController extends Controller
{
    // homeページを返却
    public function home(): View
    {
        return view('static_pages.home');
    }

    // helpページを返却
    public function help(): View
    {
        return view('static_pages.help');
    }

+   // aboutページを返却
+   public function about(): View
+   {
+       return view('static_pages.about');
+   }
+   
}
```

この状態でテストを再実行すると`InvalidArgumentException: View [static_pages.about] not found.`となり、ビューが存在していないことがわかります。

こちらもRailsチュートリアル同様作成していきます。
`touch resources/views/static_pages/about.blade.php`

```php:resources/views/static_pages/about.blade.php
<h1>StaticPages#about</h1>
<p>Find me in app/views/static_pages/about.html.erb</p>
```

ここまででテストを再実行すると、今度はパスします。
![スクリーンショット 2024-11-21 8.54.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3821993/ec398ec7-07e6-ee5f-4b6d-0eca737b165e.png)

## 3.4少しだけ動的なページ
このセクションでは、ページのtitleを動的に変更するということをしています。
まずは、失敗するテストを書いていきます。

```ruby:test/controllers/static_pages_controller_test.rb
require "test_helper"

class StaticPagesControllerTest < ActionDispatch::IntegrationTest

  def setup
    @base_title = "Ruby on Rails Tutorial Sample App"
  end

  test "should get home" do
    get static_pages_home_url
    assert_response :success
    assert_select "title", "Home | #{@base_title}"
  end

  test "should get help" do
    get static_pages_help_url
    assert_response :success
    assert_select "title", "Help | #{@base_title}"
  end

  test "should get about" do
    get static_pages_about_url
    assert_response :success
    assert_select "title", "About | #{@base_title}"
  end
end
```

同等のテストをLaravelでも書いていきます。

```php:tests/Feature/StaticPageTest.php
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithFaker;
use Tests\TestCase;

class StaticPageTest extends TestCase
{
    // タイトルの固定文言部分を各テストケースで共有する
    private static $static_title;

    public static function setUpBeforeClass(): void
    {
        self::$static_title = "Ruby on Rails Tutorial Sample App";
    }

    /**
     * HTMLから<title>タグの内容を抽出
     *
     * @param string $html
     * @return string
     */
    private function extractTitle(string $html): string
    {
        preg_match('/<title>(.*?)<\/title>/', $html, $matches);

        return $matches[1] ?? '';
    }

    /**
     * homeページへのアクセスが正常であること
     */
    public function test_home_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/home');
        $response->assertStatus(200);
        // ページ内容からタイトルを抽出
        $title = $this->extractTitle($response->getContent());
        $this->assertStringContainsString("Home | " . self::$static_title, $title);
    }

    /**
     * helpページへのアクセスが正常であること
     */
    public function test_help_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/help');
        $response->assertStatus(200);
        $title = $this->extractTitle($response->getContent());
        $this->assertStringContainsString("Help | " . self::$static_title, $title);
    }

    /**
     * aboutページへのアクセスが正常であること
     */
    public function test_about_page_returns_a_successful_response(): void
    {
        $response = $this->get('/static_pages/about');
        $response->assertStatus(200);
        $title = $this->extractTitle($response->getContent());
        $this->assertStringContainsString("About | " . self::$static_title, $title);
    }

}
```

ここで`setUpBeforeClass()`メソッドを使っています。

:::note info
`setUpBeforeClass()`
テスト実行前に1度だけ呼び出されるメソッド
毎回呼び出すと冗長となってしまうテストデータの設定などに使用できる
:::

各ビューファイルは、チュートリアルに沿って、`<title>About | Ruby on Rails Tutorial Sample App</title>`のように、固定文言を含むレイアウトに書き換えます。

## AWS EC2にデプロイする
最後に、EC2インスタンスにデプロイを行います。
別記事を書いています↓

https://qiita.com/daigo-takayama/items/e81cd6fe04c7c9d2edd0