Rubyでクラスを定義するには2通りの方法があります。class
キーワードとClass.new
です。
class
キーワードはクラス定義しつつそのクラスの中にコンテキストを移します。
このクラス定義の仕方は一般的ですが、一方でいくつかの制約があります。例えば、
- 名前を動的に指定できない
class
キーワードの外部にある変数にアクセスできない
これらの制約は通常あまり問題になりませんが、問題になるケースでは以下のClass.new
を使います。
Rubyの世界ではクラスはオブジェクトですので、new
メソッドで作ることができます。この方法で作られたクラスは定数に代入されるまで無名なので「無名クラス」と言われることもあります。
無名クラスでは動的に名前付けができますし、名前をつける必要がないときは無名のままにできます。また、変数のスコープを作らないため、define_method
と組み合わせることで現在のスコープにある変数をクラス定義の内部で使うことができます。
例を見てみましょう。
lvar = "foo"
klass = Class.new do
define_method :foo do
lvar
end
end
Object.const_set("Class_#{1+1}", klass)
puts Class_2.new.foo
# => foo
DSLを構築する際に多く使われるのはinstance_eval
です。また、アプリケーションクラスと呼ばれるDSL評価用のクラスを作って各DSLをそこへのポインタ的に使う手法もあります(Rakeなど)。しかし、これらの方法には制約があります。スコープが区切られないため、メソッドを定義すると定義がmain
オブジェクトに漏れ出してしまうのです。
試しに、以下のコードをrake foo:bar
で実行してみましょう。
# 適当なディレクトリに配置
# Rakefile
namespace :foo do
desc 'Sample task'
task :bar do
bar
end
def bar
puts 'bar'
end
end
bar # これはエラーになってほしい
するとbar
が2回出力されます。これは一番下のbar
がエラーになっていないことによるものです。これは一見直観に反しますが、namespace
メソッドがクラス定義をしているわけではない、ということを理解すると結局def bar
はmain
オブジェクトに対してのメソッド定義になっていることがわかるかと思います。
では、RSpecのDSLではどうでしょうか。以下のコードをrspec foo_spec.rb
などで実行してみましょう。
# foo_spec.rb
require 'rspec'
RSpec.describe 'This is the description' do
describe 'This is inner description' do
def some_method
puts self.class
end
it 'works' do
some_method
end
end
end
some_method
今度はエラーになったかと思います。ここで、最下部のsome_method
呼び出しを削除して再実行してみましょう。すると、RSpec::ExampleGroups::ThisIsTheDescription::ThisIsInnerDescription
のような出力が得られます。これはputs self.class
の結果ですので、def some_method
はこの(定義した覚えのない)クラスに対して定義されていることがわかります。
ここで使われているのが無名クラスです。無名クラスには名前を後から与えることができるので、上のようにdescribe
メソッドに与えた文字列を元にクラス名を決定することもできるわけですね。
Class.new
で定義したクラスに対してメソッドを定義するにはClass.new
にブロックを与え、その中で通常通りメソッドを定義します。
klass = Class.new do
def foo
puts 'foo'
end
end
klass.new.foo # => foo
Class.new
に引数を与えるとそのクラスは親クラスになります。
class Parent
def foo
puts 'foo'
end
end
Child = Class.new(Parent)
Child.new.foo # => foo