Help us understand the problem. What is going on with this article?

クラスメソッド定義のイディオム

More than 1 year has passed since last update.

この記事は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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away