経緯
最近、「Rubyにこういうのあったら便利かなぁ」と思ったメソッドをrstdというgemに追加して個人プロジェクトとかで使えるようにするのがマイブームになっています。
で、色々実装している時に、サクッとRefinementsが使えないだろうかと思い、実装してみた記事になります。
やりたかったこと
以下のようなコードで特定のクラス内などでサクッとRefinementsが使いたかった。
class A using refine_method(Integer, :inc, ->() {self + 1}) def inc puts 41.inc end end A.new.inc # => 42
実装
で、実際の実装は以下のような感じです。
module RefineKernel refine Kernel do def refine_method(klass, method_id, expr) Module.new{ refine klass do define_method method_id.to_sym, expr end } end end end using RefineKernel class A using refine_method(Integer, :inc, ->() {self + 1}) def inc puts 41.inc end end A.new.inc # => 42
やってること
module RefineKernel # Kernel で refine_method が使えるようにする refine Kernel do def refine_method(klass, method_id, expr) Module.new{ refine klass do define_method method_id.to_sym, expr end } end end end
Kernel
で refine_method
というメソッドが使えるようにしています。
またrefine_method
は引数として klass
、method_id
、expr
を受け取ります。
klass
はRefinementsでメソッドを追加したいクラスを引数として渡しており、method_id
が追加するメソッド名、expr
がメソッドの実装となっています。
あとはusing RefineKernel
すれば、以下のようにRefinementsを使ってメソッドを追加し、利用することができます。
using RefineKernel using refine_method(Integer, :inc, ->() {self + 1}) p 41.inc # => 42
ちなみに以下のような感じでも動作するので意外と便利かも……?
using refine_method(Array, :pow, ->(num = 2){ self.map{|v| v ** num }}) ary = [1, 2, 3] puts ary.pow # => [1, 4, 9] puts ary.pow(3) # => 1, 8, 27 using refine_method(Object, :xbox, ->(title = "HALO", *args){ puts title if args.size == 0 args.each do |arg| puts "#{title} #{arg}" end }) obj = Object.new obj.xbox # => HALO obj.xbox("HALO", "CE", 2, 3) # => HALO CE # => HALO 2 # => HALO 3 obj.xbox("Devil May Cry", "", "2", "4", "5") # => Devil May Cry # => Devil May Cry 2 # => Devil May Cry 4 # => Devil May Cry 5
ただ、一度に一つのメソッドしか定義できないのが不満かな。 そこらへんはいい感じにHashオブジェクト内にSymbolとProcを格納してmapとかで回せばいいのかもしれないけど。