Thus far, all the methods we've created are instance methods. That is, they are methods that pertain to an instance or object of the class. There are also class level methods, called class methods. Class methods are methods we can call directly on the class itself, without having to instantiate any objects. We haven't implemented any class methods at this point, so let's do that now.
When defining a class method, we prepend the method name with the reserved word self.
, like this:
# ... rest of code ommitted for brevity
def self.what_am_i # Class method definition
"I'm a GoodDog class!"
end
Then when we call the class method, we use the class name GoodDog
followed by the method name, without even having to instantiate any objects, like this:
GoodDog.what_am_i # => I'm a GoodDog class!
Why do we need a class method for this? This example is a little contrived, but class methods are where we put functionality that does not pertain to individual objects. Objects contain state, and if we have a method that does not need to deal with states, then we can just use a class method, like our simple example. We'll take a look at a more useful example in the next section.
Just as instance variables capture information related to specific instances of classes (i.e., objects), we can create variables for an entire class that are appropriately named class variables. Class variables are created using two @
symbols like so: @@
. Let's create a class variable and a class method to view that variable.
class GoodDog
@@number_of_dogs = 0
def initialize
@@number_of_dogs += 1
end
def self.total_number_of_dogs
@@number_of_dogs
end
end
puts GoodDog.total_number_of_dogs # => 0
dog1 = GoodDog.new
dog2 = GoodDog.new
puts GoodDog.total_number_of_dogs # => 2
We have a class variable called @@number_of_dogs
, which we initialize to 0. Then in our constructor (the initialize
method), we increment that number by 1. Remember that initialize
gets called every time we instantiate a new object via the new
method. This also demonstrates that we can access class variables from within an instance method (initialize
is an instance method). Finally, we just return the value of the class variable in the class method self.total_number_of_dogs
. This is an example of using a class variable and a class method to keep track of a class level detail that pertains only to the class, and not to individual objects.
When creating classes there may also be certain variables that you never want to change. You can do this by creating what are called constants. You define a constant by using an upper case letter at the beginning of the variable name. While technically constants just need to begin with a capital letter, most Rubyists will make the entire variable uppercase.
class GoodDog
DOG_YEARS = 7
attr_accessor :name, :age
def initialize(n, a)
self.name = n
self.age = a * DOG_YEARS
end
end
sparky = GoodDog.new("Sparky", 4)
puts sparky.age # => 28
Here we used the constant DOG_YEARS
to calculate the age in dog years when we created the object, sparky
. Note that we used the setter methods in the initialize
method to initialize the @name
and @age
instance variables given to us by the attr_accessor
method. We then used the age
getter method to retrieve the value from the object.
DOG_YEARS
is a variable that will never change for any reason so we use a constant. It is possible to reassign a new value to constants but Ruby will throw a warning.
The to_s
instance method comes built in to every class in Ruby. In fact, we have been using it all along. For example, suppose we have the GoodDog
class from above, and the sparky
object as well from above.
puts sparky # => #<GoodDog:0x007fe542323320>
What's happening here is that the puts
method automatically calls to_s
on its argument, which in this case is the sparky
object. In other words puts sparky
is equivalent to puts sparky.to_s
. The reason we get this particular output lies within the to_s
method in Ruby. By default, the to_s
method returns the name of the object's class and an encoding of the object id.
Note: puts
method calls to_s
for any argument that is not an array. For an array, it writes on separate lines the result of calling to_s
on each element of the array.
To test this, we can add a custom to_s
method to our GoodDog
class, overriding the default to_s
that comes with Ruby.
class GoodDog
DOG_YEARS = 7
attr_accessor :name, :age
def initialize(n, a)
@name = n
@age = a * DOG_YEARS
end
def to_s
"This dog's name is #{name} and it is #{age} in dog years."
end
end
Let's try again:
puts sparky # => This dog's name is Sparky and is 28 in dog years.
And yes, it works! We were able to change the output by overriding the to_s
instance method.
There's another method called p
that's very similar to puts
, except it doesn't call to_s
on its argument; it calls another built-in Ruby instance method called inspect
. The inspect
method is very helpful for debugging purposes, so we don't want to override it.
p sparky # => #<GoodDog:0x007fe54229b358 @name="Sparky", @age=28>
This output implies that p sparky
is equivalent to puts sparky.inspect
.
Besides being called automatically when using puts
, another important attribute of the to_s
method is that it's also automatically called in string interpolation. We've seen this before when using integers or arrays in string interpolation:
irb :001 > arr = [1, 2, 3]
=> [1, 2, 3]
irb :002 > x = 5
=> 5
irb :003 > "The #{arr} array doesn't include #{x}."
=> The [1, 2, 3] array doesn't include 5.
Here, the to_s
method is automatically called on the arr
array object, as well as the x
integer object. We'll see if we can include our sparky
object in a string interpolation:
irb :001 > "#{sparky}"
=> "This dog's name is Sparky and it is 28 in dog years."
In summary, the to_s
method is called automatically on the object when we use it with puts
or when used with string interpolation. This fact may seem trivial at the moment, but knowing when to_s
is called will help us understand how to read and write better OO code.
As shown above, you can customize the behavior of #to_s
in a class. If you don't customize #to_s
, Ruby looks up the inheritance chain for another version of #to_s
, which is usually Object#to_s
.
When overriding (customizing) #to_s
for use in a custom class, you must remember that Ruby expects #to_s
to always return a string. If it does not return a string, #to_s
won't work as expected in places where #to_s
is implicitly invoked like puts
and interpolation. Instead of printing (or inserting) the value returned by #to_s
, Ruby will ignore the non-string value and look in the inheritance chain for another version of #to_s
that does return a string. In most cases, it will use the value returned byObject#to_s
instead. For instance:
class Foo
def to_s
42
end
end
foo = Foo.new
puts foo # Prints #<Foo:0x0000000100760ec0>
puts "foo is #{foo}" # Prints: foo is #<Foo:0x0000000100760ec0>
If you change 42
to a string, then the code will work as intended:
class Foo
def to_s
"42"
end
end
foo = Foo.new
puts foo # Prints 42
puts "foo is #{foo}" # Prints: foo is 42
It's also worth noting that overridding #to_s
only works for objects of the type where the customized #to_s
method is defined. In particular, if you have a Bar
object named bar
that has an attribute named xyz
and a Bar#to_s
method, then puts bar.xyz
will not use the customized #to_s
. The value returned by xyz
is not a Bar
object, so Bar#to_s
does not apply it:
class Bar
attr_reader :xyz
def initialize
@xyz = { a: 1, b: 2 }
end
def to_s
'I am a Bar object!'
end
end
bar = Bar.new
puts bar # Prints I am a Bar object!
puts bar.xyz # Prints {:a=>1, :b=>2}
We talked about self
earlier, but let's try to dive a little deeper so you can understand exactly what self
is and how to understand what it's referencing. self
can refer to different things depending on where it is used.
For example, so far we've seen two clear use cases for self
:
Use self
when calling setter methods from within the class. In our earlier example we showed that self
was necessary in order for our change_info
method to work properly. We had to use self
to allow Ruby to disambiguate between initializing a local variable and calling a setter method.
Use self
for class method definitions.
Let's play around with self
to see why the above two rules work. Let's assume the following code:
class GoodDog
attr_accessor :name, :height, :weight
def initialize(n, h, w)
self.name = n
self.height = h
self.weight = w
end
def change_info(n, h, w)
self.name = n
self.height = h
self.weight = w
end
def info
"#{self.name} weighs #{self.weight} and is #{self.height} tall."
end
end
This is our standard GoodDog
class, and we're using self
whenever we call an instance method from within the class. We know the rule to use self
, but what does self
really represent here? Let's add one more instance method to help us find out.
class GoodDog
# ... rest of code omitted for brevity
def what_is_self
self
end
end
Now we can instantiate a new GoodDog
object.
sparky = GoodDog.new('Sparky', '12 inches', '10 lbs')
p sparky.what_is_self
# => #<GoodDog:0x007f83ac062b38 @name="Sparky", @height="12 inches", @weight="10 lbs">
That's interesting. From within the class, when an instance method uses self
, it references the calling object. In this case, that's the sparky
object. Therefore, from within the change_info
method, calling self.name=
acts the same as calling sparky.name=
from outside the class (you can't call sparky.name=
inside the class, though, since it isn't in scope). Now we understand why using self
to call instance methods from within the class works the way it does!
The other place we use self
is when we're defining class methods, like this:
class MyAwesomeClass
def self.this_is_a_class_method
end
end
When self
is prepended to a method definition, it is defining a class method. We talked about these earlier. In our GoodDog
class method example, we defined a class method called self.total_number_of_dogs
. This method returned the value of the class variable @@number_of_dogs
. How is this possible?
Let's add a line to our GoodDog
class:
class GoodDog
# ... rest of code omitted for brevity
puts self
end
If you run the good_dog.rb
file with the GoodDog
class definition, you'll see that GoodDog
is output.
Thus, you can see that using self
inside a class but outside an instance method refers to the class itself. Therefore, a method definition prefixed with self
is the same as defining the method on the class. That is, def self.a_method
is equivalent to def GoodDog.a_method
. That's why it's a class method; it's actually being defined on the class. Using self.a_method
, rather than ClassName.a_method
is a convention. It is useful because if in the future we rename the class, we only have to change the name of the class, rather than having to rename all of the class methods too.
To be clear, from within a class...
self
, inside of an instance method, references the instance (object) that called the method - the calling object. Therefore, self.weight=
is the same as sparky.weight=
, in our example.
self
, outside of an instance method, references the class and can be used to define class methods. Therefore if we were to define a name
class method, def self.name=(n)
is the same as def GoodDog.name=(n)
.
Thus, we can see that self
is a way of being explicit about what our program is referencing and what our intentions are as far as behavior. self
changes depending on the scope it is used in, so pay attention to see if you're inside an instance method or not. self
is a tricky concept to grasp in the beginning, but the more often you see its use, the more you will understand object oriented programming. If the explanations don't quite make sense, just memorize those two rules above for now.
This chapter covered a lot of information about classes, objects and OOP. But you are becoming much more proficient in the language so you are able to digest more and process more complicated material. You are growing as a Ruby programmer. This is where things finally start to get fun.
In this chapter we covered...
new
method
attr_*
methods generate getters and setters
to_s
method is used and that we've been using it implicitly all along.
self
If you've never seen OOP before, this chapter should've been a doozy, so we need lots of practice to truly understand it. Don't skip the exercises; OOP is a very important piece of foundational knowledge that you'll need to truly understand Ruby code and frameworks, like Rails.
Let's get to some exercises so you can cement this knowledge into your brain and fingers. Then we can move on to the next chapter.
Add a class method to your MyCar class that calculates the gas mileage (i.e. miles per gallon) of any car.
class MyCar
# code omitted for brevity...
def self.gas_mileage(gallons, miles)
puts "#{miles / gallons} miles per gallon of gas"
end
end
MyCar.gas_mileage(13, 351) # => "27 miles per gallon of gas"
Video Walkthrough
Override the to_s method to create a user friendly print out of your object.
class MyCar
# code omitted for brevity...
def to_s
"My car is a #{color}, #{year}, #{@model}!"
end
end
my_car = MyCar.new("2010", "Ford Focus", "silver")
puts my_car # => My car is a silver, 2010, Ford Focus.
## Note the "puts" calls "to_s" automatically.
Video Walkthrough
When running the following code...
class Person
attr_reader :name
def initialize(name)
@name = name
end
end
bob = Person.new("Steve")
bob.name = "Bob"
We get the following error...
test.rb:9:in `<main>': undefined method `name=' for
#<Person:0x007fef41838a28 @name="Steve"> (NoMethodError)
Why do we get this error and how do we fix it?
We get this error because attr_reader
only creates a getter method. When we try to reassign the name instance variable to "Bob"
, we need a setter method called name=
. We can get this by changing attr_reader
to attr_accessor
or attr_writer
if we don't intend to use the getter functionality.
class Person
attr_accessor :name
# attr_writer :name ## => This also works but doesn't allow getter access
def initialize(name)
@name = name
end
end
bob = Person.new("Steve")
bob.name = "Bob"
Video Walkthrough