Ruby模块 - included do end块

49

有一个名为MyModule的模块:

module MyModule

  extend ActiveSupport::Concern

  def first_method
  end

  def second_method
  end

  included do
    second_class_method
  end

  module ClassMethods
    def first_class_method
    end

    def second_class_method
    end
  end
end
当某个类include这个模块时,它会有两个实例方法(first_methodsecond_method)以及两个类方法(first_class_methodsecond_class_method) - 这很清晰。
据说,

included块将在包含该模块的类上下文中执行。

这到底是什么意思?也就是说,这个方法(second_class_method)何时被执行呢?
2个回答

101

以下是一个实际的例子。

class MyClass
  include MyModule
end

当您将模块包含在类中时,included钩子将被调用。因此,second_class_method将在Class的范围内调用。

这里发生的是

  1. first_methodsecond_method被包含为MyClass的实例方法。

    instance = MyClass.new
    instance.first_method
    # => whatever returned value of first_method is
    
  2. ClassMethods 的方法会自动混合为 MyClass 类的类方法。这是 Ruby 中常见的模式,ActiveSupport::Concern 进行了封装。非 Rails 的 Ruby 代码为:

  3. module MyModule
      def self.included(base)
        base.extend ClassMethods
      end
    
      module ClassMethods
        def this_is_a_class_method
        end
      end
    end
    

    导致结果为

    MyClass.this_is_a_class_method
    

    或者在你的情况下

    MyClass.first_class_method
    
  4. included是一个钩子函数,其实就是下面这段代码的效果

  5. # non-Rails version
    module MyModule
      def self.included(base)
        base.class_eval do
          # somecode
        end
      end
    end
    
    # Rails version with ActiveSupport::Concerns
    module MyModule
      included do
        # somecode
      end
    end
    

    大多数情况下,这只是常见模式的“语法糖”。实际上,在混入模块时,该代码将在混合器类的上下文中执行。


2
一切听起来都很合乎逻辑,但是...让我们看一个例子。如果 second_class_method 实现为 raise "When am I raised?",那么它什么时候会被触发? - Andrey Deineko
6
只要包含你的模块,它就会立即被调用。这实际上会触发Ruby代码解析过程,并导致解释器立即崩溃。你可以自己尝试一下。 - Simone Carletti

29

included 在你将module包含到类中时调用,它用于定义关系、作用域、验证等等。在创建该类的对象之前就已经被调用。

例子

module M
  extend ActiveSupport::Concern
 ...
  included do
    validates :attr, presence: true
    has_many :groups
  end
 ...
end

那么,拥有验证会对模块可以被包含的位置产生限制吗? - stackjlei
如果你在 include do 块之外设置作用域,为什么它不起作用呢?它仍将在包含该模块的类的上下文中设置,对吧? - stackjlei
4
它不会“设置在包含模块的类的上下文中”。 validates是一个方法。在模块主体内运行的任何方法都是在“加载时”在该模块的上下文中运行的。因为对于模块而言,validates没有什么意义,所以你将会得到"NoMethodError"。 - meandre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接