Implementing Prototype’s Event.observe in Ruby – Take 2

I revisited my Event.observe proof of concept and tidied it up a bit with a little help from stackoverflow.com.

Here’s how it do

This here defines a method to determine which method is currently running. There’s a slicker way to do this in 1.9 and you can read about them both on stackoverflow.com.

module Kernel
 private
  def current_method_name
    caller[0] =~ /`([^']*)'/ and $1
  end
end

Here’s my actual observer class, named after it’s inspiration.

class Event

  def initialize
    @events = []
  end

  def observe object, method, callback
    handle   = Handle.new self, object, method, callback
    @events << handle
    handle
  end

  def delete handle
    @events.delete handle
  end

  def call object, method
    handle = Handle.new self, object, method
    @events.each do |e|
      e.call if e.shares_type_with 
    end
  end

end

And finally there's a basic handle class:

class Handle

  attr_reader :observer, :object, :method, :callback

  def initialize observer, object, method, callback = nil
    @observer  = observer
    @object    = object
    @method    = method
    @callback  = callback
  end

  def shares_type_with? handle
    object == handle.object && method == handle.method
  end

  def call
    callback.call
  end

  def stop_observing
    observer.delete self
  end
end

And here's how you use it, In the following example we want our "second" Test_Object to run it's test_method whenever we run the same method for our "first" object.

The important thing here is the "notify" method, you'll need to drop this method into whatever you'd like to be able to observe. If you wanted to be able to observe everything you could always loop through the methods, alias and override.

class Test_Object

  def initialize observer, label
    @observer = observer
    @label    = label
  end

  def notify method
    @observer.call self, method
  end

  def test_method
    puts "Calling => #{@label}"
    notify current_method_name 
  end

end

observer = Event.new

first    = Test_Object.new observer, "First"
second   = Test_Object.new observer, "Second"

h1  = observer.observe(
  first,
  "test_method",
  second.method :test_method
)

first.test_method

# Calling First
# Calling Second
#=> nil

And we can drop the handle by simply calling it's stop_observing method

h1.stop_observing
first.test_method

# Calling First
#=> nil

That's it.

Download the Proof of Concept!