基于以上答案,我做了一些实验以获得预期的结果。我最终制作了一个“私有”密码哈希属性和一个名为密码的虚拟访问器。
在这个过程中我做了一些观察:
ActiveRecord 似乎没有任何私有属性的概念。使用诸如此类的符号将访问器方法设为私有private :password, :password=
不是一种选择,因为 RailsNameError: undefined method
在实例化模型时会抛出一个,因为模型本身没有定义这两个方法(它们似乎是从 继承的ActiveRecord::Base
)。
用纯无覆盖 password_hash 访问器对于防止对属性的操作非常有用,但这也意味着 ActiveRecord 本身在更新 password_hash 属性时会失败,因为它正在调用空实现。
因此,将访问器设为私有会失败,因为它们在实际模型中未定义。定义它们也失败了,因为它破坏了 ActiveRecord。所以,你可以做什么?
我做了两个,还有更多。我将访问器设为私有,定义它们并通过调用super
. 这可以防止控制器(和 rails 控制台)通过抛出 a 来访问它们NoMethodError
,但不会拒绝 ActiveRecord。
旁注:验证问题
我的方法遇到的一个问题是验证失败。对 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