Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby blocks - wrapping and customizing superclass methods (using define_method and blocks)

Imagine, you frequently need to wrap a set of methods from a super class inside some static code or apects but with few lines results/different for each method, for example, wrap update notification around array methods to get an observable array.

My first thought, I want a class level method to be used like:

class MyArray < Array
  extend ObserverHelper # defines :wrap_method

  wrap_notify(:[]=) do |index_or_range, *args|
    [self[index_or_range], super(index_or_range, *args)]
    # much nicer it would be to define
    # removed = self[index_or_range]
    # added = super(index_or_range, *args)
    # but this makes it even more complicated (suggestions welcome!)
  end
...
end

So far so good. How to implement :wrap_method now? My most promising approach reached this far. Does not work because of super:

require 'observer'

module ObserverHelper
  include Observable

  def wrap_notify(meth, &block)
    define_method meth,->(*args) {
      removed, added = instance_exec(*args, &block)
      changed
      notify_observers(removed, added)
      added
    }
  end
end

Errors:

  • If defined directly on array subclass: super called outside of method

  • If defined in an module and included to array: "super from singleton method that is defined to multiple classes is not supported; this will be fixed in 1.9.3 or later" or "Segmentation fault" if the method has the proper return value)

I searched some time but didn't find an advisable solution to change the binding of a closure, however, maybe I miss something or you can solve it without?

Rails does those things by evaluating strings, not sure yet If I would like to got this way. Performance is not yet important but will be. Thus, I need feedback about the different possibilities and performance issues.

Finally, I got two more verbose solutions running. First, pass the instance, no super and no self:

  wrap_observer(:[]=) do |instance, index_or_range, *args|
    [instance[index_or_range], instance.send(meth, index_or_range, *args)]
  end

The other one uses wrap_observer as instance_method:

  def []=(index_or_range, *args)
    wrap_observer do
      [self[index_or_range], super(index_or_range, *args)]
    end
  end

which I regard as preferable.

A solution for the DSL may be to define the methods without the wrapper and than iterate through the defined methods adding it.

Are there more ways to solve this performant and maintainable with my intended DSL?

like image 789
Micha Avatar asked Dec 07 '25 09:12

Micha


1 Answers

The only way to rebind a block is to define a method with it, which also allows to use super. So, you would want to define two methods -- something that can be done using modules:

module ObserverHelper
  def wrap_notify(meth, options={}, &block)
    override = Module.new do
        define_method(meth, &block)
    end
    notifier = Module.new do
        define_method meth do |*args|
            removed, added = super(*args)
            options ||= {}
            changed
            notify_observers(removed, added)
            after
        end
    end
    include override
    include notifier
  end
end
like image 156
sylvain.joyeux Avatar answered Dec 09 '25 00:12

sylvain.joyeux



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!