hoodwink.d enhanced
RSS
2.0
XHTML
1.0

RedHanded

Hyperextended #

by why in bits

As you well know, mixins only copy plain instance methods into the target class. And may I ask what world-class solutions we have if you need to copy class methods? Um, you can’t copy the metaclass (which includes class methods.)

 >> class GiftedClass
 >>   def self.capabilities
 >>     [:swimming, :diving, :chess, :valour]
 >>   end
 >> end

 >> class LesserClass
 >>   extend (class << GiftedClass; self; end)
 >> end
 TypeError: wrong argument type Class (expected Module)
         from (irb):8:in `extend'
         from (irb):8

I’ve been trying to write a hyperextend method that will take a class and mixin instance and class methods alike, but no.

Nobu’s three-year-old solution is to put all your class methods in a nested module, then alter append_features to allow the mixin to copy the nested module functions into the metaclass. And don’t forget to extend the original module, to make sure the nested module functions are still usable as class (module) methods in the original module.

 module Mix
   def inst_meth
     puts 'inst_meth'
   end

   module ClassMethods
     def class_meth
       puts 'class_meth'
     end
   end

   extend ClassMethods

   def self.append_features(klass)
     super
     klass.extend(ClassMethods)
   end
 end

 class LesserClass
   include Mix
 end

While the behind-the-scenes is a bit wordy, it’s excellent that you can still use include and get the class methods to come through. (Unearthed from ruby-talk:35979.)

said on 05 Jul 2005 at 00:56

I’ve tried to coax append_features into auto-extending with ClassMethods when present, but singleton methods defy wrapping. It is written.

said on 05 Jul 2005 at 01:26

Rails uses this extensively. It doesn’t have the module extend its own ClassMethods though, because they’re not intended to be used independently.

said on 05 Jul 2005 at 07:01

Very cool :) I tried to this myself some days ago and failed. Next time, I will search ruby-talk first ;)

said on 05 Jul 2005 at 08:11

Hey, _why. The nice thing about the internal ClassMethods pattern is that it can be automated:

class Module
  alias :append_features_no_classmethods \
        :append_features
  def append_features(base)
    append_features_no_classmethods(base)
    begin clsm = self::ClassMethods
    rescue NameError
      clsm = nil
    end
    base.extend(clsm) if clsm
  end
end

Somewhat ironic is that if you try to use a module to extend append_features (and thus avoid alias) it doesn’t work.

module AutoExtendClassMethods
  def append_features(base)
    super(base)
    ...
  end
end
class Module
  include AutoExtendClassMethods
end

As an appendum, I should add that it is a good idea to use self::ClassMethods—if you just use ClassMethods, you might inherit ClassMethods from a parent scope, which could lead to some hard to figure out bugs.

(Sorry for rambling on,)

said on 05 Jul 2005 at 09:36
>>   extend (class << GiftedClass; self; end)

This ought to be possible with evil-ruby. Just do class << self; inherit class << GiftedClass; self; end; end—sorry that it looks so complex.

said on 05 Jul 2005 at 20:41

you must have eaten all meta kryptonite

said on 07 Jul 2005 at 17:09

Wow, yeah. Rails is all over this one. Haroomph.

Comments are closed for this entry.