简体   繁体   中英

ruby monkey patching on the fly

Is there a way to implement monkey patching while an object is being instantiated?

When I call:

a = Foo.new

Prior to the instance being instantiated, I would like to extend the Foo class based on information which I will read from a data store. As such, each time I call Foo.new , the extension(s) that will be added to that instance of the class would change dynamically.

tl;dr: Adding methods to an instance is possible.

Answer: Adding methods to an instance is not possible. Instances in Ruby don't have methods. But each instance can have a singleton class, where one can add methods, which will then be only available on the single instance that this singleton class is made for.

class Foo
end

foo = Foo.new

def foo.bark
  puts "Woof"
end
foo.bark

class << foo
  def chew
    puts "Crunch"
  end
end
foo.chew

foo.define_singleton_method(:mark) do
  puts "Widdle"
end
foo.mark

are just some of the ways to define a singleton method for an object.

module Happy
  def cheer
    puts "Wag"
  end
end
foo.extend(Happy)
foo.cheer

This takes another approach, it will insert the module between the singleton class and the real class in the inheritance chain. This way, too, the module is available to the instance, but not on the whole class.

Sure you can!

method_name_only_known_at_runtime = 'hello'
string_only_known_at_runtime = 'Hello World!'

test = Object.new
test.define_singleton_method(method_name_only_known_at_runtime) do
  puts(string_only_known_at_runtime)
end

test.hello
#> Hello World!

Prior to the instance being instantiated, I would like to extend

Given a class Foo which does something within its initialize method:

class Foo
  attr_accessor :name

  def initialize(name)
    self.name = name
  end
end

And a module FooExtension which wants to alter that behavior:

module FooExtension
  def name=(value)
    @name = value.reverse.upcase
  end
end

You could patch it via prepend :

module FooPatcher
  def initialize(*)
    extend(FooExtension) if $do_extend # replace with actual logic
    super
  end
end

Foo.prepend(FooPatcher)

Or you could extend even before calling initialize by providing your own new class method:

class Foo
  def self.new(*args)
    obj = allocate
    obj.extend(FooExtension) if $do_extend # replace with actual logic
    obj.send(:initialize, *args)
    obj
  end
end

Both variants produce the same result:

$do_extend = false

Foo.new('hello')
#=> #<Foo:0x00007ff66582b640 @name="hello">

$do_extend = true

Foo.new('hello')
#=> #<Foo:0x00007ff66582b280 @name="OLLEH">

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM