Permalink
Browse files

Bringing publisher into atomicobject.rb project

git-svn-id: http://atomicobjectrb.rubyforge.org/svn/trunk/publisher@19 7bd720c3-caf9-44c9-a48d-612f81e63739
  • Loading branch information...
dcrosby42
dcrosby42 committed Nov 21, 2007
0 parents commit bdbcc94d87dd47b289ac203386ea4d7b903be126
Showing with 592 additions and 0 deletions.
  1. +3 −0 History.txt
  2. +7 −0 Manifest.txt
  3. +75 −0 README.txt
  4. +27 −0 Rakefile
  5. +73 −0 lib/publisher.rb
  6. +29 −0 sample_code/synopsis.rb
  7. +358 −0 test/publisher_test.rb
  8. +20 −0 test/test_helper.rb
@@ -0,0 +1,3 @@
+== 1.1.0 / 2007-11-21
+
+* Moved publisher out of internal AO repository into atomicobject.rb project.
@@ -0,0 +1,7 @@
+History.txt
+Manifest.txt
+README.txt
+Rakefile
+lib/publisher.rb
+test/publisher_test.rb
+test/test_helper.rb
@@ -0,0 +1,75 @@
+publisher
+* http://rubyforge.org/projects/atomicobjectrb/
+* http://atomicobjectrb.rubyforge.org/constructor
+
+== DESCRIPTION:
+
+publisher is a module for extending a class with event subscription and firing capabilities. This is helpful for implementing objects that participate in the Observer design pattern.
+
+== FEATURES/PROBLEMS:
+
+* Nice syntax for declaring events that can be subscribed for
+* Convenient event firing syntax
+* Subscribe / unsubscribe functionality
+* Several method name aliases give you the flexibility to make your code more readable (eg, *fire*, *notify*, *emit*)
+
+== SYNOPSIS:
+
+ require 'rubygems'
+ require 'publisher'
+
+ class CustomerListHolder
+ extend Publisher
+ can_fire :customer_added, :list_cleared
+
+ def add_customer(cust)
+ (@list ||= []) << cust
+ fire :customer_added, cust
+ end
+
+ def clear_list
+ @list.clear
+ fire :list_cleared
+ end
+ end
+
+ holder = CustomerListHolder.new
+ holder.when :customer_added do |cust|
+ puts "Customer added! #{cust}"
+ end
+ holder.when :list_cleared do
+ puts "All gone."
+ end
+
+ holder.add_customer("Croz")
+ holder.add_customer("Matt")
+ holder.clear_list
+
+== INSTALL:
+
+* sudo gem install publisher
+
+== LICENSE:
+
+(The MIT License)
+
+Copyright (c) 2007 Atomic Object
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,27 @@
+require 'rubygems'
+require 'hoe'
+require './lib/publisher.rb'
+require 'rake'
+require 'rake/testtask'
+
+task :default => [ :test ]
+
+desc "Run the unit tests in test"
+Rake::TestTask.new("test") { |t|
+ t.libs << "test"
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+}
+
+Hoe.new('publisher', Publisher::VERSION) do |p|
+ p.rubyforge_name = 'atomicobjectrb'
+ p.author = 'Atomic Object'
+ p.email = 'dev@atomicobject.com'
+ p.summary = 'Event subscription and firing mechanism'
+ p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n")
+ p.url = p.paragraphs_of('README.txt', 1).first.gsub(/\* /,'').split(/\n/)
+# p.url = p.paragraphs_of('README.txt', 1).first.split(/\n/)[1..-1]
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
+end
+
+# vim: syntax=Ruby
@@ -0,0 +1,73 @@
+# See README.txt for synopsis
+module Publisher
+ VERSION = "1.1.0" #:nodoc:#
+
+ # Use this method (or one of the aliases) to declare which events you support
+ # Once invoked, your class will have the neccessary supporting methods for subscribing and firing.
+ def has_events(*args)
+ include InstanceMethods unless @published_events
+ @published_events ||= []
+ @published_events << args
+ @published_events.flatten!
+ end
+ alias :has_event :has_events
+ alias :can_fire :has_events
+
+ # Use this method to allow subscription and firing of arbitrary events.
+ # This is convenient if, eg, your class has dynamic event names.
+ # Don't use this unless you have to; it's better to declare your events if you
+ # can.
+ def has_any_event
+ include InstanceMethods unless @published_events
+ @published_events = :any_event_is_ok
+ end
+ alias :can_fire_anything :has_any_event
+
+ # Container for the instance methods that will be mixed-in to extenders of Publisher.
+ # These methods get mixed in when you use the 'has_events' call.
+ module InstanceMethods
+ # Sign up a code block to be executed when an event is fired.
+ # It's important to know the signature of the event, as your proc needs
+ # to accept incoming parameters accordingly.
+ def subscribe(event, &block)
+ ensure_valid event
+ @subscriptions ||= {}
+ listeners = @subscriptions[event]
+ listeners ||= []
+ listeners << block
+ @subscriptions[event] = listeners
+ end
+ alias :when :subscribe
+ alias :on :subscribe
+
+ # Unsubscribe for an event. 'listener' is a reference to the object who enacted the
+ # subscription... often, this is 'self'. If this object has subsribed more than once
+ # for the given event (unusual), all of the subscriptions will be removed.
+ def unsubscribe(event, listener)
+ ensure_valid event
+ if @subscriptions && @subscriptions[event]
+ @subscriptions[event].delete_if do |block|
+ eval('self',block.binding).equal?(listener)
+ end
+ end
+ end
+
+ protected
+ # Fire an event with 0 or more outbound parameters
+ def fire(event, *args) #:nod
+ ensure_valid event
+ listeners = @subscriptions[event] if @subscriptions
+ listeners.each do |l| l.call(*args) end if listeners
+ end
+ alias :emit :fire
+ alias :notify :fire
+
+ # Does nothing if the current class supports the named event.
+ # Raises RuntimeError otherwise.
+ def ensure_valid(event) #:nodoc:#
+ events = self.class.class_eval { @published_events }
+ return if events == :any_event_is_ok
+ raise "Event '#{event}' not available" unless events and events.include?(event)
+ end
+ end
+end
@@ -0,0 +1,29 @@
+require 'rubygems'
+require 'publisher'
+
+class CustomerListHolder
+ extend Publisher
+ can_fire :customer_added, :list_cleared
+
+ def add_customer(cust)
+ (@list ||= []) << cust
+ fire :customer_added, cust
+ end
+
+ def clear_list
+ @list.clear
+ fire :list_cleared
+ end
+end
+
+holder = CustomerListHolder.new
+holder.when :customer_added do |cust|
+ puts "Customer added! #{cust}"
+end
+holder.when :list_cleared do
+ puts "All gone."
+end
+
+holder.add_customer("Croz")
+holder.add_customer("Matt")
+holder.clear_list
Oops, something went wrong.

0 comments on commit bdbcc94

Please sign in to comment.