From: Robert Klemme Date: 2007-06-16T05:35:05+09:00 Subject: Re: Using extend for initialization settings? On 15.06.2007 21:13, Trans wrote: > > On Jun 15, 1:05 pm, Robert Klemme wrote: >> On 15.06.2007 16:44, Trans wrote: >> >> >> >>> It's not uncommon to see initialize method take a hash or a setting >>> proc and apply that to accessors. Eg. >>> def initialize( settings ) >>> settings.each{|k,v| send("{#k}=",v) >>> end >>> or >>> def initialize( &settings ) >>> settings.call(self) >>> end >>> Today I come up with another potential approach: >>> class Hash >>> def to_module(module_function=false) >>> m = Module.new >>> each do |k,v| >>> m.send(:define_method, k){ v } >>> m.send(:module_function, k) if module_function >>> end >>> return m >>> end >>> end >>> M = { :a => 1 }.to_module(true) >>> p M.a #=> 1 >>> class Foo >>> def initialize( settings ) >>> extend settings.to_module >>> end >>> end >>> f = Foo.new(:x => 9) >>> p f.x #=> 9 >>> Thoughts? >> irb(main):001:0> require 'ostruct' >> => true >> irb(main):002:0> f = OpenStruct.new(:x => 9) >> => # >> irb(main):003:0> f.x >> => 9 > > Hmm... I think maybe my point is being missed. The goal is to > initialize a class, not create a simple data struct. Actually your code is about initializing an instance - not a class. Basically you just transform a Hash into some other data structure, a module with constant accessors in your case. At the moment I fail to see the benefit over something like this: class Hash def init(x) each do |var, val| x.send("#{var}=", val) end end end class Foo def initialize(settings) settings.init(self) end end I find the approach using a module somewhat convoluted. Also a module simply does not seem the right vehicle IMHO. For example, what happens to attr_accessors that you define in that class? Either they override your anonymous module's methods or the other way round. This does not seem a good solution to me. > I think the neat thing about this technique is that it could go beyond > just assigning values, and provide a clean means of dependency > injection. > > class Module > def to_module; self; end > end > > module Container > def log(msg) > puts msg > end > end > > class Foo > def initialize( settings ) > extend settings.to_module > end > def report_ > log("Ready.") > end > end > > f = Foo.new(Container) > > f.report > > produces > > Ready. Basically what you do is you require an instance that implements a particular method that you introduce and which has to return a module and you promise to extend the instance with that module returned. But this means at the same time that there always *has* to be a module. I am not sure whether that's a good idea because for one every module comes at a cost and I think it's not the proper means to carry some initialization info. Also, Module#to_module and Hash#to_module server two completely different purposes: the former helps extending an instance with a predefined module (functionality) and the latter provides key value pairs via a particular interface. The more general solution for the "pass argument X that implements method Y" is of course - blocks. So the pattern that you quoted earlier is more flexible and general IMHO. I mean def initialize(&b) instance_eval(&b) end Combining that with Hash#init from above you can solve these tasks with f = Foo.new { {:foo=>"bar"}.init(self) } f = Foo.new { extend Container } Kind regards robert