This post originated from an RSS feed registered with Ruby Buzz
by Aslak Hellesøy.
Original Post: Object templates in Ruby
Feed Title: Aslak Hellesøy's uncommon sense
Feed URL: http://aslakhellesoy.com/rss
Feed Description: Ruby-related blog posts from Aslak Hellesøy
The concept of templates are very common in many contexts of programming. A boiler-plate representation of something can be turned into a concrete instance by supplying some extra data. The important point is that the supplied data can vary, and that has an impact on the structure of the concrete instance of the template.
In HTML programming, common template techniques are JSP, PHP, ERB and ASP. By passing some extra data to a template engine, a template can be turned into an HTML page that displays the data, using the layout from the template.
In OO programming, the most common concept of a template is a Class. A piece of a program can supply some data to a class' constructor and get a new object.
Sometimes, instantiating a new object by simply calling a constructor with some extra data is not the optimal way to create an object though. Consider the common situation where the desired object is composed of an aggregation of several other objects. -A complex graph of objects.
One way to approach this is to design classes (aka templates) to honour Dependency Injection (DI), and use a DI container supporting a configuration file (in XML or other format). NanoContainer or Spring are two frameworks that use this approach to instantiate complex graphs of objects composed of "variable data" from a configuration file.
Assuming you want the ability to create object graphs that are configured in an external file, and your language is Java, then it doesn't get much simpler than with NanoContainer or Spring.
But in Ruby...
A similar effect that is radically simpler and extremely easy to use can be achieved with a dash of Ruby magic using YAML and eval under the covers. Consider this object template:
codehaus_project_template = Project.new
codehaus_project_template.home_page = "http://\#{unix_name}.codehaus.org"
jabber_publisher = JabberPublisher.new
jabber_publisher.id_resource = "damagecontrol@jabber.codehaus.org/#{unix_name}"
codehaus_project_template.publishers << jabber_publisher
(The template object can also be deserialised from a YAML file if we don't wish to do this in Ruby code, but this is not important from the perspective of describing how this object template technique works).
The point I'm getting at is that I can create new instances from this template object like this:
project = codehaus_project_template.dupe("unix_name" => "mooky")
assert_equal("http://mooky.codehaus.org", project.home_page);
assert_equal("damagecontrol@jabber.codehaus.org/mooky", project.publishers[0].id_resource);
The dupe method takes a Hash that should have one key/value pair for each variable in the object template. The dupe method simply creates a new object graph similar to the one in the template, but substitutes all variables with the values from the hash. -All the way out to the leaves of the object graph!
The object template's variables are simply backslash-escaped #{blah} s in various String instance variable values (fields). In the example there is only one (#{unix_name}), but we could use as many as we like, at any level in our object graph.
In Ruby it's ridiculously easy to make this happen. Just make the classes whose instances you want to use as templates include ObjectTemplate:
module ObjectTemplate
def dupe(variables)
template_yaml = YAML::dump(self)
b = binding
variables.each { |key, value| eval "#{key} = variables[\"#{key}\"]", b }
new_yaml = eval(template_yaml.dump.gsub(/\\#/, "#"), b)
YAML::load(new_yaml)
end
end
A similar approach can be achieved in Java with XStream and Velocity (or Jelly, haha) and maybe a little bit of AOP - in 10 times or more as many lines of code - without the same ease of use.