How do I defer execution of some Ruby code until later and run it on demand in this scenario?
- by Kyle Kaitan
I've got some code that looks like the following. First, there's a simple Parser class for parsing command-line arguments with options.
class Parser
def initialize(&b); ...; end # Create new parser.
def parse(args = ARGV); ...; end # Consume command-line args.
def opt(...); ...; end # Declare supported option.
def die(...); ...; end # Validation handler.
end
Then I have my own Parsers module which holds some metadata about parsers that I want to track.
module Parsers
ParserMap = {}
def self.make_parser(kind, desc, &b)
b ||= lambda {}
module_eval {
ParserMap[kind] = {:desc => "", :validation => lambda {} }
ParserMap[kind][:desc] = desc
# Create new parser identified by `<Kind>Parser`. Making a Parser is very
# expensive, so we defer its creation until it's actually needed later
# by wrapping it in a lambda and calling it when we actually need it.
const_set(name_for_parser(kind), lambda { Parser.new(&b) })
}
end
# ...
end
Now when you want to add a new parser, you can call make_parser like so:
make_parser :db, "login to database" do
# Options that this parser knows how to parse.
opt :verbose, "be verbose with output messages"
opt :uid, "user id"
opt :pwd, "password"
end
Cool. But there's a problem. We want to optionally associate validation with each parser, so that we can write something like:
validation = lambda { |parser, opts|
parser.die unless opts[:uid] && opts[:pwd] # Must provide login.
}
The interface contract with Parser says that we can't do any validation until after Parser#parse has been called. So, we want to do the following:
Associate an optional block with every Parser we make with make_parser.
We also want to be able to run this block, ideally as a new method called Parser#validate. But any on-demand method is equally suitable.
How do we do that?