object_pub_sub 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in object_pub_sub.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 John Wilger
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # ObjectPubSub
2
+
3
+ ObjectPubSub is an implementation of the Observer pattern for Ruby that
4
+ provides finer-grained controll over message passing than does the
5
+ Observable module from Ruby's stdlib. The including class can publish
6
+ multiple event types, and the subscribers can specify both which event
7
+ types to listen for and what method on the subscriber should be invoked
8
+ for each event type.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'object_pub_sub'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install object_pub_sub
23
+
24
+ ## Usage
25
+
26
+ In the class that will be publishing events (the "Observable"), simply
27
+ include `ObjectPubSub` and then publish events where appropriate:
28
+
29
+ class UserValidator
30
+ include ObjectPubSub
31
+
32
+ attr_reader :user, :errors
33
+
34
+ def initialize(user)
35
+ @user = user
36
+ @errors = {}
37
+ end
38
+
39
+ def validate
40
+ validate_username #def exluded for brevity
41
+ validate_password #def exluded for brevity
42
+ validate_email #def exluded for brevity
43
+
44
+ notify_subscribers
45
+ end
46
+
47
+ private
48
+
49
+ def notify_subscribers
50
+ if errors.any?
51
+ publish_event(:invalid, errors)
52
+ else
53
+ publish_event(:valid, user)
54
+ end
55
+ end
56
+ end
57
+
58
+ Then, objects that care about those events can subscribe to them:
59
+
60
+ class ValidationLogger
61
+ def initialize(name, logger = nil)
62
+ @logger = logger || Logger.new(STDERR)
63
+ @name = name
64
+ end
65
+
66
+ def log_validation_error(errors)
67
+ logger.warn("Validation errors on #{name}: #{errors}")
68
+ end
69
+ end
70
+
71
+ class CreateUser
72
+ def initialize(logger = nil)
73
+ @logger = logger
74
+ end
75
+
76
+ def perform(user_attrs)
77
+ user = User.new(attrs)
78
+ validation_logger = ValidationLogger.new("User creation", logger)
79
+ validator = UserValidator.new(user)
80
+ validator.add_subscriber(self, :valid => :handle_valid_user, :invalid => :handle_invalid_user)
81
+ validator.add_subscriber(validation_logger, :invalid => :log_validation_error)
82
+ validator.validate
83
+ end
84
+
85
+ def handle_valid_user(user)
86
+ # ...
87
+ end
88
+
89
+ def handle_invalid_user(errors)
90
+ # ...
91
+ end
92
+ end
93
+
94
+ CreateUser.new \
95
+ .perform(:username => 'foo', :email => 'foo@example.com', :password => 'foopassword')
96
+
97
+ See the examples under `/spec` for more information.
98
+
99
+ ## Contributing
100
+
101
+ 1. Fork it
102
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
103
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
104
+ 4. Push to the branch (`git push origin my-new-feature`)
105
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require 'bundler/setup'
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task :default => :spec
@@ -0,0 +1,3 @@
1
+ module ObjectPubSub
2
+ VERSION = "1.0.0"
3
+ end
@@ -0,0 +1,75 @@
1
+ require 'active_support/concern'
2
+
3
+ module ObjectPubSub
4
+ extend ActiveSupport::Concern
5
+
6
+ def add_subscriber(*args)
7
+ object_pub_sub_subscriber_lists.add_subscriber(*args)
8
+ end
9
+
10
+ private
11
+
12
+ def publish_event(*args)
13
+ object_pub_sub_subscriber_lists.publish_event(*args)
14
+ end
15
+
16
+ def object_pub_sub_subscriber_lists
17
+ @object_pub_sub_subscriber_lists ||= SubscriberListManager.new
18
+ end
19
+
20
+ class SubscriberListManager
21
+ class SubscriberList
22
+ def initialize
23
+ @subscribers = []
24
+ end
25
+
26
+ def add_subscriber(subscriber, callback_method)
27
+ callback_pair = [subscriber, callback_method]
28
+ return if @subscribers.include?(callback_pair)
29
+ @subscribers << callback_pair
30
+ end
31
+
32
+ def publish_event(data = nil)
33
+ @subscribers.each do |subscriber, callback_method|
34
+ subscriber.send(callback_method, data)
35
+ end
36
+ end
37
+ end
38
+
39
+ def initialize
40
+ @subscriber_lists ||= {}
41
+ end
42
+
43
+ def add_subscriber(subscriber, *event_names)
44
+ callback_map(event_names).each do |event_name, callback_method|
45
+ subscriber_list_for(event_name).add_subscriber(subscriber, callback_method)
46
+ end
47
+ end
48
+
49
+ def publish_event(event_name, data = nil)
50
+ subscriber_list_for(event_name).publish_event(data)
51
+ end
52
+
53
+ private
54
+
55
+ def callback_map(event_names)
56
+ callback_map = {}
57
+ last_one = event_names.pop
58
+ if last_one.kind_of?(Hash)
59
+ callback_map.merge!(last_one)
60
+ else
61
+ callback_map[last_one] = last_one
62
+ end
63
+ event_names.each do |event_name|
64
+ callback_map[event_name] = event_name
65
+ end
66
+ callback_map
67
+ end
68
+
69
+ def subscriber_list_for(event_name)
70
+ @subscriber_lists.fetch(event_name) do
71
+ @subscriber_lists[event_name] = SubscriberList.new
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'object_pub_sub/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "object_pub_sub"
8
+ gem.version = ObjectPubSub::VERSION
9
+ gem.authors = ["John Wilger"]
10
+ gem.email = ["johnwilger@gmail.com"]
11
+ gem.description = %q{An Observer implementation with finer-grained controll over event publishing.}
12
+ gem.summary = %q{An Observer implementation with finer-grained controll over event publishing.}
13
+ gem.homepage = "http://github.com/jwilger/object_pub_sub"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency 'activesupport', '>= 3.0.0'
21
+ gem.add_development_dependency 'rspec'
22
+ end
@@ -0,0 +1,94 @@
1
+ require 'object_pub_sub'
2
+
3
+ describe ObjectPubSub do
4
+ describe "instance methods added to including class" do
5
+ it "forwards subscription requests to the object's subscriber list" do
6
+ subscriber_lists.should_receive(:add_subscriber) \
7
+ .with(subscriber, :event_a, :event_b, :event_c, :and_so_on)
8
+ publisher.add_subscriber(subscriber, :event_a, :event_b, :event_c, :and_so_on)
9
+ end
10
+
11
+ it "publishes events to the object's subscriber list" do
12
+ subscriber_lists.should_receive(:publish_event) \
13
+ .with(:did_a_thing, :some_data)
14
+ publisher.do_a_thing
15
+ end
16
+
17
+ let(:subscriber_lists) { stub('subscriber_lists') }
18
+ let(:subscriber) { stub('subscriber') }
19
+
20
+ let(:publisher) {
21
+ klass = Class.new do
22
+ include ObjectPubSub
23
+
24
+ def do_a_thing
25
+ publish_event(:did_a_thing, :some_data)
26
+ end
27
+ end
28
+
29
+ klass.new.tap do |p|
30
+ p.stub!(:object_pub_sub_subscriber_lists => subscriber_lists)
31
+ end
32
+ }
33
+ end
34
+
35
+ describe ObjectPubSub::SubscriberListManager do
36
+ it "distributes published events to all subscribers" do
37
+ subscriber_a = stub('subscriber_a')
38
+ subscriber_b = stub('subscirber_b')
39
+
40
+ subject.add_subscriber(subscriber_a, :event_a)
41
+ subject.add_subscriber(subscriber_b, :event_a)
42
+
43
+ subscriber_a.should_receive(:event_a)
44
+ subscriber_b.should_receive(:event_a)
45
+ subject.publish_event(:event_a)
46
+ end
47
+
48
+ it "does not distribute an event to subscribers that do not subscribe to that event" do
49
+ subject.add_subscriber(subscriber, :event_b)
50
+
51
+ subscriber.should_receive(:event_a).never
52
+ subject.publish_event(:event_a)
53
+ end
54
+
55
+ it "only notifies a subscriber once even if it subscribes multiple times" do
56
+ subject.add_subscriber(subscriber, :event_a)
57
+ subject.add_subscriber(subscriber, :event_a)
58
+
59
+ subscriber.should_receive(:event_a).once
60
+ subject.publish_event(:event_a)
61
+ end
62
+
63
+ it "allows a subscriber to subscribe to multiple events" do
64
+ subject.add_subscriber(subscriber, :event_a, :event_b)
65
+
66
+ subscriber.should_receive(:event_a)
67
+ subject.publish_event(:event_a)
68
+
69
+ subscriber.should_receive(:event_b)
70
+ subject.publish_event(:event_b)
71
+ end
72
+
73
+ it "can publish data along with an event" do
74
+ subject.add_subscriber(subscriber, :event_a)
75
+
76
+ subscriber.should_receive(:event_a).with(:some_data)
77
+ subject.publish_event(:event_a, :some_data)
78
+ end
79
+
80
+ it "allows the subscriber to specify a custom callback method for some events" do
81
+ subject.add_subscriber(subscriber, :event_b, :event_a => :handle_event_a)
82
+
83
+ subscriber.should_receive(:handle_event_a)
84
+ subject.publish_event(:event_a)
85
+
86
+ subscriber.should_receive(:event_b)
87
+ subject.publish_event(:event_b)
88
+ end
89
+
90
+ let(:subject) { ObjectPubSub::SubscriberListManager.new }
91
+
92
+ let(:subscriber) { stub('subscriber') }
93
+ end
94
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: object_pub_sub
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Wilger
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-04 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: An Observer implementation with finer-grained controll over event publishing.
47
+ email:
48
+ - johnwilger@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - lib/object_pub_sub.rb
59
+ - lib/object_pub_sub/version.rb
60
+ - object_pub_sub.gemspec
61
+ - spec/object_pub_sub_spec.rb
62
+ homepage: http://github.com/jwilger/object_pub_sub
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ segments:
75
+ - 0
76
+ hash: 4599717495677016490
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ! '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ segments:
84
+ - 0
85
+ hash: 4599717495677016490
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 1.8.23
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: An Observer implementation with finer-grained controll over event publishing.
92
+ test_files:
93
+ - spec/object_pub_sub_spec.rb