Раздел «Язык Ruby».AdaptiveStruct:

Adaptive struct

Sometimes it's convenient to write configuration files in ruby as structure with attributes defined on demands (PHP programmers really like it).

Let's take a look at how this adaptive structure can be defined.

Hash with dynamic methods

Config is nested hash. So it's natural to write method_mising method, with will treat missing method name as key.

class Hash
  def method_missing(method, *args)
    method = method.to_s
    if method.gsub!(/=$/, '')
      self[method.to_sym] = args.first
    else
      self[method.to_sym] ||= {}
    end
  end
end


a = {}
a.bb.cc.dd = 1
a.bb.ff.ee = 2
a.xxx = 'yyy'

require 'yaml'
puts a.to_yaml

It prints

--- 
:bb: 
  :cc: 
    :dd: 1
  :ff: 
    :ee: 2

Cool! But we need more fun.

Nested configs

class Hash
  def method_missing(method, *args, &block)
    method = method.to_s
    if  method.gsub!(/=$/,'') || !args.empty? 
      self[method.to_sym] = args.first
    else
      res = self[method.to_sym] ||= {}
      res.instance_eval(&block) if block
      res
    end
  end
end
a = {}
a.bb.cc { 
  dd.ee 3; 
  ff 4 
}
a.xxx {
  yyy 'y'
  zzz 'z'
}

require 'yaml'
puts a.to_yaml

Output:

--- 
:xxx: 
  :yyy: y
  :zzz: z
:bb: 
  :cc: 
    :dd: 
      :ee: 3
    :ff: 4

We are so good!

But here we stop using equal sign. Why? Code

a.xxx {
  yyy = 'y'
  zzz = 'z'
}
does not work, because yyy and zzz are treated as local variables.

My be we can use binding to fix it? Try it!

At least we can easily get this code working:

a.xxx.ttt.www {|cfg|
  cfg.yyy = 'y'
  cfg.zzz = 'z'
}

-- ArtemVoroztsov - 15 Apr 2008