直接引用模型和在类方法中使用self有什么区别?

时间:2013-12-12 16:48:31

标签: ruby-on-rails ruby class-method

我正在尝试理解迈克尔哈特尔精彩的Rails教程中的一些代码。

在第8章中,我们向User模型添加了一些类方法:

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

如果没有差异,是否有一些东西使前者比后者更可取?

3 个答案:

答案 0 :(得分:4)

在类定义(例如class User ...)中,def encrypt ...定义实例方法,而def self.encrypt ...定义类方法

定义实例方法和类方法

假设我们像这样定义了User类:

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.new创建User的实例,它会自动调用上面的initialize

my_user = User.new("Tim")

initialize中,我们为构造函数赋予的值被分配给实例变量 @name_instance_variable

现在我们可以在my_user,我们的User类的实例上调用实例方法

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 上调用类方法

User.encrypt
# => Received User.encrypt class method.
#    - `self` is an instance of Class.
#    - `@name_instance_variable` is nil.

我们nil获得了@name_instance_variable,因为方法无法访问我们创建的任何实例,我们还没有'做了任何可以为其赋值的事情。

另一个类方法,并在另一个

中使用一个类方法

之后我们创建了User类,我们也可以定义一个新的类方法:

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

现在我们有一个User.create_remember_token类方法,其工作方式如下:

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.

请注意,即使我们在实例方法User.encrypt内调用方法User#encrypt实例变量 @name_instance_variable类方法中仍然不可用。

何时使用类方法

那么为什么存在类方法呢?因为您并不总是使用实例

在Rails中有很好的例子。假设我们有一个“评论”模型。 Comment#update_attributes实例方法,因为它适用于Comment的实例。它知道实例的属性及其内部状态。当您使用update_attributes时,您知道您正在对该评论实例进行更改。

但是,

Comment.find类方法。当您使用Comment.find时,您还没有评论实例。如果find实例方法,我们将无法在未先创建实例的情况下使用它:

id = 6
comment = Comment.new.find(id) # => #<Comment id: 6, ...>

......这没有多大意义。

Rails可以用另一种方式完成,比如定义一个CommentFinder类,这使得 little 更有意义:

comment_finder = CommentFinder.new
comment = comment_finder.find(id) # => #<Comment id: 6, ...>

...实际上在某些语言中很常见(甚至在一些Ruby库中;流行的factory pattern也这样做)。但是在Rails中,他们决定通过使这个功能成为Comment类的类方法来使这更简单,所以我们可以这样做:

comment = Comment.find(id) # => #<Comment id: 6, ...>

最后的笔记

  1. 您可能已经注意到我一直在引用实例方法,如下所示:User#encode类方法,如下所示:{{1} }。这是一个您将在Ruby和Rails的文档以及关于这两者的书籍和文章中看到的约定。类名后面的User.encode表示它是一个实例方法,而#表示它是一个类方法。

  2. Ruby是一种“消息传递”语言。你真的不需要担心这种区别,但是由于你可能会遇到一些术语,因此知道这一点很方便。当我们写.时,我们在技术上 my_user.encode_video 发送到encode_video对象。 my_user 收到邮件my_user

    通俗地说,我们经常说“我调用 encode_video方法”或“此代码调用 encode”,并且可以使用相反,这些术语(实际上更多的开发人员可能会理解它们)。你可能会在很长一段时间内找到代码,但这样做会像encode,甚至是my_user.send(:encode_video)这样,当你理解“发送”意味着“调用”或“调用”时更有意义。 / p>

  3. Ruby是一种非常灵活的语言。在我写的一些输出中,“type = "video"; ... my_user.send("encode_#{type}")是Class的实例”。这不是一个错误 - 在Ruby中,即使类是对象,您创建的任何类都将是名为Class的类的实例。甚至有一些方法可以在类本身(而不是它的实例)上定义实例方法和实例变量,但这超出了本答案的范围。如果你因好奇而死,请点击“eigenclass。”

  4. 我希望这有用。

答案 1 :(得分:2)

编辑 - 要回答最初的问题,def ClassName.method是类的显式声明,def self.method是一个类方法,引用该方法在类中定义的类。我想不出ClassName.method的用法,但我确信在广泛的元编程范围内有一个地方。

区别在于

  def encrypt(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

实例方法。它只能在类(对象)的实例上调用。

def User.encrypt(token)
  Digest::SHA1.hexdigest(token.to_s)
end

类方法。它只能在课堂上调用。

在实例方法中,self表示实例,ClassName始终引用该类。在类方法中,self指的是类。

差异的原因是设计模式。在这种情况下,因为该方法从不使用User类的实例,所以最好留作类方法。

答案 2 :(得分:0)

我相信self.method和Class.method是等价的。显然,如果你想在多个类之间混合代码,你需要使用self。否则我认为它们在所有意图和目的上是相同的。