跳至內容

Hook

存在一些特殊的巨集,會在某些情況下以 Hook 的形式在編譯時期被調用

  • 當定義子類別時會調用 inherited@type 為繼承的類型。
  • 當包含模組時會調用 included@type 為包含的類型。
  • 當擴展模組時會調用 extended@type 為擴展的類型。
  • 當找不到方法時會調用 method_missing
  • 當在目前作用域中定義新方法時會調用 method_added
  • 在解析完成後調用 finished,因此所有類型及其方法都是已知的。

inherited 的範例

class Parent
  macro inherited
    def lineage
      "{{@type.name.id}} < Parent"
    end
  end
end

class Child < Parent
end

Child.new.lineage # => "Child < Parent"

method_missing 的範例

macro method_missing(call)
  print "Got ", {{call.name.id.stringify}}, " with ", {{call.args.size}}, " arguments", '\n'
end

foo          # Prints: Got foo with 0 arguments
bar 'a', 'b' # Prints: Got bar with 2 arguments

method_added 的範例

macro method_added(method)
  {% puts "Method added:", method.name.stringify %}
end

def generate_random_number
  4
end
# => Method added: generate_random_number

method_missingmethod_added 都只適用於定義巨集的同一個類別或其後代的呼叫或方法,如果巨集定義在類別之外,則只適用於頂層。例如

macro method_missing(call)
  puts "In outer scope, got call: ", {{ call.name.stringify }}
end

class SomeClass
  macro method_missing(call)
    puts "Inside SomeClass, got call: ", {{ call.name.stringify }}
  end
end

class OtherClass
end

# This call is handled by the top-level `method_missing`
foo # => In outer scope, got call: foo

obj = SomeClass.new
# This is handled by the one inside SomeClass
obj.bar # => Inside SomeClass, got call: bar

other = OtherClass.new
# Neither OtherClass or its parents define a `method_missing` macro
other.baz # => Error: Undefined method 'baz' for OtherClass

finished 在類型完全定義後會被調用一次 - 這包含該類別的擴展。考慮以下程式

macro print_methods
  {% puts @type.methods.map &.name %}
end

class Foo
  macro finished
    {% puts @type.methods.map &.name %}
  end

  print_methods
end

class Foo
  def bar
    puts "I'm a method!"
  end
end

Foo.new.bar

print_methods 巨集會在遇到時立即執行 - 並列印一個空列表,因為在該點沒有定義任何方法。一旦編譯了第二次 Foo 的宣告,將會執行 finished 巨集,並列印 [bar]