Ruby / Problems / If you override method, pp will break
From WhyNotWiki
Contrived example:
irb -> 'foo'.method(:inspect)
=> #<Method: String#inspect>
irb -> class String; def method; rand<0.5 ? 'get' : 'post'; end; end
=> nil
irb -> 'foo'.method
=> "get"
irb -> 'foo'.method
=> "post"
irb -> pp 'foo'
ArgumentError: wrong number of arguments (1 for 0)
from /usr/lib/ruby/1.8/pp.rb:258:in `method'
from /usr/lib/ruby/1.8/pp.rb:258:in `pretty_print'
from /usr/lib/ruby/1.8/pp.rb:140:in `pp'
from /usr/lib/ruby/1.8/prettyprint.rb:201:in `group'
from /usr/lib/ruby/1.8/prettyprint.rb:227:in `nest'
from /usr/lib/ruby/1.8/prettyprint.rb:200:in `group'
from /usr/lib/ruby/1.8/prettyprint.rb:212:in `group_sub'
from /usr/lib/ruby/1.8/prettyprint.rb:199:in `group'
from /usr/lib/ruby/1.8/pp.rb:140:in `pp'
from /usr/lib/ruby/1.8/pp.rb:77:in `pp'
from /usr/lib/ruby/1.8/pp.rb:119:in `guard_inspect_key'
from /usr/lib/ruby/1.8/pp.rb:77:in `pp'
from /usr/lib/ruby/1.8/pp.rb:60:in `pp'
from /usr/lib/ruby/1.8/pp.rb:59:in `each'
from /usr/lib/ruby/1.8/pp.rb:59:in `pp'
from (irb):14
That's because pretty_print is defined thusly in /usr/lib/ruby/1.8/pp.rb :
def pretty_print(q)
if /\(Kernel\)#/ !~ method(:inspect).inspect
q.text self.inspect
elsif /\(Kernel\)#/ !~ method(:to_s).inspect && instance_variables.empty?
q.text self.to_s
else
q.pp_object(self)
end
end
That happened to me in real life in a Rails application when I tried to do this from a controller:
pp request
Because, of course, ActionController::AbstractRequest defines the method method in terms of an HTTP request (get, post, delete, etc.).
This seems to me a bit too dangerous, that we're allowed to casually and easily override method only to run into an unusual run-time error later on due to the redefined method method!
Once a method is overridden, can you still access the original method? No, not unless you've explicitly created an alias to the method before redefining it (and who has the foresight to do that??).
It would be nice if you always had access to every version of a method that an object has ever had. Maybe you could look it up by module/class name. For example, if I wanted my Request object to use the "original" version of method during the portion of my code in which I called pp, then I could do something like this:
request.use_method_version(:method, from = Kernel) do
pp request
end
That's not currently possible. [Correction: it is.]
Maybe one could remove_method the method, re-mix-in the original module (Kernel), call the method that needs the original method, and then remove_method the method again and re-mix-in the last module?? Even if that would work (and I kind of doubt it), it would be very messy.
Or maybe all built-in/core methods could have a shadow method, like _method or _inspect, that is always available. The main problem with that is that you'd have to make sure that everywhere that wants the original implementation is calling the shadow method and not the normal method -- something that's nearly impossible to ensure.
Wow! I just discovered how to do it ... by looking at the source for PP, of all the unlikely places! PP defines this interesting method:
def PP.mcall(obj, mod, meth, *args, &block)
mod.instance_method(meth).bind(obj).call(*args, &block)
end
When I saw that, I was like "Wait a second! That does exactly what I'm trying to do here!" It calls the method implementation from the module of your choice on the object of your choice.
So all I had to do was change this line:
if /\(Kernel\)#/ !~ method(:inspect).inspect
to this line:
if /\(Kernel\)#/ !~ Kernel.instance_method(:method).bind(self).call(:inspect).inspect
and my test case (pp 'foo') started working!
Kind of ironic that the authors of PP knew about this trick but they didn't think to use it in their implementation of pretty_print.
This is the final version of the patched method (2 lines were changed) [Patches (category)]:
/usr/lib/ruby/1.8/pp.rb
def pretty_print(q)
if /\(Kernel\)#/ !~ Kernel.instance_method(:method).bind(self).call(:inspect).inspect
q.text self.inspect
elsif /\(Kernel\)#/ !~ Kernel.instance_method(:method).bind(self).call(:to_s).inspect && instance_variables.empty?
q.text self.to_s
else
q.pp_object(self)
end
end
(Also, for reference (in case you wonder what that regexp comparison is all about):
irb -> 'foo'.method(:inspect)
=> #<Method: String#inspect>
irb -> Kernel.method(:inspect)
=> #<Method: Module(Kernel)#inspect>
irb -> Class.method(:inspect)
=> #<Method: Class(Kernel)#inspect>
irb -> Array.method(:inspect)
=> #<Method: Class(Kernel)#inspect>
irb -> puts String.instance_methods.sort.map { |m| 'foo'.method(m) }.select { |m| m.inspect =~ /Kernel/ }
#<Method: String(Kernel)#===>
#<Method: String(Kernel)#__id__>
#<Method: String(Kernel)#__send__>
#<Method: String(Kernel)#class>
#<Method: String(Kernel)#clone>
#<Method: String(Kernel)#display>
#<Method: String(Kernel)#dup>
#<Method: String(Kernel)#equal?>
#<Method: String(Kernel)#extend>
#<Method: String(Kernel)#freeze>
#<Method: String(Kernel)#frozen?>
#<Method: String(Kernel)#gem>
#<Method: String(Kernel)#id>
#<Method: String(Kernel)#instance_eval>
#<Method: String(Kernel)#instance_of?>
#<Method: String(Kernel)#instance_variable_get>
#<Method: String(Kernel)#instance_variable_set>
#<Method: String(Kernel)#instance_variables>
#<Method: String(Kernel)#is_a?>
#<Method: String(Kernel)#kind_of?>
#<Method: String(Kernel)#method>
#<Method: String(Kernel)#methods>
#<Method: String(Kernel)#nil?>
#<Method: String(Kernel)#object_id>
#<Method: String(Kernel)#pretty_inspect>
#<Method: String(Kernel)#private_methods>
#<Method: String(Kernel)#protected_methods>
#<Method: String(Kernel)#public_methods>
#<Method: String(Kernel)#require>
#<Method: String(Kernel)#require_gem>
#<Method: String(Kernel)#respond_to?>
#<Method: String(Kernel)#send>
#<Method: String(Kernel)#singleton_methods>
#<Method: String(Kernel)#taint>
#<Method: String(Kernel)#tainted?>
#<Method: String(Kernel)#type>
#<Method: String(Kernel)#untaint>
)
