Ruby on Rails初心者の学習記録 Part3：CRUD（前編）

# はじめに
Ruby on Rails初心者の学習記録Part3です。
[Part1](https://qiita.com/Yasushi-Mo/items/d24fd4abab2c1333c522)では、Rubyの基本文法やRailsを利用したWebアプリケーションの開発環境構築手順及びルーティングについて学びました。
[Part2](https://qiita.com/Yasushi-Mo/items/5dfe81d39b5a153fe43c)では、MVCアーキテクチャのモデルについて学びました。
Part3とPart4では、 ["Getting Started with Rails"](https://guides.rubyonrails.org/getting_started.html) の ["7 CRUDit Where CRUDit Is Due"](https://guides.rubyonrails.org/getting_started.html#crudit-where-crudit-is-due) を教材にして、**CRUD**を学んでいきたいと思います。まずPart3では、CRUDの **Create** と **Read** を学びます。Railsには、CRUDをシンプルにコーディングできる多くの機能があるようなので、実際にコーディングしながらそれらの機能を扱っていければと思っています。

# 1. 記事の表示
最初はCRUDの **R（Read）** を扱います。
[Part2](https://qiita.com/Yasushi-Mo/items/5dfe81d39b5a153fe43c)で、記事一覧画面を作成しました。次は各記事を表示する画面を作成します。

## ⅰ. ルーティングの追加
まず、新しいルーティングを追加します。`config/routes.rb`を開き、`get "/articles/:id", to: "articles#show"`を追加します。
```routes.rb
Rails.application.routes.draw do
  root "articles#index"
  
  get "/articles", to: "articles#index"
  get "/articles/:id", to: "articles#show"
end
```

## ⅱ. コントローラーへのアクションの追加
ⅰ. で追加したパスの末尾の`:id`は、ルーティングのパラメーターを指定します。ルーティングパラメーターはリクエストパスの特定の値を取得し、`params`というハッシュ内にその値を追加します。`params`はコントローラーのアクションからアクセスできます（コントローラーの`show`というアクション内で`params[:id]`のようにしてアクセスできます）。
では、`app/controllers/articles_controller.rb`内に`show`アクションを追加します。
```articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @articles = Article.find(params[:id])
  end
end
```
`show`アクションはルーティングパラメーターで取得したIDを引数に`Artcle.find`をコールしています（findについては、[Part2](https://qiita.com/Yasushi-Mo/items/5dfe81d39b5a153fe43c#4-%E3%83%A2%E3%83%87%E3%83%AB%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A8%E3%81%AE%E3%82%84%E3%82%8A%E3%81%A8%E3%82%8A)でも扱いました）。戻り値は`@article`インスタンス変数内に保存され、ビューからアクセスできるようになります。

## ⅲ. ビューの追加
デフォルトでは、ⅱ. で追加した`show`アクションは`app/views/articles/show.html.erb`にレンダリングされます。
そのため、`app/views/articles/show.html.erb`を作成します。
```show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>
```
これで http://localhost:3000/articles/1 にアクセスすると、1つ目の記事が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/df435902-028b-8b88-3400-5e47ad6d238b.png)
最後に記事一覧画面から記事画面へ遷移するリンクを追加します。
```index.html.erb
<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="/articles/<%= article.id %>">
        Title: <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>
<p>Find me in app/views/articles/index.html.erb</p>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/c632f940-828d-aca8-db3d-9b1a62240dea.png)

# 2. Resourceful Routing
**Create** を扱う前により便利なルーティング方法があるようなので、確認します。
1.で実施したように新しくCRUDを追加する際は、以下3つ作業が必要になります。
1. ルーティングの追加
1. コントローラーへのアクションの追加
1. ビューの追加

ルーティング、コントローラーのアクション、ビューはいつでもエンティティのCRUD操作として機能します。このようなエンティティは *resource (リソース)* と呼ばれます。例えば、今開発しているアプリケーションでは、1つの記事が1つのリソースに該当します。
Railsは`resources`というルーティングメソッドを提供しています。`resources`は`articles`といったリソースのコレクション向けの全てのルーティングを紐づけます。
ということで、`config/routes.rb`内の`get`を利用したルーティングを`resources`に置き換えます。
```routes.rb
Rails.application.routes.draw do
  root "articles#index"

  # Before
  # get "/articles", to: "articles#index"
  # get "/articles/:id", to: "articles#show"
  # After
  resources :articles
end
```
以下のコマンドでルーティングの紐づけを確認できます。
```
rails routes
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/df1c9eae-a01c-7da8-a7a2-2a83930120d8.png)
`resources`メソッドはURLとパスのヘルパーメソッドも設定します。パスのヘルパーメソッドにより、コードが特定のルーティング設定に依存することを防げます。Prefixカラムの値には、`_url`や`_path`といったサフィックスが追加されます。例えば、`article_path`ヘルパーは、記事を1件渡されると、`"/articles/#{article.id}"`を返します。これにより`app/views/articles/index.html.erb`内のリンクを簡潔にできます。
```index.html.erb
<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
    　# Before
      # <a href="/articles/<%= article.id %>">
      # After
      <a href="<%= article_path(article) %>">
        Title: <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>
<p>Find me in app/views/articles/index.html.erb</p>
```
さらに`link_to`ヘルパーを使うことでより便利にできます。`link_to`ヘルパーは第一引数をリンクのテキスト、第二引数をリンク先として、リンクをレンダリングします。モデルオブジェクトを第二引数にすると、`link_to`は適切なパスヘルパーを呼び出してオブジェクトをパスに変換します。例えば、articleを渡すと`link_to`は`article_path`をコールします。これを利用すると、` app/views/articles/index.html.erb`を以下のように変えることができます。
```index.html.erb
<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="<%= article_path(article) %>">
        # Before
        # Title: <%= article.title %>
        # After
        Title: <%= link_to article.title, article %>
      </a>
    </li>
  <% end %>
</ul>
<p>Find me in app/views/articles/index.html.erb</p>
```
:::note
より詳しいルーティングの説明は、["Rails Routing from the Outside In"](https://guides.rubyonrails.org/routing.html) に記載があります。
:::

# 3. 記事の作成
リソースフルルーティングを学んだので、CRUDの学習に戻ります。ここでは、**Create**を扱います。Webアプリケーションでは、新しいリソースを作成する際、以下のように複数のプロセスが必要になります。
```mermaid
sequenceDiagram
    Client->>Server: request Form
    Server-->>Client: Form
    Client->>Server: submit Form
    alt Valid Form
    Server->>Server: create Resource
    Server-->>Client: View    
    else Invalid Form
    Server-->>Client: Error Message
    end
```
Rialsを利用したアプリケーションでは、コントローラー`new`と`create`アクションによって通常これらのステップは制御されます。では、これらのアクションを`app/controllers/articles_controller.rb`の`show`アクションの下に追加します。
```articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(title: "...", body: "...")

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
end
```
`new`アクションは新しい記事をインスタンス化しますが、保存はしません。この記事はフォーム作成時にビュー内で利用されます、デフォルトでは、`new`アクションは次に作成する`app/views/articles/new.html.erb`でレンダリングされます。
`create`アクションは、タイトルと本文を持つ新しい記事をインスタンス化し、保存します。保存が成功すれば、その記事の画面（`"http://localhost:3000/articles/#{@article.id}"`）にリダイレクトします、失敗したら、`app/views/articles/new.html.erb`で 422 Unprocessable Entity というステータスコードを表示します。ここのタイトルと本文はダミーの値が入っています。フォームが作成された後、ユーザーが変更します。

:::note warn
`redirect_to`を使うとブラウザで新しいリクエストが発生しますが、`render`は指定のビューを現在のリクエストとしてレンダリングします。そのため、データベースやアプリケーションのステートが変更された後に`redirect_to`を利用することが重要です。そうしないとユーザーが画面をリロードしたら、同じリクエストが再送信され、変更が重複してしまいます。
:::

## ⅰ. Form Builderの利用
フォームを作成するために *Form Builder* というRailsの機能を利用します。Form Builderを使うことで、最小限のコードでRailsの規約に沿った設定が全てできあがったフォームが作成できます。
それでは、`app/views/articles/new.html.erb`を作成します。
```new.html.erb
<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :bidy %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>
```
`form_with`ヘルパーメソッドがForm Builderをインスタンス化します。フォーム要素を出力するために`form_with`ブロック内で`label`や`text_field`のようなメソッドをコールします。

:::note
より詳しいForm Builderの説明は、["Action View Form Helpers"](https://guides.rubyonrails.org/form_helpers.html) に記載があります。
:::
これで http://localhost:3000/articles/new にアクセスすると、新規記事作成画面が表示され、記事のタイトルと本文を入力することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/3dbee425-caee-9b3b-39f5-1d80e921f15a.png)

## ⅱ. Strong Parametersの利用
送信されたフォームデータは`params`ハッシュ内に保存され、ルーティングパラメータも同様に取得されます。そのため、`create`アクションでは、タイトルは`params[:article][:title]`から取得でき、本文は`params[:article][:body]`から取得できます、これらの値をそれぞれ`Article.new`へ渡すこともできますが、複雑化するとエラーの発生確率も増えます。
その代わりに1つのハッシュに全ての値を含めて渡します。しかし、その場合もハッシュ内でどの値が許容されているか指定しなければなりません。そうしないと悪意のあるユーザーがフィールを追加し、プライベートなデータを上書きすることができます。実際には、フィルターがない`params[:article] `ハッシュを直接`Article.new`に渡すと、Railsは`ForbiddenAttributesError`を出力します。そのため、`params`をフィルターするために *strong parameters* というRailsの機能を利用します。
それでは、`app/controllers/articles_controller.rb`の最後に`params`をフィルターする`article_params`を定義します。そして、`article_params`を`create`アクションで使うよう変更します。
```articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end
```

:::note
より詳しいStrong Parametersの説明は、["Action Controller Overview § Strong Parameters"](https://guides.rubyonrails.org/action_controller_overview.html#strong-parameters) に記載があります。
:::

## ⅲ. バリデーションとエラーメッセージ
これまで見てきたようにリソースの作成は複数のプロセスがありました。ここでは、その中の1つであるバリデーションを扱います。Railsにはバリデーション機能があります。バリデーションは、モデルオブジェクトが保存される前に実施されます。エラーがあった場合、保存はされず、モデルオブジェクトの`errors`属性にエラーメッセージが追加されます。
それでは、`app/models/article.rb`内のモデルにいくつかバリデーションを追加します。
```article.rb
class Article < ApplicationRecord
  validates :title, presence: true
  validates:body, presence: true, length: { minimum: 10 }
end
```

:::note warn
Active Recordは全てのテーブルのカラムにモデル属性を自動的に定義します。そのため、モデルファイル内で`title`や`body`属性を定義する必要はありません。
:::
バリデーションを追加したので、エラーメッセージを表示させるために`app/views/articles/new.html.erb`を修正しましょう。
```new.html.erb
<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title>
    <% @article.errors.full_message_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :bidy %>
    <% @article.errors.full_message_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>
```
`full_messages_for`メソッドは、エラーメッセージの配列を返します。エラーがない場合、配列は空になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/df359f97-1225-69c8-53f8-71a5bbb49fa9.png)
これらがどのように動作しているか理解するために改めてコントローラーの`new`と`create`のアクションを確認します。
```articcles_controller.rb
  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
```
- http://localhost:3000/articles/new アクセス時
    - `new`アクションにマッピングされるため、保存されない。そのため、バリデーションは発生しない。
```mermaid
sequenceDiagram
    Client->>+Controller: GET /articles/new
    Note right of Controller: new action
    Controller-->>-Client: Form
    Note over Client,Controller: no validation, no error message
```
- フォーム送信時
    - `create`アクションにマッピングされるため、保存される。そのため、バリデーションが発生する。
```mermaid
sequenceDiagram
    Client->>+Controller: POST /articles
    Note right of Controller: create action
    Controller-->>Client: View
    Note over Client,Controller: validation, error message
```

:::note
より詳しいバリデーションの説明は、["Active Record Validations"](https://guides.rubyonrails.org/active_record_validations.html) に記載があります。また、より詳しいバリデーションエラーメッセージの説明は、["Active Record Validations § Working with Validation Errors" ](https://guides.rubyonrails.org/active_record_validations.html#working-with-validation-errors)に記載があります。
:::

## ⅳ. 仕上げ
仕上げとして、記事一覧画面（`app/views/articles/index.html.erb`）に記事作成画面のリンクを追加します。
```index.html.erb
<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="<%= article_path(article) %>">
        Title: <%= link_to article.title, article %>
      </a>
    </li>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>
```
これで記事一覧画面から新規記事作成画面へ遷移することができるようになります。
![ezgif-5-d236611bfd.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/e2733346-0d28-d6ae-bc26-634fbac2300a.gif)


# 最後に
今回は["Getting Started with Rails"](https://guides.rubyonrails.org/getting_started.html) の ["7 CRUDit Where CRUDit Is Due"](https://guides.rubyonrails.org/getting_started.html#crudit-where-crudit-is-due) を教材にして、**CRUD** の **Create** と **Read** を学びました。Railsでは、Form Builder や Strong Parameters などCRUD実装に役立つ機能が多数提供されていました。
次も引き続きCRUDを学んでいきます。次は残りの **Update** と **Delete** を学びます。
最後までお読みいただきありがとうございました！

- [Part1](https://qiita.com/Yasushi-Mo/items/d24fd4abab2c1333c522)
- [Part2：モデル](https://qiita.com/Yasushi-Mo/items/5dfe81d39b5a153fe43c)
- [Part4：CRUD（後編）](https://qiita.com/Yasushi-Mo/items/337d5e31e0ea1f56485f)

# 参考
- [Getting Started with Rails](https://guides.rubyonrails.org/getting_started.html)