Suppose you have a base class that defines some class methods, and that
these class methods are intended to be used in the derived classes to
(for example) add fields at incrementally increasing offsets in some
storage. Maybe you want to define named accessors for positions in an
array or string. Or maybe you are generating code to access fields in a
C struct. The point is that these class methods must be called in
sequence as the class is being defined, because some index is being
updated (as a class variable perhaps), or a because a C struct def is
being built up.
The problem is: how can you define such fields in a module so that you
can include the module in one of your derived classes, and have these
fields use the correct offset in the context of the class they are being
added to (which is not known at the time the module is defined)?
In other words you want to write code like this:
module NameFields
field :first, :last, :middle
end
module AddressFields
field :street, :city, :state, :zip
end
class Person < ArrayWithNamedFields
include NameFields
include AddressFields
end
per = Person.new
per.first = “Fred”
per.last = “Flintstone”
per.city = “Bedrock”
This doesn’t work, not just because #field is undefined in NameFields
and AddressFields, but because if you did define it, it would use the
wrong array indexes when called in the module. The #field method should
only be called on the class that the field is being added to.
This statement of the problem is more wordy and confusing than the
solution. It’s probably not original, but it’s very simple, and a nice
example of meta-meta-programming that I happened to need right now. So
here it is. Maybe someone can think of a better name.
module ModuleMethodSaver
def method_missing(meth, *args, &block)
@saved ||= []
@saved << [meth, args, block]
end
def included(m)
if @saved
@saved.each do |meth, args, block|
m.send(meth, *args, &block)
end
end
end
end
First, a simple example:
module M
extend ModuleMethodSaver
foo 1,2,3
bar “zap”
end
class Base
def self.foo(*args)
puts “#{self}.foo with #{args.inspect}”
end
def self.bar(*args)
puts “#{self}.bar with #{args.inspect}”
end
end
class C < Base
include M
# output:
# ==> C.foo with [1, 2, 3]
# ==> C.bar with [“zap”]
end
The next example shows how ModuleMethodSaver can be used with a
fixed-offset storage system, in this case based on Array. It could
also be used with String (or BitStruct) or with CShadow (from
cgenerator).
class ArrayWithNamedFields < Array
def self.field(*names)
names.each do |name|
pos = @pos ||= 0
define_method name do ||
self[pos]
end
define_method “#{name}=” do |v|
self[pos] = v
end
@pos += 1
end
end
end
module NameFields
extend ModuleMethodSaver
field :first, :last, :middle
end
module AddressFields
extend ModuleMethodSaver
field :street, :city, :state, :zip
end
class Person < ArrayWithNamedFields
include NameFields
include AddressFields
end
per = Person.new
per.first = “Fred”
per.last = “Flintstone”
per.city = “Bedrock”
p per # ==> [“Fred”, “Flintstone”, nil, nil, “Bedrock”]