rubymonkeypatchingrefinements

Refinements and namespaces


Trying to patch net/http and have it only apply to one service class. Refinements seem to be the way to go. The monkey patch below works but the refinement doesn't. Is this a namespace issue? The project is on ruby 2.3.0 but have tried with 2.4.1 as well and only the monkey patch seems to get applied.

With a monkey patch:

module Net
  class HTTPGenericRequest
    def write_header(sock, ver, path)
      puts "monkey patched!"
      # patch stuff...
    end
  end
end

Service.new.make_request
# monkey patched!

With a refinement:

module NetHttpPatch
  refine Net::HTTPGenericRequest do
    def write_header(sock, ver, path)
      puts "refined!"
      # patch stuff...
    end
  end
end

class Service
  using NetHttpPatch
end

Service.new.make_request
# :(

UPDATE:

This seems to be similar scope wise? Obviously more complex things are happening when net/http makes a request does it lose scope then?

module TimeExtension
  refine Fixnum do
    def hours
      self * 60
    end
  end
end

class Service
  using TimeExtension

  def one_hour
    puts 1.hours
  end
end

puts Service.new.one_hour
# 60

UPDATE UPDATE:

nvm, I see what's happening now :) have to keep your brain from mixing using up with how mixins work.

module TimeExtension
  refine Fixnum do
    def hours
      self * 60
    end
  end
end

class Foo
  def one_hour
    puts 1.hours
  end
end


class Service
  using TimeExtension

  def one_hour
    puts 1.hours
  end

  def another_hour
    Foo.new.one_hour
  end
end

puts Service.new.one_hour
# 60
puts Service.new.another_hour
# undefined method `hours' for 1:Fixnum (NoMethodError)

Solution

  • Is this a namespace issue?

    It is a scope issue. Refinements are lexically scoped:

    class Service
      using NetHttpPatch
      # Refinement is in scope here
    end
    
    # different lexical scope, Refinement is not in scope here
    
    class Service
      # another different lexical scope, Refinement is *not* in scope here!
    end
    

    Originally, there was only main::using, which was script-scoped, i.e. the Refinement was in scope for the entire remainder of the script. Module#using came later, it scopes the Refinement to the lexical class definition body.