Rails——自我与@
我正在关注 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设置了 crypto_password 属性,但为什么 @encrypted_password 不这样做呢?在对上一篇关于 before_save
不起作用的帖子的回复中,有人说在该方法以我最初编码的方式结束后,实例变量被“遗忘”——为什么会出现这种情况?有人可以解释一下 self 和 @ 在上面代码的上下文中如何不同工作吗?
注意:我已经看过这些帖子 这里和这里,但它们都是说“自己”正在调用 attribute =
方法,我什至不明白该方法如何存在于此处,因为我从未创建它或使用 attr_accessor
声明加密的密码。所以我仍然很困惑,这不是这些问题的重新发布。
I am following Michael Hartl's RoR tutorial, and it is covering the basics of password encryption. This is the User model as it currently stands:
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
I posted a previous question about before_save
not working, and it turns out that what I had accidentally done is written my encrypt_password as:
def encrypt_password
@encrypted_password = encrypt(password)
end
I understand that if self.encrypted_password sets the encrypted_password attribute, but why does @encrypted_password not do that as well? In the response to the previous post about before_save
not working someone said that the instance variable was "forgotten" after the method ended with the way I had originally coded it -- why was this the case? Can someone please explain how self and @ work differently in the context of the code above?
NOTE: I already took a look at the posts here and here, but they both say that "self" is calling the attribute =
method, and I don't even understand how that method could exist here since I never created it or declared the encrypted_password w/ attr_accessor
. So I am still confused, and this is not a re-posting of those questions.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
Rails 已自动为您添加
encrypted_password
的访问器,因为users
表中存在具有该名称的字段。您添加到表中的任何字段都将通过
self.field_name
自动提供。此处是 Michael Hartl 的教程在以下位置创建
encrypted_password
字段的位置users
表。另请查看链接页面中的
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
中的属性。请注意,您还可以通过从
User
类中调用的@attributes["encrypted_password"]
获取/设置数据(但访问器方法是执行此操作的便捷方法) )。The accessors for
encrypted_password
have been automatically added by Rails for you because a field by that name exists in theusers
table.Any field you add to a table will be automatically made available via
self.field_name
.Here is where Michael Hartl's tutorial creates the
encrypted_password
field in theusers
table.Also look at the
user_spec.rb
(Listing 7.3) in the linked page, where the author is testing for the presence of theencrypted_password
field.UPDATED:
As @mu points out, the
@
is used for Ruby instance variables (aka "iv"). Butencrypted_password
is an "attribute" defined by Rails, and is not an instance variable.If you run
User.find(1).instance_variables
, you will see that there is an iv called@attributes
, which is of typeHash
.Inside that iv is where the
encrypted_password
is stored. Rails has defined accessor methods forencrypted_password
, which gets/sets the data for thatattribute in the
@attributes
Hash
.Note that you could also get/set the data via
@attributes["encrypted_password"]
called from within theUser
class (but the accessor methods are convenient way to do just that).如果你允许的话,我想重新表述一下答案。
我在这篇帖子中解释说,一旦您创建一个与数据库的(复数)表名之一具有相同(单数)名称的(rails-)模型,rails 的“魔力”将创建setter 和 getter 以便修改表的记录。
这是因为您的模型继承了 ActiveRecord::Base 类的所有方法,该类定义了基本的 CRUD 访问器(创建、读取、更新、删除)。
与您的问题相关的关键点是,您不知道rails如何实现与数据库表列相关的实例变量,而且您不应该这样做。 :) 您需要知道的是,此时您已经拥有可用于 CRUD(创建、读取、更新、删除)数据库列“加密密码”的 setter 和 getter。
在您的示例中,也许rails使用名为 @encrypted_password的实例变量,也许rails使用名为 @attributes[“encrypted_password”]的哈希实例变量,或者也许rails使用了一个名为@you_will_never_guess_encrypted_password的实例变量。
-
这是一个很好的观点,你不知道实例变量的内部 Rails 行为。 2019 年,Rails 的进一步开发可能会导致框架使用 @complicated-hash-instance-variable 来存储 cryptod_password 值。
事实上,最好的方法是让 Rails 使用实例变量来管理其“私有”“事务”;),并且只使用它提供给您的 getter 和 setter 方法。
因此,您的应用程序在下个世纪仍将使用 crypto_password(我希望如此 ^^)。
因此,如果您使用@encrypted_password,它可能适用于某些“虚构”版本的rails,并且不再适用于其他rails版本。实际上,对于当前版本的 Rails 来说,它不起作用。
-
第二个关键点是,当您想要使用为 crypto_password 数据库表列创建的 getter“encrypted_password”Rails 时,您可以在其前面加上“self< /strong>”,以便告诉 Ruby:“好吧,我想使用我的 User 实例变量的 crypto_password 方法。”
在 Ruby 中,通过将方法的名称传递给接收者来调用方法。
您可以这样编写:
my_receiver.my_method
在您的情况下,我们将方法 encrypted_password 传递给 User 实例变量。但我们不知道这个实例变量将如何命名,因此我们使用单词 self 告诉 Ruby:“我正在谈论 User 的任何实例变量调用 crypto_password 方法的类”。
例如,我们可以将实例变量命名为“toto”:
这样toto.encrypted_password将显示加密的密码,而在这种情况下,我们的代码中的self将引用toto。
然而,多亏了 Ruby,如果您在调用方法时没有提供任何接收者,Ruby 会假设您将其传递给 self。
参考: 实用程序员指南
因此,在您的示例中,您甚至不需要加上“自我”。作为前缀。
你可以这样写:
我希望这有助于澄清这个有趣的主题。
If you let me, I'd like to rephrase the answer.
I explained in this post, that as soon as you create a (rails-) Model with the same (singular) name as one of the (plural) tablenames of your database, the "magic" of rails will create setters and getters in order to modify your table's records.
This is because your model inherits all methods from the ActiveRecord::Base Class, which defines basic CRUD accessors (Create, Read, Update, Delete).
The key point related to your question, is that you don't know how rails implements the instance variable related to your database table column, And you shouldn't. :) All you have to know is that at that point, you have setters and getters available to CRUD (create, read, update, delete) your database column "encrypted_password".
In your example, maybe rails uses an instance variable called @encrypted_password, maybe rails uses an hash-instance-variable called @attributes["encrypted_password"], or maybe rails uses an instance variable called @you_will_never_guess_encrypted_password.
-
And that's a good point you don't know about the internal rails behavior with instance variables. In 2019 Rails further development may lead the framework to use @complicated-hash-instance-variable to store the encrypted_password value.
In fact the best approach is to let rails manage its "private" "affair" ;) with instance variables, and just use the getter and setter methods it provides to you.
So your application will still work with encrypted_password in the next century (I hope so ^^).
So if you use @encrypted_password it may work with some "imaginary" version of rails and it won't work anymore with other rails versions. Actually with a current version of rails it doesn't work.
-
The second key point is that when you want to use the getter "encrypted_password" Rails created for your encrypted_password database table column, you prefix it with "self" in order to tells Ruby : "ok I want to use the encrypted_password method of my User instance variable."
In Ruby, a method is called by passing its name to a receiver.
You write it like this :
my_receiver.my_method
In your case we pass the method encrypted_password to the User instance variable. But we don't know how this instance variable will be named, so we use the word self to tell Ruby : "I'm talking about any instance variable of the User class that calls the encrypted_password method".
For instance we could have named our instance variable "toto" :
so
toto.encrypted_password
would display the encrypted password, and self in this very case in our code would reference toto.However, thanks to Ruby, if you don't give any receiver when calling a method, Ruby will assume you pass it to self.
Reference : Pragmatic Programmer's guide
So in your example, you even don't need to put "self." as prefix.
You could have it written like this :
I hope this helps to clarify this interesting subject.
TL;DR -
如果您打算将 widget_count 保存回数据库,请始终写入
self.widget_count = 123
。(但请务必阅读长答案,因为了解其原因很有价值。)
TL;DR -
Always write
self.widget_count = 123
if you intend to save widget_count back to the database.(But please do read the long answers, as the reason why is valuable to know.)