この記事はRuby開発環境 Advent Calendar / Jul.の4日目の記事です.
3日目はokitanさんのテストの自動実行あれこれでした.
開発環境とはちょっと離れてしまいますが, 便利なイディオムの枠としてクラスメソッドの定義について書いていきます.
動作原理自体は単純ですがあまり目にやさしくない記法のため慣れていない人は全く読めない処理が時々あります.
今回はその中からclass_eval(フラットスコープ)を使ったクラスメソッドの定義について少し書いて皆の理解に役立てばと思います ; )
class_eval
class_evalについて簡単に説明しておきます.
class_evalメソッドは、ブロックをクラス定義やモジュール定義の中のコードであるように実行します。ブロックの戻り値がメソッドの戻り値になります。
引用:http://ref.xaio.jp/ruby/classes/module/class_eval
とあるように所謂eval族の一員です. これを使うことで
class Qiita; end
Qiita.class_eval { def hello; "hello qiita!"; end }
puts Qiita.new.hello #=> hello qiita!
こんな感じで、あたかもclass Qiita
の中で定義しているかのように実行できます.
class Qiita
#ここ
end
class_evalを使ったクラスメソッド
それではここからが今回のメインです. class_evalを使ってクラスメソッドを定義してみましょう.
とにかく一番シンプルにいくならば
class Qiita; end
Qiita.class_eval do
def self.hello
"hello qiita!"
end
end
のような感じでしょうか. 次は少し難しく書いてみます
class Qiita; end
Qiita.class_eval do
class << self; self; end.class_eval do
def hello
"hello qiita!"
end
end
end
#または
Qiita.class_eval { class << self; self; end }.class_eval do
def hello
"hello qiita!"
end
end
いきなり少しわかりづらくなりましたが, class << self; self; end
の戻り値は実行した部分の特異クラスのselfです.
class Qiita; end
Qiita.class_eval do
# これだとselfはQiita
p self
# これだとselfはQiitaの特異クラス
p class << self; self; end
end
また, 上で書いたようにclass_evalの戻り値はブロックの戻り値なので
Qiita.class_eval { class << self; self; end }.class_eval do
の部分は特異クラスに対してclass_evalを行なっていることになります. 特異クラスに対してclass_evalを行うことは以下の場所で定義していることと同じになります.
class Qiita
class << self
#ここ
end
end
さらに
class Qiita; end
message = 'hello qiita!'
Qiita.class_eval { class << self; self; end }.class_eval do
define_method('hello') do
message
end
end
p Qiita.hello #=> 'hello qiita!'
message = 'hello kobito!'
p Qiita.hello #=> 'hello kobito!'
define_method
は引数にメソッド名や引数名を取り, blockに実際の動作を渡すことでdef
のスコープを超えることが出来るメソッドです.
こう書くことで、本来はclass (module)
def
で区切られたスコープ外の変数(例ではmessage
)を参照できます. これをフラットスコープとも言ったりします.
他にもsendを使ってこういった書き方も可能です.
class Qiita; end
message = 'hello qiita!'
Qiita.class_eval { class << self; self; end }.send('define_method', 'hello') { message }
puts Qiita.hello #=> hello qiita!
message = 'hello kobito!'
puts Qiita.hello #=> hello kobito!
まとめ
何もわからない状態だと結構何をやっているのか想像しにくい処理だったと思いますが, 実際に行なっていることの1つ1つはそんなに難しいことではないと思います.
ですがリフレクション系は使い過ぎると大変なことになってしまうので用法用量を守って正しくお使いください :p