これ僕.com:行動分析学マニアがおくる行動戦略

意図と行動のギャップから生じる「不自由さ」への挑戦。果たして僕たちに自由はあるのか?

HashからMethodを動的に作りたい → define_methodでいけるのね!

Hashの値にあわせて動的にMethodを定義したいのです

例えば、

hash = {:foo => "foofoofoo"}

obj = Hoge.create hash
obj.foo # => "foofoofoo"

みたいなことは出来ないものか、と。method_missingを使えばいけそうだけど、あんまり使うなって「るびま本」に書いてあったような気がするので、別の手段を調べる。で、define_methodなるものを発見。っていうか、これも「るびま」に書いてあるやん。
早速、http://jp.rubyist.net/magazine/?0011-CodeReviewを参考にサンプルコードを書いてみる。

class Foo
  def self.create_methods(hash)
    hash.each do |key, val|
      define_method(key) do
        val
      end
    end
  end
end

Foo.create_methods :hoge => "hogehogehoge", :moge => "mogemogemoge"

foo = Foo.new
p foo.hoge # => "hogehogehoge"
p foo.moge # => "mogemogemoge"

ん、動いた。

望ましくないのです → るびまのコードを真似る

でも、この続きで

Foo.create_methods :bar => "barbarbar"

foo = Foo.new
p foo.bar # => "barbarbar"
p foo.hoge # => "hogehogehoge"

ってやると foo.hoge も有効になってて、これは望む結果ではない。Fooのクラス定義を共有してるから当たり前か〜と思いつつ、るびまの記事を読む。で、こんなコードを書いてみた。

class Foo
  class << self
    def define(&block)
      new_class(&block)
    end
    
    def methods(hash)
      hash.each do |key, val|
        define_method(key) do
          val
        end
      end
    end
    
    def new_class(&block)
      c = Class.new(::Foo)
      c.instance_variable_set :@field_specs, []
      c.module_eval(&block)
      c
    end
  end
end

使い方は

Foo_ = Foo.define do
  methods :hoge => "hogehogehoge", :moge => "mogemogemoge" 
end

foo = Foo_.new
p foo.hoge # => "hogehogehoge"
p foo.moge # => "mogemogemoge"

な感じ。今度は、ここから更に

Foo_ = Foo.define do
  methods :bar => "barbarbar"
end

foo = Foo_.new
p foo.bar # => "barbarbar"
p foo.hoge # => undefined method

としてみると、ちゃんとfoo.hogeが無効(undefined)になっててグー。特異クラス(class << self)?ってのと、new_class(&block)辺りがポイントっぽい。

使いやすいようにごにょごにょごにょ

で、動かすことは出来たので、自分の使いやすい形にゴリゴリと書き換える。最終的に、以下の形になりますた。

module BuildFromHash
  def create(&block)
    c = new_class(&block)
    c.new
  end
  
  def methods(hash)
    hash.each do |key, val|
      define_method(key) do
        val
      end
    end
    new
  end
  
  private
  
  def new_class(&block)
    c = Class.new(self)
    c.instance_variable_set :@field_specs, []
    c.module_eval(&block)
    c
  end
end

class Foo
  class << self
    include BuildFromHash
  end
  
  def org
    "orgorgorg"
  end
end

foo = Foo.create do
  methods :hoge => "hogehogehoge", :moge => "mogemogemoge" 
end
p foo.hoge # => "hogehogehoge"
p foo.moge # => "mogemogemoge"
p foo.org  # => "orgorgorg"

満足。