Instance Variables In Ruby
on June 23, 2007 @ 01:38 AM
Instance variables are the heart of an object’s encapuslated data. In ruby all instance variables are hidden behind the walls of an object although their exists ways to access and modify those instance variables from afar.
This post will go review:
- Creating instance variables
- Nil – The default value of an instance variable
- Accessing instance variables
- Checking for instance variable existence
- Breaking encapsulation
- Injecting instance variables into other objects
- Using named parameters to set instance variables
Creating instance variables
Instance variables are created with a single @ symbol.
(copy and paste code example)1 2 3 4 5 6 |
# the instance variable @a is initialized to 5 class A def initialize @a = 5 end end |
Nil – The default value of an instance variable
Unlike methods that don’t exist trying to reference a uninitialized instance variable will return nil rather then raise an exception.
1 2 3 4 |
@b # => nil @b.nil? # => true @b = 5 # => 5 @b.nil? # => false |
Accessing instance variables
In most languages to access an instance variable you have to write your own get/set methods.
1 2 3 4 5 6 7 8 9 10 11 12 |
class Person def name=(name) @name = name end def name @name end end joe = Person.new # => #<Person:0x524f04> joe.name = "joe" # => "joe" joe.name # => "joe" |
- attr_accessor – this writes a getter and a setter for you
- attr_writer – this writes a writer for you
- attr_reader – this writes a getter for you
Here’s a modified example from the above Person class:
1 2 3 |
sally = Person.new # => #<Person:0x4e3a90> sally.name = "sally" # => "sally" sally.name # => "sally" |
If we had used attr_writer we would have gotten the “name=” method for free, but we wouldn’t have been able to call “name”. Likewise, if we had used the attr_reader we would have been able to access Sally’s “name” but we would have never been able to set it with “name=”.
Checking for instance variable existence
Sometimes it’s useful to be able to know whether an instance variable has been defined or not. With an instance variable having the default value of nil it’s hard to rely on ”@varname” to determine if nil was the defined value.
So we rely on:- instance_variable_defined? – can be used to determine whether an instance variable has been defined
- instance_variables – an array of instance variables for a given object
1 2 3 4 5 6 7 8 9 |
class Person attr_accessor :name end joe = Person.new # => #<Person:0x1109f40> joe.instance_variable_defined?("@name") # => false joe.instance_variables # => [] joe.name = "joe" # => "joe" joe.instance_variable_defined?("@name") # => true joe.instance_variables # => ["@name"] |
In the above example notice that ”@name” did not exist until we assigned it a value.
Breaking encapsulation
You can break object encapsulation in ruby pretty easily due to it’s dynamic and highly reflective nature. I’ve seen people instance_eval to get and set the value of an instance variable, but there is a more proper way to break encapsulation.
1 2 |
joe.instance_variable_set :@name, 'joe' # => "joe" joe.instance_variable_get :@name # => "joe" |
This is also about 70x faster then using instance_eval.
Injecting instance variables into other objects
Frameworks like Ruby on Rails use the power of ruby to do magical things. One of those things is injecting variables into a given object, such as in the case of controllers and views. You can do this with what we’ve just seen with “instance_variable_set” and “instance_variable_get”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class BlankSlate ; end class ValueHolder def initialize @a, @b, @c = 1, 2, 3 end end slate = BlankSlate.new # => #<BlankSlate:0x101a224> slate.instance_variables # => [] values = ValueHolder.new # => #<ValueHolder:0x1014ef0 @c=3, @b=2, @a=1> values.instance_variables # => ["@c", "@b", "@a"] values.instance_variables.each do |varname| slate.instance_variable_set(varname, values.instance_variable_get(varname)) end # => ["@c", "@b", "@a"] slate.instance_variables # => ["@c", "@b", "@a"] |
Using named parameters to set instance variables
A common thing to do in JavaScript is to use object literals when calling functions to ease simulate named arguments. In Ruby the most common construct you’ll see is people passing hashes into a method call.
Person.new :first_name => "Joe", :last_name => "Gee", :middle_initial => "B" |
The calling syntax looks great, but what you have to do in the constructor for Person is quite hideous:
1 2 3 4 5 6 7 |
class Person def initialize(options) @first_name = options[:first_name] @last_name = options[:last_name] @middle_initial = options[:middle_initial] end end |
Given this trivial example calling “options[key]” may not be so annoying, but if you had a more complex example where you were using dependency injection it can get a little cumbersome.
We can start to scratch this itch by automatically setting instance variables to their passed in named parameters:
1 2 3 4 5 6 7 |
class Person def initialize(options) options.each_pair do |key,value| instance_variable_set "@#{key}", value end end end |
This approach has its own set of issues though, but given a good set of tests you could confidently use this approach. You could also add code to verify the arguments being passed in.
Another solution which would give you closer syntax that you get in JavaScript is forwarding method calls to hash keys:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
module ActAsNamedParameters def act_as_named_parameters(hsh) parent = class <<hsh ; self; end hsh.each_key do |key| parent.send(:define_method, key) { hsh[key] } end end end class Person include ActAsNamedParameters def initialize(attributes) act_as_named_parameters attributes @first_name = attributes.first_name @middle_initial = attributes.middle_initial @last_name = attributes.last_name end end Person.new :first_name => "Joe", :last_name => "Gee", :middle_initial => "B" |
Here we’re invoking a helper module to do the dirty work for us. A downside here is that the helper module modifies the hash we pass it. In most cases side effects to method calls is undesired, but if you are using simple hashes for argument passing that may not be a large concern for you.
Even though there are lots of ways you can accomplish different ways to treat arguments in ruby I’ll leave you with one last small refactoring to our previous example. Rather then rely on a helper module that we must invoke we could always add the accessors to the Hash itself.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class Hash def method_missing(method, *args) if self[method.to_sym] and args.empty? self[method.to_sym] else super end end end class Person def initialize(attributes) @first_name = attributes.first_name @middle_initial = attributes.middle_initial @last_name = attributes.last_name end end Person.new :first_name => "Joe", :last_name => "Gee", :middle_initial => "B" |


0 comments
Jump to comment form | comments rss [?]