informator 0.1.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 53d7f784fa22bf7f691c99d5e59f054c0c0ba3fb
4
- data.tar.gz: 37dcc4751f1433fdcfdc9fc5650f6776ec065100
3
+ metadata.gz: 0de58e03a57880d1061c5810a877e69086c0b71f
4
+ data.tar.gz: 022a5537c463a1c746a02153d039a87ed9c88b18
5
5
  SHA512:
6
- metadata.gz: e22da1a142f55e6535b54db59652546cd750d23cfc237329eb7d3a11cb21b2ce6cfaffaab64563f941c91f0336f4f2af197a6bdf6702baf951646a2a7586b34d
7
- data.tar.gz: 5463ec47db2b542e465f0bbc546068ee9a48dc9aca6e11c2f03c82c8e0a1be183ff912a0e47a6a69098873ffb6610668bb30e2200885b9581bb9777bd02e1ac7
6
+ metadata.gz: b9e8716e59f38f6a2a2e0bcad4238fcd6ea13ffd7dd6fc0ce67b76e7410200cc6dadcab8f99619c1cf4075b7bd99616a8e0b048209883eade6d1fe0f25b996b9
7
+ data.tar.gz: 88c7f671d6b646d0663aa285fccdb9f60ae7d2958ea5fedb288cd5f98b96b22b11cff1741e8eb3bbe0876c972f9948ef072618499f4c8858b72042ee21b478cc
@@ -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
@@ -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 tiny implementation of publish/subscribe design pattern.
18
+ The [wisper]-inspired micro-gem, that implements publish/subscribe design pattern in immutable style.
19
19
 
20
- The implementation differs from the original wisper's approach in the following aspects:
20
+ **Notice a version `1.0` has been re-written from scratch to provide immutability. Its API has been changed.**
21
21
 
22
- * Unlike `Wisper::Publisher` that calls listener methods depending on the event, the `Informator` uses the same listener's callback for sending all events. Instead it wraps attributes to `Informator::Event` container and uses its as the argument for the callback.
22
+ Motivation
23
+ ----------
23
24
 
24
- * The `Informator` has two separate methods - `#remember` and `#publish`. The first one is used to build and collect events, while the second one publishes all unpublished events at once (and clears the list of events to be published).
25
+ The `informator` differs from `wisper` in the following aspects:
25
26
 
26
- * The `Informator` also defines `#publish!` method that is the same as `#publish` except for it throws the `:published`, that should be catched somewhere. This is just a shortcut for exiting from a use cases.
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 `#publish` method returns the list of published events. The exception thrown by `#publish!` carries that list as well. This allows not only to publish them to listeners but to return them to caller. This is necessary to simplify chaining of service objects that includes the `Informator`.
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` has no global neither async subscribers. The gem's scope is narrower. Its main goal is to support service objects, that are either chained to each other, or publishes their results to decoupled listeners.
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
- Synopsis
35
- --------
40
+ API
41
+ ---
42
+
43
+ The `Informator::Publisher` class describes immutable publishers with 6 instance methods:
36
44
 
37
- The `Informator` module API defines 4 instance methods:
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
- * `#subscribe` to subscribe listeners for receiving events, published by the informator
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
- Except for the `Informator` the module defines public class `Informator::Event` for immutable events, that has 3 attributes:
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
- * `#type` for symbolic type of the event
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
- The event instance is build by the `#remember`, `#publish` or `#publish!` methods and is sent to listeners by either `#publish` or `#publish!`. When sending an event, the informator just calls the listeners callback, defined by `#subscribe` method, and gives it a corresponding event object as the only argument.
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 Listener
56
- def receive(event)
57
- puts "The listener received #{ event.type }: #{ event.messages }"
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
- listener = Listener.new
81
+ Provide translations for events:
62
82
 
63
- class Service
64
- include Informator
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
- def call
67
- catch(:published) { do_some_staff }
68
- end
93
+ Provide a listener with a callback method:
69
94
 
