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, whereasdef self.encrypt ...
defines a class method.Defining instance methods and class methods
Suppose we defined the User class like this:
Invoking an instance method
Now we can create an instance of User with the constructor
User.new
, which (automatically) invokesinitialize
above: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:We can create another instance and give a different value to the constructor:
Invoking a class method:
We can also invoke class methods on the User class:
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:
...that's equivalent (but not necessarily preferable) to:
Now we have a
User.create_remember_token
class method, which works like this:Defining another instance method, and using instance methods inside other instance methods
Suppose we defined another instance method:
Now we have a
User#create_remember_token
instance method, which does this: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:
Now it will work like this:
Note that even though we invoked the class method
User.encrypt
inside of the instance methodUser#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 useupdate_attributes
you know that you're making changes to that instance of Comment.Comment.find
, however, is a class method. When you useComment.find
you don't have an instance of Comment (yet). Iffind
was an instance method we wouldn't be able to use it without first creating an instance:...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:
...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:
Final notes
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.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 themy_user
object.my_user
receives the messageencode_video
.Colloquially, we often say "I called the
encode
method" or "this code invokesencode
," 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 likemy_user.send(:encode_video)
, or eventype = "video"; ... my_user.send("encode_#{type}")
which makes more sense when you understand that "send" means "call" or "invoke."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.