Rails – 自我与@

我正在关注Michael Hartl的RoR教程,它涵盖了密码加密的基础知识。 这是目前的用户模型:

class User  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_pa​​ssword设置encrypted_pa​​ssword属性,但为什么@encrypted_pa​​ssword也没有这样做呢? 在上before_save文章的回复中,关于before_save没有工作,有人说实例变量在方法以我最初编码的方式结束后被“遗忘” – 为什么会这样呢? 有人可以解释一下self和@ 在上面代码的上下文中的工作方式有何不同

注意:我已经看过这里和这里的post,但是他们都说“self”正在调用attribute =方法,我甚至不知道这个方法在这里是如何存在的,因为我从来没有创建过它或者声明了encrypted_pa​​ssword w / attr_accessor 。 所以我仍然感到困惑, 这不是这些问题的重新发布。

您已经为Rails自动添加了encrypted_password的访问者,因为该users表中存在该名称的字段。

您添加到表中的任何字段都将通过self.field_name自动提供。

以下是Michael Hartl的教程在users表中创建encrypted_password字段的位置 。

另请查看链接页面中的user_spec.rb (清单7.3),其中作者正在测试encrypted_password字段是否存在。

更新:

正如@mu指出的那样, @用于Ruby实例变量(又名“iv”)。 但encrypted_password是由Rails定义的“属性”,不是实例变量。

如果运行User.find(1).instance_variables ,您将看到有一个名为@attributes的静脉@attributes ,它的类型为Hash

在iv里面是encrypted_password的存储位置。 Rails为encrypted_password定义了访问器方法,它在@attributes Hash获取/设置该属性的数据。

请注意,您还可以通过在User类中调用的@attributes["encrypted_password"]来获取/设置数据(但是访问器方法是方便的方法)。

如果你让我,我想重温一下答案。

我在这篇文章中解释说,只要你创建一个(rails-)模型,其名称与数据库的(复数)表名之一相同(单数),rails的“魔力”将按顺序创建setter和getter修改表的记录。

这是因为您的模型inheritance了ActiveRecord :: Base类中的所有方法,后者定义了基本的CRUD访问器(创建,读取,更新,删除)。

与您的问题相关的关键点是,您不知道rails如何实现与数据库表列相关的实例变量,您不应该这样做。 :)所有你必须知道的是,在那一点上,你有可用于CRUD(创建,读取,更新,删除)数据库列“encrypted_pa​​ssword”的setter和getter。

在您的示例中,rails可能使用名为 @encrypted_pa​​ssword 的实例变量也许rails使用名为 @attributes [“encrypted_pa​​ssword”] 的哈希实例变量或者rails可能使用名为 @you_will_never_guess_encrypted_pa​​ssword 的实例变量

这是一个很好的观点,你不了解实例变量的内部轨道行为。 在2019年,Rails进一步开发可能会导致框架使用@complex-hash-instance-variable来存储encrypted_pa​​ssword值。

实际上,最好的方法是让rails使用实例变量管理其“私有”“事件”;并且只使用它为您提供的getter和setter方法。 因此,您的应用程序仍将在下个世纪使用encrypted_pa​​ssword(我希望如此^^)。

因此,如果您使用@encrypted_pa​​ssword,它可能会使用一些“虚构”版本的rails,它将不再适用于其他rails版本。 实际上使用当前版本的rails它不起作用。

第二个关键点是,当你想使用为你的encrypted_pa​​ssword数据库表列创建的getter“ encrypted_pa​​ssword ”Rails时,你用“ self ”作为前缀,以便告诉Ruby:“好吧我想使用我的用户的encrypted_pa​​ssword方法实例变量。“

在Ruby中,通过将其名称传递给接收者来调用方法。 你这样写:

my_receiver.my_method

在您的情况下,我们将方法encrypted_pa​​ssword传递给User实例变量。 但是我们不知道如何命名这个实例变量,因此我们使用self这个词告诉Ruby:“我正在谈论调用encrypted_pa​​ssword方法的User类的任何实例变量”。

例如,我们可以将实例变量命名为“toto”:

 toto = User.new 

所以toto.encrypted_password会显示加密的密码,在这种情况下我们的代码中的self会引用toto。

但是,感谢Ruby, 如果在调用方法时没有给任何接收器 ,Ruby会假设你将它传递给self

参考: 实用程序员指南

所以在你的例子中,你甚至不需要把“自我”。 作为前缀。 你可以这样写:

 class User < ActiveRecord::Base def encrypt_password encrypted_password = encrypt(password) end end 

我希望这有助于澄清这个有趣的主题。

TL; DR –

如果您打算将widget_count保存回数据库,请始终写self.widget_count = 123

(但请阅读很长的答案,因为知道的原因很有价值。)