LoginSignup
1
0

More than 5 years have passed since last update.

STIのtypeを変える

Posted at

はじめに

STIでtypeを変える方法。

動機

本来は禁じ手だが、STIのtypeを変えたい時がある。
STIのオブジェクトが他のモデルから関連付けられている場合などにおいて、削除->追加 をすると、アソシエーションの管理が面倒になる。

構成

  • 『番号フォーマット』はある番号を生成するフォーマットであり、内容は複数の仕様のフォーマットで構成される。
    • Time: strftimeのフォーマット (e.g. '%Y%m%d')
    • Sequential: sprintfのフォーマット (e.g. '%04d')
  • 『番号フォーマット』は、複数の『部分』から構成される
  • 『部分』クラスは自分の仕様に従って文字列を生成する (日付、連番、etc..)

例) '%Y%m_Number%03d' # '201612_Number003' のように生成される

download.png

クラス

  • NumberFormat
    ユーザへの公開クラス
    NumberPartのリストを持つ。build()で各NumberPartのbuild()を呼び出し連携つする

  • NumberPart
    STIの親。

  • DatePart
    STIの子。
    build()で、登録されたフォーマットに従い文字列を生成する。フォーマットはstrftimeに準じる。

  • SequentialPart
    STIの子。
    build()で、登録されたフォーマットに従い文字列を生成する。フォーマットはsprintfに準じる。
    連番を生成するため、オブジェクトは『現在の値』を管理する。

STIのtypeを変える

モデル。
STIでは各々の子クラスに必要なすべてのカラムを同一レコードで持つため、typeを変更したときに前のクラスのアトリビュートが残る。
つまり、type変更に伴って自分に必要なカラム・不要なカラムを適切に初期化する必要がある。

class NumberFormat < ActiveRecord::Base
  has_many :number_parts
  ...
  def build
    number_parts.map{|np| np.build()}.inject(&:+)
  end


class NumberPart < ActiveRecord::Base
  #  id               :integer
  #  type             :string   # STI
  #  format           :string   # 番号フォーマット
  #  current_number   :integer  # NumberPartが使う『現在の値』
  ...
  belongs_to :number_format
  after_save :reset

  def build
    ''
  end

  private
    # 親クラスで全てのSTIの子に特異なアトリビュートをリセット
    def reset
      update_column(:current_number, nil)
    end


class DatePart < NumberPart
  def build
    Time.zone.now.strftime format
  end


class SequentialPart < NumberPart
  # STIのtype変更時にパラメータをリセットする必要のある必要な子クラスは
  # 初期化をオーバーライドする。
  after_save :reset

  def build
    num = format % current_number
    increment! :current_number
    num
  end

  def reset
    update_column(:current_number, 0) unless self.current
  end

変更するコード

class HogeHogeController
  ...
  def transform_parts_type(number_part, new_type_name)
    # まずtypeのみDBレコード直で書き換える
    number_part.update_column(:type, new_type_name)

    # becomes!でクラスを変更する
    type_updated = number_part.becomes!(new_type_name.constantize)

    # モデルのsaveコールバックを発生させ、タイプ変更時の初期化を行う
    type_updated.save
  end

図作成

1
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
1
0