I am actually pretty excited about this approach, but for sanity's sake I wanted to hear some thoughts on others on my strategy here. My basic goal is to parse a YAML file and recursively create module constants using eval
. The core business logic is here:
#
# configure application based on an easily-managed .yml file -- use 'eval'
# to generate constants based on the structure of the hash we get from the yaml.
# most of the business logic for recursively parsing a hash and creating module
# constants basically just involves tracking module names properly.
#
def parse_and_recursively_create_constants!(hash, module_context = [])
hash.each do |key, value|
if value.is_a? Hash
module_name = convert_string_to_camel_case(key)
context = module_context.dup << module_name
parse_and_recursively_create_constants! hash[key], context
else
value_is_a_module = false
if value.is_a? String
if value.include? "::"
value_is_a_module = true
else
value = "\"#{value}\""
end
end
if value_is_a_module
# explicitly build out this module (since it doesn't exist) so that we can talk about it
expression = value.split('::').map { |module_name| "module #{module_name}; "}.join(' ')
value.split('::').count.times { expression << "end; "}
eval(expression, $__global_scope)
end
# define the given constant
depth = module_context.count
expr = module_context.map { |module_name| "module #{module_name}; " }.join(" ")
expr << "#{key.upcase} = #{value}; "
depth.times { expr << "end; "}
expr = "module #{@project_name}; #{expr}; end"
eval(expr, $__global_scope)
end
end
Note I am having to bind the default scope to a global $__global_scope
which occurs just before the class definition containing this method. I was wondering if there were some cleaner way of handling that, but either way the behavior certainly is what I want it to be. Here's an example configuration file:
key: value
numeric_data: 3.221
sample_module:
some_key: another value
key_should_be_module: I::Am::A::Module
And the associated test case:
configure! "Sample", "spec/resources/sample.yml"
Sample::KEY.should == 'value'
Sample::NUMERIC_DATA.should == 3.221
Sample::SampleModule::SOME_KEY.should == 'another value'
Sample::KEY_SHOULD_BE_MODULE.should be_a_kind_of(Module)
Sample::KEY_SHOULD_BE_MODULE.should == I::Am::A::Module
I have released this as a gem, and if you'd like more context the full source is on Github here.