我正在关注Michael Hartl的RoR教程,它涵盖了密码加密的基础知识。这是目前的用户模型:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email,: password, :password_confirmation
email_regex = /^[A-Za-z0-9._+-]+@[A-Za-z0-9._-]+\.[A-Za-z0-9._-]+[A-Za-z]$/
#tests for valid email addresses.
validates :name, :presence => true,
:length => {:maximum => 50}
validates :email, :presence => true,
:format => {:with => email_regex},
:uniqueness => {:case_sensitive => false}
validates :password, :presence => true,
:length => {:maximum => 20, :minimum => 6},
:confirmation => true
before_save :encrypt_password
private
def encrypt_password
self.encrypted_password = encrypt(password)
end
def encrypt(string)
string
end
end
我发布了一个关于before_save
无法正常工作的上一个问题,事实证明我不小心做的是将我的encrypt_password写成:
def encrypt_password
@encrypted_password = encrypt(password)
end
我知道如果self.encrypted_password设置encrypted_password属性,但为什么@encrypted_password也没有这样做?在上一篇关于before_save
没有工作的帖子的回复中,有人说实例变量在方法以我最初编码的方式结束后被“遗忘” - 为什么会这样呢?有人可以解释一下self和@在上面的代码中是如何以不同的方式工作
注意:我已经查看了帖子here和here,但他们都说“自我”正在调用attribute =
方法,我甚至都不明白该方法如何存在于此,因为我从未创建它或声明encrypted_password w / attr_accessor
。所以我仍然感到困惑,这不是这些问题的重新发布。
答案 0 :(得分:20)
Rails已自动为您添加encrypted_password
的访问者,因为users
表中存在该名称的字段。
您添加到表格的任何字段都将通过self.field_name
自动提供。
Here is where Michael Hartl's tutorial creates the encrypted_password
field in the users
table
另请查看链接页面中的user_spec.rb
(清单7.3),作者正在测试encrypted_password
字段是否存在。
<强>更新:强>
正如@mu指出的那样,@
用于Ruby实例变量(又名“iv”)。但是encrypted_password
是由Rails定义的“属性”,并不是实例变量。
如果您运行User.find(1).instance_variables
,您会看到有一个名为@attributes
的iv,其类型为Hash
。
在iv里面是存储encrypted_password
的地方。 Rails为encrypted_password
定义了访问器方法,用于获取/设置数据
@attributes
Hash
中的属性。
请注意,您还可以通过@attributes["encrypted_password"]
类中调用的User
来获取/设置数据(但访问方法是方便的方法)。
答案 1 :(得分:2)
如果你让我,我想重新解释一下。
我在这个post中解释说,只要你创建一个(rails-)模型,其名称与数据库的(复数)表名之一相同(单数),就是rails的“魔力”将创建setter和getter以修改表的记录。
这是因为你的模型继承了ActiveRecord :: Base Class中的所有方法,它定义了基本的CRUD访问器(Create,Read,Update,Delete)。
与您的问题相关的关键点是,您不知道rails如何实现与您的数据库表列相关的实例变量,而您不应该这样做。 :)所有你必须知道的是,在那一点上,你有可用于CRUD(创建,读取,更新,删除)数据库列“encrypted_password”的setter和getter。
在您的示例中,rails可能使用名为 @encrypted_password的实例变量,也许rails使用名为 @attributes [“encrypted_password”]的哈希实例变量,< em>或者rails可能使用名为的实例变量@you_will_never_guess_encrypted_password。
-
这是一个很好的观点,你不了解实例变量的内部轨道行为。在2019年,Rails进一步开发可能会导致框架使用@complex-hash-instance-variable来存储encrypted_password值。
实际上最好的办法是让rails用实例变量来管理它的“私有”“事件”;并且只使用它为你提供的getter和setter方法。 所以你的应用程序在下个世纪仍然可以使用encrypted_password(我希望如此^^)。
因此,如果您使用@encrypted_password,它可能会与某些“虚构”版本的rails一起使用,并且它将不再适用于其他rails版本。实际上使用当前版本的rails它不起作用。
-
第二个关键点是,当您要使用为encrypted_password数据库表列创建的getter“ encrypted_password ”Rails时,请在其前面加上“自我“为了告诉Ruby:”好吧我想使用用户实例变量的encrypted_password方法。“
在Ruby中,通过将其名称传递给接收者来调用方法。 你这样写:
my_receiver.my_method
在您的情况下,我们将 encrypted_password 方法传递给用户实例变量。但是我们不知道如何命名这个实例变量,所以我们用 self 这个词告诉Ruby:“我说的是用户的任何实例变量调用encrypted_password方法的类“。
例如,我们可以将实例变量命名为“toto”:
toto = User.new
所以toto.encrypted_password
会显示加密的密码,在这种情况下,我们的代码中的self会引用toto。
但是,感谢Ruby,如果在调用方法时没有给任何接收者,Ruby会认为你将它传递给 self 。
参考:Pragmatic Programmer's guide
所以在你的例子中,你甚至不需要把“自我”。作为前缀。 你可以这样写:
class User < ActiveRecord::Base
def encrypt_password
encrypted_password = encrypt(password)
end
end
我希望这有助于澄清这个有趣的主题。
答案 2 :(得分:1)
TL; DR -
如果您打算将widget_count保存回数据库,请始终写self.widget_count = 123
。
(但是请阅读很长的答案,因为知道的原因很有价值。)