Writing Hygienic Mixins In Ruby


by Ara T. Howard


=begin

  it's quite common, in ruby, for modules to provide functionality via mixins.


  for example:


=end

  module Mixin
    def foo
      42
    end
  end

  class C
    include Mixin
  end

  p C.new.foo #=> 42

=begin

  althouge handy, the guts of the mixin, especially modules, can easily leak
  into the target unintentially.

  here the 'Util' module gets zippered in-between the Mixin and C:


=end

  reset!

  module Mixin
    module Util
      def Util.foo
        42
      end
    end
  end

  class C
    include Mixin

    p Util.foo #=> 42

    module Util
      def Util.bar
        42.0
      end
    end

    p Util.bar #=> 42.0
  end



=begin

  this creates a challenge for the author's of mixins: how to keep code
  organized *and* provide a module that is safe to mixin to any target.


  two main approaches exist:


  1) carve out the mixin seperately from the top-level namespace

=end

  reset!

  module M
    module Mixin
      def foo
        42
      end
    end

    module Util
    end
  end

  class C
    include M::Mixin
  end

  p C.new.foo #=> 42
  p C.const_defined?(:Util) #=> false


=begin

  or

  2) leverage const_missing to allow simple const aliases into a private namespace


=end

  reset!

  module M
    module Namespace
      module Util
        def foo
          42
        end
      end
    end

    def Mixin.const_missing(const)
      begin
        Namespace.const_get(const)
      rescue Object
        raise
      end
    end

    def foo
      Util.foo
    end
  end

  class C
    include M::Mixin
  end

  p C.new.foo #=> 42
  p C.const_defined?(:Util) #=> false 



=begin


  so, there you have it: please think carefully about dropping common names
  inside your mixins as they absolute vommit all their internals into the
  mixee.


=end









BEGIN {
  def reset!
    self.class.send :remove_const, :Mixin rescue false
    self.class.send :remove_const, :C rescue false
  end
}