HoboSupport - Module extensions

Module#included_in_class_callbacks

Bit involved, this one :-)

When a module is included in a class, it gets a callback on the included method, with the class passed as argument. However, if a module M2 is included in M1, and a class C includes M1, then M2 never gets to know about C. So, for example, M2 could not alias_method_chain a class method on C.

included_in_class_callbacks makes it easy to implement a notification from M1 to M2, so that M2 does have full access to the class. All you do is insert a call to included_in_class_callbacks(base) as the end of the module’s self.included method.

(Note we’re using the metaid extensions here too)

>>
 module M2
   def self.included_in_class(klass)
     klass.metaclass_eval do
       def name_with_shouting; name_without_shouting.upcase; end
       alias_method_chain :name, :shouting
     end
   end
 end

 module M1
   def self.included(base)
     included_in_class_callbacks(base)
   end
   include M2
 end

 class C
   def self.name
     "my name is C"
   end
   include M1
 end

 C.name
=> "MY NAME IS C"

Module#inheriting_cattr_reader

Declares a reader for an instance variable on the class. The attribute is looked up on the superclass if not defined on the receiving class. In other words, the superclass defines a default that subclasses can override. The declaration can also give a default, as shown here.

>>
 class A
   inheriting_cattr_reader :nickname => "Andy"
 end

 class B < A; end

B has the same nickname as its superclass A.

>> A.nickname
=> "Andy"
>> B.nickname
=> "Andy"

Now we change the nickname of B. A retains it’s existing nickname.

>> class B; @nickname = "Bob"; end
>> B.nickname
=> "Bob"
>> A.nickname
=> "Andy"

Kernel#classy_module

In Ruby we use modules to factor out features that are shared by more than one class. By including the module, a class gets the module’s features, just as if they were defined inside the class. This mechanism is very simple if the shared features are all instance methods, but starts to get complicated when we want to share class-level features. For example, to create a module that adds class-methods to the including class, Ruby programmers often use the ClassMethods sub-module idiom:

>>
 module M
   def self.included(base)
     base.send(:extend, ClassMethods)
   end

   module ClassMethods
     def foo; 123; end
   end
 end

 class C; include M; end

>> C.foo
=> 123

It’s a shame that such a basic capability isn’t more declarative.

classy_module provides a mechanism for creating a module that, when included, will class_eval it’s entire body (the block passed to classy_module). This means we can write shared class features exactly as we would write them if they were inside the class:

>>
 MyClassyModule = classy_module do

   def self.foo; 123; end

 end

>> MyClassyModule.class
=> Module
>> class C2; include MyClassyModule; end
>> C2.foo
=> 123

Edit this page