Spike Solutions
I have a very simple Declan prototype up and running, but it has one little snag: declarative Declan statements must be part of the body of a class declaration, or they won't work. I want to be able to write a file with rules, and then load those rules with a load or require statement. This would be easy to do, but for one thing: as the rules file is loaded, Ruby will execute the statements. This is not a problem per se, but the Declan statements will change the state of the application. I need to store that state, without having an object handy to store it in.
Being something of a Ruby tenderfoot, I'd like to explore the solution I have in mind before committing serious effort to refactoring the Declan prototype. A spike solution seems to be in order, but first well have a look at how the prototype works. You'll soon see why I am not happy with it.
In the prototype the Declan statements are declared in a superclass. The subclass, where the transformations are declared, creates a singleton instance of itself, and then call declan statements as methods on the singleton instance. This not only sounds complex, it looks horrible. The following is an excerpt from a test case for the prototype:
class TestTransformer < Test::Unit::TestCase
XPATH_ROOT = "root"
XPATH_CH_MATCHED = '//ch[@class="matched"]'
class Template < Declan::Transformer
attr_reader :rules, :node_hash
# Make a singleton
private_class_method :new
@@template = nil
def Template.create
@@template = new unless @@template
@@template
end
# Instantiate the singleton
Template.create
# These method calls are executed when the class is read in.
@@template.match(XPATH_ROOT) {|root|
<<-"ENDTEMPLATE"
<book>
#{@@template.apply_templates}
</book>
ENDTEMPLATE
}
@@template.match(XPATH_CH_MATCHED) {|ch|
<<-"ENDTEMPLATE"
<s status="#{ch.attributes['class']}">
#{@@template.apply_templates}
</s>
ENDTEMPLATE
}
end
#Setup and tests follow here
end
The first thing to do is to make a simplified model of the system I have now. This is the simplest thing I could come up with:
#! /usr/bin/ruby
class Flag
attr_reader :state
def initialize
@state = false
flip # Declarative statement
end
def flip
@state = !@state
end
end
flag = Flag.new()
puts flag.state
I have mashed things together a bit. The declarative statement (the flip method) is defined in the same class where it is used. I also dispensed with the declare a singleton nonsense by moving flip into the initialize method. Still, it's the same thing in principle, given that we have sufficiently flexible principles. BTW, when executed, the program prints true.
It would be nice if I could just break out the call to flip in a separate file, then include it, but I don't think that'l work. Nevertheless, I give it a try:
#! /usr/bin/ruby
class Flag
attr_reader :state
def initialize(rules)
@state = false
load rules
end
def flip
@state = !@state
end
end
flag = Flag.new('declarative_rules.rb')
puts flag.state
The declarative_rules.rb file looks like this:
flip
As expected, it does not work. The rules file executes as it loads, and at that time, there is of course no object to call the method on. However, the following does work:
#! /usr/bin/ruby
@state = false
def flip
@state = !@state
end
load 'declarative_rules.rb'
puts @state
The flip method is defined before I load the rules file, so the flip statement in the rules file can execute with no problem. The catch is that the @state property and the flip method are both defined in the top-level execution environment. This isn't good, because sometimes I do want to write Declan code inside an object. I just do not want to have to do it. However, I can move them out into a module of its own so I can mix it back in however I want. This is my mixin module:
@state = false
def flip
@state = !@state
end
And this is the final version of the program that executes the declarative code:
#! /usr/bin/ruby
require 'decltest4_mixin'
load 'declarative_rules.rb'
puts @state
Doesn't look like much, but it does tell me how to proceed when refactoring Declan. I'll move the definitions of the Declan statements from the Declan::Transformer class to a separate module that I can mix into any class that I want. I also know that I can dispense with the awkward singleton by moving the statements from the class body to the initialize method.
Now that I have done the spike, it seems very obvious. Can't understand why I didn't think of it right away...
Comments