Instance Variables In Ruby 23 Jun 2007
Instance variables are the heart of an object’s encapuslated data. In “ruby”:http://www.ruby-lang.org 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)

# 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.

@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.

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"

In Ruby you can bypass this using builtin the accessor methods:

  • “attr_accessor”:http://www.ruby-doc.org/core/classes/Module.html#M001704 - this writes a getter and a setter for you
  • “attr_writer”:http://www.ruby-doc.org/core/classes/Module.html#M001703 - this writes a writer for you
  • “attr_reader”:http://www.ruby-doc.org/core/classes/Module.html#M001702 - this writes a getter for you

Here’s a modified example from the above Person class:

sally = Person.new # => #<Person:0x4e3a90>
sally.name = "sally" # => "sally"
sally.name # => "sally"

If we had used “attr_writer”:http://www.ruby-doc.org/core/classes/Module.html#M001703 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”:http://www.ruby-doc.org/core/classes/Module.html#M001702 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?”:http://www.ruby-doc.org/core/classes/Object.html#M000367 - can be used to determine whether an instance variable has been defined

  • “instance_variables”:http://www.ruby-doc.org/core/classes/Object.html#M000364 - an array of instance variables for a given object

    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.

  • “instance_variable_get”:http://www.ruby-doc.org/core/classes/Object.html#M000365

  • “instance_variable_set”:http://www.ruby-doc.org/core/classes/Object.html#M000366

    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”:http://www.rubyonrails.org 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”.

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:

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:

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:

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.

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"

blog comments powered by Disqus