Rails - 自我与@

时间:2011-06-13 06:12:43

标签: ruby ruby-on-rails-3 instance-variables self

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

注意:我已经查看了帖子herehere,但他们都说“自我”正在调用attribute =方法,我甚至都不明白该方法如何存在于此,因为我从未创建它或声明encrypted_pa​​ssword w / attr_accessor。所以我仍然感到困惑,这不是这些问题的重新发布。

3 个答案:

答案 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_pa​​ssword”的setter和getter。

在您的示例中,rails可能使用名为 @encrypted_pa​​ssword的实例变量,也许rails使用名为 @attributes [“encrypted_pa​​ssword”]的哈希实例变量,< em>或者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时,请在其前面加上“自我“为了告诉Ruby:”好吧我想使用用户实例变量的encrypted_pa​​ssword方法。“

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

my_receiver.my_method

在您的情况下,我们将 encrypted_pa​​ssword 方法传递给用户实例变量。但是我们不知道如何命名这个实例变量,所以我们用 self 这个词告诉Ruby:“我说的是用户的任何实例变量调用encrypted_pa​​ssword方法的类“。

例如,我们可以将实例变量命名为“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

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