Robert Klemme
6/15/2007 8:30:00 PM
On 15.06.2007 21:13, Trans wrote:
>
> On Jun 15, 1:05 pm, Robert Klemme <shortcut...@googlemail.com> 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)
>> => #<OpenStruct 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