Pardon the total newbiew question but why is @game_score always nil?
#bowling.rb
class Bowling
@game_score = 0
def hit(pins)
@game_score = @game_score + pins
end
def score
@game_score
end
end
What's an instance variable? In the Ruby programming language, an instance variable is a type of variable which starts with an @ symbol. Example: @fruit. An instance variable is used as part of Object-Oriented Programming (OOP) to give objects their own private space to store data.
Instance variables can be initialized in constructors, where error handling or other logic can be used. To provide the same capability for class variables, the Java programming language includes static initialization blocks.
Ruby Class VariablesClass variables begin with @@ and must be initialized before they can be used in method definitions. Referencing an uninitialized class variable produces an error. Class variables are shared among descendants of the class or module in which the class variables are defined.
Let's walk through the code, shall we?
#bowling.rb
class Bowling
@game_score = 0 # (1)
At this point (1), we are still inside the class Bowling
. Remember: classes are just objects like any other. So, at this point you are assigning 0
to the instance variable @game_score
of the class object Bowling
.
def hit(pins)
@game_score = @game_score + pins # (2)
Now (2), we are inside an instance method of the Bowling
class. I.e.: this is a method that is going to belong to an instance of Bowling
. So, now the instance variable @game_score
belongs to an instance of the Bowling
class, and not to the class itself.
Since this instance variable is never initialized to anything, it will evaluate to nil
(in Ruby, uninitialized variables always evaluate to nil
), so this evaluates to @game_score = nil + pins
and since nil
doesn't have a #+
method, this will result in a NoMethodError
exception being raised.
end
def score
@game_score # (3)
And here (3), we are again inside an instance method of the Bowling
class. This will always evaluate to nil
, for the reason I outlined above: @game_score
is never initialized, therefore it evaluates to nil
.
end
end
We can use Ruby's reflection capabilities to take a look at what's going on:
p Bowling.instance_variable_get(:@game_score) # => 0
b = Bowling.new
p b.instance_variable_get(:@game_score) # => nil
Now let's inject a value into the instance variable:
b.instance_variable_set(:@game_score, 1)
p b.score # => 1
b.hit(3)
p b.score # => 4
So, we see that everything works as it should, we only need to figure out how to make sure the instance variable gets initialized.
To do that, we need to write an initializer method. Strangely, the initializer method is actually a private instance method called initialize
. (The reason why initialize
is an instance method and not a class method, is actually quite simple. Ruby splits object creation in two phases: memory allocation and object initialization. Memory allocation is done by a class method called alloc
and object initialization is done by an instance method called initialize
. (Objective-C programmers will recognize this.) The reason why alloc
is a class method is simply that at this point in the execution there is no instance yet. And the reason that initialize
is an instance method is that object initialization is obviously per-object. As a convenience, there is a standard factory class method called new
that calls both alloc
and initialize
for you.)
class Bowling
def initialize
@game_score = 0
end
end
Let's test this:
c = Bowling.new
p c.score # => 0
c.hit(2)
p c.score # => 2
BTW: just some minor Ruby style tips: indentation is 2 spaces, not 1 tab. And your hit
method would more idiomatically be @game_score += pins
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With