Preferred monkeypatching technique

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

On Jul 13, 2006, at 4:28 PM, Tom W. wrote:

want to write some code that always adds a certain attribute to the
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 H. - [email protected] - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

Tom W. wrote:

write some code that always adds a certain attribute to the options
wants to modify the same method (doing something similar to me) and
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. :slight_smile:

HTH,

Dan

On 7/14/06, Tom W. [email protected] wrote:

write some code that always adds a certain attribute to the options
wants to modify the same method (doing something similar to me) and just
Tom


Tom W.
Helmets to Hardhats
Software Developer
[email protected]
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 B. wrote:

Perhaps add a namespace?

Eric H. 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

Eric H. wrote:

Pretend Firetruck is in a 3rd party application (like Rails) that is

preventing alias_method explosions. Or would that just end up causing

end

–Eric H. - [email protected] - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

Or that. :slight_smile:

  • Dan

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/DependencyInjectionInRuby.rdoc
address the sort of thing you’re talking about, Tom?

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

Tom W. 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

Hi –

On Fri, 14 Jul 2006, Tom W. wrote:

that always adds a certain attribute to the options hash. I could do this:
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 :slight_smile: 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

[email protected] 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 :slight_smile: 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

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

module FastFiretruck
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.

fr hal:

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?

you took a vacation :wink:
http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/9699d659bd806203

kind regards -botp

Daniel DeLorme wrote:

end
Firetruck.class_eval{ include FastFiretruck }

That only works because there is no initialize method defined in
Firetruck. You might as well have done:

class Firetruck
def initialize(*args)
super
self.extend FastFiretruck
end
end

module FastFiretruck
def put_out_fire(options = {})
super({:fast => “please!”}.merge(options))
end
end

If you’re just after one method you can alway use Facets’
module#wrap_method. If you want to do aspecting (is that what this
“monkeypatching” is all about?), well I would love to see a Cut
implementation, but short of that, I’ve been working on
#instance_interception. It still has some kinks, but it essentially
works.

class Module

def instance_interception(&block)
@instance_interception ||= Module.new do
def self.append_features(mod)
append_features_without_instance_interception( mod )
end
end
@instance_interception.module_eval(&block) if block_given?
@instance_interception
end

private :instance_interception

alias_method :append_features_without_instance_interception,
:append_features

Append features

def append_features( mod )

aspect = instance_interception
aspect.__send__( :append_features_without_instance_interception,

mod )

aspect.instance_methods.each do |meth|
  if mod.method_defined?( meth )
    aspect.advise( mod, meth )
  end
end

append_features_without_instance_interception( mod )

#if mod.instance_of? Module
aspect.__send__( :append_features_without_instance_interception,

mod.send(:instance_interception) )
#end

end

Apply the around advice.

def advise( mod, meth )
advice = instance_method( meth )
instance_target = mod.instance_method(meth)
mod.send( :define_method, meth ) { |*args| #, &blk|
target = instance_target.bind( self )
(class << target; self; end).class_eval { define_method( :super
){ call( *args ) } }
advice.bind( self ).call( target, *args ) #, &blk )
}
end

If a method is added to the module/class that is advised.

def method_added( meth )
return if @method_added_short
if instance_interception.method_defined?( meth )
include instance_interception
@method_added_short = true
instance_interception.advise( self, meth )
@method_added_short = false
end
end

end

EXAMPLE

module A

def f ; “F” ; end
def g ; “G” ; end

instance_interception do
def f( target, *args, &blk )
‘{’ + target.super + ‘}’
end
def g( target, *args, &blk )
‘{’ + target.super + ‘}’
end
end

end

class X
def f ; super ; end
include A
def g ; super ; end
end

x = X.new
p x.f
p x.g

T.

On 7/13/06, Hal F. [email protected] wrote:

[email protected] 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 :slight_smile: 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?

