1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Ruby on Rails初心者の学習記録 Part3:CRUD(前編)

Last updated at Posted at 2023-01-23

はじめに

Ruby on Rails初心者の学習記録Part3です。
Part1では、Rubyの基本文法やRailsを利用したWebアプリケーションの開発環境構築手順及びルーティングについて学びました。
Part2では、MVCアーキテクチャのモデルについて学びました。
Part3とPart4では、 "Getting Started with Rails""7 CRUDit Where CRUDit Is Due" を教材にして、CRUDを学んでいきたいと思います。まずPart3では、CRUDの CreateRead を学びます。Railsには、CRUDをシンプルにコーディングできる多くの機能があるようなので、実際にコーディングしながらそれらの機能を扱っていければと思っています。

1. 記事の表示

最初はCRUDの R(Read) を扱います。
Part2で、記事一覧画面を作成しました。次は各記事を表示する画面を作成します。

ⅰ. ルーティングの追加

まず、新しいルーティングを追加します。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でも扱いました)。戻り値は@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
最後に記事一覧画面から記事画面へ遷移するリンクを追加します。

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

2. Resourceful Routing

Create を扱う前により便利なルーティング方法があるようなので、確認します。
1.で実施したように新しくCRUDを追加する際は、以下3つ作業が必要になります。

  1. ルーティングの追加
  2. コントローラーへのアクションの追加
  3. ビューの追加

ルーティング、コントローラーのアクション、ビューはいつでもエンティティのCRUD操作として機能します。このようなエンティティは resource (リソース) と呼ばれます。例えば、今開発しているアプリケーションでは、1つの記事が1つのリソースに該当します。
Railsはresourcesというルーティングメソッドを提供しています。resourcesarticlesといったリソースのコレクション向けの全てのルーティングを紐づけます。
ということで、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
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_toarticle_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>

より詳しいルーティングの説明は、"Rails Routing from the Outside In" に記載があります。

3. 記事の作成

リソースフルルーティングを学んだので、CRUDの学習に戻ります。ここでは、Createを扱います。Webアプリケーションでは、新しいリソースを作成する際、以下のように複数のプロセスが必要になります。

Rialsを利用したアプリケーションでは、コントローラーnewcreateアクションによって通常これらのステップは制御されます。では、これらのアクションをapp/controllers/articles_controller.rbshowアクションの下に追加します。

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 というステータスコードを表示します。ここのタイトルと本文はダミーの値が入っています。フォームが作成された後、ユーザーが変更します。

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ブロック内でlabeltext_fieldのようなメソッドをコールします。

より詳しいForm Builderの説明は、"Action View Form Helpers" に記載があります。

これで http://localhost:3000/articles/new にアクセスすると、新規記事作成画面が表示され、記事のタイトルと本文を入力することができます。
image.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_paramscreateアクションで使うよう変更します。

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

より詳しいStrong Parametersの説明は、"Action Controller Overview § 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

Active Recordは全てのテーブルのカラムにモデル属性を自動的に定義します。そのため、モデルファイル内でtitlebody属性を定義する必要はありません。

バリデーションを追加したので、エラーメッセージを表示させるために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
これらがどのように動作しているか理解するために改めてコントローラーのnewcreateのアクションを確認します。

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アクションにマッピングされるため、保存されない。そのため、バリデーションは発生しない。
  • フォーム送信時
    • createアクションにマッピングされるため、保存される。そのため、バリデーションが発生する。

より詳しいバリデーションの説明は、"Active Record Validations" に記載があります。また、より詳しいバリデーションエラーメッセージの説明は、"Active Record Validations § 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

最後に

今回は"Getting Started with Rails""7 CRUDit Where CRUDit Is Due" を教材にして、CRUDCreateRead を学びました。Railsでは、Form Builder や Strong Parameters などCRUD実装に役立つ機能が多数提供されていました。
次も引き続きCRUDを学んでいきます。次は残りの UpdateDelete を学びます。
最後までお読みいただきありがとうございました!

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?