🐷

ActiveRecordモデルのライフサイクルにあわせてインスタンス変数の初期化などを行うには

2023/01/19に公開

save や reload でクリアしたい

重いクエリ発行などを抑制するために、インスタンス変数を使ってメモ化するのはよくある手段だと思います。
まれにActiveRecordモデルの保存やreloadでクリアしたいときがあります。

そんな時はこのように

def a_slow_query
  return @memo if defined?(@a_slow_query)
  @a_slow_query = huge_processing
end

after_save { remove_instance_variables! } 

def reload(*)
  super.tap { remove_instance_variables! }
end

private

def remove_instance_variables!
  remove_instance_variable :@a_slow_query
end

モデルインスタンス作成時に初期化したい

また、モデルインスタンス作成時に初期化したい場合は

after_initialize { @store = Store.new }

のように書けますが、インスタンス作成後になるので、次のようにモデル属性のsetterをオーバーライドして使う用途では使えません。

class MyApiKey < ActiveRecord::Base
  after_initialize { @key = Key.new(encrypted_key, key_salt) }

  before_save do
    if @key.changed?
      self.key_salt = @key.salt
      self.encrypted_key = @key.encrypted_value
    end
  end

  def key=(new_key)
    @key.value = new_key
  end

  def key
    @key.value
  end
end

MyApiKey.new(key: "api_key") #=>  undefined local variable or method `value'

そのような用途ではメソッドが呼ばれたときにインスタンス変数を作成するようにしましょう

class MyApiKey < ActiveRecord::Base
  before_save do
    if key_store&.value_changed?
      self.key_salt = key_store.salt
      self.encrypted_key = key_store.encrypted_value
    end
  end

  def reload(*)
    super.tap { remove_key_store! }
  end

  def key=(new_key)
    key_store.value = new_key
  end

  def key
    key_store.value
  end

  private

  def key_store
    @key_store ||= Key.new(encrypted_key, key_salt)
  end

  def remove_key_store!
    remove_instance_variable :@key_store
  end
end
タケユー・ウェブ株式会社

Discussion