Railsの学習(アソシエーション編)
ポリモーフィック関連付けを理解しよう
本日の記事について
現在、フィヨルドブートキャンプでプログラミングを学習中です。本記事では、スクールでの課題を進める上での気づきなどをまとめています。
まずアプリ作成の流れなどを振り返ります。
- 『Railsの教科書』内で紹介されていたBookアプリを作成します
- 日本語に対応するためにgem
i18n
を実装 - ページネーション機能をgem
kaminari
で実装 - ログイン・ログアウト機能をgem
devise
とdevise-i18n
で実装 - GitHubによるログイン機能をgem
OmniAuth
で実装 - ActiveStorageによるプロフィール画像のアップロード機能実装
- コメント機能を実装してみよう!←今日はここです!
現状では、以下の3つのモデルが存在しています。
- 書籍の登録情報を記録する
Bookモデル
- 日報のデータを記録する
Report
モデル - ユーザー情報を記録する
Userモデル
reports
とそのCRUD機能については、books
とほぼ同様ですので、作成手順などを記事では紹介していません。
コメント機能実装の目的と手順
実装の目的は以下の2つです。
- コメント機能を
books
とreports
に追加する - ポリモーフィック関連付けを用いて、
books
およびreports
につけたコメントを共通のコードで動かす
実装のざっくりした手順は以下となります。
- commentsのモデル(
Comment
)とcontroller(comments_controller.rb
)を生成する - モデル生成時に、t.referenceの記述を入れてmigrateする
- models/のファイルやconfig/routes.rbにルーティングを記述する
- viewsとcontrollerの処理などを独自で記述する
手順の詳解
1. commentsのモデル(Comment
)とcontroller(comments_controller.rb
)を生成する
まず、Commentのモデル作りましょう。
rails g model Comment title:string body:text
class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.string :title, null: false t.text :body, null:false t.references :commentable, polymorphic: true, index: true t.timestamps end end end
新たにモデルを作成した際の通常のマイグレーションスクリプトとは少しだけ内容が異なっているのがお分かり頂けますでしょうか。
実は、t.references :commentable, polymorphic: true, index: true
という記述を追記しています。
こちらは何を意味しているのでしょうね?この内容を理解するために、まずポリモーフィックについて確認する必要があります。
ポリモーフィック関連付けとは
少し触れましたが、本課題では、books
およびreports
につけたコメントを共通のコードで動かすことが条件となっています。
よく考えるとすごく難しそうですよね。何故なら、CommentはBookにもReportにも属していることになるからです。どのように関連付けしたら良いのか不明ですね。
このような場合に役立つのがポリモーフィック関連付けです。
こちらを用いると、ある1つのモデル(Comment
)が他の複数のモデル(Book
とReport
)に属していることを、1つの関連付けだけで表現することが可能になります。
# 記載方法1 class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.string :title, null: false t.text :body, null:false t.integer :commentable_id t.string :commentable_type t.timestamps end add_index :comments, [:commentable_type, :commentable_id] end end
上記のER図の通り、commentable_id
とcommentable_type
は外部参照キーとなります。必ずカラムの型を宣言するのを忘れないようにしましょう。
そして、app/models/
の各ファイルに以下を記述します。
2. モデル生成時に、t.referenceの記述を入れてmigrateする
3. models/のファイルやconfig/routes.rbにルーティングを記述する
# app/models/comment.rb class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true end # app/models/book.rb class Book < ApplicationRecord has_many :comments, as: :commentable end # app/models/report.rb class Report < ApplicationRecord has_many :comments, as: :commentable end
このように宣言してあげることにより、commentable
で関連付けが可能になります。
ルーティングの設定も忘れないようにしましょう。
# config/routes.rb resources :books do resources :comments, only: [:show, :create, :destroy, :edit, :update] end resources :reports do resources :comments, only: [:show, :create, :destroy, :edit, :update] end # only: [:show, :create, :destroy, :edit, :update]は適宜
こうすることで、 下記のようにパスを返すメソッドが設定されます。
例: report_comment_path(パスは/reports/:report_id/comments/:id
)
関連付けできているか見てみよう!
注意:この画面は一通り実装が終わった後の画面です。
rails console(rails c
)でテーブルの情報を実際に確認しましょう!
$ comment = Comment.find(1)
外部キーのcommentable_type
に親モデルのクラスであるBook
が入っていることが確認できますね。また、commentable_id
にはBook
のidが入っています。
では、models/以下のファイル
やマイグレーションスクリプトに記載したcommentable
で親モデルの情報を取得しましょう。
$ comment.commentable
コメントに紐づいたBookモデルのインスタンスが確認できましたね!関連付け、成功です。
ちなみに、t.references
を用いるとマイグレーションスクリプトの記述内容を減らすことができます。簡略化した記載方法ですので、記載方法1と共に参考にしてください。
# 記載方法2 class CreateComments < ActiveRecord::Migration[5.2] def change create_table :comments do |t| t.string :title, null: false t.text :body, null:false t.references :commentable, polymorphic: true, index: true t.timestamps end end end
buildの利用について
ここまでは主に関連付けの方法について確認しました。ここからは、処理を記述する際に悩みやすいポイントについて整理します。
まず、日報(report)に紐づいたコメント(comment)を新規作成する処理について考えましょう。
それぞれのモデルである、インスタンス変数@reportと@commentを利用します。
コントローラの処理内で@commentを親モデルの@reportに紐づけるにはどうしたらよいのでしょうか。
このような場合はbuild
を用いると比較的楽に実装できます。
# comments_controller.rb @report = Report.find(params[:report_id]) @comment = @report.comments.build
上記のように記述してあげると、外部参照キーなどの設定をしなくても自動で紐づきが可能となります。
あとは、フォームを記述すればOKです。
# show.html.erb <%= form_with(model: [@report,@comment], local: true)do |f| %> <%= f.label :content %> <%= f.text_area :content %> <%= f.submit "コメントする" %> <% end %>
処理の詳細については、割愛いたします。
最後に
いかがでしたでしょうか。関連付け、とても奥が深いですね!
参考文献: