Setting instance variables when instance_evaling a block in Ruby (Dual-Purpose Accessors to the rescue!)

Yesterday I ran into a nasty problem while doing a task for Mendicant University (it is an awesome free university for learning Ruby and other stuff). I am not allowed to show you my work from there yet so please go with this kind of constructed example for now:


class Person
  attr_accessor :name, :age

  # just for demonstration purposes
  # (otherwise it'd be initialize(name, age) )
  def initialize(&block)
    instance_eval(&block) if block_given?
    self
  end
end

p = Person.new do
  name = "Tobi"
  age = 22
end

puts p.name # nothing (nil)
puts p.age # nothing (nil)

So what’s wrong with this? What happens in the block is that local variables are assigned. So Ruby doesn’t use the setters for the instance variables.

Solution

We could go ahead and do:


p = Person.new do
  self.name = "Tobi"
  self.age = 22
end

But who would want that? I don’t want to write self. all the time, it feels like too much repetition to me!

Well Dual-Purpose Accessors to the rescue! Those are accessors that work as getters and setters at the same time (you’ll see how in a second). This way you are able to maintain a nice DSL feel to what you are doing:


class Person

  def name(n=nil)
    return @name unless n
    @name = n
  end

  def age(a=nil)
    return @age unless a
    @age = a
  end

  # keep the other cool setters
  alias_method :name=, :name
  alias_method :age=, :age

  # just for demonstration purposes
  # (otherwise it'd be initialize(name, age)
  def initialize(&block)
    instance_eval(&block) if block_given?
    self
  end
end

p = Person.new do
  name "Tobi"
  age 22
end

puts p.name # Tobi
puts p.age # 22

This is more code, yes. But it really comes in handy when you have got a lot of different values you want to set. I really like this technique as it has a real nice DSL-ish feeling to it. However you shouldn’t overdo it, as in this case it’d be overkill and you could just write an initialize method which as name and age as arguments. However I looked for a small simple example and hopefully nailed it.

The idea is taken from Gregory Browns free awesome book “Ruby Best Practices” which is also part of my Resources page.

Opinions or anything else you want to say? Please comment!

PS: Thanks again to everyone on IRC who helped me track down the problem 🙂

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s