動的な機能を使ったコードを解説してみる

Rubyベストプラクティスの第1章〜第3章を読みました。 特に印象的だった第3章から学んだことを活かして、「動的な機能を使ったコード」を解説してみようと思います。

コード

※ 本著 p.107より引用

module NaiveCampingRoutes
  extend self

  def R(url)
    route_lookup = routes

    klass = Class.new
    meta = class << klass; self; end
    meta.send(:define_method, :inherited) do |base|
      raise "Already defined" if route_lookup[url]
      route_lookup[url] = base
    end
    klass
  end

  def routes
    @routes ||= {}
  end

  def process(url, params={})
    routes[url].new.get(params)
  end
end

module NaiveCampingRoutes
  class Hello < R '/hello'
    def get(params)
      puts "hello #{params[:name]}"
    end
  end

  class Goodbye < R '/goodbye'
    def get(params)
      puts "goodbye #{params[:name]}"
    end
  end
end

NaiveCampingRoutes.process('/hello', name: "greg") #=> "hello greg"
NaiveCampingRoutes.process('/goodbye', name: "joe") #=> "goodbye joe"

解説

主に3カ所を取り上げて解説してみます。
まず1カ所目。上記コード26行目あたりです。

  class Hello < R '/hello'
    ...
  end

このソースコードでは、主に以下2つのことをやっています。

  • 匿名クラスを継承したHelloクラスを定義している
  • '/hello'をキーに、NaiveCampingRoutes::Helloクラスを値に持つハッシュ(routes)を生成している

2つ目のハッシュ生成は、クラスが定義された時に呼び出されるinheritedメソッド内で行われています。
そのinheritedメソッドが定義されるのが、下記の箇所です。解説2カ所目、上記コード7行目あたりです。

    klass = Class.new
    meta = class << klass; self; end
    meta.send(:define_method, :inherited) do |base|
      raise "Already defined" if route_lookup[url]
      route_lookup[url] = base
    end

1行目でClassクラスのオブジェクト(klass)を生成しています。
2行目では、klassオブジェクトにメソッドを定義するため、klassオブジェクトの特異クラスを公開し、特異クラスを変数metaに代入しています。
3行目〜6行目で、特異クラスにinheritedメソッドを定義し、継承を捕捉できるようにしています。baseには、inheritedメソッドの引数であるサブクラスが入ります。inheritedメソッドでは、ブロック内のコードが実行されます。ブロック内では、まずurlが既に定義済みかどうかの検証をしています。定義済みの場合は、例外を発生させ、未定義の場合は、urlをキーに、サブクラスを値に持つハッシュを生成しています。

そして解説3カ所目。上記コード20行目あたりです。

  def process(url, params={})
    routes[url].new.get(params)
  end

routes[url].newで定義済みクラス(NaiveCampingRoutes::Hello、あるいはNaiveCampingRoutes::Goodbye)のオブジェクトを生成し、get(params)メソッドを呼び出しています。

所感

動的な機能を使いこなせるようになるために、今後機会があればどんどん使っていきたいですが、本著には下記のようなことも書いてありました。

Rubyの動的な機能は比較的わかりやすく、適切に使えば有効な手段になる。しかし、これらをすべて考慮して、使えるだけ自分のコードに利用すると、間違いなくひどい目にあうだろう。
(中略)
どんな要件を満足しなければならないかではなく、どんな動的な機能が私のコードに必要になるかを考えることから始めてしまうと、開発は混乱し、予期せぬ中断を引き起こすことになるだろう。

「どれだけコードを動的にする必要があるのか」という観点も意識しつつ、今後コーディングしていきたいです。

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック

Rubyベストプラクティス -プロフェッショナルによるコードとテクニック