• 2008-09-02

    模块中的类方法

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/28433895.html

    作为Ruby程序员的你正在编写一个权限管理的模块,受到DSL风潮的影响,你希望用户这样使用它:
    class User
      include Permission

      grant :operation, Role::Admin
    end

    这样的话,意味着grant是一个类方法,于是,按照惯性思路,你写出了这样的代码:
    module Permission
      def self.grant operation, *roles
        ..
      end
    end

    兴奋的你迫不及待的开始运行这段代码,不过,Ruby解释器却无情的告诉你:
      undefined method `grant' for User:Class (NoMethodError)

    这么会这样?我明明是定义了grant方法!

    你知道,写代码的时候,烦躁不会给你带来任何好处,冷静思考才是最好的出路。所有关于模块的一切开始在你脑海中浮现,以往,我们在类中包含模块的方法都是实例方法,包含类方法还真的是头一遭,难道Ruby只能包含实例方法?源码是最好的答案,于是,你决定翻出Ruby的实现一探究竟(《管窥Ruby——类层次结构》)。

    在构造include类的时候,你看到了这样的代码:
    static VALUE
    include_class_new(module, super)
        VALUE module, super;
    {
      ...
      klass->iv_tbl = RCLASS(module)->iv_tbl;
      klass->m_tbl = RCLASS(module)->m_tbl;
      klass->super = super;
      ...
    }

    这里明白无误的告诉你,在构造include类时,只将m_tbl(方法表)赋值过去,也就是说,只有实例方法会随着模块一起包含到类里面,只有模块级别上的方法,那只能说声抱歉了。

    难道这是一个不能跨越的天堑?你显然并不甘心就此打住。你突然想起,有一些Rails的plugin确实做到了这一点,于是你翻出了一些Plugin的实现,你发现了一些蛛丝马迹,照猫画虎,你也写出了类似的代码:
    module Permission
      def self.included(base)
        base.extend PermissionClassMethod
      end

      module PermissionClassMethod
        def grant operation, *roles
          ..
        end
      end
    end

    这一次,你小心翼翼的把这段代码送给了Ruby解释器,它居然通过了!

    仅仅拥有可运行的代码是不够的,你需要一个合理的解释,否则,这段代码会是你心中一直的不安。你开始尝试理解这段代码的运作机制。

    self.included,你记得你阅读Ruby实现的时候,曾经有缘相识,它实际上是一个回调方法,当一个模块被其他类(或模块)包含的时候,这个方法就会得到调用,而传入的参数,就是包含这个模块的类(或模块)对象。剩下的部分就是一马平川了。对这个对象进行扩展(extend),扩展模块中的方法,就会变成这个对象自身的方法。因为这里的对象是类对象,类对象的方法自然就变成了类的方法。到这里,你一下子豁然开朗。

    你发现,原来这里并不像看起来那么神奇。基于你对Ruby实现的了解,你发现除了运行included这个回调方法之外,用append_features同样可以解决问题,因为append_features也是一个会在包含模块过程中调用的方法。

    突破了这里,你的心平稳下来。于是,你回到原来的路上,继续你DSL体验之旅!
    分享到:
    引用地址:

    评论

  • 以下是一般插件的做法:

    {{{
    module Permission
    def self.included(base)
    base.extend(ClassMethods)
    end

    module ClassMethods

    def method(options = {})
    extend SingletonMethods
    include InstanceMethods
    end

    end

    module SingletonMethods
    # ...
    end

    module InstanceMethods
    def self.included(base)
    # ...
    end
    end
    end
    }}}

    之后一进行include就可以得到模块中的类方法和实例方法。
  • 恩,标准的DSL实现方式。呵呵