上記の回答に基づいて、望ましい結果を得るためにいくつかの実験を行いました。最終的に、「プライベート」な password_hash 属性と password という仮想アクセサーを作成しました。
このプロセスでいくつかの観察を行いました。
ActiveRecord にはプライベート属性の概念がないようです。などのシンボルを使用してアクセサメソッドをプライベートにするprivate :password, :password=
ことはオプションではありませんNameError: undefined method
.Railsはモデルをインスタンス化するときに をスローします.モデル自体にはこれら2つのメソッドが定義されていないためです(これらはから継承されているようですActiveRecord::Base
).
password_hash アクセサーを純粋なものでオーバーライドすると、属性の操作を防ぐことができますが、password_hash 属性を更新するときに ActiveRecord 自体が失敗することも意味します。これは、空の実装を呼び出しているためです。
したがって、実際のモデルでは未定義であるため、アクセサーを非公開にすることは失敗します。それらの定義も失敗します。ActiveRecord が壊れるためです。それで、あなたは何ができますか?
私は両方ともう少しやりました。アクセサーを非公開にし、それらを定義して、 を呼び出して両方を実装しましたsuper
。これにより、コントローラー (および Rails コンソール) が をスローしてそれらにアクセスできなくなりますNoMethodError
が、ActiveRecord は拒否されません。
補足: 検証の問題
私のアプローチで遭遇した問題の 1 つは、検証が壊れていたことです。password_hash に最小長を強制することは、パスワードが (何もなくても) 128 文字の SHA512 ハッシュになるため、適切ではありませんでした。そのため、ハッシュを検証することはほとんど意味がありませんでした。代わりに、仮想パスワード アクセサーに検証を追加しbefore_save :hash_password
、仮想アクセサー属性が設定されているかどうかを確認し、設定されている場合はそれをハッシュして password_hash 属性に書き込むコールバックを追加しました。
最終実装
私の実装は次のようになりました。
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email
attr_accessor :password
validates :password, :length => { :minimum => 8 }, :if => :password_changed?
validates :first_name, :last_name, :email, presence: true
# Various associations
before_save :hash_password
def password_correct?(p)
if(password.present?)
password == p
else
read_attribute(:password_hash) == hash_string(p)
end
end
def role_symbols
roles.collect do |r|
r.name.to_sym
end
end
private
def hash_string(input)
Digest::SHA2.new(512).update(input).hexdigest
end
def hash_password
if(password.present?)
write_attribute(:password_hash, hash_string(password))
self.password = nil
end
end
def password_changed?
password.present? or new_record?
end
def password_hash
super
end
def password_hash=(p)
super
end
end