【Ruby】Railsのアソシエーションとリレーションを理解し、コメント機能を実装
はじめに:
プログラミング初学者がRailsアプリケーションを動かす際に必要な知識として、アソシエーションとリレーションがある。プログラミング初学者がVScodeを用いてRails環境の構築後、アソシエーションとリレーションの知識を用いて投稿コメント機能を実装できることを目標とした記事となっている。
環境:
環境構築がまだの方はこちらから
- windows 11
- Vscode 1.87.2
- Ubuntu 22.04
- wsl 2.1.5.0
- ruby 3.2.3
- rails 6.1.7
アソシエーションとリレーションとは
1: アソシエーションとリレーションについて理解しよう。
以上のことから、Postsモデル(投稿に関連するモデル)とCommentモデル(コメント)を関連づければ、X(旧Twitter)やInstagramのような投稿1つに対して、多数のコメントを保存できるようになりそうであることが予測できる。
コメント機能を実装しよう。
1: モデルの作成し、アソシエーションを組んでみよう。
1: comment
モデルを作成する。(post
モデルとuser
モデルは作成済み)
rails g model comment body:text
$ rails g model comment body:text
Running via Spring preloader in process 35843
invoke active_record
create db/migrate/20240413124219_create_comments.rb
create app/models/comment.rb
invoke test_unit
create test/models/comment_test.rb
create test/fixtures/comments.yml
references型とは(応用編)
2: 各モデルに必要なアソシエーションを記述する。
Railsでアソシエーションを組むためには、has_many
とbelongs_to
メソッドを理解しなければならない。
【Post
モデル】
class Post < ApplicationRecord
has_many :comments, dependent: :destroy # 追加
belongs_to :user, dependent: :destroy # 追加
validates :title, presence: true, length: { maximum: 20 }
validates :body, presence: true, length: { maximum: 400 }
end
【Comment
モデル】
class Comment < ApplicationRecord
belongs_to :post # 追加
belongs_to :user # 追加
end
【User
モデル】
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :comments, dependent: :destroy # 追加
has_many :posts, dependent: :destroy # 追加
end
ここまでで、各モデルのアソシエーションを組むことができました。
2: マイグレーションファイルを確認し、リレーションを組んでみよう。
各モデルの関連付けは完了しましたが、どのカラムで関連を結ぶかの記述と確認ができていません。通常、post_id
やuser_id
など主キー(PK)となるものでリレーションを結んだ際には、マイグレーションファイルに新たなカラムが必要になるため、マイグレーションファイルに記述を追加し、マイグレーションを実行する必要があります。
1: 各マイグレーションファイルにリレーションを結ぶうえで必要な記述する。
【20240101_create_posts.rb
】
class CreatePosts < ActiveRecord::Migration[6.1]
def change
create_table :posts do |t|
t.integer :user_id, null:false, default: 0 # 追加
t.string :title
t.text :body
t.timestamps
end
end
end
【20240101_create_comments.rb
】
class CreateComments < ActiveRecord::Migration[6.1]
def change
create_table :comments do |t|
t.integer :user_id, null:false, default: 0 # 追加
t.integer :post_id, null:false, default: 0 # 追加
t.text :body
t.timestamps
end
end
end
【20240101_devise_create_users.rb
】※特に変更なし。
class DeviseCreateUsers < ActiveRecord::Migration[6.1]
def change
create_table :users do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
t.string :name, null: false, default: ""
## Recoverable
t.string :reset_password_token
t.datetime :reset_password_sent_at
## Rememberable
t.datetime :remember_created_at
## Trackable
# t.integer :sign_in_count, default: 0, null: false
# t.datetime :current_sign_in_at
# t.datetime :last_sign_in_at
# t.string :current_sign_in_ip
# t.string :last_sign_in_ip
## Confirmable
# t.string :confirmation_token
# t.datetime :confirmed_at
# t.datetime :confirmation_sent_at
# t.string :unconfirmed_email # Only if using reconfirmable
## Lockable
# t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
# t.string :unlock_token # Only if unlock strategy is :email or :both
# t.datetime :locked_at
t.timestamps null: false
end
add_index :users, :email, unique: true
add_index :users, :reset_password_token, unique: true
# add_index :users, :confirmation_token, unique: true
# add_index :users, :unlock_token, unique: true
end
end
2: マイグレーションを実行する。
$ rails db:migrate:reset
== 20240405125535 CreatePosts: migrating ======================================
-- create_table(:posts)
-> 0.0049s
== 20240405125535 CreatePosts: migrated (0.0054s) =============================
== 20240408152336 DeviseCreateUsers: migrating ================================
-- create_table(:users)
-> 0.0067s
-- add_index(:users, :email, {:unique=>true})
-> 0.0029s
-- add_index(:users, :reset_password_token, {:unique=>true})
-> 0.0015s
== 20240408152336 DeviseCreateUsers: migrated (0.0136s) =======================
== 20240411111914 CreateComments: migrating ===================================
-- create_table(:comments)
-> 0.0087s
== 20240411111914 CreateComments: migrated (0.0091s) ==========================
3: db/schema.rb
ファイルに正しくテーブルが定義されているか確認する。
ActiveRecord::Schema.define(version: 2024_04_11_111914) do
create_table "comments", force: :cascade do |t|
t.integer "user_id", default: 0, null: false
t.text "body"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "posts", force: :cascade do |t|
t.integer "user_id", default: 0, null: false
t.string "title"
t.text "body"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "name", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
end
3: コントローラを作成し、アクションなどを定義する。
1: comments
コントローラを作成する。
$ rails g controller comments
2: comments
コントローラにbefore_action
,create
,ストロングパラメータ
を定義する。
class CommentsController < ApplicationController
before_action :authenticate_user!
def create
# 投稿詳細ページで渡ってきているpost_idと同じpost_idの投稿をpostへ代入(1)
@post = Post.find(params[:post_id])
# 送られてきたcomment_paramsをログインしているユーザーのものと関連づける(2)
comment = current_user.comments.new(comment_params)
# (1)のpost_idと(2)のpost.idが同じであれば保存する
comment.post_id = @post.id
comment.save
# 投稿詳細ページへリダイレクトする。
redirect_to post_path(@post)
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
4: ルーティングを設定する。
1: 変更前のルーティングファイルとルーティング状況を把握する。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations',
sessions: 'users/sessions'
}
resources :posts
resources :comments
end
$ rails routes -g comment
Prefix Verb URI Pattern Controller#Action
comments GET /comments(.:format) comments#index
POST /comments(.:format) comments#create
new_comment GET /comments/new(.:format) comments#new
edit_comment GET /comments/:id/edit(.:format) comments#edit
comment GET /comments/:id(.:format) comments#show
PATCH /comments/:id(.:format) comments#update
PUT /comments/:id(.:format) comments#update
DELETE /comments/:id(.:format) comments#destroy
posts
とcomments
コントローラへのルーティングは、resources
でそれぞれ定義している。
2: Postコントローラのルーティングの中にCommentsコントローラのルーティングをネストする。
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: 'users/registrations',
sessions: 'users/sessions'
}
resources :posts do
resources :comments, only: [:create]
end
end
$ rails routes -g comments
Prefix Verb URI Pattern Controller#Action
post_comments POST /posts/:post_id/comments(.:format) comments#create
5: コメントフォーム(ビュー)を作成しよう。
1: 投稿詳細のビューファイルにコメントフォームとコメント表示一覧を作成する。
<h1>Hello</h1>
<div>
<div>
<label for="article_title">Title</label><br>
<%= @post.title %>
</div>
<div>
<label for="article_body">Body</label><br>
<%= @post.body %>
</div>
<div>
<%= link_to "削除", post_path(@post), method: :delete, data: { confirm: "本当に削除しますか?" } %>
</div>
</div>
<!--コメントフォームを追加-->
<div>
<%= form_with(model: [@post, Comment.new], url: post_comments_path(@post)) do |f| %>
<div class="field">
<%= f.label :body, "コメント追加" %>
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit "コメントする" %>
</div>
<% end %>
</div>
<!-- ここまで -->
コード解説
<%= form_with(model: [@post, Comment.new], url: post_comments_path(@post)) do |f| %>
・通常であれば、<%= form_with model: @comment ~ do |f| %>
だが、model: [@post, Comment.new]
となっている。この配列の記述をすることで、post
とcomment
モデル間の関連付けを持たせることが可能となる。
このフォームに入力された内容が送信(submit)され、Create
アクションが実行されたとき、新しいコメントとしてcomment
モデルに保存されると同時に、関連するpost
オブジェクトにも関連付けられて保存される。
・@post
はどこからでてきたのかと思う方がいらっしゃると思いますが、それはコントローラのメソッドで定義しているものです。(以下参照)
def show
@post = Post.find(params[:id])
# ↑この@postのこと
end
~~
private
def post_params
params.require(:post).permit(:title, :body)
end
・投稿一覧(index)から投稿詳細(show)に遷移した際に、params[:id]
でその投稿のid(post_id
)が事前に送られている。あとはその投稿(post)とコメント(comment)を関連付けて保存すればよいという流れ。
2: http://localhost:3000/posts/1
にアクセスし、フォームが正しく表示されるか確認する。
app/views/posts/show.html.erb
6: コメントを投稿してみよう。
1: 新規登録などユーザーの登録が完了し、ログインしている状態かつ1つ以上の投稿が完了している状態で、ブラウザの検索バーにhttp://localhost:3000/posts/1
と入力し、投稿へアクセスする。➡5: コメントフォーム(ビュー)を作成しよう。の最後の画像が表示されていればよい。
2: 適当にコメントを行い、エラー画面にならないか確認する。
3: rails c
を立ち上げ、コメントが投稿されているかをターミナルから確認する。
rails c
irb(main):001:0> Comment.all
$ rails c
Running via Spring preloader in process 58777
Loading development environment (Rails 6.1.7.7)
irb(main):001:0> Comment.all
(1.6ms) SELECT sqlite_version(*)
Comment Load (0.3ms) SELECT "comments".* FROM "comments"
=>
[#<Comment:0x0000556d8cb0ded0
id: 1,
user_id: 1,
post_id: 1,
body: "test comment!",
created_at: Sat, 13 Apr 2024 13:39:27.800416000 UTC +00:00,
updated_at: Sat, 13 Apr 2024 13:39:27.800416000 UTC +00:00>]
7: コメントがブラウザで表示されるようにしよう。(コントローラとビュー)
現段階では、保存ができている。ビューで表示させるための記述を行っていないため、ブラウザでは見えない状況。
1: posts_controller.rb
ファイルのshow
アクションにコメントを表示させるための、インスタンス変数を定義する。
def show
@post = Post.find(params[:id])
@comments = Comment.all # 追加 (Commentテーブルにあるコメントを全て表示)
end
2: app/views/posts/show.html.erb
ファイルの中にコメントを表示一覧させるための記述を追加する。
<h1>Hello</h1>
<div>
<div>
<label for="article_title">Title</label><br>
<%= @post.title %>
</div>
<div>
<label for="article_body">Body</label><br>
<%= @post.body %>
</div>
<div>
<%= link_to "削除", post_path(@post), method: :delete, data: { confirm: "本当に削除しますか?" } %>
</div>
</div>
<!--コメントフォーム-->
<div>
<%= form_with(model: [@post, Comment.new], url: post_comments_path(@post)) do |f| %>
<div class="field">
<%= f.label :body, "コメント追加" %>
<%= f.text_area :body %>
</div>
<div class="actions">
<%= f.submit "コメントする" %>
</div>
<% end %>
</div>
<!-- コメント一覧の表示追加 -->
<div>
<% @post.comments.each do |comment| %>
<div>
<p><%= comment.user.name %></p>
<p><%= comment.body %></p>
<p><%= comment.created_at.strftime('%Y/%m/%d') %></p>
</div>
<% end %>
</div>
<!-- ここまで -->
3: 表示を確認する。
app/views/posts/show.html.erb
まとめ
・コメント機能を実装するためには、【アソシエーション】・【リレーション】について理解する必要がある。
・モデル➡マイグレーションファイル➡コントローラ➡ビューの順でファイルを作成、編集を行うことでコメント機能が実装できる。
参考サイト
おわりに
今回、プログラミング初学者がRailsアプリケーションを動かす際に必要な知識であるアソシエーションとリレーションについて学んだ。データベースからデータを取ってきて表示するための基礎的な流れをつかむことでその他の機能に応用することが可能となると考える。
Discussion