From: Joel VanderWerf Date: 2006-05-28T10:41:08+09:00 Subject: modularizing class methods [LONG] 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"] -- vjoel : Joel VanderWerf : path berkeley edu : 510 665 3407