🐷
ActiveRecordモデルのライフサイクルにあわせてインスタンス変数の初期化などを行うには
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