I think the term “monkypatching” as applied to this context (the
ability to re-open existing classes in Ruby and define new methods (or
redefine/undefine/alias existing methods) began in a Pythonista blog
that was criticizing Ruby because this sort of thing is possible and
done fairly frequently.

Now it would appear that people are willing to wear this term ‘as a
badge’.

Did ‘duck typing’ have similar origins :wink:

Phil

On Jul 13, 2006, at 5:04 PM, Tom W. wrote:

Eric H. 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.

Can you give a concrete example where this takes place?


Eric H. - [email protected] - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

Daniel DeLorme wrote:

end
Firetruck.class_eval{ include FastFiretruck }

Hey, thanks Daniel! You actaully led me to a new potential approach.
Here, try this:

class Module
def monkey_patch(mod)
_new = method(:new)
_allocate = method(:allocate)
(class << self; self; end).class_eval do
define_method(:new) do |*args|
o = _new.call(*args)
o.extend mod
o
end
define_method(:allocate) do |*args|
o = _allocate.call(*args)
o.extend mod
o
end
end
end
end

EXAMPLE

class Firetruck
def put_out_fire(options = {})
“put out #{options}”
end
end

module FastFiretruck

def put_out_fire(options = {})
super({:fast => “please!”}.merge(options))
end
end

Firetruck.monkey_patch(FastFiretruck)

ft = Firetruck.new
p ft.put_out_fire

T.

Hi –

On Fri, 14 Jul 2006, Phil T. wrote:

I think the term “monkypatching” as applied to this context (the
ability to re-open existing classes in Ruby and define new methods (or
redefine/undefine/alias existing methods) began in a Pythonista blog
that was criticizing Ruby because this sort of thing is possible and
done fairly frequently.

Now it would appear that people are willing to wear this term ‘as a badge’.

The badge looks an awful lot like the criticism… :slight_smile:

Did ‘duck typing’ have similar origins :wink:

No, that was introduced as is by Dave T. to characterize an
approach to Ruby programming that sought harmony with the dynamic
conditions inherent in Ruby.

As for “monkeypatching”, I’m actually rooting to go in the opposite
direction: to reach a point where we don’t even perceive a difference
between programming and “metaprogramming”. That seems to me to be
what’s on offer from Ruby.

David

Tom W. [email protected] writes:

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

It seems to me that you’re asking for a ruby version of gensym. (See
any lisp reference that talks about lisp macro writing) So let’s try
to make something like that:

In the framework core

class Firetruck
def put_out_fire(options = {})
puts “Extinguishing with options #{options.inspect}”
end
end

In plugin1

class Symbol
def Symbol.gensym
@@gensym_count ||= 0
@@gensym_count += 1
(“gensym%X” % @@gensym_count).intern
end
end

class Firetruck
oldmethsym = Symbol.gensym
alias_method oldmethsym, :put_out_fire
class_eval %Q[
def put_out_fire(options = {})
#{oldmethsym}(options.merge({:nozzle => :big}))
end
]
end

Now, in plugin 2

Note that the same exact code is included for gensym, but

that this isn’t a problem

class Symbol
def Symbol.gensym
@@gensym_count ||= 0
@@gensym_count += 1
(“gensym%X” % @@gensym_count).intern
end
end

class Firetruck
oldmethsym = Symbol.gensym
alias_method oldmethsym, :put_out_fire
class_eval %Q[
def put_out_fire(options = {})
#{oldmethsym}(options.merge({:material => :foam}))
end
]
end


Now in irb:
irb(main):132:0> load “c:\temp\firetruck.rb”
=> true
irb(main):133:0> Firetruck.new.put_out_fire
Extinguishing with options {:nozzle=>:big, :material=>:foam}
=> nil

Now, what I’d really like for doing stuff like this is an easy ruby
equivalent of lisp’s quasi-quote, with the ability to nest levels of
quotation easily. As it is, %Q[] combined with an eval that takes a
string helps in many practical cases where you want something vaguely
like bits of lisp’s macro facility, but it’s cumbersome to write
things like macro-generating macros, for example.

On Jul 14, 2006, at 8:25 AM, Daniel M. wrote:

any lisp reference that talks about lisp macro writing) So let’s try

In plugin1

oldmethsym = Symbol.gensym

Neat idea, gensym in Ruby. I would suggest making it thread-safe though:

% cat gensym.rb
require ‘mutex’
class Symbol
GensymLock = Mutex.new
def self.gensym
GensymLock.synchronize {
@@gensym_count ||= 0
@@gensym_count += 1
(“gensym%X” % @@gensym_count).intern
}
end
end

And just require it whenever you need it.

require ‘gensym’
sym = Symbol.gensym
alias_method sym, :blah

Eric H. wrote:

Can you give a concrete example where this takes place?

My specific purpose is to write a plugin for Rails that adds an option
to each form field helper (the ones that create tags) that
specifies an appropriate class (since CSS attribute selector support is
lacking in IE). So I’m using code like this:

module ActionView
module Helpers
module FormHelper

  # text
  alias_method :__classless_text_field, :text_field
  def text_field(object_name, method, options = {})
    __classless_text_field(object_name, method,

options.merge({:class => (options[:class].to_s + ’ text’).strip}))
end

end

end
end

Which works fine, but is subject to the infinite loop problem should
someone else happen to alias in an identical manner. Even the
possibility of this happening makes me feel dirty, and so I’m looking
for a cleaner way to do it. As these helpers may be called quite often,
a solution that doesn’t badly impact performance is essential. There are
some other ideas on this thread that I still need to investigate, I was
just curious if there’s some standard practice for this sort of thing.
Thanks!!

Tom

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs