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

···

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

You just described subclasses:

class BigNozzleFiretruck < Firetruck

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

end

···

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?

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

http://trackmap.robotcoop.com

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. :slight_smile:

HTH,

Dan

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

···

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

Hi --

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 :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

···

On Fri, 14 Jul 2006, Tom Werner wrote:

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
Ruby for Rails => RUBY FOR RAILS (reviewed on
                                     Slashdot, 7/12/2006!)
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
dblack@wobblini.net => me

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

Tom Werner <tom@helmetstohardhats.org> 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.

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?

Not a solution, but:

   <http://rcrchive.net/rcr/show/321&gt;

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

(Trans, can I omit the `MyCut <' part?)

Cheers,
Daniel

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.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com

Or that. :slight_smile:

- Dan

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

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 Moore

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

···

-----Original Message-----
You just described subclasses:

class BigNozzleFiretruck < Firetruck

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

end

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 :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

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.

···

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

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 }

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.

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 }

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.

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

···

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

Tom Werner <tom@helmetstohardhats.org> 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

Daniel Schierbeck wrote:

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?

Not a solution, but:

   <http://rcrchive.net/rcr/show/321&gt;

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

(Trans, can I omit the `MyCut <' part?)

Sure. Although, the name provides a handle if you need to dynamically
effect the cut later --just like any Ruby class. But you can of course
do it annonymously too:

  Cut.new(Firetruck) do
    ...
  end

And if you make a module you can "preclude" (in contrast to include)

  class FireTruck
    preclude BigFireTruck
  end

T.

Daniel Schierbeck wrote:

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?

Not a solution, but:

  <http://rcrchive.net/rcr/show/321&gt;

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

(Trans, can I omit the `MyCut <' part?)

Cheers,
Daniel

I looked at that RCR a while back, but only glanced over it as it was not applicable to my situation at the time. Looking at it again, I see the beauty and power in it. I would love to see that end up in 2.0!

Tom

···

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

dblack@wobblini.net wrote:

Hi --

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 :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

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

···

On Fri, 14 Jul 2006, Tom Werner wrote:

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

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