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

标签 ruby-on-rails ruby class-method

我试图理解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_usernil
另一个类方法,并在另一个类方法中使用一个类方法
创建用户类之后,我们还可以定义一个新的类方法,如下所示:
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/

相关文章:

ruby-on-rails - 异常通知 - 如何在开发中关闭。模式

ruby - 从 ruby​​ 中的标准输入获取整数数组的高效/直接方法

ruby - 创建/编辑 Vagrant base box 以预安装 Recipe

html - 发现很难对齐登录界面

ruby-on-rails - 如何在 active_admin formtastic 中嵌入原始 html

ruby-on-rails - 在 Nitrous.io 上安装 ElasticSearch?

iphone - Objective C - 在主线程上调用类方法?

javascript - 将局部变量传递到另一个 View 的 JS - Rails

python - 我应该什么时候使用@classmethod,什么时候使用def method(self)?

JavaScript:利用 setInterval() 调用类方法