JQ Blog

ブロック②

instance_eval

instance_evalとは

フラットスコープよりもっと簡単にコードと束縛を好きなように組み合わせる方法がある。instance_evalメソッドだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyClass
  def initialize
    @v = 1
  end

  def my_method
    private_method
  end

  private

  def private_method
    puts @v
  end
end

obj = MyClass.new
obj.my_method # => 1

v = 2
obj.instance_eval do
  @v = v
  def private_method
  puts @v + 1
end

obj.my_method # => 3

ブロックはレシーバをselfとしたコンテキストで評価されるので、ブロックからレシーバのプライベートメソッドや@vなどのインスタンス変数にアクセスできる。
instance_eval()に渡したブロックをコンテキスト探索機と呼ぶ。

カプセル化の破壊

このコンテキスト探索機を使うとカプセル化を破壊できてしまう。でもこのコンテキスト探索機が必要になる時がある。例えば、irbからオブジェクトの中身をすぐにみたいとかの場合もある。こういう場合、そのオブジェクトに入っていくにはinstance_evalが最も近道になる。
テスト環境で何かのオブジェクトをテストする時も、データベースにアクセスするメソッドをシンプルな定数値と入れ替えることができる。

呼び出し可能オブジェクト

呼び出し可能オブジェクトに対しては前記事を書いたので、簡単にまとめてみる。
前の記事
ブロックの使用は2つに分けられる。まず、コードを保管する。それから、そのブロックを呼び出して実行する。でも、「コードを保管して、あとで呼び出す。」方式はブロックに限らない。Rubyではコードを保管できる場所が他に3つがある。

  • Procの中。(ブロックがオブジェクトになったもの)
  • lambdaの中。(Procの変形)
  • メソッドの中。

Procオブジェクト

  • ブロックはオブジェクトじゃない
  • ブロックを保管して、あとで呼び出すためにはブロックがオブジェクトになる必要がある。そのために、Rubyの標準ライブラリProcがある
  • ブロックを評価するには、Proc#call()で呼び出す
  • ブロックを保管して、あとで呼び出す技術を遅延評価と呼ぶ
  • ブロックをProcに変換する方法は4つがある
    • Proc.new
    • lambda()
    • proc()
    • &修飾

&Checkでのブロック

&Checkにはいくつかのところでブロックを使用している。まず、モデルクラスでのscopeにlambdaを多く使っている。
あと、SurveyGroupモデルのvalidationに使っていて、そのコードは

1
2
3
4
5
6
with_options on: [:update] do |v|
  v.validate :validate_relation_participants
  v.validates :group_id, presence: true, unless: Proc.new { owner.is_a?(Conceptor) } # ここ
  v.validates :not_send_from, presence: true
  v.validates :not_send_to, presence: true
end

ここはProcを使わずowner.is_a?(Conceptor)だけ書くとエラーになるため、Procオブジェクトを使って値を渡している。
あとはVisualizeLogicServiceで使っている。

1
2
3
4
5
6
# 遷移パターンを表示する
def visualize
  trees = []
  @tree.root_node.print_tree(0, nil, lambda { |node, prefix| trees << "#{prefix} #{node.name}" }) # ここ
  trees
end

上記のコードの中のprint_treerubytree gemからrequireされて使っていて、深くは知らないけど、そのメソッドの引数を渡すためにlambdaを使っている。