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
How to Stub Things in Minitest
Why Isn't Current Directory on My Ruby Path
Validation Failed: Upload File Has an Extension That Does Not Match Its Contents
What's the Precedence of Ruby'S Method Call
403 Forbidden on Rails App W/ Nginx, Passenger
In Ruby, Why Does Nil[1]=1 Evaluate to Nil
Best Ruby on Rails Social Networking Framework
How Does One Use Rescue in Ruby Without the Begin and End Block
How to Group by Count in Array Without Using Loop
Can't Install Pg Gem on Windows
Rails Installation Failed on Ubuntu With "Cannot Load Such File - Mkmf"
Rvm Warning! Path Is Not Properly Set Up
Installed Ruby Using Apt-Get Install Ruby 2.0.0 Succeeded But Not Using Correct Ruby Version