happn 1.1.7 → 1.1.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69a473282e160c8ae5a0f8eea7615bae2700e63e2b458ae59c0e221a36824a26
4
- data.tar.gz: 19140eaf6bac43db02fb65fbfdd1b6ffaa8eb95bb29307d0a2be561649ff11e5
3
+ metadata.gz: 97331b5fd43edeae737ee8937f4b838d9788285404dbef888e77a28109e3d384
4
+ data.tar.gz: 173f02ea108c6911b6077399765e3326a3b792fd5368d5e4ca89028f37406f9b
5
5
  SHA512:
6
- metadata.gz: 8bb235e0cf250362916c7f399829248eaef752e2e4772178c7690896901aa763eb9dc9f2741bf12ff18caad3f925bbb151edd8109ea056b64cafa7f69494327d
7
- data.tar.gz: 1b30f2f49e6450d670c903f16096d42bc188ac279154a8d4d256795e446274ca0dc3424118e6a8b494611ab3773992c69afd6d59629bb9cc5620a5be087670fa
6
+ metadata.gz: 6b5c37de3a0d01ff984bd92cebf76c09b7905f7ddb556eb5757defca1bfb0957d488aa8e6909f922db84418ac623dd81b6c63cac5f5742f8d96efa3928e7ac5d
7
+ data.tar.gz: 95c55b9d1cadec849aaf6ad4107fbc05d4ba8411dc5aaaa62bb63c108bd7d2a1affe89545f82928913c155fcb43e9a78cfca58ea1a97bd8121ee1d2d3cb82fe6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ### [1.1.8] - 2026-08-03
9
+
10
+ * `Happn` no longer runs a handler several times for a single event. `"all"` is both the wildcard a query is stored under and a value an event may legitimately carry, and the dispatch used to list it twice: an event whose `emitter`, `kind` and `status` were all `"all"` ran each of its matching handlers 8 times, and 16 times when its `name` carried it too.
11
+ * `Happn` now reads the existing bindings of its queue on the vhost its broker connection uses, instead of always on the default one. On any other vhost it used to read someone else's bindings, and to unbind legitimate ones from its own queue as a result. Both `vhost` and `virtual_host` are honoured in `bunny_options`, with the precedence Bunny gives them.
12
+ * `Happn` now only unbinds the routing keys bound to its own exchange. It used to consider every binding of the queue, whatever its source, so the implicit binding each queue carries to the default exchange triggered a useless `unbind` on every start up.
13
+ * `Happn.stop` stops the consumption started by `Happn.start` and releases the thread it blocks. It cancels the consumer, lets the messages already handed over run to completion, then closes the broker connection.
14
+ * `Happn` builds an event from its payload about 5 times faster. The conversion of a payload key into an underscored symbol is now memoized instead of being recomputed on every key of every message.
15
+ * `Happn` allocates far fewer arrays while looking up the handlers of an event: the list under construction is filled in place instead of being reallocated on each of the 16 combinations looked up, which also lightens the garbage collector on a busy queue.
16
+ * `Happn.register` is now really private. It was meant to be, and read as such, but the `private` guarding it had no effect on a method defined on the module itself, leaving it callable from the outside.
17
+ * Publishing a version now requires the whole test suite to pass.
18
+
8
19
  ### [1.1.7] - 2026-08-01
9
20
 
10
21
  * **Breaking**: `required_ruby_version` is raised from `>= 3.0` to `>= 3.2`.
data/README.md CHANGED
@@ -43,7 +43,8 @@ Each configuration is detailed below.
43
43
  * `Happn` consumes a single queue through the RabbitMQ's [Topic Exchange Model](https://www.rabbitmq.com/tutorials/amqp-concepts.html#exchange-topic).
44
44
  * If the queue does not exist when `Happn` starts, it is created automatically.
45
45
  * When connecting a queue, please be careful that each connection parameter must match the existing queue's parameters. For instance, the value of `x-queue-mode` must match to avoid a `PRECONDITION FAILED` error.
