こんにちは。ウェブ開発担当の清水です。
Ruby 2.0 で実験的に導入されていた Refinements が、バージョン 2.1 より正式に導入となりました。
今回はこの機能を利用してクラスの拡張をしてみたいと思います。
オープンクラス
Refinements とはクラスの拡張の範囲を限定する機能です。使用の前に、まず Ruby のオープンクラスについて説明します。
オープンクラスとは、既存クラスを再定義(再オープン)することで、メソッドの追加・上書きなどが容易にできる機能です。
この機能により組み込みクラスであっても自由に拡張できるため、使い道次第で強力な効果を発揮します。
以下の例では、 Array クラスへ bogo_sort! メソッドを追加することで、全ての Array インスタンスから呼び出せるようにしています。
class Array def bogo_sort! shuffle! until sorted? end private def sorted? each_cons(2).all? { |a, b| a <= b } end end [3, 1, 2].bogo_sort! [/ruby] <p>なお、組み込みクラスに対する拡張をモンキーパッチと呼びます。</p> <h3 class="blue">モンキーパッチの危険性</h3> <p>モンキーパッチは強力である反面、危険性も持ち合わせています。<br>極端な例ですが、以下の処理を見てみましょう。</p> [ruby] 10 + 20 # => 30 class Fixnum def +(args) self * args end end 10 + 20 # => 200
もしプログラム全体に適用されていたとしたら…恐ろしいですね。
モンキーパッチはその影響範囲をよく考える必要があります。
拡張範囲の限定
前置きが長くなりましたが、上記のような問題の対処法として Refinements を使ってみましょう。
以下のように、拡張したいクラスを refine の引数として定義します。
module ArrayEx refine Array do def bogo_sort! shuffle! until sorted? end private def sorted? each_cons(2).all? { |a, b| a <= b } end end end [/ruby] <p>有効にする場合は using [モジュール名] とします。</p> [ruby] using ArrayEx [3, 1, 2].bogo_sort!
拡張は using を記述したファイルの記述位置以降で有効になるため、不要な箇所に影響を与える可能性は低くなります。
スコープに注意
Refinements のスコープはやや特殊です。
上記の通り using はファイルをまたがないため、記述したファイルを require や load しても拡張は有効になりません。
string_ex.rb
module StringEx refine String do def string_ex self + ' extension' end end end using StringEx 'string'.string_ex # => "string extension"
refine_test.rb
require 'string_ex' 'string'.string_ex # => NoMethodError
include などに比べ、より限定的なスコープであることが分かります。
Ruby 2.1 ではトップレベル以外にも using を記述できます。(2.0 では例外となるため注意してください)
module HelloWorld refine String do def hello puts "#{self} says : Hello, world" end end end class User using HelloWorld attr_reader :user def initialize(user) @user = user end def say user.hello end end User.new('User1').say # => "User1 says : Hello, world" 'User2'.hello # => NoMethodError
まとめ
これまでクラスの拡張、まして影響の大きい組み込みクラスを弄るなんて恐ろしい!とお思いだった方も、Refinements で拡張範囲を限定的にしながらモンキーパッチを活用してみてはいかがでしょうか。
フェンリルのオフィシャル Twitter アカウントでは、フェンリルプロダクトの最新情報などをつぶやいています。よろしければフォローしてください!