LoginSignup
2
0

More than 5 years have passed since last update.

組み込みオブジェクトを汚さない拡張のやり方

Posted at

当たり前と言えば当たり前なのですが Gem を作る時とかに使えそうなので。

普通に拡張すると何が問題なのか?

Ruby 自体は組み込みのライブラリに含まれるクラスも簡単に拡張できます。

class Array
  def <<(obj)
    nil
  end
end

簡単に Array<< メソッドを壊す事ができました。これは極端な例ですが、こうした衝突があちこちで起こるのを避けたいというのが本題です。

やり方

継承を使う

class MyArray < Array
  def <<(obj)
    2.times { super(obj) }
  end
end

実行。

# 普通に Array の initialize が呼べる
my_array = MyArray.new(2, "hoge")
# 上書きしたメソッドを実行してみる
my_array << 1
p my_array

結果。

["hoge", "hoge", 1, 1]

もちろん元の Array クラスでは何ら変更は起こりません。

class_eval を用いたメソッド実装

class_eval の第一引数に文字列を渡すと、その中身が直接クラス内に定義されます。

また第二引数と第三引数はスタックトレースで利用されるので、このまま書いておくとデバッグの時に便利です。

おまじないにしたくない人は公式で詳しい動作をチェックしましょう。

これの何が嬉しいかと言うと、人が作った拡張と名前が被った時などに衝突をコンフィグで回避できる点です。

# このようにコンフィグで設定したメソッド名を…
METHOD_NAME = "cut".freeze

class MyArray < Array
  # class_eval で直接定義する
  class_eval <<-RUBY, __FILE__, __LINE__ +1
    def #{METHOD_NAME}(count = 2)
      self.class.new self.slice(0, count)
    end
  RUBY
end

実行。

my_array = MyArray.new(10, "hoge")
p my_array
p my_array.cut
p my_array.cut(1)

結果。

["hoge", "hoge", "hoge", "hoge", "hoge", "hoge", "hoge", "hoge", "hoge", "hoge"]
["hoge", "hoge"]
["hoge"]

この場合、テストコードなどもコンフィグに合わせて実行されるように注意する必要があります(そちらは send を使えば簡単にできると思います)。

余談

このケースでも全てのメソッド名が被る事を避ける事はできませんが、そのためにネームスペースが切られています。そこまで被ってしまうケースではどうしようもありませんが、変わった名前の Gem はその辺の被りを嫌ってネーミングしているのかもしれません。

サードパーティが自社コンテンツ向け Gem でネーミングする際にかなり一般的な名前を使っている意図など考慮すると少し面白い気がします。

実装の例

Kaminari はこの両方の方式を使っていました。というより、自分で Gem を作った方が良さそうな要件が発生したので Clean and Easy to Use を謳っている Kaminari の実装を参考にさせて頂きました。

継承を使う

元々の配列を引数にして new できるようになっています。

class_eval を使う

中身の実装は意外とシンプルです。

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0