Hi,
I was quite surprised today to find out that you can define both a
class and a function with the same name and still be able to reference
them both separately.
This makes it possible to define factory functions that work like the
built-in Array() and String() functions, e.g. so that Thing(1,2,3) is
the same as Thing.new(1,2,3).
(I know that Array(4) is different from Array.new(4) but I'm talking
about what it looks like here.)
Here's an example:
class Thing
factory # defaults to name of class
# ...
end
t = Thing(:name => "glove", :description => "white")
t = Thing :name => "glove", :description => "white"
t = Thing() { |x|
x.name = "Alice"
}
t = Thing :name => "White Rabbit" do |x|
x.description = "flustered"
end
Here's the implementation (it really is very simple):
def factory(name = self.to_s)
Kernel::eval "
def #{name}(*args, &block)
#{name}.new(*args, &block)
end
"
end
Note that I use eval here rather than define_method because you can't
pass blocks to blocks (yet).
With this function, you can define factory functions for any class, e.g.
factory :SomeOtherClass
soc = SomeOtherClass(*args, &block)
Here's a script to test interaction with blocks:
if __FILE__ == $0
class Thing
attr_accessor :attrs
factory
def initialize(attrs = {}, &block)
@attrs = attrs
block.call(self) if block_given?
end
ends
t = Thing()
p t
t = Thing :name => "glove", :description => "white"
p t
p Thing() { |x|
x.attrs[:name] = "Alice"
}
wr = Thing :name => "White Rabbit" do |obj|
obj.attrs[:description] = "flustered"
end
p wr
# and just to show that the class is still there
t = Thing.new(:name => "Carpenter")
p Module.const_get(:Thing)
p method(:Thing)
end
I think this is a very natural idiom which doesn't seem to have any
unwelcome side-effects. I'm curious what the general opinion is. It
seems a cleaner solution than,say, aliasing [] to new (which I used to
do a lot).
So... what do you think?
Regards,
Sean