70
- def do_some_staff
71
- # ...
72
- remember :success, "for now all is fine", foo: :bar
73
- #
74
- publish! :error, "OMG!", bar: :baz
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
- service = Service.new
79
- service.subscribe listener, :receive
80
- result = service.call
81
- # The listener received success: ["for now all is fine"]
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
- In the example above the `listener#receive` method is called twice with the first, and then with the second event as an argument.
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
- Then the `publish!` throws an exception that carries the list of messages.
93
- The `service#call` catches it and returns the array of events. With this feature it is possible not only to subscribe for the service's events but receive it directly, combining [both telling and asking](http://martinfowler.com/bliki/TellDontAsk.html) when necessary.
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 2.1+](.travis.yml).
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
@@ -24,6 +24,6 @@ task :mutant do
24
24
  end
25
25
 
26
26
  desc "Exhort all evils"
27
- task :mutant do
27
+ task :exhort do
28
28
  system "bundle exec mutant -r informator --use rspec Informator*"
29
29
  end
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  folders: # The list of folders to be used by any metric.
3
- - lib
4
- - app
3
+ - lib/informator.rb
4
+ - lib/informator
5
5
  metrics: # The list of allowed metrics. The other metrics are disabled.
6
6
  - cane
7
7
  - churn
@@ -15,6 +15,10 @@ Lint/RescueException:
15
15
  Exclude:
16
16
  - '**/*_spec.rb'
17
17
 
18
+ Metrics/LineLength:
19
+ Exclude:
20
+ - 'lib/rspec/*.rb'
21
+
18
22
  Style/AccessorMethodName:
19
23
  Exclude:
20
24
  - '**/*_spec.rb'
@@ -3,24 +3,27 @@ require "informator/version"
3
3
 
4
4
  Gem::Specification.new do |gem|
5
5
 
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"
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 = "~> 2.1"
19
+ gem.required_ruby_version = "~> 1.9.3"
20
20
 
21
- gem.add_runtime_dependency "equalizer", "~> 0.0"
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
@@ -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/reporter"
9
+ require_relative "informator/publisher"
9
10
 
10
- # The module provides subscribe/publish/report features
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
@@ -2,63 +2,68 @@
2
2
 
3
3
  module Informator
4
4
 
5
- # Class Event provides an immutable container for hash of some attributes, to
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
- # The primary goal of events is folding attributes to be returned and/or sent
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(:type, :attributes)
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] type
19
+ # @!attribute [r] name
24
20
  #
25
- # @return [Symbol] the type of the event
21
+ # @return [Symbol] The name of the event
26
22
  #
27
- attr_reader :type
23
+ attr_reader :name
28
24
 
29
25
  # @!attribute [r] attributes
30
26
  #
31
- # @return [Hash] the event-specific attributes
27
+ # @return [Hash] The event-specific attributes
32
28
  #
33
29
  attr_reader :attributes
34
30
 
35
- # @!attribute [r] messages
31
+ # @!attribute [r] time
36
32
  #
37
- # @return [Array<String>] human-readable messages, describing the event
33
+ # @return [Time] The time the event was created
38
34
  #
39
- attr_reader :messages
35
+ attr_reader :time
40
36
 
41
37
  # @!scope class
42
- # @!method [](type, messages, attributes)
38
+ # @!method new(publisher, name, attributes)
43
39
  # Builds the event
44
40
  #
45
- # @param [#to_sym] type
46
- # @param [#to_s, Array<#to_s>] messages
41
+ # @param [Informator::Publisher] publisher
42
+ # @param [#to_sym] name
47
43
  # @param [Hash] attributes
48
44
  #
49
45
  # @return [Informator::Event]
50
- def self.[](*args)
51
- new(*args)
52
- end
46
+ #
47
+ # @api private
53
48
 
54
49
  # @private
55
- def initialize(type, *messages, **attributes)
56
- @type = type.to_sym
57
- @messages = messages.flatten.map(&:to_s)
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