46
- * All bindings between queues and their exchange are reset when starting `Happn`. Based on all the projectors that have been registered (option `projector_classes`), `Happn` detects which events must be consumed and binds its queue to the exchange depending on these event matchers.
46
+ * The bindings between the queue and the exchange `Happn` consumes are reset when starting `Happn`. Based on all the projectors that have been registered (option `projector_classes`), `Happn` detects which events must be consumed, binds its queue to the exchange depending on these event matchers, and unbinds the routing keys that no longer match any projector. Bindings the same queue may have to _other_ exchanges are left untouched.
47
+ * `Happn` reads those existing bindings through the RabbitMQ management API, on the vhost its broker connection uses. To consume a vhost other than the default one, declare it in `bunny_options` (`vhost` or `virtual_host`, as Bunny accepts both): `Happn` follows it on both the AMQP connection and the management API.
47
48
 
48
49
  ## About projectors
49
50
 
@@ -72,6 +73,28 @@ Starting `Happn` consumes events sequentially. For instance, it can be started f
72
73
  end
73
74
  ```
74
75
 
76
+ ### Shutting Down
77
+
78
+ `Happn.start` blocks the calling thread for as long as the consumption lasts. `Happn.stop` releases it: it cancels the consumer, lets the messages already being handled run to completion, and closes the broker connection. It is meant to be called from another thread than the one blocked in `Happn.start`.
79
+
80
+ `Happn` installs no signal handler of its own, because only the application knows what a graceful shutdown means for it. Wiring one is up to you:
81
+
82
+ ```ruby
83
+ namespace :events do
84
+ desc "Listen all events and consume them."
85
+ task consume: :environment do
86
+ Happn.init
87
+ Signal.trap("TERM") { Thread.new { Happn.stop } }
88
+ Signal.trap("INT") { Thread.new { Happn.stop } }
89
+ Happn.start
90
+ end
91
+ end
92
+ ```
93
+
94
+ Signal handlers run in a context where very little is allowed, hence the `Thread.new`.
95
+
96
+ Without it, an orchestrator stopping the process interrupts the handler in flight. Nothing is lost, the message was never acknowledged, so the broker redelivers it, but it is consumed twice, once partially.
97
+
75
98
  ### Define a Projector
76
99
 
77
100
  A projector is a class that defines how to consume one or multiple types of events. This class must:
@@ -87,7 +110,7 @@ class LoggerProjector < Happn::Projector
87
110
  end
88
111
 
89
112
  on kind: "request", status: :new do |event|
90
- Rails.logger("This is a new request to the controller: #{event.data["controller_name"]}")
113
+ Rails.logger("This is a new request to the controller: #{event.data[:controller_name]}")
91
114
  end
92
115
  end
93
116
  end
@@ -145,7 +168,7 @@ All options have a default value. However, all of them can be changed in your `H
145
168
  | `rabbitmq_queue_mode` | `nil` | String | Optional | When creating the queue, this option can be passed to set `x-queue-mode`. For instance, a queue can be made _"lazy"_ by passing `"lazy"` as a value. See [RabbitMQ's documentation](https://www.rabbitmq.com/lazy-queues.html) for more details. | `lazy` |
146
169
  | `rabbitmq_prefetch_size` | `10` | Integer | Optional | Also known as RabbitMQ's QOS. From the [RabbitMQ's documentation](http://www.rabbitmq.com/consumer-prefetch.html): _"AMQP specifies the basic.qos method to allow you to limit the number of unacknowledged messages on a channel (or connection) when consuming (aka "prefetch count")."_ | `1000` |
147
170
  | `projector_classes` | `[]` | Array of constants | Required | All Projector classes to register. This value can be generated by reading all descendant classes from `Happn::Projector`. | `[MyProjector]` |
148
- | `bunny_options` | `{}` | Hash of symbols | Optional | Additional options to add when connecting the RabbitMQ broker. This overrides the existing options with the same name. | `{ verify_peer: true }` |
171
+ | `bunny_options` | `{}` | Hash of symbols | Optional | Additional options to add when connecting the RabbitMQ broker. This overrides the existing options with the same name. A `vhost` (or `virtual_host`) declared here is also the vhost `Happn` queries through the management API. | `{ verify_peer: true }` |
149
172
  | `management_options` | `{}` | Hash of symbols | Optional | Additional options to add when accessing the RabbitMQ Managmement. This overrides the existing options with the same name. The options are defined at https://github.com/ruby-amqp/rabbitmq_http_api_client | `{ verify: false }` |
150
173
  | `on_error` | `nil` | `block` with an argument `exception` | false | When the consumption of an event raises an Error, the consumption exits. However, this block can be called before exiting the consumption execution. | `lambda { |exception| Raven.capture_exception(exception) }` (see Sentry's [documentation](https://github.com/getsentry/raven-ruby) |
151
174
 
@@ -161,8 +184,8 @@ scoped RubyGems credential.
161
184
  3. Tag the commit and push the tag:
162
185
 
163
186
  ```
164
- $ git tag -a v1.1.7 -m "Version 1.1.7"
165
- $ git push origin v1.1.7
187
+ $ git tag -a v1.1.8 -m "Version 1.1.8"
188
+ $ git push origin v1.1.8
166
189
  ```
167
190
 
168
191
  The workflow then checks that the tag matches `Happn::VERSION`, runs the tests, builds the gem
data/lib/happn/event.rb CHANGED
@@ -1,42 +1,131 @@
1
1
  require "date"
2
2
 
3
3
  module Happn
4
+ # A single CREPE event, as it was consumed from the exchange.
5
+ #
6
+ # An event is built from the parsed payload of a message. That payload carries
7
+ # two entries: `meta`, describing the event itself, and `data`, describing what
8
+ # happened. A projector handler receives an instance of this class.
9
+ #
10
+ # ## Key conversion
11
+ #
12
+ # Every key of the payload, at every depth, is converted into an underscored
13
+ # Symbol when the event is built. A payload emitted in camel case is therefore
14
+ # read in snake case, and always through symbols:
15
+ #
16
+ # # {"meta" => {…}, "data" => {"requestMetadata" => {"controllerName" => "countries"}}}
17
+ # event.data[:request_metadata][:controller_name] # => "countries"
18
+ # event.data["request_metadata"] # => nil
19
+ #
20
+ # Dashes become underscores, `::` becomes `/`, and the capitals of an acronym
21
+ # are kept together: `"HTTPResponseCode"` is read as `:http_response_code`.
22
+ #
23
+ # ## Changes
24
+ #
25
+ # An entity change carries a `changes` entry, mapping an attribute to the pair
26
+ # of values it went through:
27
+ #
28
+ # event.changes # => { name: ["France", "Belgium"] }
29
+ #
30
+ # Events of another shape carry no such entry at all, a `request` for instance.
31
+ # {#changes} then returns `nil`, and the five methods reading through it raise
32
+ # a NoMethodError: guard them with {#changes} when a handler may be reached by
33
+ # events of several shapes.
34
+ #
35
+ # @example Reading an event in a projector
36
+ # on kind: "entity_change", name: "update country" do |event|
37
+ # Rails.logger.info("#{event.emitter} renamed a country at #{event.timestamp}")
38
+ # Rails.logger.info("from #{event.change_before(:name)} to #{event.change_after(:name)}")
39
+ # end
4
40
  class Event
5
41
 
42
+ # The whole `data` entry of the payload, keys converted.
43
+ #
44
+ # This is the hash the event holds, not a copy: {#changes=} and {#add_change}
45
+ # write into it, and so does anything the caller does to it.
46
+ #
47
+ # @return [Hash] the payload data, or whatever `data` held if it was no hash
48
+ attr_reader :data
49
+
50
+ # Builds an event from a parsed payload.
51
+ #
52
+ # @param args [Hash] the parsed payload, with its `"meta"` and `"data"`
53
+ # entries, both keyed by String
54
+ # @raise [KeyError] if the payload carries no `"meta"` or no `"data"` entry
6
55
  def initialize(args)
7
56
  @meta = deep_underscore_keys(args.fetch("meta"))
8
57
  @data = deep_underscore_keys(args.fetch("data"))
9
58
  end
10
59
 
11
- def data
12
- @data
13
- end
14
-
60
+ # The metadata the emitter attached to the user behind the event.
61
+ #
62
+ # @return [Hash, nil] nil when the payload carries no `user_metadata`
15
63
  def user_metadata
16
64
  @data[:user_metadata]
17
65
  end
18
66
 
67
+ # The metadata the emitter attached to the request behind the event.
68
+ #
69
+ # @return [Hash, nil] nil when the payload carries no `request_metadata`
19
70
  def request_metadata
20
71
  @data[:request_metadata]
21
72
  end
22
73
 
74
+ # The attributes the event changed, each mapped to its before and after
75
+ # values.
76
+ #
77
+ # @return [Hash{Symbol => Array}, nil] nil when the payload carries no
78
+ # `changes` entry, which is the case of every event that is not an entity
79
+ # change
23
80
  def changes
24
81
  @data[:changes]
25
82
  end
26
83
 
84
+ # Replaces the whole set of changes.
85
+ #
86
+ # Keys are taken as they are given: unlike the ones read from the payload,
87
+ # they go through no conversion.
88
+ #
89
+ # @param new_changes [Hash{Symbol => Array}] the changes to substitute
90
+ # @return [Hash{Symbol => Array}] the changes that were set
27
91
  def changes=(new_changes)
28
92
  @data[:changes] = new_changes
29
93
  end
30
94
 
95
+ # Records a change on an attribute.
96
+ #
97
+ # The "before" value is always `nil`: the method describes a value that was
98
+ # set, not a transition. An empty String is normalized into `nil`, so that a
99
+ # blank emitted value and an absent one are recorded alike.
100
+ #
101
+ # @example
102
+ # event.add_change(:name, "Belgium") # => [nil, "Belgium"]
103
+ # event.add_change(:name, "") # => [nil, nil]
104
+ #
105
+ # @param name [Symbol, String] the attribute the change bears on
106
+ # @param value [Object] the value the attribute was set to
107
+ # @return [Array] the pair of values recorded
108
+ # @raise [NoMethodError] if the payload carries no `changes` entry
31
109
  def add_change(name, value)
32
110
  new_value = value == "" ? nil : value
33
111
  changes[name.to_sym] = [nil, new_value]
34
112
  end
35
113
 
114
+ # The entities the event relates to.
115
+ #
116
+ # @return [Hash, nil] nil when the payload carries no `associations`
36
117
  def associations
37
118
  @data[:associations]
38
119
  end
39
120
 
121
+ # When the event was emitted.
122
+ #
123
+ # The raw value is parsed on every call, and its offset is kept as it was
124
+ # emitted rather than being normalized.
125
+ #
126
+ # @return [DateTime, nil] nil when the payload carries no timestamp, or an
127
+ # empty one
128
+ # @raise [Date::Error] if the timestamp cannot be parsed
40
129
  def timestamp
41
130
  raw_timestamp = @meta[:timestamp]
42
131
  if raw_timestamp.nil? || raw_timestamp.to_s.strip.empty?
@@ -46,48 +135,101 @@ module Happn
46
135
  end
47
136
  end
48
137
 
138
+ # The identifier the emitter gave the event.
139
+ #
140
+ # @return [String, nil] nil when the payload carries no id
49
141
  def id
50
142
  @meta[:id]
51
143
  end
52
144
 
145
+ # What the event says happened, matched by the `name` of a query.
146
+ #
147
+ # @return [String, nil] nil when the payload carries no name
53
148
  def name
54
149
  @meta[:name]
55
150
  end
56
151
 
152
+ # The state of the event, matched by the `status` of a query.
153
+ #
154
+ # @return [String, nil] nil when the payload carries no status
57
155
  def status
58
156
  @meta[:status]
59
157
  end
60
158
 
159
+ # The category of the event, matched by the `kind` of a query.
160
+ #
161
+ # @return [String, nil] nil when the payload carries no kind
61
162
  def kind
62
163
  @meta[:kind]
63
164
  end
64
165
 
166
+ # The application the event comes from, matched by the `emitter` of a query.
167
+ #
168
+ # @return [String, nil] nil when the payload carries no emitter
65
169
  def emitter
66
170
  @meta[:emitter]
67
171
  end
68
172
 
173
+ # The value an attribute was changed to.
174
+ #
175
+ # @param attribute_name [Symbol, String] the attribute to read
176
+ # @return [Object, nil] nil when the attribute did not change
177
+ # @raise [NoMethodError] if the payload carries no `changes` entry
69
178
  def change_after(attribute_name)
70
179
  changes[attribute_name.to_sym]&.last
71
180
  end
72
181
 
182
+ # The value an attribute was changed from.
183
+ #
184
+ # @param attribute_name [Symbol, String] the attribute to read
185
+ # @return [Object, nil] nil when the attribute did not change
186
+ # @raise [NoMethodError] if the payload carries no `changes` entry
73
187
  def change_before(attribute_name)
74
188
  changes[attribute_name.to_sym]&.first
75
189
  end
76
190
 
191
+ # Whether an attribute changed.
192
+ #
193
+ # @param attribute_name [Symbol, String] the attribute to look for
194
+ # @return [Boolean]
195
+ # @raise [NoMethodError] if the payload carries no `changes` entry
77
196
  def has_change?(attribute_name)
78
197
  !changes[attribute_name.to_sym].nil?
79
198
  end
80
199
 
200
+ # Drops the change recorded on an attribute.
201
+ #
202
+ # @param attribute_name [Symbol, String] the attribute to drop
203
+ # @return [Array, nil] the pair of values that was dropped, nil when the
204
+ # attribute did not change
205
+ # @raise [NoMethodError] if the payload carries no `changes` entry
81
206
  def delete_change(attribute_name)
82
207
  changes.delete(attribute_name.to_sym)
83
208
  end
84
209
 
210
+ # Underscoring a key is five regular expression passes and three String
211
+ # allocations, and an event stream keeps sending the very same keys. The cache
212
+ # is therefore bound by the vocabulary of the payloads, which is closed: an
213
+ # emitter putting a variable part inside a key name would make it grow forever.
214
+ UNDERSCORED_KEYS = {}
215
+ private_constant :UNDERSCORED_KEYS
216
+
85
217
  private
86
218
 
219
+ # Converts a payload key into the symbol the event is read through, memoized
220
+ # for every event at once.
221
+ #
222
+ # @param key [String] a key as the emitter wrote it
223
+ # @return [Symbol] its underscored form
87
224
  def underscore_key(key)
88
- underscore(key).to_sym
225
+ UNDERSCORED_KEYS[key] ||= underscore(key).to_sym
89
226
  end
90
227
 
228
+ # Converts the keys of a payload fragment, walking through hashes and arrays
229
+ # down to the values, which are left as they are.
230
+ #
231
+ # @param value [Object] a fragment of the payload
232
+ # @return [Object] the same fragment, keyed by underscored symbols
91
233
  def deep_underscore_keys(value)
92
234
  case value
93
235
  when Array
@@ -99,6 +241,10 @@ module Happn
99
241
  end
100
242
  end
101
243
 
244
+ # Underscores a word, splitting it on its capitals and turning `::` into `/`.
245
+ #
246
+ # @param camel_cased_word [String] the word to convert
247
+ # @return [String] its underscored, downcased form
102
248
  def underscore(camel_cased_word)
103
249
  camel_cased_word.gsub(/::/, '/').
104
250
  gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
@@ -17,6 +17,7 @@ module Happn
17
17
  automatically_recover: true
18
18
  }.merge(@configuration.bunny_options || {})
19
19
  @connection = Bunny.new(options)
20
+ @vhost = options[:virtual_host] || options[:vhost] || "/"
20
21
 
21
22
  management_options = {
22
23
  username: @configuration.rabbitmq_user,
@@ -43,6 +44,13 @@ module Happn
43
44
  consume
44
45
  end
45
46
 
47
+ def stop
48
+ @logger.info("Stopping events consumption...")
49
+ @consumer&.cancel
50
+ @consumer = nil
51
+ @connection.close if @connection.open?
52
+ end
53
+
46
54
  private
47
55
 
48
56
  def connect
@@ -60,9 +68,8 @@ module Happn
60
68
  arguments = {}
61
69
  arguments["x-queue-mode"] = @configuration.rabbitmq_queue_mode unless @configuration.rabbitmq_queue_mode.nil?
62
70
  @queue = @channel.queue(@queue_name, durable: true, arguments: arguments)
63
- exchange = @channel.send(:topic,
64
- @configuration.rabbitmq_exchange_name,
65
- durable: @configuration.rabbitmq_exchange_durable)
71
+ exchange = @channel.topic(@configuration.rabbitmq_exchange_name,
72
+ durable: @configuration.rabbitmq_exchange_durable)
66
73
 
67
74
  routing_keys = @subscription_repository.find_all.map do | subscription |
68
75
  subscription.query.to_routing_key
@@ -77,18 +84,19 @@ module Happn
77
84
  @logger.info("Ready!")
78
85
  end
79
86
 
87
+ # 'Queue#subscribe' blocks before it can return the consumer it built, so the
88
+ # consumer is built here instead: 'stop' needs a handle on it to cancel it.
89
+ # Bunny's fourth argument is 'no_ack', hence false for a manual acknowledgement.
80
90
  def consume
81
- options = {
82
- manual_ack: true,
83
- block: true
84
- }
85
- @queue.subscribe(options) do | delivery_info, _properties, event |
91
+ @consumer = Bunny::Consumer.new(@channel, @queue, @channel.generate_consumer_tag, false)
92
+ @consumer.on_delivery do | delivery_info, _properties, event |
86
93
  begin
87
94
  handle_message(event, delivery_info)
88
95
  rescue => exception
89
96
  handle_exception(exception, delivery_info)
90
97
  end
91
98
  end
99
+ @queue.subscribe_with(@consumer, block: true)
92
100
  end
93
101
 
94
102
  def handle_message(message, delivery_info)
@@ -111,8 +119,6 @@ module Happn
111
119
  raise exception
112
120
  end
113
121
 
114
- private
115
-
116
122
  def unbind_useless_routing_keys(queue, exchange, useful_routing_keys)
117
123
  all_routing_keys = find_all_routing_keys_of(queue)
118
124
  keys_to_remove = all_routing_keys - useful_routing_keys
@@ -122,8 +128,9 @@ module Happn
122
128
  end
123
129
 
124
130
  def find_all_routing_keys_of(queue)
125
- @management_client.list_queue_bindings("/", queue.name).map do | binding |
126
- binding.routing_key
131
+ exchange_name = @configuration.rabbitmq_exchange_name
132
+ @management_client.list_queue_bindings(@vhost, queue.name).filter_map do | binding |
133
+ binding.routing_key if binding.source == exchange_name
127
134
  end
128
135
  end
129
136
  end
@@ -25,16 +25,17 @@ module Happn
25
25
  end
26
26
 
27
27
  def find_subscriptions_for(event)
28
- possible_event_statuses = ["all", event.status.to_s]
29
- possible_event_emitters = ["all", event.emitter.to_s]
30
- possible_event_names = ["all", event.name.to_s]
31
- possible_event_kinds = ["all", event.kind.to_s]
28
+ possible_event_statuses = ["all", event.status.to_s].uniq
29
+ possible_event_emitters = ["all", event.emitter.to_s].uniq
30
+ possible_event_names = ["all", event.name.to_s].uniq
31
+ possible_event_kinds = ["all", event.kind.to_s].uniq
32
32
  subscriptions = []
33
33
  possible_event_statuses.each do | status |
34
34
  possible_event_emitters.each do | emitter |
35
35
  possible_event_kinds.each do | kind |
36
36
  possible_event_names.each do | name |
37
- subscriptions += @subscriptions.dig(status, emitter, kind, name) || []
37
+ found = @subscriptions.dig(status, emitter, kind, name)
38
+ subscriptions.concat(found) if found
38
39
  end
39
40
  end
40
41
  end
@@ -47,7 +48,7 @@ module Happn
47
48
  def flatten(item)
48
49
  if item.instance_of?(Hash)
49
50
  item.values.inject([]) do | result, value |
50
- result += flatten(value)
51
+ result.concat(flatten(value))
51
52
  end
52
53
  else
53
54
  item
data/lib/happn/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Happn
2
- VERSION = "1.1.7"
2
+ VERSION = "1.1.8"
3
3
  end
data/lib/happn.rb CHANGED
@@ -24,7 +24,7 @@ module Happn
24
24
  def self.init
25
25
  @logger = @configuration.logger || Logger.new(STDOUT)
26
26
  subscription_repository = SubscriptionRepository.new(@logger)
27
- Happn::register(@configuration.projector_classes, subscription_repository)
27
+ register(@configuration.projector_classes, subscription_repository)
28
28
  @event_consumer = EventConsumer.new(@logger, @configuration, subscription_repository)
29
29
  end
30
30
 
@@ -32,6 +32,10 @@ module Happn
32
32
  @event_consumer.start
33
33
  end
34
34
 
35
+ def self.stop
36
+ @event_consumer&.stop
37
+ end
38
+
35
39
  def self.create_queue_only
36
40
  Happn.init
37
41
  @event_consumer.wait_until_connected
@@ -56,8 +60,6 @@ module Happn
56
60
  config.management_options = {}
57
61
  end
58
62
 
59
- private
60
-
61
63
  def self.register(projector_classes, subscription_repository)
62
64
  @logger.info("#{projector_classes.size} projector are going to be registered...")
63
65
  projector_classes.each do | projector_class |
@@ -66,5 +68,6 @@ module Happn
66
68
  @logger.info("Projector '#{projector_class}' registered")
67
69
  end
68
70
  end
71
+ private_class_method :register
69
72
 
70
73
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: happn
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.7
4
+ version: 1.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Commuty