Happy Holidays!

It always concerned me that instance variables of a class propagate to
its subclasses and that instance variables of a module propagate to
classes which include it.

This lack of private variables caused me great unrest until I learned
a little more ruby and then realized private variables can be
implemented in ruby itself!

What do you think?  Criticism is welcome; this one of my first
non-hello-world ruby scripts so be nice :) And thanks to the irc folks
who patiently answered my questions.

#----------------------------------
# synopsis
#----------------------------------

require 'wrap'

module A
  def a_f
    @x = 11
  end
  
  def a_x
    @x
  end
end

module B
  def b_f
    @x = 22
  end

  def b_x
    @x
  end
end

class C
  include Wrap(A)
  include Wrap(B)
end

c = C.new
c.a_f
c.b_f
puts "A's @x is #{c.a_x}"
puts "B's @x is #{c.b_x}"
# => A's @x is 11
# => B's @x is 22

class P
  def initialize
    @x = 33
  end
  
  def p_x
    @x
  end
end

class Q < Wrap P
  def initialize
    super()
    @x = 44
  end

  def q_x
    @x
  end
end

q = Q.new
puts "P's @x is #{q.p_x}"
puts "Q's @x is #{q.q_x}"
# => P's @x is 33
# => Q's @x is 44
  
#----------------------------------
# wrap.rb
#
# privatize data using delegation to an anonymous class.
#----------------------------------

def Wrap mod
  if mod.class == Module
    wrap_module(mod)
  elsif mod.class == Class
    wrap_class(mod)
  else
    raise "expected Class or Module for Wrap: got #{mod.class}"
  end
end

def wrap_module mod
  wrap = Module.new

  classname = "Private__class#{wrap.object_id}"
  instance = "@__private#{wrap.object_id}"

  wrap.const_set(classname.intern, Class.new{ include mod })

  mod.constants.each do |name|
    wrap.const_set(name.intern, mod.const_get(name.intern))
  end

  create = Proc.new do |visibility, method|
    next if method =~ /^__/
    wrap.class_eval <<-end_eval
      def #{method}(*args, &block)
        #{instance} ||= #{classname}.new
        #{instance}.__send__(:#{method}, *args, &block)
      end
    end_eval
    wrap.__send__(visibility, method)
  end

  mod.public_instance_methods(true).each{|m|create.call(:public,m)}
  mod.protected_instance_methods(true).each{|m|create.call(:protected,m)}
  mod.private_instance_methods(true).each{|m|create.call(:protected,m)}
  
  wrap  
end

def wrap_class aclass 
  wrap = Class.new

  instance = "@__private#{wrap.object_id}"

  aclass.constants.each do |name|
    wrap.const_set(name.intern, aclass.const_get(name.intern))
  end
  
  aclass.singleton_methods(true).each do |method|
    wrap.class_eval <<-end_eval
      def self.#{method}(*args, &block)
          #{aclass.name}.#{method}(*args, &block)
      end
    end_eval
  end

  wrap.class_eval <<-end_eval
    def initialize(*args, &block)
      #{instance} = #{aclass.name}.new(*args, &block)
    end
  end_eval

  create = Proc.new do |visibility, method|
    next if method == "initialize"
    next if method =~ /^__/
    wrap.class_eval <<-end_eval
      def #{method}(*args, &block)
        #{instance}.__send__(:#{method}, *args, &block)
      end
    end_eval
    wrap.__send__(visibility, method)
  end

  aclass.ancestors.each do |c|
    break if c == Object
    c.public_instance_methods(false).each{|m|create.call(:public,m)}
    c.protected_instance_methods(false).each{|m|create.call(:protected,m)}
    c.private_instance_methods(false).each{|m|create.call(:private,m)}
  end
  
  wrap
end

#----------------------------------

It's both like and unlike delegation.  With delegation, you pass an
object explicitly and the dual-object relationship is part of the
design.

With wrapping there is no object explicitly passed and the delegation
is hidden; there are still two objects but you pretend one of them
doesn't exist.  But essentially it's just a notational convenience for
a certain form of delegation.

I see some specialized things would make it faster, for example direct
evals of public methods (no __send__) and re-evaling after the first
method call to remove the nil test for module methods.

Anyway, all of this may be too silly.  It's really an anecdote of
sorts.  I was surprised that ruby was flexible enough that I could
seemingly add (or simulate) a new language construct from inside the
language itself.

-Jeff (quix)