回答とコメントが時系列順に並んだページを作る
はじめに
今回は、かなり悩まされた複数のテーブルの情報を同じ配列に入れて一つのタイムライン上で表示する方法を解説します。
回答とコメントが一緒に入ったインスタンス配列を作る
gituhubのプルリク画面をイメージして作っています。作成されたのが古い順に並べています。
ポリモーフィック関連
以上のようなイメージを実現するための手段としてポリモーフィック関連という方法を用います。ポリモーフィックとはある一つのモデルが複数のモデルに属しているのを一つのアソシエーションの定義だけで表現します。通常のアソシエーションで表現した際に生じる以下の課題を解決しています。以下はimageがcompanyとuserに属する関係の場合の具体例です。
- 新たにImageが従属するモデルを作った際、Imageのbelongs_toを増やす必要がある(=カラムを追加しないといけない)
- Imageモデルのインスタンスから直接、従属しているモデル(CompanyなのかUseを判別する事ができない。)
コード例
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Company < ActiveRecord::Base
has_many :images, as: :imageable
end
class User < ActiveRecord::Base
has_many :images, as: :imageable
end
|
テーブルのカラム例
imageable_typeにはCompanyやUserなどの所属するクラス名がimageable_idにはリレーションしているインスタンスのidが格納されている
#例1:Companyモデルに帰属する複数の画像を取得する company = Company.find(1) company.images #=> [<Image id:1 ..>,<Image id:2 ..>] #例2:Userモデルに帰属する複数の画像を取得する user = User.find(1) user.images #=> [<Image id:3 ..>,<Image id:4 ..>]
例1、例2はhas_many定義により所属する画像を取得しています。通常のアソシエーションとの違いとしては、親であるcompany, もしくはuserのidを保持するカラム(imageable_id,imageable_type)はあるが、それがcompany_idやuser_idといった名前ではない、という点です。
#例3:Imageモデルが帰属するインスタンスを取得する(帰属先が企業の場合) image = Image.find(1) image.imageable #=> <Company id:1 ..> #例4:Imageモデルが帰属するインスタンスを取得する(帰属先がユーザーの場合) image = Image.find(2) image.imageable #=> <User id:1 ..>
例3、例4は、Imageが帰属するインスタンスを取得しています。image.imageableと記述するだけで関連先のインスタンスを取得することができます。ここで重要なのは、Imageクラスのインスタンスがどのモデルに帰属しているかが分からなくも.imageableと記述するだけで関連先のインスタンスを取得できるという点です。
#例5: Companyモデルに帰属するImageクラスのインスタンスを作成する company = Company.create Image.create(imageable_id: company.id,imageable_type: company.class.to_s) #=> INSERT INTO `Images` (`imageable,_id`, `imageable,_type`, `created_at`, `updated_at`) VALUES (1, 'Company', '2015-0x-0x..', '2015-0x-0x..') #例6: Companyモデルに帰属するImageクラスのインスタンスを作成する company = Company.create company.images.create #=> INSERT INTO `Images` (`imageable,_id`, `imageable,_type`, `created_at`, `updated_at`) VALUES (1, 'Company', '2015-0x-0x..', '2015-0x-0x..')
例5と例6は同じ処理を行っており、どちらもimageクラスのインスタンスを生成しています。
回答とコメント用のモデルを作る
では、ポリモーフィック関連を利用して、回答とコメント用のモデルを生成していきます、ここのモデル名はreviewモデルにします。
app/model/answer_commnet_content.rb
class Review < ActiveRecord::Migration def change create_table :reviews do |t| t.integer :content_id t.string :content_type t.integer :question_id t.timestamps end end end
ポリモーフィック関連の定義
app/models/AnswerCommentContent
class Review < ActiveRecord::Base belongs_to :content, polymorphic: true end
app/models/Comment
class Comment < ActiveRecord::Base has_one :review, as: :content, dependent: :destroy
app/models/Answer
class Answer < ActiveRecord::Base has_one :review, as: :content, dependent: :destroy
質問、回答の投稿時にAnswerCpmmentContentsも同時に追加する
ポリモーフィック関連を利用するためには、質問及び回答が投稿された時にReviewを同時に追加する必要があります。
class QuestionsController < ApplicationController def create question = Question.create(create_params) question.review = Review.create(group_id: question.group_id, updated_at: question.updated_at) redirect_to :root and return end
コールバックを利用して部分的に分けるのと良いです。
ビューにインスタンスの配列を渡す
質問の詳細ページで、質問に対する回答とコメントを取得するためには、該当の質問のreviewを取得する必要がある。
class QuestionController < ApplicationController def show review = current_user.question.reviews.includes(:content) @review = reviews.map(&:content) //reviewsからcontentのみ取得する end end
コントローラから渡したインスタンス配列でタイムラインを表示する
ビューはインスタンスの種類によってビューを出し分けたいと思います。
<%= render @reviews %>
renderメソッドにインスタンス配列を渡した時は、インスタンスに応じて部分テンプレートを自動で呼び出し分けてくれます。
つまり、Answerのインスタンスだった場合は、app/views/answers/_answer.html.erbを呼び、Commentのインスタンスだった場合はapp/views/questions/_comment.html.erbを呼び出します。
非常に長い解説になりましたが、非常に便利な機能ですね。絶対使いこなしたい機能であることは間違いない。。。