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