ruby で動的にクラスを作る
結論
- 定数に class クラスのオブジェクトを代入すると、(ruby で一般的な) class として扱われる
- module or class の配下にネストして class を作る場合、その class が持つ定数として定義する
- class.new する際に引数として、class クラスのオブジェクトを渡すと、引数で渡されたクラスで継承されたクラスが生成される。
- メソッドも作りたい場合はブロックで記述する
主な使いどころとしては、文字列で与えたクラス名で、クラスを新たに作成するとき。
実演
何言ってるのかわからないので確かめてみる
クラスを作る
結論1: 定数にclassクラスのオブジェクトを代入すると、(ruby で一般的な) class として扱われる
- module#const_set メソッドを使う
- 第一引数に定数名、第二引数にセットしたいオブジェクトを渡す
self.class.const_set :'Creature', Class.new => Creature Creature => Creature
以下と同じ
class Creature end
もちろんクラス名を Array に入れて each すると
['Human', 'Birds', 'Fishes'].each do |species| self.class.const_set :"#{species}", Class.new end
人類、魚類、鳥類が作成できる。 以下に紹介する方法もこの方法が応用できる。
ネストしたクラスを作る
結論2: module or class の配下にネストして class を作る場合、その class が持つ定数として定義する
- 以下の場合、const_set メソッドの self は Creature という class クラスのオブジェクト
- Creature クラスに、Human という定数を定義している
class Creature const_set :'Human', Class.new end => Creature::Human
以下と同じ
class Creature class Human end end
クラスを継承させる
結論3: class.new する際に引数として、class オブジェクトを渡すと、継承される
- 以下の例では、Creature::Base を継承させて Creature::Human を作っている
- Class#new を使うだけ
class Creature const_set :'Base', Class.new const_set :'Human', Class.new(Base) end Creature::Human.superclass => Creature::Base
つまり
class Creature class Base end class Human < Base end end
と同じ
メソッドも作りたい場合
結論4: メソッドも作りたい場合はブロックで記述する
- Creature::Base クラスに eat メソッド、Creature::Human クラスに think メソッドを定義している
- Class#new にブロックを渡して、def メソッドで定義する
- さらに Base を継承して Human を作っている
- 複数行にわたってブロックを書く場合、const_set の引数渡しには ( ) が必要なので注意
- これで少しハマッた
class Creature const_set(:'Base', Class.new do |klass| def eat(food) p "#{food} mgmg..." end end) const_set(:'Human', Class.new(Base) do |klass| def think p 'I think, therefore I am' end end) end => Creature::Human h = Creature::Human.new => #<Creature::Human:0x007fa324b6a7a0> h.think => "I think, therefore I am" h.eat('rice') => "rice mgmg..."
もちろん以下とおなじ
class Creature class Base def eat(food) p "#{food} mgmg..." end end class Human < Base def think p 'I think, therefore I am' end end end
おまけ
- ruby が単一継承だったり
- class 名が先頭大文字始まりだったり
- クラスを二度定義しようとすると、"warning: already initialized constant" が出たり
- class 定義がブロックっぽい書き方だったり
の理由が理解できました。