Advanced Rails - Building Industrial-Strength Web Apps in Record Time

(Tuis.) #1
Metaprogramming Techniques | 25

If this were a language without scriptable test fixtures, you might be writing these by
hand. This gets messy when the data starts growing, and is next to impossible when
the fixtures have strange dependencies on the source data. Naïve generative pro-
gramming would have you writing a script to generate this fixture from the source.
Although not ideal, this is a great improvement over writing the complete fixtures by
hand. But this is a maintenance headache: you have to incorporate the script into your
build process, and ensure that the fixture is regenerated when the source data changes.


This is rarely, if ever, needed in Ruby or Rails (thankfully). Almost every aspect of
Rails application configuration is scriptable, due in large part to the use of internal
domain-specific languages (DSLs). In an internal DSL, you have the full power of the
Ruby language at your disposal, not just the particular interface the library author
decided you should have.


Returning to the preceding example, ERb makes our job a lot easier. We can inject
arbitrary Ruby code into the YAML file above using ERb’s<% %>and<%= %>tags,
including whatever logic we need:


<% User.find_all_by_active(true).each_with_index do |user, i| %>
<%= user.login %>_project:
id: <%= i %>
owner_id: <%= user.id %>
billing_status_id: <%= user.billing_status.id %>

<% end %>

ActiveRecord’s implementation of this handy trick couldn’t be simpler:


yaml = YAML::load(erb_render(yaml_string))

using the helper methoderb_render:


def erb_render(fixture_content)
ERB.new(fixture_content).result
end

Generative programming often uses eitherModule#define_methodorclass_evaland
defto create methods on-the-fly. ActiveRecord uses this technique for attribute
accessors; thegenerate_read_methodsfeature defines the setter and reader methods
as instance methods on theActiveRecordclass in order to reduce the number of
timesmethod_missing (a relatively expensive technique) is needed.


Continuations


Continuationsare a very powerful control-flow mechanism. A continuation repre-
sents a particular state of the call stack and lexical variables. It is a snapshot of a
point in time when evaluating Ruby code. Unfortunately, the Ruby 1.8 implementation
of continuations is so slow as to be unusable for many applications. The upcoming
Ruby 1.9 virtual machines may improve this situation, but you should not expect good
performance from continuations under Ruby 1.8. However, they are useful constructs,

Free download pdf