Modularizing class methods

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”]

Interesting approach. It’s a little bit of a misnomer mind you, becasue
you have taken over the include processes such that you are not
actually including module definitions, but rather are defining code in
the base class itself. In other words, while your class will report
ancestors of NameFields and AddressFields, there are actually no
methods defined in those modules.

I have seen another common way of doing this:

module NameFields
def included( base )
base.class_eval %{
field :first, :last, :middle
}
end
end

This works too, of course, though I think your technique is more clever
and worth additional study. Nonetheless there is still the fact in
either case of being real module inclusion. Maybe it would be better
not to use the include mechinism. Ie. just create a different module
method to do the work. You could still go about it the same way, but
just use a different method other than #include.

Here is an example of what I came up with some time ago.

class Module

def package( name, &block )
@package ||= {}
return @package unless block_given?
@package[name.to_sym] = block
end

def provide_features( base, *selection )
selection.each do |s|
base.class_eval( &@package[s.to_sym] )
end
end

def use( package, *selection )
if String === package or Symbol === package
package = constant(package)
end
package.provide_features( self, *selection )
end

end

_____ _

|_ |_ __| |

| |/ _ / __| __|

| | /_ \ |

||_||/__|

require ‘test/unit’

class TCModule < Test::Unit::TestCase

module MyPackages
  package :foo do
    def foo
      "yes"
    end
  end
end

class Y
  use MyPackages, :foo
end

def  test_package
  y = Y.new
  assert_equal( "yes", y.foo )
end

end

Though it may need some tweaking to work for your usecase, I suspect
something like this woud do the job nicely.

In any case I’m gogin to give your code some more thought. Please let
us know if you improve upon it.

T.

[email protected] wrote:

Interesting approach. It’s a little bit of a misnomer mind you, becasue
you have taken over the include processes such that you are not
actually including module definitions, but rather are defining code in
the base class itself. In other words, while your class will report
ancestors of NameFields and AddressFields, there are actually no
methods defined in those modules.

True, but the parallel with the following is just so appealing, that I
thought #include was the best mechanism to use:

module NameFields
attr_accessor :first, :last, :middle
end

module AddressFields
attr_accessor :street, :city, :state, :zip
end

class Person
include NameFields
include AddressFields
end

(The above depends on the fact that attrs are stored by name, and not
incrementally allocated in some linear store.)

Anyway, you might want to put some definitions in the modules, and
have them propagate to the class via include, like this (in the context
of my example from the previous post):

module AddressFields
extend ModuleMethodSaver
field :street, :city, :state, :zip
def postal_address
[street, city, “#{state} #{zip}”].join("\n")
end
end

Using include is the most natural way to do this.

I have seen another common way of doing this:

module NameFields
def included( base )
base.class_eval %{
field :first, :last, :middle
}
end
end

Yep, same effect, but a little harder to write naturally. Also, you
cannot copy and paste from the class def to the module def, as you can
with ModuleMethodSaver:

class Person < ArrayWithNamedFields
field :first, :last, :middle
end

is the same as

module NameFields
extend ModuleMethodSaver
field :first, :last, :middle
end

class Person < ArrayWithNamedFields
include NameFields
end

So it makes refactoring easier. (Well, ok, you still have to type the
“extend” line. But that’s easier to remember than the def included(…
stuff.)

Thanks for the comments!