我试图理解michael hartl的很棒的rails教程中的一些代码。
在ch 8中,我们向用户模型添加了一些类方法:
class User<ActiveRecord::Base
...
...
before_create :create_remember_token
def User.new_remember_token
SecureRandom.urlsafe_base64
end
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
这样做的区别是什么:
def User.encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
...
private
def create_remember_token
self.remember_token = User.encrypt(User.new_remember_token)
end
而这个:
def encrypt(token)
Digest::SHA1.hexdigest(token.to_s)
end
private
def create_remember_token
self.remember_token = self.encrypt(self.new_remember_token)
end
如果没有区别,有没有什么东西能使前者比后者更可取呢?
最佳答案
在类定义(例如class User ...
)中,def encrypt ...
定义实例方法,而def self.encrypt ...
定义类方法。
定义实例方法和类方法
假设我们这样定义用户类:
class User
def initialize(name)
@name_instance_variable = name
end
# This is an instance method definition, creating a method called `encrypt`
# on instances of the User class.
def encrypt
puts "Received User#encrypt instance method.\n" +
"- `self` is an instance of #{ self.class }.\n" +
"- `@name_instance_variable` is #{ @name_instance_variable.inspect }.\n"
end
# This is a class method definition, creating a method called `encrypt` on
# the User class itself.
def self.encrypt
puts "Received User.encrypt class method.\n" +
"- `self` is an instance of #{ self.class }.\n" +
"- `@name_instance_variable` is #{ @name_instance_variable.inspect }.\n"
end
end
调用实例方法
现在我们可以用构造函数创建一个User实例,它(自动)调用上面的
User.new
:my_user = User.new("Tim")
在
initialize
中,我们给构造函数的值被分配给实例变量initialize
。现在我们可以在用户类的实例
@name_instance_variable
上调用实例方法:my_user.encrypt
# => Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Tim".
我们可以创建另一个实例并为构造函数提供不同的值:
User.new("Jordan").encrypt
# => Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Jordan".
调用类方法:
我们还可以在用户类上调用类方法:
User.encrypt
# => Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
因为类方法没有访问我们创建的任何实例的权限,而且我们也没有做任何可以给它赋值的事情,所以我们得到了
my_user
的nil
。另一个类方法,并在另一个类方法中使用一个类方法
创建用户类之后,我们还可以定义一个新的类方法,如下所示:
def User.create_remember_token
puts "Received User.create_remember_token class method."
# Now let's use the User.encrypt class method from within this method.
encrypt
# We could also write this, because inside this class method `self` refers
# to the User class itself:
#
# self.encrypt
#
# This is equivalent too--almost (the difference appears when you use
# inheritance, i.e. create a subclass of User, but that's beyond the scope
# of this answer):
#
# User.encrypt
#
end
……这相当于(但不一定更可取):
class User
def self.create_remember_token
# ...
end
end
现在我们有一个
@name_instance_variable
类方法,其工作方式如下:User.create_remember_token
# => Received User.create_remember_token class method.
# Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
定义另一个实例方法,并在其他实例方法中使用实例方法
假设我们定义了另一个实例方法:
class User
# An instance method this time:
def create_remember_token
puts "Received User#create_remember_token instance method.\n"
"- `@name_instance_variable` is #{ @name_instance_variable }."
encrypt
# We could also write this, because inside this instance method `self`
# refers to this instance of User:
#
# self.encrypt
#
end
end
现在我们有了一个
User.create_remember_token
实例方法,它可以:my_user = User.new("Alice")
my_user.create_remember_token
# => Received User#create_remember_token instance method.
# - `@name_instance_variable` is "Alice."
# Received User#encrypt instance method.
# - `self` is an instance of User.
# - `@name_instance_variable` is "Alice".
在实例方法中使用类方法
最后,有两种方法可以从实例方法内部调用类方法假设我们这样定义实例方法:
class User
def create_remember_token
puts "Received User#create_remember_token instance method."
"- `@name_instance_variable` is #{ @name_instance_variable }."
# Since `self` refers to this instance of User, `self.class` equals
# the User class itself.
self.class.encrypt
# That's is (almost) equivalent to this:
#
# User.encrypt
#
# ("Almost" again because of the inheritance issue mentioned above.)
#
end
end
现在它将像这样工作:
User.new("Bob").create_remember_token
# => Received User#create_remember_token instance method.
# - `@name_instance_variable` is "Alice."
# Received User.encrypt class method.
# - `self` is an instance of Class.
# - `@name_instance_variable` is nil.
注意,即使我们在实例方法内部调用了类方法,实例变量在类方法内部仍然不可用。
何时使用类方法
那么为什么存在类方法呢?因为你并不总是在处理一个实例。
在Rails中有很好的例子假设我们有一个“评论”模型
User#create_remember_token
是一个实例方法,因为它与Comment的实例一起工作它知道实例的属性及其内部状态当您使用User.encrypt
时,您知道您正在对Comment的实例进行更改。User#encrypt
是一个类方法当您使用@name_instance_variable
时,您还没有Comment的实例如果Comment#update_attributes
是一个实例方法,则在不首先创建实例的情况下,我们将无法使用它:id = 6
comment = Comment.new.find(id) # => #<Comment id: 6, ...>
……这没什么意义。
Rails可以用另一种方式来实现,比如定义一个CommentFinder类,这样就更有意义了:
comment_finder = CommentFinder.new
comment = comment_finder.find(id) # => #<Comment id: 6, ...>
……而且在某些语言中非常常见(甚至在一些Ruby库中,流行的factory pattern也会这样做)但是在Rails中,他们决定通过将这个功能作为Comment类的一个类方法来简化这个过程,所以我们可以这样做:
comment = Comment.find(id) # => #<Comment id: 6, ...>
最后说明
你可能注意到我一直在引用这样的实例方法:
update_attributes
和类方法:Comment.find
这是您将在ruby和rails的文档、书籍和文章中看到的一个约定。类名后面的aComment.find
表示它是一个实例方法,find
表示它是一个类方法。Ruby是一种“消息传递”语言你不必担心区别,但是知道这一点很方便,因为你可能会遇到一些术语。当我们写
User#encode
时,我们从技术上讲是将“消息”User.encode
发送到#
对象.
接收消息。通俗地说,我们经常说“我调用了
my_user.encode_video
方法”或“这段代码调用了encode_video
”,而使用这些术语是可以的(事实上,更多的开发人员可能会理解它们)。不过,您很可能很快就会发现代码,它会执行类似于my_user
,甚至my_user
的操作,当您理解“send”意味着“call”或“invoke”时,这就更有意义了红宝石是一种非常灵活的语言。在上面我写的一些输出中,“
encode_video
是类的实例”这不是一个错误——在Ruby中,甚至类也是对象,您创建的任何类都将是名为Class的类的实例。甚至可以在类本身(而不是类的实例)上定义实例方法和实例变量,但这远远超出了这个答案的范围如果你垂死于好奇心,谷歌“eigenclass”我希望这有帮助。
关于ruby-on-rails - 直接引用模型和在类方法中使用self有什么区别?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20549285/