observable_roles 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []