basyura's blog

あしたになったらほんきだす。

動的に module を定義

new(superclass = Object) -> Class
new(superclass = Object) {|klass| ... } -> Class
新しく名前の付いていない superclass のサブクラスを生成します。
名前のないクラスは、最初に名前を求める際に代入されている定数名を検索し、見つかった定数名をクラス名とします。

Class.news

sequel のまねっこをしてこんなことをしてみた。テーブル名を指定してクラスを定義し、そのクラスがアクセスするテーブルを固定する。同時に接続先のデータソースも保持。

module MOD
  def self.Base(table_name)
    c = Class.new(Base)
    sig = class << c ; self end
    sig.send :define_method , :table_name , Proc.new{table_name}
    #
    # ごにょごにょして接続先のデータソースも保持しておく
    #
    c
  end
  class Base 
    def find
      # ActiveRecord ちっくなメソッドを色々定義
    end
  end
end

class A < MOD::Base('DUMMY_TABLE') ; end

puts A.table_name #=> DUMMY_TABLE

実際には、データソースの数だけ each で回して切り替えながら各環境のテーブルを検索するようにしていたのだけど、class A を再度定義するとエラーになる。

class A < MOD::Base('DUMMY_TABLE') ; end
class A < MOD::Base('DUMMY_TABLE') ; end
#=> superclass mismatch for class A (TypeError)

もっと簡単な例。

class B < Array ; end
class B < Hash ; end
#=> superclass mismatch for class B (TypeError)

これは ruby-list:39567 で提案されて 1.8.2 で追加された変更点。

新たなクラスを同名で定義しようとしたときに、
「already initialized constant Foo」という有り触れた警告ではなく、もっと
強い警告を出すか、またはできれば例外を生成してくれないでしょうか?

ruby-list:39567

でも、最初に書いたようにデータソースを切り替えてつなぎ直すけど、クラス名は使い回したい。なんとかしたい。

考える ・・・・ module で名前空間分ければいいんじゃね?

Module#module_eval あたりを使えばいいなじゃないかと思ったけどうまく行かず。結局 eval 。

def define_class(mod_name , class_name , table_name)
  eval <<-EOF
    module #{mod_name}
      class #{class_name} < MOD::Base('#{table_name}')
      end
    end
    #{mod_name}::#{class_name}
  EOF
end

module MOD
  def self.Base(table_name)
    c = Class.new(Base)
    sig = class << c ; self end
    sig.send :define_method , :table_name , Proc.new{table_name}
    c
  end
  class Base 
    def find
      "hi !!!"
    end
  end
end

a1 = define_class("M1" , "A" , "DUMMY_TABLE")
a2 = define_class("M2" , "A" , "DUMMY_TABLE")
puts a1 #=> M1::A
puts a2 #=> M2::A

puts a1.table_name #=> DUMMY_TABLE
puts a1.new.find #=> hi !!!

クラスを変数に突っ込めば再代入もできるので特に問題なし。