observable_roles 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cb3f4e951c750d292221ff30110a7b1b444643bc
4
+ data.tar.gz: 2911b86876b569632a45d353af8b52d6cf167ae0
5
+ SHA512:
6
+ metadata.gz: 7d42563121dd2e1ab84df31cc6401ff9d199daa755673ac7f5b0e7203d0117a93fa171a50f06df87dc065632bc074f8ca2c529eb39a9c49a3ed579a80709604c
7
+ data.tar.gz: 626fae1055853d4c904f04f647b0284681bb9c03f6762c9d20ca26c49168de5323f5cf95c94687158bfd4500df84d96039c805a31cd07070f41bf95fb231828c
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0"
10
+ gem "jeweler", "~> 2.0.1"
11
+ end
12
+
13
+ group :test do
14
+ gem 'rspec'
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,64 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ builder (3.2.2)
6
+ descendants_tracker (0.0.3)
7
+ diff-lcs (1.2.5)
8
+ faraday (0.9.0)
9
+ multipart-post (>= 1.2, < 3)
10
+ git (1.2.6)
11
+ github_api (0.11.3)
12
+ addressable (~> 2.3)
13
+ descendants_tracker (~> 0.0.1)
14
+ faraday (~> 0.8, < 0.10)
15
+ hashie (>= 1.2)
16
+ multi_json (>= 1.7.5, < 2.0)
17
+ nokogiri (~> 1.6.0)
18
+ oauth2
19
+ hashie (2.0.5)
20
+ highline (1.6.21)
21
+ jeweler (2.0.1)
22
+ builder
23
+ bundler (>= 1.0)
24
+ git (>= 1.2.5)
25
+ github_api
26
+ highline (>= 1.6.15)
27
+ nokogiri (>= 1.5.10)
28
+ rake
29
+ rdoc
30
+ json (1.8.1)
31
+ jwt (0.1.11)
32
+ multi_json (>= 1.5)
33
+ mini_portile (0.5.2)
34
+ multi_json (1.9.0)
35
+ multi_xml (0.5.5)
36
+ multipart-post (2.0.0)
37
+ nokogiri (1.6.1)
38
+ mini_portile (~> 0.5.0)
39
+ oauth2 (0.9.3)
40
+ faraday (>= 0.8, < 0.10)
41
+ jwt (~> 0.1.8)
42
+ multi_json (~> 1.3)
43
+ multi_xml (~> 0.5)
44
+ rack (~> 1.2)
45
+ rack (1.5.2)
46
+ rake (10.1.1)
47
+ rdoc (4.1.1)
48
+ json (~> 1.4)
49
+ rspec (2.14.1)
50
+ rspec-core (~> 2.14.0)
51
+ rspec-expectations (~> 2.14.0)
52
+ rspec-mocks (~> 2.14.0)
53
+ rspec-core (2.14.8)
54
+ rspec-expectations (2.14.5)
55
+ diff-lcs (>= 1.1.3, < 2.0)
56
+ rspec-mocks (2.14.6)
57
+
58
+ PLATFORMS
59
+ ruby
60
+
61
+ DEPENDENCIES
62
+ bundler (~> 1.0)
63
+ jeweler (~> 2.0.1)
64
+ rspec
data/LICENSE.txt ADDED
@@ -0,0 +1 @@
1
+ Use however you want, I don't care.
data/README.markdown ADDED
@@ -0,0 +1,111 @@
1
+ Observable Roles
2
+ ===============================================================================
3
+ Thread safe implementation of the Observable pattern which also supports roles.
4
+
5
+
6
+ ## Installation
7
+
8
+ gem install observable_roles
9
+
10
+
11
+ ## Usage
12
+
13
+ Let's start with an example. First, create two classes:
14
+
15
+ class DummySubscriber
16
+ include ObservableRoles::Subscriber
17
+ end
18
+
19
+ class DummyPublisher
20
+ include ObservableRoles::Publisher
21
+ end
22
+
23
+ And now create objects out of them and connect them:
24
+
25
+ subscriber = DummySubscriber.new
26
+ publisher = DummyPublisher.new
27
+ publisher.role = :kitty
28
+ publisher.subscribe(subscriber)
29
+
30
+ Let's try triggering an event:
31
+
32
+ publisher.publish_event(:myau, "saying myau")
33
+
34
+ And nothing is going to happen. Why? Because `subscriber` cannot yet handle kitty events.
35
+ He knows nothing about kittens, so even though he is subscribed to that kitty, he ignores it.
36
+ Let's help him learn:
37
+
38
+ class DummySubscriber
39
+ set_observed_publisher_callbacks(
40
+ kitty: { myau: -> (me, data) { puts data }}
41
+ )
42
+ end
43
+
44
+ `me` in this case is a reference to the object which you might or might not need and `data`
45
+ holds some arbitrary data that an event passes (in this case, a String). Now let's see if it works:
46
+
47
+ publisher.publish_event(:myau, "saying myau") # => saying myau
48
+
49
+ You can also control which subscriber are getting notified from the publisher itself. Of course,
50
+ the publisher doesn't need to who the subscribers are, but may simply rely on some characteristics,
51
+ essentially using polymorphism. Suppose we only want women to be notified when a kitten myaus, because
52
+ men are too busy hunting. We'll then do this:
53
+
54
+
55
+ class DummyPublisher
56
+ include ObservableRoles::Publisher
57
+ end
58
+
59
+ class DummySubscriber
60
+ attr_accessor :gender
61
+ include ObservableRoles::Subscriber
62
+ set_observed_publisher_callbacks(
63
+ kitty: { myau: -> (me, data) { puts "I'm a #{me.gender}, I hear kitten said #{data}" }}
64
+ )
65
+ end
66
+
67
+ subscriber1 = DummySubscriber.new
68
+ subscriber2 = DummySubscriber.new
69
+ publisher = DummyPublisher.new
70
+ publisher.role = :kitty
71
+ subscriber1.gender = 'female'
72
+ subscriber2.gender = 'male'
73
+
74
+ publisher.subscribe(subscriber1)
75
+ publisher.subscribe(subscriber1)
76
+
77
+ publisher.publish_event(:myau, "saying myau") { |subscriber| subscriber.gender == 'female' }
78
+
79
+ This will result in only output:
80
+
81
+ I'm a female, I hear kitten said myau
82
+
83
+
84
+ Both of these examples can be found in `/examples`.
85
+
86
+ ## The following will be a short, but somewhat deeper explanation of the implementation
87
+
88
+ You have two objects: one is a Subscriber, another one is a Publisher.
89
+ You subscribe a Subscriber to the Publisher events with a Publisher#subscribe.
90
+ However the Subscriber would still ignore anything that Publisher publishes.
91
+
92
+ In order for it to be notified of the events, you must define callbacks with
93
+ Subscriber.set_observed_publisher_callbacks. These callbacks have the following form:
94
+
95
+ role_name: { event_name: -> (me, data) { } }
96
+
97
+ where `me` is a reference to the Subscriber object and `data` is a hash of some info
98
+ that is passed from the Publisher.
99
+
100
+ Now `role_name` is a role of the Pubslisher, which can be set as follows:
101
+
102
+ publisher.role = :good_cop
103
+
104
+ Obviously, each role may have many different events and those events may come from various
105
+ publishers who play the same role. This approach is more flexible than the standard Observer pattern,
106
+ since it allows easy many-to-many relationship to be established.
107
+
108
+ ## Thread safety
109
+
110
+ Each new event that has a callback doesn't execute this callback immediately after said event is caught.
111
+ Instead, it is added into a queue of events, which are then executed one by one. This ensures that each event callback execution doesn't interfere with the other.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
17
+ gem.name = "observable_roles"
18
+ gem.homepage = "http://github.com/snitko/observable_roles"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Thread-safe Observable pattern implementation with a queue}
21
+ gem.description = %Q{Thread-safe Observable pattern implementation with a queue}
22
+ gem.email = "roman.snitko@gmail.com"
23
+ gem.authors = ["Roman Snitko"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,20 @@
1
+ require_relative '../lib/observable_roles'
2
+
3
+ class DummySubscriber
4
+ include ObservableRoles::Subscriber
5
+ set_observed_publisher_callbacks(
6
+ kitty: { myau: -> (me, data) { puts data }}
7
+ )
8
+ end
9
+
10
+ class DummyPublisher
11
+ include ObservableRoles::Publisher
12
+ end
13
+
14
+ subscriber = DummySubscriber.new
15
+ publisher = DummyPublisher.new
16
+ publisher.role = :kitty
17
+ publisher.subscribe(subscriber)
18
+
19
+
20
+ publisher.publish_event(:myau, "saying myau")
@@ -0,0 +1,25 @@
1
+ require_relative '../lib/observable_roles'
2
+
3
+ class DummyPublisher
4
+ include ObservableRoles::Publisher
5
+ end
6
+
7
+ class DummySubscriber
8
+ attr_accessor :gender
9
+ include ObservableRoles::Subscriber
10
+ set_observed_publisher_callbacks(
11
+ kitty: { myau: -> (me, data) { puts "I'm a #{me.gender}, I hear kitten said #{data}" }}
12
+ )
13
+ end
14
+
15
+ subscriber1 = DummySubscriber.new
16
+ subscriber2 = DummySubscriber.new
17
+ publisher = DummyPublisher.new
18
+ publisher.role = :kitty
19
+ subscriber1.gender = 'female'
20
+ subscriber2.gender = 'male'
21
+
22
+ publisher.subscribe(subscriber1)
23
+ publisher.subscribe(subscriber2)
24
+
25
+ publisher.publish_event(:myau, "saying myau") { |subscriber| subscriber.gender == 'female' }
@@ -0,0 +1,76 @@
1
+ module ObservableRoles
2
+
3
+ module Subscriber
4
+
5
+ module ClassMethods
6
+ def set_observed_publisher_callbacks(callbacks)
7
+ @observed_publisher_callbacks = callbacks
8
+ end
9
+ def get_observed_publisher_callbacks
10
+ @observed_publisher_callbacks
11
+ end
12
+ end
13
+
14
+ def self.included(base)
15
+ attr_accessor :subscriber_lock
16
+ attr_reader :captured_observable_events
17
+ base.extend(ClassMethods)
18
+ end
19
+
20
+ def capture_observable_event(role, event_name, data={})
21
+ return if role.nil? || event_name.nil?
22
+ role = role.to_sym
23
+ event_name = event_name.to_sym
24
+ if self.class.get_observed_publisher_callbacks.nil? || self.class.get_observed_publisher_callbacks[role].nil? || self.class.get_observed_publisher_callbacks[role][event_name].nil?
25
+ return
26
+ end
27
+
28
+ @captured_observable_events ||= []
29
+ @captured_observable_events.push({ callback: self.class.get_observed_publisher_callbacks[role][event_name], data: data })
30
+ release_captured_events unless @subscriber_lock
31
+ end
32
+
33
+
34
+ private
35
+
36
+ def release_captured_events
37
+ @subscriber_lock = true
38
+ while !@captured_observable_events.empty?
39
+ e = @captured_observable_events.shift
40
+ e[:callback].call(self, e[:data])
41
+ end
42
+ @subscriber_lock = false
43
+ end
44
+
45
+ end
46
+
47
+
48
+ module Publisher
49
+
50
+ def self.included(base)
51
+ attr_accessor :role
52
+ end
53
+
54
+ def subscribe(s)
55
+ @observing_subscriber = [] unless @observing_subscriber
56
+ @observing_subscriber << s
57
+ end
58
+
59
+ def unsubscribe(s)
60
+ unless @observing_subscriber.blank?
61
+ @observing_subscriber.delete(s)
62
+ end
63
+ end
64
+
65
+ def publish_event(event_name, data={})
66
+ return unless @observing_subscriber
67
+ @observing_subscriber.each do |s|
68
+ if !block_given? || yield(s)
69
+ s.capture_observable_event(role, event_name, data)
70
+ end
71
+ end
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,53 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "observable_roles"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Roman Snitko"]
12
+ s.date = "2014-03-14"
13
+ s.description = "Thread-safe Observable pattern implementation with a queue"
14
+ s.email = "roman.snitko@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.markdown"
18
+ ]
19
+ s.files = [
20
+ "Gemfile",
21
+ "Gemfile.lock",
22
+ "LICENSE.txt",
23
+ "README.markdown",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "examples/example1.rb",
27
+ "examples/example2.rb",
28
+ "lib/observable_roles.rb",
29
+ "observable_roles.gemspec",
30
+ "spec/observable_roles_spec.rb"
31
+ ]
32
+ s.homepage = "http://github.com/snitko/observable_roles"
33
+ s.licenses = ["MIT"]
34
+ s.require_paths = ["lib"]
35
+ s.rubygems_version = "2.0.3"
36
+ s.summary = "Thread-safe Observable pattern implementation with a queue"
37
+
38
+ if s.respond_to? :specification_version then
39
+ s.specification_version = 4
40
+
41
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
42
+ s.add_development_dependency(%q<bundler>, ["~> 1.0"])
43
+ s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
44
+ else
45
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
46
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
47
+ end
48
+ else
49
+ s.add_dependency(%q<bundler>, ["~> 1.0"])
50
+ s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
51
+ end
52
+ end
53
+
@@ -0,0 +1,56 @@
1
+ require File.dirname(__FILE__) + '/../lib/observable_roles'
2
+
3
+ class DummySubscriber
4
+
5
+ attr_accessor :dumbness_level
6
+
7
+ include ObservableRoles::Subscriber
8
+
9
+ set_observed_publisher_callbacks(
10
+ kitty: { myau: -> (me, data) { data.myau }}
11
+ )
12
+
13
+ end
14
+
15
+ class DummyPublisher
16
+ include ObservableRoles::Publisher
17
+ end
18
+
19
+ describe ObservableRoles do
20
+
21
+ before(:each) do
22
+ @subscriber = DummySubscriber.new
23
+ @publisher = DummyPublisher.new
24
+ @publisher.role = :kitty
25
+ @publisher.subscribe(@subscriber)
26
+ end
27
+
28
+ it "executes a callback when an event is published" do
29
+ kitty_mock = double()
30
+ kitty_mock.should_receive(:myau).once
31
+ @publisher.publish_event(:myau, kitty_mock)
32
+ end
33
+
34
+ it "puts callbacks in a queue and executes them one by one until the queue is empty" do
35
+ kitty_mock = double()
36
+ kitty_mock.should_receive(:myau).exactly(3).times
37
+ @subscriber.subscriber_lock = true
38
+ 3.times { @publisher.publish_event(:myau, kitty_mock) }
39
+ @subscriber.subscriber_lock = false
40
+ @subscriber.captured_observable_events.should have(3).items
41
+ @subscriber.send(:release_captured_events)
42
+ @subscriber.captured_observable_events.should have(0).items
43
+ end
44
+
45
+ it "published event only to certain filtered subscribers" do
46
+ subscriber2 = DummySubscriber.new
47
+ @publisher.subscribe(subscriber2)
48
+ subscriber2.dumbness_level = 7
49
+ @subscriber.dumbness_level = 3
50
+
51
+ kitty_mock = double()
52
+ kitty_mock.should_receive(:myau).once
53
+ @publisher.publish_event(:myau, kitty_mock) { |subscriber| subscriber.dumbness_level < 5 }
54
+ end
55
+
56
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: observable_roles
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Roman Snitko
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jeweler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.1
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.1
41
+ description: Thread-safe Observable pattern implementation with a queue
42
+ email: roman.snitko@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files:
46
+ - LICENSE.txt
47
+ - README.markdown
48
+ files:
49
+ - Gemfile
50
+ - Gemfile.lock
51
+ - LICENSE.txt
52
+ - README.markdown
53
+ - Rakefile
54
+ - VERSION
55
+ - examples/example1.rb
56
+ - examples/example2.rb
57
+ - lib/observable_roles.rb
58
+ - observable_roles.gemspec
59
+ - spec/observable_roles_spec.rb
60
+ homepage: http://github.com/snitko/observable_roles
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - '>='
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ required_rubygems_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ requirements: []
79
+ rubyforge_project:
80
+ rubygems_version: 2.0.3
81
+ signing_key:
82
+ specification_version: 4
83
+ summary: Thread-safe Observable pattern implementation with a queue
84
+ test_files: []