Ruby のクラスはオープン


Ruby のクラスはオープンなんです. どういうことですかって?

つまりは, あらかじめ定義されている Ruby のクラスに後から好きなようにメソッドを追加で定義できるんです.

この機能は使いようによってはとても便利なのです.

有用性の実証

例えば String クラスでその有用性を実証してみたいと思います.

def from_foo_to_bar(text)
  text.gsub('foo', 'bar')
end

text = 'foo foo bar bar'
puts from_foo_to_bar(text).upcase.inspect  # "BAR BAR BAR BAR"

foobar に変換する from_foo_to_bar という関数を定義して, それを呼び出し , 返した文字列を upcase メソッドで大文字に変換しています.

このようなコードを Ruby のクラスはオープンという機能を利用すると次のように書き換えることができます:

class String
  def from_foo_to_bar
    self.gsub('foo', 'bar')
  end
end

text = 'foo foo bar bar'
puts text.from_foo_to_bar.upcase.inspect  # "BAR BAR BAR BAR"

どうでしょうか.

text は文字列なので, String クラスのインスタンスオブジェクトです:

puts text.class  # String

なので String クラスのインスタンスメソッドとして from_foo_to_bar を定義することによって, text.from_foo_to_bar のように他の String メソッドを呼び出すときのように呼び出すことができます.

self.gsub のように self で String のインスタンスオブジェクトを参照しているところがポイントです.

この場合は text なので, self.gusb というのは, 内部的には text.gsub と同等のことが行われています.

最初の例のように from_foo_to_bar(text) のように呼び出す方法もいいですが, 使用できるオブジェクトを String に絞った text.from_foo_to_bar の方が少しいいのではないでしょうか.

Ruby のローカルスコープに定義するのではなく, String クラスのインスタンススコープに定義するので, 影響を抑えることができますし.

またメソッドチェインと呼ばれるオブジェクトのインスタンスメソッドを繋げて元のオプジェクトの値を徐々に編集していく方法とも相性がいいです.

このように既存のクラスで新たな自分だけのインスタンスメソッドを追加したいという場合は, そのクラスを定義し, そのクラス内に自分が定義したいインスタンスメソッドを定義すれば, そのインスタンスメソッドが既存のクラスに追加され, 使えるようになります .

つまりは既存のクラスに新たな機能を追加して, 既存のクラスの機能を拡張したいという場合に使われる方法です.

Rails でも使われている

この方法はとてもポピュラーなので, Rails などでも使われています.

例えば Rails の Time クラスは advanced というインスタンスメソッドが既存の Time クラスにこの方法で追加されています.

次のコードをみていただけると:

https://github.com/rails/rails/blob/v5.2.0/activesupport/lib/active_support/core_ext/time/calculations.rb#L162

advance というインスタンスメソッドが Time クラスに定義されているのがわかります .

ただ実際のコードはそのようにやや複雑ですが, 既存のクラスを再定義してその中にインスタンスメソッド定義するという手順は一貫しています.

Array や Hash でも試してみる

先ほどは String クラスで試してみましたが, 折角なので Array や Hash でも同じようにインスタンスメソッドを追加して試してみたいと思います.

Array の場合

class Array
  def from_foo_to_bar
    self.map do |element|
      element == 'foo' ? 'bar' : element
    end
  end
end

array = ['foo', 'foo', 'bar', 'bar']
puts array.from_foo_to_bar.inspect  # => ["bar", "bar", "bar", "bar"]

先ほどと同じように Array クラスを定義し, その中に from_foo_to_bar を定義しました.

そして self で Array のインスタンスオブジェクトを参照し, map で要素が foo と一致するものは bar に変更し, それ以外の要素はそのままにして新たな配列を作り, それを返します.

便利ですよねこの機能.

Hash の場合

class Hash
  def from_foo_to_bar
    self.to_a.map do |(key, value)|
      value = 'bar' if value == 'foo'
      [key, value]
    end.to_h
  end
end

hash = { 1 => 'foo', 2 => 'foo', 3 => 'bar', 4 => 'bar' }

puts hash.from_foo_to_bar.inspect  # => {1=>"bar", 2=>"bar", 3=>"bar", 4=>"bar"}

同じように Hash クラスを定義し, その中に from_foo_to_bar メソッドを定義し, selfhash を参照し, まず to_a で Hash から Array に変換してから, それぞれの要素を map でまわり, (key, value) で第一要素を key, 第二要素を value として割り当て, valuefoo なら bar を代入しています.

そして新しく作り変えられた配列を to_h メソッドで Hash に戻して, その値を返します.

こんな感じでどんなクラスにも使えてしまうのですよ.

まとめ

今回は, Ruby のクラスはオープンということでしたが, そのオープンさと利点を知っていただけたのではないでしょうか.

今までのような感じで, String, Array, Hash クラスに限らず, どんな既存のクラスでもいくらでも拡張することができますので, Ruby を使われている方だったらぜひ知っておきたいテクニックです.

そのぐらい便利な機能ですので, ぜひ知っていただけたらと思いそのような内容を今回は書かせていただきました.

最後までお読みくださいましてありがとうございました.