Metaprograming Ruby -- C10 Activ

2015-03-24  本文已影响42人  我要走多远

Summary

  1. ruby can include instance methods and extend class methods, but can't do those two things together.
    Rails just want do though this together(powerful language with powerful framework ...).
  2. at first, rails use included hook,
    when a class includes the instance methods, it extend class methods by the includes hook
    so rails do those two things together.
  3. but when a module includes a module, problem happens.
    the second level module extended the first level module,
    yeah, the first level's ClassMethod's methods have became the second level's class method.
    but, the second level module's ClassMethods' methods do not have the first level's ClassMethods' methods,
    so when include the second level module in a class, the class can not get the first level's ClassMethods' methods. Because the first level module's class methods do not in the second level module's ClassMethods module
  4. rails solve this by overwriting the apppend_features
    it flatten all nest included modules with right order.
    and let the class include all those included module in the right order.

put include and extend together

includes and extend

included is a hook, when a class includes a module, this hook will be called
def self.included(base)
base.extend ClassMethods # ...
end

nested include

 Problem happens when BaseClass includes the FirstLevelModule, so the please read the comment in it and then read comment in FirstLevelModule's `included`
  module SecondLevelModule
    def self.included(base)
      base.extend ClassMethods
    end

    def second_level_instance_method; 'ok'; end

    module ClassMethods
      def second_level_class_method; 'ok'; end
    end
  end

  module FirstLevelModule
    def self.included(base)
      # called because the BaseClass me(`FirstLevelModule`)
      # I let the BaseClass extend all the methods in my ClassMethods module
      # but wait a minute, I include the SecondLevelModule,
      # and I have second_level_class_method as my class method
      # but it is not in my ClassMethods module,
      # so the BassClass dose not have second_level_class_method as its class method :(
      # include trick broken ...
      base.extend ClassMethods
    end

    def first_level_instance_method; 'ok'; end

    module ClassMethods
      def first_level_class_method; 'ok'; end
    end

    include SecondLevelModule
  end

  class BaseClass
    # the FirstLevelModule's included hook will be called
    include FirstLevelModule
  end

append_features - solving module includes module problem

the append_features is overwrote by rails

goal: never include a concern in another concern,
so just let the includer(class or module) 'includes' all concerns in the right order.

it has two part

  1. a concern includes a concern,
    checked by base.instance_variable_defined?("@_dependencies")
    it add the included concern as a dependent and add it to its @_dependencies

  2. a module includes a concern
    def append_features(base)
    # recursive
    # no need include and extend
    return false if base < self
    @_dependencies.each { |dep| base.send(:include, dep) }

        # to include class method by call original append_features
        super
        # extend class methods
        base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
        base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
      end
    
上一篇下一篇

猜你喜欢

热点阅读