はじめに
Ruby on Rails初心者の学習記録Part5です。
今までRubyの基礎文法から始まり、RailsにおけるMVCやCRUDについて学んできました。
今回は、"Getting Started with Rails" の "8 Adding a Second Model" を教材にコメント機能の実装を通して、今まで学んできたことを復習します。
1. モデルの作成
Part2で記事データを扱うために作成したArticle
モデルのようにコメントデータを扱うComment
モデルを作成します。Comment
モデルには、記事への参照も追加します。
rails generate model Comment commenter:string body:text article:references
実行後、以下4つのファイルが作成されます。
Comment
モデルのファイルは以下のようになっています。
class Comment < ApplicationRecord
belongs_to :article
end
モデル作成時のコマンド、記事への参照(article:references
)を追加したので、belongs_to :article
が記載されています。これにより Active Recordアソシエーション(関連づけ) というものが設定されます(詳細は次章で確認)。
また、:references
はモデルの特殊なデータ型を表しています。指定されたモデル名の後ろに_id
を追加した名前を持つカラムをデータベース内に作成します(マイグレーション後、実際にdb/schema.rb
で確認)。
また、マイグレーションファイルはPart2で確認したときと同じようにデータベーステーブルと一致するように作成されています。
class CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :article, null: false, foreign_key: true
t.timestamps
end
end
end
t.references
では、article_id
というinteger型のカラムとそのインデックス、articles
テーブルのid
カラムを指す外部キー制約を作成します。
それでは、実際にマイグレーションを実行します。
rails db:migrate
Railsは現在のデータベースに対して、今まで実行されていないマイグレーションのみを実行します。そのため、実行結果のメッセージは、今回作成したcomments
のみ表示されます。
そして、db/schema.rb
には、comments
が追加されました。
ActiveRecord::Schema[7.0].define(version: 2023_02_01_002601) do
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "comments", force: :cascade do |t|
t.string "commenter"
t.text "body"
t.integer "article_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["article_id"], name: "index_comments_on_article_id"
end
add_foreign_key "comments", "articles"
end
2. モデルのアソシエーション(関連づけ)
次に先ほどのモデル追加時に出てきたActive Recordアソシエーションについてみていきます。この機能により、2つのモデル間にリレーションを宣言することができます。今回の記事とコメントというケースでは、以下のどちらかの方法でリレーションを設定できます。
- Each comment belongs to one article.
- One article can have many comments.
この方法は、Railsがアソシエーションを宣言する方法と似ています。例えば、先ほど作成したComment
モデルはコメントが1つの記事に紐づいています。
class Comment < ApplicationRecord
belongs_to :article
end
なお、この時点ではComment
モデル側のみアソシエーションがなされています。そのため、Article
モデル側も対応する必要があります。
class Article < ApplicationRecord
has_many :comments
validates :title, presence: true
validates:body, presence: true, length: { minimum: 10 }
end
これら2つの宣言により、かなりの動作が自動化されます。例えば、記事が1件含まれている@article
というインスタンス変数があれば、@article.comments
と書くことでその記事に紐づいている全てのコメントを取得することができます。
より詳しいActive Recordアソシエーションの説明は、"Active Record Associations"に記載があります。
3. コメントのルーティング
Part1やPart3でarticles
コントローラーに対して行ったときと同様、ルーティングを追加する必要があります。config/routes.rb
にcomments
用のルーティングを追加します。
Rails.application.routes.draw do
root "articles#index"
resources :articles do
resources :comments
end
end
ここでは、comments
をarticles
内にネストされたリソースとして作成しています。これは、モデルの記述とは別の観点から記事とコメントのリレーションシップを階層的に捉えたものです。
4. コントローラーの作成
モデルを作成できたので、次はコントローラーを作成します。
Part1のときと同じように以下のコマンドで作成します。
rails generate controller Comments
実行後、以下のファイルが作成されます。
この状態だと記事とコメントは別画面になります。ただ、一般的なブログは記事とコメントが同じ画面であることが多いと思います。
そのため、まずは記事表示画面(app/views/articles/show.html.erb
)に新たなコメント作成欄を追加します。
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Edit", edit_article_path(@article) %></li>
<li><%= link_to "Destroy", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
</ul>
<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<P>
<%= form.submit %>
</p>
<% end %>
ここで追加されたフォームは、CommentsController
のcreate
アクションを呼び出すことでコメントを新規作成します。
また、form_with
には配列を渡し、/articles/1/comments
のようなネストしたルーティングをビルドします。
次はapp/controllers/comments_controller.rb
にcreate
アクションを作成し、実際に先ほど作成したフォームから呼び出せるようにします。
class CommentsController < ApplicationController
def create
@article = Article.find(params[:article_id])
@comment = @article.comments.create(comment_params)
redirect_to article_path(@article)
end
private
def comment_params
params.require(:comment).permit(:commenter, :body)
end
end
記事のコントローラーより少し複雑になっています。これはネストを使ったことによる影響です。コメントのリクエストでは、コメントを記事に紐づける必要があります。そのため、Article
モデルのfind
を最初に呼び、リクエストに含まれている記事を取得します。
加えて、このコードではアソシエーションにより利用できるようになったメソッドをいくつか利用しています。@article.comments
では、コメントを保存するためにcreate
を利用しています。これにより、コメントが記事と自動的に紐づき。記事に対してコメントが従属するようになります。
次は作成したコメントを画面上に表示できるようにします。
新しいコメントを作成すると、article_path(@article)
ヘルパーを利用してユーザーは元の記事の画面に戻ります。このヘルパーを呼び出すとArticlesController
のshow
アクションが呼び出され、記事画面(show.html.erb
)がレンダリングされます。そのため、この画面にコメントを作成したコメントを表示できるようにします。
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<ul>
<li><%= link_to "Edit", edit_article_path(@article) %></li>
<li><%= link_to "Destroy", article_path(@article), data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
</ul>
<h2>Comments</h2>
<% @article.comments.each do |comment| %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<% end %>
<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
<p>
<%= form.label :commenter %><br>
<%= form.text_field :commenter %>
</p>
<p>
<%= form.label :body %><br>
<%= form.text_area :body %>
</p>
<P>
<%= form.submit %>
</p>
<% end %>
最後に
今回はコメント機能の実装を通して、モデルやルーティング、コントローラーの作成について復習しました。また、新たにモデルのアソシエーションについても学習しました。
最後までお読みいただきありがとうございました!