leah blogs

November 2004

15nov2004 · attr_inject: A step towards interface injection

During the needlefication of Nukumi2, I came across a lot of service definitions like this:

class Blog
  attr_accessor :backends
  attr_accessor :page
  attr_accessor :view
  attr_accessor :topictree
  # ...
end

blog {
  b = Nukumi2::Blog.new
  b.backends = backends
  b.config = config
  b.page = page
  b.topictree = topictree
  b
}

Needing to define all these accessors was a thorn in my side. Although I would need some attr_readers for them, writing is not a thing I want to allow.

The use of attr_reader, attr_writer and attr_accessor is not just a shortcut in Ruby; it also has a semantical meaning: You are allowed to read this attribute, to write this attribute or to do both. RDoc, for example, will base it’s documentation on them, there will be a special section Attributes in the class documentation.

Therefore, I whipped up a new kind of “accessor”—attr_inject—that allows to declare an attribute for dependency injection. Now, the class will look like:

class Blog
  attr_inject :backends
  attr_inject :page
  attr_inject :view
  attr_inject :topictree

  attr_reader :topictree

  # ...
end

It only opens the things for the “general public” that are actually wanted. However, having these declarations, we can now simply do:

blog {
  Nukumi2::Blog.new.inject_attributes(this_container)
}

Pretty nice, heh? :-)

For now, I just have some very simple code; some additions will be needed to make use of all possibilities. It would be nice to allow users to override attribute mappings (for now, attributes and service names need to match):

blog {
  Nukumi2::Blog.new.inject_attributes(this_container,
                     :backends => my_special_backend)
}

Allowing the use of inject_attributes exactly once would be good too.

By the way, all that’s needed for attr_inject is just these 15 lines:

class Class
  def attr_inject(attribute)
    (@__attributes_to_inject ||= []) << attribute
  end
end

class Object
  def inject_attributes(registry)
    (self.class.instance_variable_get(:@__attributes_to_inject) ||
        []).each { |attribute|
      instance_variable_set "@#{attribute}", registry[attribute]
    }
    self
  end
end

For “serious” use, this code will need more checks and features, of course. Another problem is that attr_inject is not in the standard library and therefore will break code that may be used with and without Needle. This can easily be avoided by defining attr_inject to do nothing unless it’s already defined, though…

NP: Bob Dylan—Is Your Love in Vain?

Copyright © 2004–2022