attr_encryptedという暗号化をしてくれるgemでちょっとハマりました。
現象
class AbstractUser < ActiveRecord::Base
self.abstract_class = true
attr_accessible :name, :password
attr_encrypted :password, key: 'secretkey'
end
class User < AbstractUser
end
irb> user = User.new(name: 'hoge', password: 'fuga')
irb> user.encrypted_password # => 暗号化されたパスワード
irb> user.password # => 'fuga'
irb> user[:encrypted_password] # => nil !?
irb> user.save # => encrypted_passwordはNULLで保存される(NOT NULL制約があるならエラー)
原因
#encrypted_password
と[:encrypted_password]
(read_attribute(:encrypted_password)
)の返す値が違うんだと思って定義元を調べてみたら、以下のコードを発見。
131: attr_reader encrypted_attribute_name unless instance_methods_as_symbols.include?(encrypted_attribute_name)
132: attr_writer encrypted_attribute_name unless instance_methods_as_symbols.include?(:"#{encrypted_attribute_name}=")
こういう時pryの$ method
は便利ですね。
つまり、#encrypted_password
, #encrypted_password=
はActiveRecordとは切り離された、プレーンなアクセサになっていました。
abstractな親クラスでattr_encrypted
していたため、AbstractUser.instance_methods
がencrypted_password
を含まず、attr_encrypted
が独自のアクセサを定義してしまったようです。
attr_encryptedはActiveRecordに限らず、すべてのオブジェクトで使えるように設計されている(Objectをextendしている)ので、こういう実装になっているんでしょうね。
ActiveRecordの実装までは追っていませんが、カラム名のメソッドはおそらくmethod_missing
などで実装されていて、instance_methods
も具象クラスならカラム名のメソッドをすべて含むようにオーバーライドされているんでしょう。
アクセサが定義されたことにより、ActiveRecordの実装が無効化されてしまったという事だろうと思っています。