From: Tom Werner Date: 2006-07-15T06:48:56+09:00 Subject: Re: Preferred monkeypatching technique dblack@wobblini.net wrote: > Hi -- > > On Fri, 14 Jul 2006, Tom Werner wrote: > >> Allow me to present a scenario: >> >> class Firetruck >> def put_out_fire(options = {}) >> # code >> end >> end >> >> Pretend Firetruck is in a 3rd party application (like Rails) that is >> happy to allow plugins to modify core code. Now, let's say I want to >> write some code that always adds a certain attribute to the options >> hash. I could do this: >> >> class Firetruck >> alias_method :__old_put_out_fire, :put_out_fire >> def put_out_fire(options = {}) >> __old_put_out_fire(options.merge({:nozzle => :big})) >> end >> end >> >> Which works just fine until someone else comes up with a plugin that >> wants to modify the same method (doing something similar to me) and >> just so happens to also use :__old_put_out_fire as THEIR alias. Now >> we've got my plugin's method as the alias calling itself, which leads >> to, you know, badness. >> >> So I'm wondering if there's a better way. Perhaps some way to turn >> Firetruck into an ancestor of itself, so to speak, so that my plugin >> would create a new Firetruck class, pushing the old Firetruck >> backward in the chain and allowing me to call super instead and >> preventing alias_method explosions. Or would that just end up causing >> more havoc? > > I don't know whether it qualifies as "monkeypatching" (I always > thought that meant doing something sloppy and ill-advised, which I > hope I'm not :-) but see if this helps: > > class FireTruck > def put_out_fire(options = {}) > p options > end > end > > FireTruck.class_eval do > m = instance_method(:put_out_fire) > define_method(:put_out_fire) do |options| > m.bind(self).call(options.merge({ :nozzle => "big" })) > end > end > > FireTruck.new.put_out_fire(1 => 2) > > # Now someone else uses the alias: > class FireTruck > alias old_put_out_fire put_out_fire > def put_out_fire(options = {}) > puts "New version!" > old_put_out_fire(options) > end > end > > FireTruck.new.put_out_fire(1 => 2) > > Output: > > {1=>2, :nozzle=>"big"} > New version! > {1=>2, :nozzle=>"big"} > > > David > This is a bit unrelated, but is there a way to give the block parameter of define_method a default (because otherwise you now have to send put_out_fire at least an empty hash)? define_method(:put_out_fire) do |options| m.bind(self).call(options.merge({ :nozzle => "big" })) end -- Tom Werner Helmets to Hardhats Software Developer tom@helmetstohardhats.org www.helmetstohardhats.org