What is the difference between referring to the model directly and using self in class methods?

StackOverflow https://stackoverflow.com/questions/20549285

سؤال

I'm trying to understand some code in Michael Hartl's awesome Rails Tutorial.

In ch 8, we add some class methods to the User model:

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

What is the difference between doing this:

  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

And this:

  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

If there is no difference, is there something that makes the former more desirable than the latter?

هل كانت مفيدة؟

المحلول

Inside a class definition (e.g. class User ...), def encrypt ... defines an instance method, whereas def self.encrypt ... defines a class method.

Defining instance methods and class methods

Suppose we defined the User class like this:

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

Invoking an instance method

Now we can create an instance of User with the constructor User.new, which (automatically) invokes initialize above:

my_user = User.new("Tim")

In initialize, the value we gave to the constructor is assigned to the instance variable @name_instance_variable.

Now we can invoke instance methods on my_user, our instance of the User class:

my_user.encrypt
# => Received User#encrypt instance method.
#    - `self` is an instance of User.
#    - `@name_instance_variable` is "Tim".

We can create another instance and give a different value to the constructor:

User.new("Jordan").encrypt
# => Received User#encrypt instance method.
#    - `self` is an instance of User.
#    - `@name_instance_variable` is "Jordan".

Invoking a class method:

We can also invoke class methods on the User class:

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

We get nil for @name_instance_variable because the class method does not have access to any of the instances we created, and we haven't done anything that would assign a value to it.

Another class method, and using one class method inside another

After we've created the User class, we can also define a new class method like this:

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

...that's equivalent (but not necessarily preferable) to:

class User
  def self.create_remember_token
    # ...
  end
end

Now we have a User.create_remember_token class method, which works like this:

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.

Defining another instance method, and using instance methods inside other instance methods

Suppose we defined another instance method:

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

Now we have a User#create_remember_token instance method, which does this:

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".

Using class methods within instance methods

Finally, there are two ways to invoke class methods from within instance methods. Suppose we defined our instance method like this instead:

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

Now it will work like this:

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.

Note that even though we invoked the class method User.encrypt inside of the instance method User#encrypt, the instance variable @name_instance_variable is still not available inside the class method.

When to use a class method

So why do class methods exist? Because you aren't always working with an instance.

There are good examples of this in Rails. Suppose we have a "Comment" model. Comment#update_attributes is an instance method because it works with an instance of Comment. It knows about the instance's attributes and its internal state. When you use update_attributes you know that you're making changes to that instance of Comment.

Comment.find, however, is a class method. When you use Comment.find you don't have an instance of Comment (yet). If find was an instance method we wouldn't be able to use it without first creating an instance:

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

...and that doesn't make much sense.

Rails could have done this another way, like defining a CommentFinder class, which makes a little more sense:

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

...and is actually pretty common in some languages (and even in some Ruby libraries; the popular factory pattern does this). But in Rails they've decided to make this simpler by making this functionality a class method of the Comment class, so we can just do this:

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

Final notes

  1. You might've noticed that I keep referring to instance methods like this: User#encode and class methods like this: User.encode. This is a convention you'll see throughout Ruby and Rails' documentation and books and articles about both. A # after the class name means it's an instance method and a . means it's a class method.

  2. Ruby is a "message-passing" language. You don't really need to worry about the distinction, but knowing this is handy because of some of the terminology you might run into. When we write my_user.encode_video we're technically sending the "message" encode_video to the my_user object. my_user receives the message encode_video.

    Colloquially, we often say "I called the encode method" or "this code invokes encode," and it's fine to use those terms instead (and in truth more developers will probably understand them). You'll probably find code before too long, though, that does something like my_user.send(:encode_video), or even type = "video"; ... my_user.send("encode_#{type}") which makes more sense when you understand that "send" means "call" or "invoke."

  3. Ruby is a very flexible language. Above in some of the output I wrote, "self is an instance of Class". That's not a mistake--in Ruby, even classes are objects, and any class you create will be an instance of the class named Class. There are even ways to define instance methods and instance variables on a class itself (rather than instances of it), but that's way beyond the scope of this answer. If you're dying of curiosity, google "eigenclass."

I hope that's helpful.

نصائح أخرى

EDIT - To answer the initial question, def ClassName.method is an explicit declaration of the class, def self.method is a class method referring to the class that the method is defined inside. I can't think of a use for ClassName.method, but I'm sure there is one somewhere in the broad broad scope of metaprogramming.

The difference is that

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

is an instance method. It can only be called on instances of a class (objects).

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

is a class method. It can only be called on the class.

In an instance method, self refers to the instance, ClassName will always refer to the class. In a class method, self refers to the class.

The reason for the difference is design pattern. In this case, because the method never uses an instance of the User class, it's better left as a class method.

I believe that self.method and Class.method are equivalent. Obviously if you wanted to mix code between multiple classes, you need to use self. Otherwise I think they are the same for all intents and purposes.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top