BrainLikeADullPencil
BrainLikeADullPencil

Reputation: 11683

Event declarations

I got the following code from @avdi's Ruby Tapas episode for today:

module Eventful
  def self.included(other)
    other.extend(Macros)
  end
  def add_listener(listener)
    (@listeners ||= []) << listener
  end
  def notify_listeners(event, *args)
    (@listeners || []).each do |listener|
      listener.public_send("on_#{event}", *args)
    end
  end

  module Macros
    def event(name)
      module_eval(%Q{
        def #{name}(*args)
          notify_listeners(:#{name}, *args)
        end
      })
    end
  end
end

class Dradis
  include Eventful
  event :new_contact
end

class ConsoleListener
  def on_new_contact(direction, range)
    puts "DRADIS contact! #{range} kilometers, bearing #{direction}"
  end
end

dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)

I understand the concept of events and listeners and the observer pattern, but don't get how/why this syntax is working, and haven't seen it in any manuals. The class Dradis has this:

 event :new_contact

At first, I thought that event was a method and :new_contact was an argument so that I would call event on an instance of Dradis, something like:

dradis = Dradis.new
dradis.event

but instead, new_contact is called on an instance of Dradis like:

dradis = Dradis.new
dradis.add_listener(ConsoleListener.new)
dradis.new_contact(120, 23000)

and that triggers the event method in the Macro module.

Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?

Upvotes: 0

Views: 78

Answers (2)

Lukas Svoboda
Lukas Svoboda

Reputation: 588

Several separated features of ruby is used: In the line event :new_contact the "evnet" is class method (http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html#UE).

Usually class methods are defined by:

  class A
    def A.my_class_method
     #code
    end
  end
  A.my_class_method #executing class_method

or

  class A
    def <<self   #usefull when you want delare several class methods
      def A.my_class_method
        #code
      end
    end
  end
  A.my_class_method #executing class_method

In the code the method is included by the module Macros.

The key thing is, that (class method) event is dynamicaly creating new (instance) method (in this case new_contact) The name of the method is passed as argument to event). And this method providing calling of the listener.

Can anyone explain why it works like this? calling :new_contact on an instance dradis to trigger the event method?

This is by the dynammically created method new_contact which is calling notify_listeners(:#{name}, *args)

Upvotes: 1

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230561

I didn't watch the episode, but look, it's right there.

  module Macros
    def event(name)
      module_eval(%Q{
        def #{name}(*args)
          notify_listeners(:#{name}, *args)
        end
      })
    end
  end

event is a method which defines another method (new_contact) which calls notify_listeners from Eventful.

and that triggers the event method in the Macro module

Incorrect. That method has finished its work a long time ago and it doesn't get invoked again. It produced a new method using module_eval / def and that new method (new_contact) is what's getting called.

It's important to understand that event method runs only once, when the Dradis class is parsed and loaded. It does not get run on every instantiation of Dradis.

Upvotes: 1

Related Questions