[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Preferred monkeypatching technique

Tom Werner

7/13/2006 11:28:00 PM

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?

Tom

--
Tom Werner
Helmets to Hardhats
Software Developer
tom@helmetstohardhats.org
www.helmetstohardhats.org


56 Answers

Eric Hodel

7/13/2006 11:46:00 PM

0

On Jul 13, 2006, at 4:28 PM, 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?

You just described subclasses:

class BigNozzleFiretruck < Firetruck

def put_out_fire(options = {})
options = options.merge :nozzle => :big
super options
end

end

--
Eric Hodel - drbrain@segment7.net - http://blog.se...
This implementation is HODEL-HASH-9600 compliant

http://trackmap.rob...



Daniel Berger

7/13/2006 11:50:00 PM

0

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?
>
> Tom
>
Perhaps add a namespace?

# monkey.rb
class Foo
def monkey
"hello"
end

def test
"test"
end
end

module Test
class Foo < ::Foo
def monkey
"world"
end
end
end

foo1 = Foo.new
foo2 = Test::Foo.new

foo1.monkey # "hello"
foo2.monkey # "world"
foo1.test # "test"
foo2.test # "test"

Hm, this thread actually gives me more fodder for my previous idea of
having objects store aliases for later reference. We could then emit a
warning if you define an alias that already exists. :)

HTH,

Dan

Sean O'Halpin

7/13/2006 11:56:00 PM

0

On 7/14/06, Tom Werner <tom@helmetstohardhats.org> 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?
>
> Tom
>
> --
> Tom Werner
> Helmets to Hardhats
> Software Developer
> tom@helmetstohardhats.org
> www.helmetstohardhats.org
>

Here's one way to do it:

class Firetruck
def put_out_fire(options = {})
p [1, self.class, options]
# code
end
end

class Firetruck
# get ref to (unbound) old instance method
old_put_out_fire = instance_method(:put_out_fire)
define_method :put_out_fire do |options|
p [2, self.class, options]
options ||= { }
old_put_out_fire.bind(self).call(options.merge({:nozzle => :big}))
end
end

f = Firetruck.new
f.put_out_fire :colour => :red

__END__
[2, Firetruck, {:colour=>:red}]
[1, Firetruck, {:nozzle=>:big, :colour=>:red}]

Note that the method rebinding happens every time you call the method
so incurs a fair bit of overhead.

Regards,
Sean

Daniel Berger

7/13/2006 11:58:00 PM

0

Eric Hodel wrote:
> On Jul 13, 2006, at 4:28 PM, 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?
>
> You just described subclasses:
>
> class BigNozzleFiretruck < Firetruck
>
> def put_out_fire(options = {})
> options = options.merge :nozzle => :big
> super options
> end
>
> end
>
> --Eric Hodel - drbrain@segment7.net - http://blog.se...
> This implementation is HODEL-HASH-9600 compliant
>
> http://trackmap.rob...
>
>
>
>
Or that. :)

- Dan

Tom Werner

7/14/2006 12:04:00 AM

0

Daniel Berger wrote:
>
> Perhaps add a namespace?
>

Eric Hodel wrote:
>
> You just described subclasses:
>

Well, the idea is that the 3rd party software is going to be using
whatever classes it has always used, so just creating a subclass doesn't
get me anywhere because the 3rd party app (i.e. Rails) doesn't give a
hoot about my BigNozzleFiretruck.

Similarly, creating a separate namespace via a module has the same
problem. I can do that all day long but the 3rd party app won't care (or
is there a way to MAKE it care?).

It occurs to me that the use of GUIDs might be in order:

alias_method :put_out_fire_baee44c012cb11dbac5d0800200c9a66, :put_out_fire

Ugly but functional?

Tom

--
Tom Werner
Helmets to Hardhats
Software Developer
tom@helmetstohardhats.org
www.helmetstohardhats.org


Ceol

7/14/2006 12:41:00 AM

0

> -----Original Message-----
> You just described subclasses:
>
> class BigNozzleFiretruck < Firetruck
>
> def put_out_fire(options = {})
> options = options.merge :nozzle => :big
> super options
> end
>
> end

Sounded to me that perhaps he was talking about the problems that I normally
associate with dependency injection* - he's trying to get an existing class
to create an object of a new class, rather than the thing that it normally
uses. In this case, I think what he's saying is that the existing class is
going to instantiate a Firetruck; having BigNozzleFiretruck exist as a
possibility of an object that could be instantiated doesn't address the
whole problem.

Do articles like
http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionI...
address the sort of thing you're talking about, Tom?

- James Moore

* A phrase that, unfortunately, I use with wild abandon and less meaning
than perhaps I should.


dblack

7/14/2006 12:50:00 AM

0

Daniel DeLorme

7/14/2006 1:03:00 AM

0

Tom Werner wrote:
> Allow me to present a scenario:
>
> class Firetruck
> def put_out_fire(options = {})
> # code
> end
> end

Here's what I'd do to keep it clean:

module FastFiretruck
def initialize(*args)
super
self.extend FastFiretruck::Obj
end
module Obj
def put_out_fire(options = {})
super({:fast => "please!"}.merge(options))
end
end
end
Firetruck.class_eval{ include FastFiretruck }

Daniel

Hal E. Fulton

7/14/2006 3:33:00 AM

0

dblack@wobblini.net wrote:
>
> 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:

Actually it was only last week that I first saw this term
being used in the Ruby community. I wonder if it's too
late to squash it?


Hal

Logan Capaldo

7/14/2006 3:54:00 AM

0


On Jul 13, 2006, at 9:03 PM, Daniel DeLorme wrote:

> Tom Werner wrote:
>> Allow me to present a scenario:
>> class Firetruck
>> def put_out_fire(options = {})
>> # code
>> end
>> end
>
> Here's what I'd do to keep it clean:
>
> module FastFiretruck
> def initialize(*args)
> super
> self.extend FastFiretruck::Obj
> end
> module Obj
> def put_out_fire(options = {})
> super({:fast => "please!"}.merge(options))
> end
> end
> end
> Firetruck.class_eval{ include FastFiretruck }
>
> Daniel
>

And I thought the blah = instance_method closure trick was cool, but
this beats the pants off it.

Note that you can use

Firetruck.send(:include, FastFiretruck)

or in 1.9 Firetruck.funcall(:include, FastFiretruck) to bypass the
need for a block.