Ruby: automatically wrapping methods in event triggers

China☆狼群 提交于 2019-12-19 10:17:37

问题


Heres what I have/want:

module Observable
  def observers; @observers; end

  def trigger(event, *args)
    good = true
    return good unless (@observers ||= {})[event]
    @obersvers[event].each { |e| good = false and break unless e.call(self, args) }
    good
  end

  def on(event, &block)
    @obersvers ||= {}
    @obersvers[event] ||= []
    @observers[event] << block
  end

end

class Item < Thing
  include Observable
  def pickup(pickuper)
    return unless trigger(:before_pick_up, pickuper)

    pickuper.add_to_pocket self

    trigger(:after_pick_up, pickuper)
  end

  def drop(droper)
    return unless trigger(:before_drop, droper)

    droper.remove_from_pocket self

    trigger(:after_drop, droper)
  end

  # Lots of other methods
end

# How it all should work
Item.new.on(:before_pickup) do |item, pickuper| 
  puts "Hey #{pickuper} thats my #{item}"
  return false # The pickuper never picks up the object
end

While starting on trying to create a game in Ruby, I thought it would be great if it could be based all around Observers and Events. The problem is have to write all of these triggers seems to be a waste, as it seems like a lot of duplicated code. I feel there must be some meta programming method out there to wrap methods with functionality.

Ideal Sceanrio:

class CustomBaseObject
   class << self
     ### Replace with correct meta magic
     def public_method_called(name, *args, &block)
       return unless trigger(:before_+name.to_sym, args)
       yield block
       trigger(:after_+name.to_sym, args)
     end
     ###
   end
end

And then I have all of my object inherit from this Class.

I'm still new to Ruby's more advanced meta programming subjects, so any knowledge about this type of thing would be awesome.


回答1:


There are a several ways to do it with the help of metaprogramming magic. For example, you can define a method like this:

def override_public_methods(c)
  c.instance_methods(false).each do |m|
    m = m.to_sym
    c.class_eval %Q{
      alias #{m}_original #{m}
      def #{m}(*args, &block)
        puts "Foo"
        result = #{m}_original(*args, &block)
        puts "Bar"
        result
      end
    }
  end
end

class CustomBaseObject
  def test(a, &block)
    puts "Test: #{a}"
    yield
  end
end

override_public_methods(CustomBaseObject)

foo = CustomBaseObject.new
foo.test(2) { puts 'Block!' }
# => Foo
     Test: 2
     Block!
     Bar

In this case, you figure out all the required methods defined in the class by using instance_methods and then override them.

Another way is to use so-called 'hook' methods:

module Overrideable
  def self.included(c)
    c.instance_methods(false).each do |m|
      m = m.to_sym
      c.class_eval %Q{
        alias #{m}_original #{m}
        def #{m}(*args, &block)
          puts "Foo"
          result = #{m}_original(*args, &block)
          puts "Bar"
          result
        end
      }
    end
  end
end

class CustomBaseObject
  def test(a, &block)
    puts "Test: #{a}"
    yield
  end

  include Overrideable
end

The included hook, defined in this module, is called when you include that module. This requires that you include the module at the end of the class definition, because included should know about all the already defined methods. I think it's rather ugly :)



来源:https://stackoverflow.com/questions/8316228/ruby-automatically-wrapping-methods-in-event-triggers

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!