Ruby-on-Rails
  Home arrow Ruby-on-Rails arrow Page 4 - Exploring Iteration
Dev Articles Forums 
ADO.NET  
Apache  
ASP  
ASP.NET  
C#  
C++  
ColdFusion  
COM/COM+  
Delphi-Kylix  
Design Usability  
Development Cycles  
DHTML  
Embedded Tools  
Flash  
Graphic Design  
HTML  
IIS  
Interviews  
Java  
JavaScript  
MySQL  
Oracle  
Photoshop  
PHP  
Reviews  
Ruby-on-Rails  
SQL  
SQL Server  
Style Sheets  
VB.Net  
Visual Basic  
Web Authoring  
Web Services  
Web Standards  
XML  
Dedicated Servers  
Moblin 
JMSL Numerical Library 
IBM® developerWorks 
Sun Developer Network 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us Get Paid 
Request Media Kit
Contact Us 
Site Map 
Privacy Policy 
Support 
 USERNAME
 
 PASSWORD
 
 
  >>> SIGN UP!  
  Lost Password? 
RUBY-ON-RAILS

Exploring Iteration
By: O'Reilly Media
  • Search For More Articles!
  • Disclaimer
  • Author Terms
  • Rating: 5 stars5 stars5 stars5 stars5 stars / 1
    2007-03-15

    Table of Contents:
  • Exploring Iteration
  • 7.9 Looping Through Multiple Iterables in Parallel
  • 7.10 Hiding Setup and Cleanup in a Block Method
  • 7.11 Coupling Systems Loosely with Callbacks

  • Rate this Article: Poor Best 
      ADD THIS ARTICLE TO:
      Del.ici.ous Digg
      Blink Simpy
      Google Spurl
      Y! MyWeb Furl
    Email Me Similar Content When Posted
    Add Developer Shed Article Feed To Your Site
    Email Article To Friend
    Print Version Of Article
    PDF Version Of Article
     
     
    ADVERTISEMENT


    Exploring Iteration - 7.11 Coupling Systems Loosely with Callbacks


    (Page 4 of 4 )

    Problem

    You want to combine different types of objects without hardcoding them full of references to each other.

    Solution

    Use a callback system, in which objects register code blocks with each other to be executed as needed. An object can call out to its registered callbacks when it needs something, or it can send notification to the callbacks when it does something.

    To implement a callback system, write a “register” or “subscribe” method that accepts a code block. Store the registered code blocks asProcobjects in a data structure: probably an array (if you only have one type of callback) or a hash (if you have multiple types). When you need to call the callbacks, iterate over the data structure andcalleach of the registered code blocks.

    Here’s a mixin module that gives each instance of a class its own hash of “listener” callback blocks. An outside object can listen for a particular event by callingsubscribewith the name of the event and a code block. The dispatcher itself is responsible for callingnotifywith an appropriate event name at the appropriate time, and the outside object is responsible for passing in the name of the event it wants to “listen” for.

      module EventDispatcher
        def setup_listeners
          @event_dispatcher_listeners = {}
        end

        def subscribe(event, &callback)
          (@event_dispatcher_listeners[event] ||= []) << callback
        end

        protected
        def notify(event, *args)
          if @event_dispatcher_listeners[event]
            @event_dispatcher_listeners[event].each do |m|
              m.call(*args) if m.respond_to? :call
           
    end
          end
          return nil
       
    end
      end

    Here’s aFactoryclass that keeps a set of listeners. An outside object can choose to be notified every time aFactoryobject is created, or every time aFactory object produces a widget:

      class Factory
        include EventDispatcher

        def initialize
          setup_listeners
        end

        def produce_widget(color)
          #Widget creation code goes here...

          notify(:new_widget, color)
        end
      end

    Here’s a listener class that’s interested in what happens withFactory objects:

      class WidgetCounter
       
    def initialize(factory)
          @counts = Hash.new(0)
          factory.subscribe(:new_widget) do |color|
           
    @counts[color] += 1
            puts #{@counts[color]} #{color} widget(s) created since I started watching.
          end
        end
      end

    Finally, here’s the listener in action:

      f1 = Factory.new
      WidgetCounter.new(f1)
      f1.produce_widget("red")
      # 1 red widget(s) created since I started watching.

      f1.produce_widget("green")
      # 1 green widget(s) created since I started watching.

      f1.produce_widget("red")
      # 2 red widget(s) created since I started watching.

      # This won't produce any output, since our listener is listening to
      # another Factory.
      Factory.new.produce_widget("blue")

    Discussion

    Callbacks are an essential technique for making your code extensible. This technique has many names (callbacks, hook methods, plugins, publish/subscribe, etc.) but no matter what terminology is used, it’s always the same. One object asks another to call a piece of code (the callback) when some condition is met. This technique works even when the two objects know almost nothing about each other. This makes it ideal for refactoring big, tightly integrated systems into smaller, loosely coupled systems.

    In a pure listener system (like the one given in the Solution), the callbacks set up lines of communication that always move from the event dispatcher to the listeners. This is useful when you have a master object (like theFactory), from which numerous lackey objects (like theWidgetCounter) take all their cues.

    But in many loosely coupled systems, information moves both ways: the dispatcher calls the callbacks and then uses the return results. Consider the stereotypical web portal: a customizable homepage full of HTML boxes containing sports scores, weather predictions, and so on. Since new boxes are always being added to the system, the core portal software shouldn’t have to know anything about a specific box. The boxes should also know as little about the core software as possible, so that changing the core doesn’t require a change to all the boxes.

    A simple change to theEventDispatcherclass makes it possible for the dispatcher to use the return values of the registered callbacks. The original implementation ofEventDispatcher#notify called the registered code blocks, but ignored their return value. This version ofEventDispatcher#notifyyields the return values to a block passed in tonotify:

      module EventDispatcher
       
    def notify(event, *args)
            if @event_dispatcher_listeners[event]
            @event_dispatcher_listeners[event].each do |m|
             
    yield(m.call(*args)) if m.respond_to? :call
            
    end
          end
          return nil
        end
      end

    Here’s an insultingly simple portal rendering engine. It lets boxes register to be rendered inside an HTML table, on one of two rows on the portal page:

      class Portal
        include EventDispatcher

        def initialize
          setup_listeners
        end

        def render
          puts '<table>'
          render_block = Proc.new { |box| puts " <td>#{box}</td>" }
          [:row1, :row2].each do |row|
           
    puts ' <tr>'
            notify(row, &render_block)
            puts ' </tr>'
         
    end
        puts '</table>'
        end
     
    end

    Here’s the rendering engine rendering a specific user’s portal layout. This user likes to see a stock ticker and a weather report on the left, and a news box on the right. Note that there aren’t even any classes for these boxes; they’re so simple they can be implemented as anonymous code blocks:

      portal = Portal.new
      portal.subscribe(:row1) { 'Stock Ticker' }
      portal.subscribe(:row1) { 'Weather' }
      portal.subscribe(:row2) { 'Pointless, Trivial News' }
     
    portal.render
      # <table>
      #  <tr>
      #   <td>Stock Ticker</td>
      #   <td>Weather</td>
      #  </tr>
      #  <tr>
      #   <td>Pointless, Trivial News</td>
      #  </tr>
      # </table>

    If you want the registered listeners to be shared across all instances of a class, you can makelistenersa class variable, and makesubscribea module method. This is most useful when you want listeners to be notified whenever a new instance of the class is created.

     


     

    * In Ruby 1.9, a block can itself take a block argument: |arg1, arg2, &block|. This makes methods like
    Module#define_method more useful. In Ruby 2.0, you’ll be able to give default values to block arguments.

    † Someone could argue that a block isn’t really a closure if it never actually uses any of the context it carries around: you could have done the same job with a “dumb” block, assuming Ruby supported those. For simplicity’s sake, we do not argue this.

    * The name lambda comes from the lambda calculus (a mathematical formal system) via Lisp.

    * Of course, behind the scenes, your method could just create an appropriate Enumerator and call its collect implemenation.

    * But your code will be more maintainable if you do HTML with templates instead of writing it in Ruby code.


    DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware.

       · This article is an excerpt from the book "Ruby Cookbook," published by O'Reilly. We...
     

    Buy this book now. This article is excerpted from chapter eight of the Ruby Cookbook, written by Lucas Carlson and Leonard Richardson (O'Reilly, 2006; ISBN: 0596523696). Check it out today at your favorite bookstore. Buy this book now.

    RUBY-ON-RAILS ARTICLES

    - Iterating and Incrementing Strings in Ruby
    - Comparing and Manipulating Strings in Ruby
    - Strings in Ruby
    - Ruby On Rails: Making Your First Dynamic Site
    - Ruby on Rails: Beginning Rails
    - Ruby: Modules, Mixins, Fixins, and Rails
    - Controlling Information Access with the Rail...
    - URLs, Filters and the Rails Action Controller
    - Flash and the Rails Action Controller
    - Rails Action Controller
    - Dropping and Sorting with AJAX and script.ac...
    - Drag and Drop with script.aculo.us and Rails
    - Introducing script.aculo.us
    - Ruby Classes and Objects
    - Ruby Loops







    © 2003-2008 by Developer Shed. All rights reserved. DS Cluster 1 hosted by Hostway