はじめに
Rails6.1ハンズオン(4)の続きです。
ようやくここまできました。今回が最終回です。
コードはGithubにあげています。章ごとにコミットしてますので、参考にしていただければ幸いです。
やること
個人的に気になった(面白そうor仕事で使いそう)機能をピックアップして、現在のRails_6.1_hands_onプロジェクトに追加してみます。
- Delegated Types
- Strict Loading
- ActiveModel::Errorオブジェクト
参考:
Ruby on Rails 6.1の主要な新機能・変更点 - Qiita
6-1. Delegated Types
※単一テーブル継承(STI)とは?
予約語「type(String)」カラムを用いて、単一のテーブルに複数のモデルを定義する
例えば今回の掲示板のCommentモデルについて、
Commentの種類をこれまで実装した「テキスト形式」と新たに追加する「emoji形式」(LINEのスタンプみたいな感じ)に分けるとしたら、以下のようなレコードになる。(具体的な実装は省略)
author | type | text | emoji |
---|---|---|---|
taro | text | (ActionTextへの参照) | NULL |
jiro | emoji | NULL | 😎 |
STIだと一つのテーブルで似たようなモデルを管理できるので、実装が楽?になるし、テーブルが無尽蔵に増えなくて管理も楽そう。(仕事で使ったことないのでなんともですが...)
しかし、ものによってはNULLの穴ぼこだらけのテーブルになって、たくさんのレコードを扱うようなシステムだとパフォーマンスに影響が出そう。。。
そこで実装されたのが今回の「Delegated Types」だと解釈しました!
STIのメリットである、代表のテーブルを用いたカラムの共通化をしつつ、モデル毎の微妙な違いは別途テーブルに分けて管理する形になります。(NULLの穴ぼこは無くなります!)
上の例だと、
authorカラムは共通なので、代表としてCommentテーブルの中にauthorカラムを残し、
「違い」である、textカラムとemojiカラムはそれぞれTextテーブルとEmojiテーブルに分けて管理します。
Delegate Typeに則った最終的なテーブル図はこうなります
6-1-1. モデル作成
すごく適当なマイグレーションファイルです。。。
def change
create_table :texts do |t|
t.timestamps
end
create_table :emojis do |t|
t.integer :emoji_type, null: false, default: 0
end
add_column :comments, :content_type, :string, null: false
add_column :comments, :content_id, :bigint, null: false
end
class Comment < ApplicationRecord
belongs_to :community
delegated_type :content, types: %w[Text Emoji]
end
class Text < ApplicationRecord
has_one :comment, as: :content, touch: true
has_rich_text :text_content
delegate :author_name, to: :comment
end
class Emoji < ApplicationRecord
has_one :comment, as: :content, touch: true
enum emoji_type: %i[😀 😂 😎]
delegate :author_name, to: :comment
end
6-1-2. コントローラー・ビュー
一部を略して書きます、詳しくはgithubをご覧ください。
app/controllers/text_comments_controller.rb
class TextCommentsController < ApplicationController
before_action :set_community
def new
@text = Text.new.build_comment
end
def create
# FIXME: バリデーションエラーを無視している
@community.comments
.create(content: Text.new(text_content: text_params[:text_content]),
author_name: text_params[:author_name])
redirect_to community_path(@community)
end
private
def set_community
@community = Community.find(params[:community_id])
end
def text_params
params.require(:comment).permit(:text_content, :author_name)
end
end
ポイントは親のCommentにcontentという属性で子供のTextやEmojiを渡しているところだと思います。
action_textとただのenumを使い分けられるようになりました。
6-2. Strict Loading
class Community < ApplicationRecord
has_many :comments, strict_loading: true
end
こんな風につけておくだけで、N+1問題が発生しているときに例外を出すようになります。例外になるので、bulletより強制力がありますね。。。(今回は意味なかったです)
6-3. ActiveModel::Errorオブジェクト
バリデーションエラーが起きたときにモデルのインスタンスに付与されていたエラー情報がただのハッシュではなく、クラスのインスタンスになりました。
app/views/shared/_error_messages.html.haml
- if object.errors.present?
.card.mb-2.border-danger
.card-header.bg-danger.text-white
エラーがあります
.card-body
%ul
- object.errors.each do |e|
%li= e.full_message
エラー文の呼び出し方が変わって、whereとかも使えるようになって、どこかで使えそうですね。。。
さいごに
このシリーズはこれで以上ですが、今回触らなかったもの(特に並列データベースとかは、大規模な開発で使いそう)もいつか触れたいと思いました。(小並感)