0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Railsで子クラスが固有のカラムを利用するSTIを使って実装したときのメモ

Posted at

Railsで子クラスに個別のカラムを持たせるSTIを実装したのでそのメモ。
内容は例になります。

仕様

LINEの投稿をイメージして以下の仕様を想定

  • 1回の投稿においてテキスト、もしくはスタンプを投稿できる
  • テキストの投稿においては投稿された文字列を保存する
  • スタンプの投稿においては投稿されたスタンプのIDを保存する
  • 投稿できるものの種類は今後増えていくことが想定されている

今後も投稿できるものの種類が増えそう、という点と投稿できるものの種類によって保存しておくデータの型が変わりそう、という点からSTIを使って実装してみることにしました。

実装方針

リクエストパラメータについて

  • 1つの投稿に対して post_type + text もしくは post_type + stamp_id が送られる
  • 投稿の種類はpost_typeで表現
  • post_typestampの場合はstamp_idに値を入れる
  • post_typetextの場合はtextに値を入れる

リクエストパラメータのバリデーションについて

  • post_typestampの場合はtextにデータを入れられないようにバリデーションを入れる
  • post_typetextの場合はstamp_idにデータを入れられないようにバリデーションを入れる

工夫したところ

バリデーションはモデルに実装するわけですが、子クラス同士はお互いがどのカラムを利用するかを知らないようにしたいと思ったので、親クラスに条件付きでバリデーションを入れて、子クラスでその条件を外すという方針で実装しました。

実装

# テーブル定義
create_table :posts do |t|
  t.string :type, null: true
  t.string :text, null: true
  t.integer :stamp_id, null: true
  t.timestamps
end

# コントローラー
class PostController < ApplicationController
  def create
    if PostClassSelector.select(params[:post][:post_type]).new(post_params).save
      # 成功時の処理
    else
      # 失敗時の処理
    end
  end

  def post_params
    params.require(:post).permit(:text, :stamp_id)
  end
end

# モデル
class Post
  validates :text,     absence: true, unless: -> { self.available_attribute.include? :text }
  validates :stamp_id, absence: true, unless: -> { self.available_attribute.include? :stamp_id }

  class_attribute :available_attribute

  def self.disable_absence_validator(attribute)
    self.available_attribute ||= []
    self.available_attribute << attribute
  end
end

class Post::Text < Post
  disable_absence_validator :text
end

class Post::Stamp < Post
  disable_absence_validator :stamp_id
end

class PostClassSelector
  def self.select(post_type)
    case post_type
    when :text   then Post::Text
    when :stamp  then Post::Stamp
    else raise StandardError.new('unsupported post type')
    end
  end
end
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?