informator 0.1.0 → 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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/CHANGELOG.md +31 -0
- data/README.md +114 -46
- data/Rakefile +1 -1
- data/config/metrics/metric_fu.yml +2 -2
- data/config/metrics/rubocop.yml +4 -0
- data/informator.gemspec +12 -9
- data/lib/informator.rb +3 -73
- data/lib/informator/event.rb +35 -30
- data/lib/informator/publisher.rb +103 -0
- data/lib/informator/rspec.rb +3 -0
- data/lib/informator/subscriber.rb +14 -28
- data/lib/informator/version.rb +1 -1
- data/lib/rspec/shared.rb +104 -0
- data/spec/integration/en.yml +6 -0
- data/spec/integration/fr.yml +6 -0
- data/spec/integration/i18n.rb +21 -0
- data/spec/integration/rspec_spec.rb +51 -0
- data/spec/unit/informator/event_spec.rb +82 -28
- data/spec/unit/informator/publisher_spec.rb +168 -0
- data/spec/unit/informator/subscriber_spec.rb +26 -40
- metadata +59 -11
- data/lib/informator/reporter.rb +0 -67
- data/spec/unit/informator/reporter_spec.rb +0 -75
- data/spec/unit/informator_spec.rb +0 -153
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0de58e03a57880d1061c5810a877e69086c0b71f
|
4
|
+
data.tar.gz: 022a5537c463a1c746a02153d039a87ed9c88b18
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b9e8716e59f38f6a2a2e0bcad4238fcd6ea13ffd7dd6fc0ce67b76e7410200cc6dadcab8f99619c1cf4075b7bd99616a8e0b048209883eade6d1fe0f25b996b9
|
7
|
+
data.tar.gz: 88c7f671d6b646d0663aa285fccdb9f60ae7d2958ea5fedb288cd5f98b96b22b11cff1741e8eb3bbe0876c972f9948ef072618499f4c8858b72042ee21b478cc
|
data/.travis.yml
CHANGED
@@ -4,13 +4,19 @@ bundler_args: --without metrics
|
|
4
4
|
cache: bundler
|
5
5
|
script: bundle exec rake test:coverage:run
|
6
6
|
rvm:
|
7
|
+
- '1.9.3'
|
8
|
+
- '2.0'
|
7
9
|
- '2.1'
|
8
10
|
- '2.2'
|
9
11
|
- ruby-head
|
12
|
+
- rbx-2 --1.9
|
10
13
|
- rbx-2 --2.1
|
14
|
+
- rbx-head
|
15
|
+
- jruby
|
11
16
|
- jruby-9.0.0.0.pre1
|
12
17
|
- jruby-head
|
13
18
|
matrix:
|
14
19
|
allow_failures:
|
15
20
|
- rvm: ruby-head
|
21
|
+
- rvm: rbx-head
|
16
22
|
- rvm: jruby-head
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,34 @@
|
|
1
|
+
## v1.0.0 2015-07-14
|
2
|
+
|
3
|
+
The API has been rewritten to support immutability of all instances.
|
4
|
+
|
5
|
+
## Added
|
6
|
+
|
7
|
+
* `Informator::Publisher` base class for immutable publishers (nepalez).
|
8
|
+
* `Informator::Event#publisher` method to check the source of the event (nepalez).
|
9
|
+
* `Informator::Event#message` to provide human-readable description on the fly instead legacy `#messages` (nepalez).
|
10
|
+
* `Informator::Event#time` for the time of event creation (nepalez).
|
11
|
+
* All classes: `Publisher`, `Subscriber`, `Event` become comparable (nepalez).
|
12
|
+
* Support for rubies 1.9.3+ (was 2.1+ only) (nepalez).
|
13
|
+
|
14
|
+
## Changed
|
15
|
+
|
16
|
+
* `Informator::Publisher#publish` publishes the event immediately (nepalez).
|
17
|
+
* `Informator::Publisher#subscribe` returns a new publisher instead of mutating the existing one (nepalez).
|
18
|
+
|
19
|
+
## Deleted
|
20
|
+
|
21
|
+
* `Informator` legacy API (including the module doesn't supported any more) (nepalez).
|
22
|
+
* `Informator::Publisher#memoize` (nepalez).
|
23
|
+
* `Informator::Event#messages` (nepalez).
|
24
|
+
|
25
|
+
## Internal
|
26
|
+
|
27
|
+
* Drop internal `Informator::Reporter` class, because no memoization supported any longer (nepalez).
|
28
|
+
* 100% mutant covered (nepalez).
|
29
|
+
|
30
|
+
[Compare v0.1.0...v1.0.0](https://github.com/nepalez/informator/compare/v0.1.0...v1.0.0)
|
31
|
+
|
1
32
|
## v0.1.0 2015-07-12
|
2
33
|
|
3
34
|
### Changed (backward-incompatible)
|
data/README.md
CHANGED
@@ -15,82 +15,150 @@ Informator
|
|
15
15
|
[travis]: https://travis-ci.org/nepalez/informator
|
16
16
|
[inch]: https://inch-ci.org/github/nepalez/informator
|
17
17
|
|
18
|
-
The [wisper]-inspired
|
18
|
+
The [wisper]-inspired micro-gem, that implements publish/subscribe design pattern in immutable style.
|
19
19
|
|
20
|
-
|
20
|
+
**Notice a version `1.0` has been re-written from scratch to provide immutability. Its API has been changed.**
|
21
21
|
|
22
|
-
|
22
|
+
Motivation
|
23
|
+
----------
|
23
24
|
|
24
|
-
|
25
|
+
The `informator` differs from `wisper` in the following aspects:
|
25
26
|
|
26
|
-
*
|
27
|
+
* While `Wisper::Publisher#subscribe` mutates the state of the publisher, `Informator::Publisher` is immutable.
|
28
|
+
Its `#subscribe` method returns the new publisher instance to which a subscriber being added.
|
27
29
|
|
28
|
-
* The
|
30
|
+
* The `Wisper::Publisher#publish` calls various listener methods depending on the published event.
|
31
|
+
The `Informator::Publisher#publish` uses the same listener's callback for all published events. It sends the `Invormator::Event` with info about the name, the attributes, and the publisher of the event.
|
29
32
|
|
30
|
-
* The `Informator`
|
33
|
+
* The `Informator::Publisher` defines `#publish!` method that is the same as `#publish`, except for it throws the `:published` exception.
|
34
|
+
This can be used to stop execution when some events occurs.
|
35
|
+
|
36
|
+
* The `Informator` has no global neither async subscribers.
|
31
37
|
|
32
38
|
[wisper]: https://github.com/krisleech/wisper
|
33
39
|
|
34
|
-
|
35
|
-
|
40
|
+
API
|
41
|
+
---
|
42
|
+
|
43
|
+
The `Informator::Publisher` class describes immutable publishers with 6 instance methods:
|
36
44
|
|
37
|
-
|
45
|
+
* `#initialize` that takes a hash of attributes.
|
46
|
+
* `#attributes` that stores a hash of initialized attributes.
|
47
|
+
* `#subscribers` that returns an array of subscribers.
|
48
|
+
* `#subscribe` that returns a new immutable publisher with the same attributes and new subscriber being added.
|
49
|
+
* `#publish` to create and publish an event to all `#subscribers`.
|
50
|
+
* `#publish!` that calls `#publish` and then throws `:published` exception.
|
38
51
|
|
39
|
-
|
40
|
-
* `#remember` to build an event and keep it unpublished
|
41
|
-
* `#publish` to publish all events keeped since last publishing
|
42
|
-
* `#publish!` the same as `#publish`, except for it throws `:published` exception which carries a list of events
|
52
|
+
The `Informator::Event` class describes immutable events with 5 instance methods:
|
43
53
|
|
44
|
-
|
54
|
+
* `#publisher` for the source of the event.
|
55
|
+
* `#time` for event creation time.
|
56
|
+
* `#name` for symbolic name of the event.
|
57
|
+
* `#attributes` for hash of attributes, carried by the event.
|
58
|
+
* `#message` for the human-readable description of the event.
|
45
59
|
|
46
|
-
|
47
|
-
* `#attributes` for hash of attributes, carried by the event
|
48
|
-
* `#messages` for array of human-readable messages, describing the event
|
60
|
+
Both publishers and events instances are comparable (respond to `==` and `eql?`).
|
49
61
|
|
50
|
-
|
62
|
+
Synopsis
|
63
|
+
--------
|
64
|
+
|
65
|
+
Define the custom publisher, inherited from the `Informator::Publisher` base class:
|
51
66
|
|
52
67
|
```ruby
|
53
68
|
require "informator"
|
54
69
|
|
55
|
-
class
|
56
|
-
def
|
57
|
-
|
70
|
+
class MyPublisher < Informator::Publisher
|
71
|
+
def call
|
72
|
+
if attributes[:luck]
|
73
|
+
publish :success, exclamation: "Wow"
|
74
|
+
else
|
75
|
+
publish :error, exclamation: "OMG"
|
76
|
+
end
|
58
77
|
end
|
59
78
|
end
|
79
|
+
```
|
60
80
|
|
61
|
-
|
81
|
+
Provide translations for events:
|
62
82
|
|
63
|
-
|
64
|
-
|
83
|
+
```yaml
|
84
|
+
# config/locales/fr.yml
|
85
|
+
---
|
86
|
+
fr:
|
87
|
+
informator:
|
88
|
+
my_publisher:
|
89
|
+
success: "éditeur dit: %{exclamation}, succès!"
|
90
|
+
error: "éditeur dit: faute, %{exclamation}!"
|
91
|
+
```
|
65
92
|
|
66
|
-
|
67
|
-
catch(:published) { do_some_staff }
|
68
|
-
end
|
93
|
+
Provide a listener with a callback method:
|
69
94
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
95
|
+
```ruby
|
96
|
+
class MyListener
|
97
|
+
def listen(event)
|
98
|
+
puts event
|
99
|
+
puts event.message
|
75
100
|
end
|
76
101
|
end
|
77
102
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
# The listener received error: ["OMG!"]
|
83
|
-
# => [
|
84
|
-
# #<Informator::Event @type=:success @attributes={ foo: :bar } @messages=["for now all is fine"]>,
|
85
|
-
# #<Informator::Event @type=:error @attributes={ bar: :baz } @messages=["OMG!"]>
|
86
|
-
# ]
|
103
|
+
listener = MyListener.new
|
104
|
+
```
|
105
|
+
|
106
|
+
Subscribe the listener, and publish the event:
|
87
107
|
|
108
|
+
```ruby
|
109
|
+
MyPublisher
|
110
|
+
.new(luck: true)
|
111
|
+
.subscribe(listener, :listen)
|
112
|
+
.call
|
113
|
+
# => #<Informator::Event
|
114
|
+
# @publisher=#<MyPublisher @attributes={ :luck => true }>,
|
115
|
+
# @name=:success,
|
116
|
+
# @attributes={ exclamation: "Wow" }
|
117
|
+
# >
|
118
|
+
# => "éditeur dit: Wow, succès!"
|
119
|
+
```
|
120
|
+
|
121
|
+
```ruby
|
122
|
+
MyPublisher
|
123
|
+
.new
|
124
|
+
.subscribe(listener, :listen)
|
125
|
+
.call
|
126
|
+
# => #<Informator::Event
|
127
|
+
# @publisher=#<MyPublisher @attributes={}>,
|
128
|
+
# @name=:success,
|
129
|
+
# @attributes={ exclamation: "OMG" }
|
130
|
+
# >
|
131
|
+
# => "éditeur dit: faute, OMG!"
|
88
132
|
```
|
89
133
|
|
90
|
-
|
134
|
+
Testing
|
135
|
+
-------
|
136
|
+
|
137
|
+
Use `:publishing_event` shared examples (defined at `informator/rspec`) to specify publisher.
|
138
|
+
|
139
|
+
```ruby
|
140
|
+
require "informator/rspec"
|
91
141
|
|
92
|
-
|
93
|
-
|
142
|
+
describe MyPublisher do
|
143
|
+
|
144
|
+
let(:publisher) { described_class.new(attributes) }
|
145
|
+
let(:listener) { double callback => nil, freeze: nil }
|
146
|
+
let(:callback) { :call }
|
147
|
+
|
148
|
+
subject { publisher.subscribe(listener, callback).call }
|
149
|
+
|
150
|
+
it_behaves_like :publishing_event do
|
151
|
+
# configurable context
|
152
|
+
let(:attributes) { { luck: true } }
|
153
|
+
let(:locale) { :fr } # :en by default
|
154
|
+
|
155
|
+
# configurable expectations (can be skipped)
|
156
|
+
let(:event_name) { :success }
|
157
|
+
let(:event_attributes) { { exclamation: "Wow" } }
|
158
|
+
let(:event_message) { "éditeur dit: Wow, succès!" }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
```
|
94
162
|
|
95
163
|
Installation
|
96
164
|
------------
|
@@ -117,7 +185,7 @@ gem install informator
|
|
117
185
|
Compatibility
|
118
186
|
-------------
|
119
187
|
|
120
|
-
Tested under rubies [compatible to MRI
|
188
|
+
Tested under rubies [compatible to MRI 1.9.3+](.travis.yml).
|
121
189
|
|
122
190
|
Uses [RSpec] 3.0+ for testing and [hexx-suit] for dev/test tools collection.
|
123
191
|
|
data/Rakefile
CHANGED
data/config/metrics/rubocop.yml
CHANGED
data/informator.gemspec
CHANGED
@@ -3,24 +3,27 @@ require "informator/version"
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
|
6
|
-
gem.name
|
7
|
-
gem.version
|
8
|
-
gem.author
|
9
|
-
gem.email
|
10
|
-
gem.homepage
|
11
|
-
gem.summary
|
12
|
-
gem.license
|
6
|
+
gem.name = "informator"
|
7
|
+
gem.version = Informator::VERSION.dup
|
8
|
+
gem.author = "Andrew Kozin"
|
9
|
+
gem.email = "andrew.kozin@gmail.com"
|
10
|
+
gem.homepage = "https://github.com/nepalez/informator"
|
11
|
+
gem.summary = "Implementation of publish/subscribe design pattern"
|
12
|
+
gem.license = "MIT"
|
13
13
|
|
14
14
|
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
15
15
|
gem.test_files = Dir["spec/**/*.rb"]
|
16
16
|
gem.extra_rdoc_files = Dir["README.md", "LICENSE"]
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
-
gem.required_ruby_version = "~>
|
19
|
+
gem.required_ruby_version = "~> 1.9.3"
|
20
20
|
|
21
|
-
gem.add_runtime_dependency "
|
21
|
+
gem.add_runtime_dependency "i18n", "~> 0.7"
|
22
22
|
gem.add_runtime_dependency "ice_nine", "~> 0.11"
|
23
|
+
gem.add_runtime_dependency "inflecto", "~> 0.0"
|
24
|
+
gem.add_runtime_dependency "equalizer", "~> 0.0"
|
23
25
|
|
24
26
|
gem.add_development_dependency "hexx-rspec", "~> 0.5"
|
27
|
+
gem.add_development_dependency "timecop", "~> 0.7"
|
25
28
|
|
26
29
|
end # Gem::Specification
|
data/lib/informator.rb
CHANGED
@@ -1,85 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
+
require "inflecto"
|
3
4
|
require "ice_nine"
|
4
5
|
require "equalizer"
|
5
6
|
|
6
7
|
require_relative "informator/event"
|
7
8
|
require_relative "informator/subscriber"
|
8
|
-
require_relative "informator/
|
9
|
+
require_relative "informator/publisher"
|
9
10
|
|
10
|
-
# The module
|
11
|
+
# The module implements immutable event publisher
|
11
12
|
#
|
12
13
|
module Informator
|
13
14
|
|
14
|
-
# Subscribes the listener for receiving events notifications via callback
|
15
|
-
#
|
16
|
-
# @param [Object] listener
|
17
|
-
# The object to send events to
|
18
|
-
# @param [#to_sym] callback (:receive)
|
19
|
-
# The name of the listener method to send events through
|
20
|
-
#
|
21
|
-
# @return [self] itself
|
22
|
-
#
|
23
|
-
def subscribe(listener, callback = :receive)
|
24
|
-
sub = Subscriber.new(listener, callback)
|
25
|
-
__subscribers__ << sub unless __subscribers__.include? sub
|
26
|
-
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
|
-
# @!method remember(type, messages, attributes)
|
31
|
-
# Builds and stores the event waiting for being published
|
32
|
-
#
|
33
|
-
# @param (see Informator::Event.new)
|
34
|
-
#
|
35
|
-
# @return [self] itself
|
36
|
-
#
|
37
|
-
def remember(*args)
|
38
|
-
__reporter__.remember(*args)
|
39
|
-
|
40
|
-
self
|
41
|
-
end
|
42
|
-
|
43
|
-
# @overload publish(type, messages, attributes)
|
44
|
-
# Builds the event and then publishes all unpublished events
|
45
|
-
#
|
46
|
-
# @param (see #remember)
|
47
|
-
#
|
48
|
-
# @overload publish
|
49
|
-
# Publishes all unpublished events without adding a new one
|
50
|
-
#
|
51
|
-
# @return [Array<Informator::Event>] list of events having been published
|
52
|
-
#
|
53
|
-
def publish(*args)
|
54
|
-
remember(*args) if args.any?
|
55
|
-
__reporter__.notify __subscribers__
|
56
|
-
end
|
57
|
-
|
58
|
-
# The same as `publish` except for it throws `:published` afterwards
|
59
|
-
#
|
60
|
-
# @overload publish(type, messages, attributes)
|
61
|
-
# Builds the event and then publishes all unpublished events
|
62
|
-
#
|
63
|
-
# @param (see #remember)
|
64
|
-
#
|
65
|
-
# @overload publish
|
66
|
-
# Publishes all unpublished events without adding a new one
|
67
|
-
#
|
68
|
-
# @raise [UncaughtThrowError, ArgumentError]
|
69
|
-
# for `:published` events being carried
|
70
|
-
#
|
71
|
-
def publish!(*args)
|
72
|
-
throw :published, publish(*args)
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def __reporter__
|
78
|
-
@__reporter__ ||= Reporter.new
|
79
|
-
end
|
80
|
-
|
81
|
-
def __subscribers__
|
82
|
-
@__subscribers__ ||= []
|
83
|
-
end
|
84
|
-
|
85
15
|
end # module Informator
|
data/lib/informator/event.rb
CHANGED
@@ -2,63 +2,68 @@
|
|
2
2
|
|
3
3
|
module Informator
|
4
4
|
|
5
|
-
#
|
6
|
-
# which a type is attached. It also contains an array of human-readable
|
7
|
-
# messages describing the event.
|
5
|
+
# Describes events provided by publishers and being sent to their subscribers
|
8
6
|
#
|
9
|
-
#
|
10
|
-
# between various objects into unified format.
|
11
|
-
#
|
12
|
-
# @example
|
13
|
-
# event = Event[:success, "bingo!", foo: :bar]
|
14
|
-
# # <Event @type=:success @messages=["bingo!"] @attributes={ :foo => :bar }>
|
15
|
-
#
|
16
|
-
# event.frozen?
|
17
|
-
# # => true
|
7
|
+
# @api public
|
18
8
|
#
|
19
9
|
class Event
|
20
10
|
|
21
|
-
include Equalizer.new(:
|
11
|
+
include Equalizer.new(:publisher, :name, :attributes, :time)
|
12
|
+
|
13
|
+
# @!attribute [r] publisher
|
14
|
+
#
|
15
|
+
# @return [Informator::Publisher] The source of the event
|
16
|
+
#
|
17
|
+
attr_reader :publisher
|
22
18
|
|
23
|
-
# @!attribute [r]
|
19
|
+
# @!attribute [r] name
|
24
20
|
#
|
25
|
-
# @return [Symbol]
|
21
|
+
# @return [Symbol] The name of the event
|
26
22
|
#
|
27
|
-
attr_reader :
|
23
|
+
attr_reader :name
|
28
24
|
|
29
25
|
# @!attribute [r] attributes
|
30
26
|
#
|
31
|
-
# @return [Hash]
|
27
|
+
# @return [Hash] The event-specific attributes
|
32
28
|
#
|
33
29
|
attr_reader :attributes
|
34
30
|
|
35
|
-
# @!attribute [r]
|
31
|
+
# @!attribute [r] time
|
36
32
|
#
|
37
|
-
# @return [
|
33
|
+
# @return [Time] The time the event was created
|
38
34
|
#
|
39
|
-
attr_reader :
|
35
|
+
attr_reader :time
|
40
36
|
|
41
37
|
# @!scope class
|
42
|
-
# @!method
|
38
|
+
# @!method new(publisher, name, attributes)
|
43
39
|
# Builds the event
|
44
40
|
#
|
45
|
-
# @param [
|
46
|
-
# @param [#
|
41
|
+
# @param [Informator::Publisher] publisher
|
42
|
+
# @param [#to_sym] name
|
47
43
|
# @param [Hash] attributes
|
48
44
|
#
|
49
45
|
# @return [Informator::Event]
|
50
|
-
|
51
|
-
|
52
|
-
end
|
46
|
+
#
|
47
|
+
# @api private
|
53
48
|
|
54
49
|
# @private
|
55
|
-
def initialize(
|
56
|
-
@
|
57
|
-
@
|
58
|
-
@attributes = attributes
|
50
|
+
def initialize(publisher, name, attributes = {})
|
51
|
+
@publisher = publisher
|
52
|
+
@name = name.to_sym
|
53
|
+
@attributes = Hash[attributes]
|
54
|
+
@time = Time.now
|
59
55
|
IceNine.deep_freeze(self)
|
60
56
|
end
|
61
57
|
|
58
|
+
# The human-readable message for the event
|
59
|
+
#
|
60
|
+
# @return [String]
|
61
|
+
#
|
62
|
+
def message
|
63
|
+
scope = [:informator, Inflecto.underscore(publisher.class)]
|
64
|
+
I18n.translate(name, attributes.merge(scope: scope)).freeze
|
65
|
+
end
|
66
|
+
|
62
67
|
end # class Event
|
63
68
|
|
64
69
|
end # module Informator
|