Managing Instance Data
Problem
You want to associate a variable with an object. You may also want the variable to be readable or writable from outside the object.
Solution
Within the code for the object's class, define a variable and prefix its name with an at sign ( @). When an object runs the code, a variable by that name will be stored within the object.
An instance of the Frog class defined below might eventually have two instance variables stored within it, @name and @speaks_english:
class Frog def initialize(name) @name = name end def speak # It's a well-known fact that only frogs with long names start out # speaking English. @speaks_english ||= @name.size > 6 @speaks_english ? "Hi. I'm #{@name}, the talking frog." : 'Ribbit.' end end Frog.new('Leonard').speak # => "Hi. I'm Leonard, the talking frog." lucas = Frog.new('Lucas') lucas.speak # => "Ribbit."
If you want to make an instance variable readable from outside the object, call the attr_reader method on its symbol:
lucas.name # NoMethodError: undefined method 'name' for # class Frog attr_reader :name end lucas.name # => "Lucas"
Similarly, to make an instance variable readable and writable from outside the object, call the attr_accessor method on its symbol:
lucas.speaks_english = false # => NoMethodError: undefined method 'speaks_english=' for # class Frog attr_accessor :speaks_english end lucas.speaks_english = true lucas.speak # => "Hi. I'm Lucas, the talking frog."
Discussion
Some programming languages have complex rules about when one object can directly access to another object's instance variables. Ruby has one simple rule: it's never allowed. To get or set the value of an instance variable from outside the object that owns it, you need to call an explicitly defined getter or setter method.
Basic getter and setter methods look like this:
class Frog def speaks_english @speaks_english end def speaks_english=(value) @speaks_english = value end end
But it's boring and error-prone to write that yourself, so Ruby provides built-in decorator methods like Module#attr_reader and Module#attr_accessor. These methods use metaprogramming to generate custom getter and setter methods for your class. Calling attr_reader :speaks_english generates the getter method speaks_english and attaches it to your class. Calling attr_accessor :instance_variable generates both the getter method speaks_english and the setter method speaks_english=.
There's also an attr_writer decorator method, which only generates a setter method, but you won't use it very often. It doesn't usually make sense for an instance variable to be writable from the outside, but not readable. You'll probably use it only when you plan to write your own custom getter method instead of generating one.
Another slight difference between Ruby and some other programming languages: in Ruby, instance variables (just like other variables) don't exist until they're defined. Below, note how the @speaks_english variable isn't defined until the Frog#speak method gets called:
michael = Frog.new("Michael") # => # michael.speak # => "Hi. I'm Michael, the talking frog." michael # => #
It's possible that one Frog object would have the @speaks_english instance variable set while another one would not. If you call a getter method for an instance variable that's not defined, you'll get nil. If this behavior is a problem, write an initialize that initializes all your instance variables.
Given the symbol for an instance variable, you can retrieve the value with Object#instance_variable_get, and set it with Object#instance_variable_set.
Because this method ignores encapsulation, you should only use it in within the class itself: say, within a call to Module#define_method.
This use of instance_variable_get violates encapsulation, since we're calling it from outside the Frog class:
michael.instance_variable_get("@name") # => "Michael" michael.instance_variable_set("@name", 'Bob') michael.name # => "Bob"
This use doesn't violate encapsulation (though there's no real need to call define_method here):
class Frog define_method(:scientific_name) do species = 'vulgaris' species = 'loquacious' if instance_variable_get('@speaks_english') "Rana #{species}" end end michael.scientific_name # => "Rana loquacious"
See Also
- Recipe 10.10, "Avoiding Boilerplate Code with Metaprogramming"