Let's write β

プログラミング中にできたことか、思ったこととか

複数のモジュールを同時includeした場合の処理を設定するNo.2

前回の記事では各モジュールのself includedの中で明示的にチェックを走らせる必要がありました。これがなかなか面倒だなという事だったので、もうすこし省略された記法で書けるようにしょうとおもっていました。
そこで、探していたところ、メソッド実行を監視できるSentinelというGemを発見し
https://github.com/lucashungaro/sentinel
早速利用してみました。

require 'sentinel'

class UnionRule
  @@UnionRule = []
  def initialize(modules, action_proc)
    @modules = modules
    @proc = action_proc
  end

  def match?(modules)
    res = true
    @modules.each do |mod|
      if !modules.include?(mod)
        res = false
        break
      end
    end
    return res
  end

  def run(klass)
    @proc.call(klass)
  end

  def self.define_union_rule(modules, action_proc)
    inst = self.new(modules, action_proc)
    @@UnionRule << inst
  end

  def self.run_match_rules(klass)
    modules = klass.included_modules
    @@UnionRule.each do |rule|
      if rule.match?(modules)
        rule.run(klass)
      end
    end
  end
end

class ModuleObserver
  include Sentinel
  def self.notify_class_method(options, *args)
    UnionRule.run_match_rules(args[0])
  end
end

module UnionableModule
  def self.included(base)
    ModuleObserver.observe(base, :included,
                           :method_to_notify => :notify_class_method, :class_method => true)
  end
end

module Feature
  include UnionableModule
end

module AnotherFeature
  include UnionableModule
end

UnionRule.define_union_rule([Feature, AnotherFeature],
                            lambda{|klass|
                              puts "#{klass}"
                              klass.class_eval do
				def foo()
                                  puts "#{self.class}->foo"
                                  return 10
				end
                              end
                            })

class Moge
    include Feature
    include AnotherFeature
end

moge = Moge.new()
puts moge.foo()

これでUnionableModuleをincludeするだけで、自動的に合成演算を定義可能な(正確には、include時に監視対象になる)Moduleにする事ができます。
実行結果は前回と同様に

Moge
Moge->foo
10

とメソッドの追加が適切にできている事がわかりますね。