Total Newbie: Instance Variables in Ruby

Total newbie: Instance variables in ruby?

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.

Ruby class variable initial value

You're mixing things a bit.

What you've defined with @a = 100 is a class instance variable.

What you're going to have access to with attr_accessor :a is an instance variable @a.

Example of instance variable usage:

class A
def initialize a
@a = a
end
attr_accessor :a
end

instance = A.new(2)
#=> 2
instance.instance_variables
#=> [:@a]
instance.a
#=> 2

Example of class instance variable usage:

class A
@a = 1
class << self
attr_accessor :a
end
end

A.a
#=> nil
A.a = 2
#=> 2
A.a
#=> 2
instance = A.new
instance.class.a # access instance's class instance variable
#=> 2

Ruby class instance variable vs. class variable

Instance variable on a class:

class Parent
@things = []
def self.things
@things
end
def things
self.class.things
end
end

class Child < Parent
@things = []
end

Parent.things << :car
Child.things << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things #=> [:doll]
p mom.things #=> [:car]
p dad.things #=> [:car]

Class variable:

class Parent
@@things = []
def self.things
@@things
end
def things
@@things
end
end

class Child < Parent
end

Parent.things << :car
Child.things << :doll

p Parent.things #=> [:car,:doll]
p Child.things #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

With an instance variable on a class (not on an instance of that class) you can store something common to that class without having sub-classes automatically also get them (and vice-versa). With class variables, you have the convenience of not having to write self.class from an instance object, and (when desirable) you also get automatic sharing throughout the class hierarchy.


Merging these together into a single example that also covers instance variables on instances:

class Parent
@@family_things = [] # Shared between class and subclasses
@shared_things = [] # Specific to this class

def self.family_things
@@family_things
end
def self.shared_things
@shared_things
end

attr_accessor :my_things
def initialize
@my_things = [] # Just for me
end
def family_things
self.class.family_things
end
def shared_things
self.class.shared_things
end
end

class Child < Parent
@shared_things = []
end

And then in action:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things << :vacuum
mama.shared_things << :car
papa.shared_things << :blender
papa.my_things << :quadcopter
joey.my_things << :bike
suzy.my_things << :doll
joey.shared_things << :puzzle
suzy.shared_things << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things #=> [:house, :vacuum]
p papa.family_things #=> [:house, :vacuum]
p mama.family_things #=> [:house, :vacuum]
p joey.family_things #=> [:house, :vacuum]
p suzy.family_things #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things #=> [:car, :blender]
p mama.shared_things #=> [:car, :blender]
p Child.shared_things #=> [:puzzle, :blocks]
p joey.shared_things #=> [:puzzle, :blocks]
p suzy.shared_things #=> [:puzzle, :blocks]

p papa.my_things #=> [:quadcopter]
p mama.my_things #=> []
p joey.my_things #=> [:bike]
p suzy.my_things #=> [:doll]

Ruby Beginner: Can't Get Created Methods To Work

I hope the code you've written is for learning purpose. As none of its bits makes any sense to me.

The error you're getting is because car array which you have defined in new_car method is local and won't be available to other instance methods. For that you need to use attr_accessor to make car to be available as instance method which can be used for reading and writing instance variable like so:

class Car

attr_accessor :car

def new_car
@car = []
end

def add_wheels
car << "wheels"
return car
end

def add_axels
car << "axels"
return car
end

def add_engine
car << "engine"
return car
end

def add_radio
car << "radio"
return car
end
end

newcar = Car.new

newcar.new_car
puts newcar.add_wheels
#=> 'wheels'

Note that @car = [] in new_car method. That defines the instance variable @car with an empty array, which can be accessed by reader method car and writer method car<< or car= in the code later.

Also, having your instance variables initialized like @car = [], is a good place for initialize method, not new_car:

def initialize
@car = []
end

So, when you call Car.new, it will call initialize method and your variable @car = [] will be initialized by default.

Declaring an instance variable outside of `initialize` method

I have been taught to declare my instance variables with def initialize

Since initialize is the first instance method call in an object's life cycle, you typically declare your instance variables right there in order to ensure properly initialized variables. It's also the first place I'd expect instance variables to be defined when reading code.

I have been under the impression that I could declare instance variables only within my initialize methods.

There's no such restriction. You can declare instance variable anywhere within your instance.

A common use is memoization:

class FooBar
def foo
@foo ||= expensive_operation
end
end

On the first call, this would evaluate expensive_operation and assign the result to @foo. On subsequent calls, @foo is returned.

Another popular example is Rails which uses instance variables to pass data from the controller to its view:

class FooController < ApplicationController
def index
@foos = Foo.all
end
end

is there a best practices rule I should follow, regarding where to declare instance variables

It depends on their purpose (see above examples). As a general rule, declare them in a way that avoids undefined variables (nil errors) and structure your code so it is easy to read / follow.

Why Are the Instance Variables Declared in the Main Method?

Variables defined within a method are local variables, they belong to an invocation of the method, not to an instance of an object.

The intent seems to be to provide an example that is understandable to beginners who haven't been introduced to constructors, instance variables, and methods. They want to teach local variable declaration, some simple calculating and if-statements, and printing to the console before getting into that other stuff.

As an exercise it would be fine for you to change the CarLoan class to give it instance variables, just to see another way to do it. Keep the variable values hard-coded, make an instance method that calculates the monthly payment, and have the main method print the result to the console.



Related Topics



Leave a reply



Submit