it seems alias/method redefing can be a slipery slope. i wanted to
make the Array class indexable by field name for some postgres work i
was doing. i use the following method of redefining the [] and []=
methods of the Array class, can any one comment on the effectiveness
of this technique?
# redef only once!
if not @oldcalls
@oldcalls = {
'[]' => method('[]'),
'[]=' => method('[]='),
};
m = Module.new
m.module_eval %q{
def [] idx
if @fields and idx.type == String
pos = @fieldpos[idx]
return nil unless pos
self[pos]
else
@oldcalls['[]'].call idx
end
end
def []= idx, value
if @fields and idx.type == String
pos = @fieldpos[idx]
if not pos
pos = size
@fieldpos[idx] = pos
end
self[pos] = value
else
@oldcalls['[]='].call idx, value
end
end
}
self.extend m
end
module M
def idx
if @fields and idx.type == String
pos = @fieldpos[idx]
return nil unless pos
self[pos]
else
super
end
end
def = idx, value
if @fields and idx.type == String
pos = @fieldpos[idx]
if not pos
pos = size @fieldpos[idx] = pos
end
self[pos] = value
else
super
end
end
end
class Array
def fields=(*fields)
extend M unless @fields @fields = fields.flatten @fieldpos = Hash.new @fields.each_with_index {|f,i| @fieldpos[f] = i}
end
attr_reader :fields
attr_reader :fieldpos
end
you use #module_eval to compile and execute a String which never
change, and this each time that #fields is called.
to use #module_eval you create a new anonymous module and because you
don’t want to have multiple include for the same object you need to
protect the call with @oldcalls
These 2 problems can be easily solved with a module
the module is compiled only once (when ruby compile the script) and
not for each call of #fields
ruby never try to include twice the same module, This mean that in #fields you can just write
def fields=(*fields)
extend M
# etc
end
If the module was previously included ruby do nothing otherwise it
execute the include
If I’ve written `extend M unless @fields’ this is just to make a little
faster (i.e. don’t try to call #extend if ruby do nothing)
You have one case where #module_eval create more problems than it solve
this works. though i do not understand how the super remains bound to
the old after the call ‘extend M’
The new methods # and #= are created in the module M and they don’t
erase the old definitions which are in the class Hash
When you write
extend M
ruby will create a proxy class which is inserted just before the class
Hash, this will give a class (and methods) specific to this object
When you call # (#=) for this object, ruby find these methods in the
proxy class, and with a call to #super ruby will retrieve the methods
stored in Hash
so, in general, extending an object always results in a new proxy
class being created as the first one to be searched in the inheritence
hierarchy, and because mutiply extending an object with a module would
result in duplicate classes being created in the inheritence tree ruby
protects against this by
not allowing ‘extend M’ to happen more than once?
-a
···
this works. though i do not understand how the super remains bound to
the old after the call ‘extend M’
The new methods # and #= are created in the module M and they don’t
erase the old definitions which are in the class Hash
When you write
extend M
ruby will create a proxy class which is inserted just before the class
Hash, this will give a class (and methods) specific to this object
When you call # (#=) for this object, ruby find these methods in the
proxy class, and with a call to #super ruby will retrieve the methods
stored in Hash
and because mutiply extending an object with a module would
result in duplicate classes being created in the inheritence tree ruby
protects against this by
not allowing 'extend M' to happen more than once?