宇宙は究極のフリーランチ

マネジメント / 意思決定 / プログラミング

スコープの外からインスタンス変数を操作

前回、privateメソッドをスコープの外から操作する方法を紹介したが、今回は(getterやsetterが用意されていない)インスタンス変数に、スコープの外からアクセスする方法について書く。

例えば、以下のクラスの@instance_variableは、普通、インスタンスの外部からは読みも書きも出来ないが、今回紹介する方法を使うとできるようになる。

やりかた

やりかたから書くと#instance_evalで@instance_variableにアクセスできる

しくみ

#instance_evalは、ブロックとして渡したコード*をレシーバーのコンテキストで実行するメソッドだ。

例えば、example.instance_eval{ p @instance_variable }を実行すると、あたかも、ブロックで渡した"p @instance_variable"と言うコードが、Exampleクラスのインスタンスメソッドとして定義されて、それが実行されたかのような振る舞いをする。
イメージとしてはこんな感じだ。

Exampleのインスタンスメソッドであれば、Exampleのインスタンス変数である@instance_variableにアクセス出来ると言うわけだ。

ちなみに、前回、private メソッドも呼び出す方法としてsendを紹介したが、sendを使わなくてもinstance_evalを使えばprivateメソッドを呼び出せる。が、sendの方がタイプ数が少ないし、例えデバックや動作確認の時でもインスタンス変数を書き換える事が余り無いので、私はほとんどsendしか使っていない。


*文字列でコードを渡す事もできる

privateメソッドを外から実行

irbとかrails consoleでデバックを行なっている時に、privateなメソッドを呼び出して動作を確認したい時がたまにある。

そんな時、my_object.private_my_methodのように"."でメソッドを実行する代わりに、my_object.send(:private_my_method)と、#sendでメソッドを実行すると、カプセル化を破ってprivateなメソッドを実行することが出来る。

#sendとは、「レシーバーが持っているメソッドを実行するメソッド」で、#sendに引数としてメソッド名を渡す事でメソッドを実行できる。 #sendは、Kernelのインスタンスメソッドなのでほぼ全てのオブジェクトに対して使う事が出来る。

やってみる

  • 例えば、このMyClassインスタンスのprivateメソッドをirbから呼び出したいとする
  • 普通に"."で実行すると(当然)NoMethodErrorとなる
[2] pry(main)> MyClass.new.private_my_method
NoMethodError: private method `private_my_method'  
called for #<MyClass:0x007ff2c9bf0e50>
  • しかし、sendを使うと呼び出せる!
[3] pry(main)> MyClass.new.send(:private_my_method)
"private!"

ちなみに、irb上だけでなく、ソースコード上でも同じ方法でprivateなメソッドを呼び出せる。
もちろんカプセル化を破壊しているので保守性が落ちるしバグを生みやすいので、乱用しないように気をつける必要がある。
自分の場合は、カプセル化を破壊するのはirb上でデバックをする時だけに限るようにしている。


さらにちなみに:
- この方法でprotectedなメソッドも呼び出せる
- #sendで無く#public_sendを使えばカプセル化を破らずに動的にメソッドが呼び出せる

irbでrequireしたファイルを再読み込みしたい時には

自分で作ったライブラリの動作をirbで確認している時に、「requireでライブラリを読み込む->動作を確認する->ライブラリをちょっと修正->もう一回動作を確認する」と、言うことがやりたい。

そんな時、ライブラリをもう一回requireしても、修正内容はirb上には反映されない。

同じファイルを複数回requireした場合、最初の1回しかロードが行われないためだ。

こう言った時は、requireの代わりにloadを使うと、irbを再起動しなくても同じファイルを何度もロード出来るので便利だ。

例えば、example.rbと言うライブラリをirb上で動作確認しつつ修正したい時には、以下のようにすると良い。

  • 修正前のライブラリ

  • irb内でloadして動作を確認
irb>load "./example.rb"
irb>Example.new.greet
#=> "hello"
  • 修正

  • irbを再起動しなくてもloadしなおせば動作が変わる
irb>load "./example.rb"
irb>Example.new.greet
#=> "hi!!!!!"

もちろん、irbではなくpryを使っている時も同じようにロードし直せる。
ちなみに、rails consoleを使っている時に同じような事がしたい時は、pryと、pry-railsをGemfileに加えて、rails console上で reload!とコマンドを打てば、少なくともapp以下のファイルに加えた変更は全部反映される。
この辺りの詳しい仕組みははっきり分かっていないので今度調べてみます…

自分で作ったクラスでmap, with_index, selectなどを使えるようにする

自分で作ったクラスに、組み込みライブラリのEnumerableをインクルードしてあげると、map, with_index,selectと言った便利メソッドが使える様になる。

やりかた

例えば、複数のアイテムを表すItemWithQuantityと言うクラスを作ったとする。
ItemWithQuantity.new(hp_recovery, 3)で「やくそう3つ」を表す。

ItemWithQuantityにEnumerableモジュールをインクルードし、eachを定義してあげる。

すると、map, with_index,selectといったEnumerableに属するメソッドの一部が使えるようになる。

補足:Enumerableに含まれるメソッドの一覧
http://ref.xaio.jp/ruby/classes/enumerable

eachに加えて<=>を定義する事で、sort, maxなどさらに沢山のメソッドが使えるようになるが、それについては今度書く。