omnes 0.1.0 → 0.2.2

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.
data/lib/omnes/event.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable"
3
+ require "omnes/configurable"
4
4
 
5
5
  module Omnes
6
6
  # Event mixin for custom classes
@@ -27,7 +27,7 @@ module Omnes
27
27
  # bus.publish(MyEvent.new(1))
28
28
  # ```
29
29
  module Event
30
- extend Dry::Configurable
30
+ extend Configurable
31
31
 
32
32
  # Generates the event name for an event instance
33
33
  #
@@ -19,25 +19,16 @@ module Omnes
19
19
  # @return [Array<Omnes::Execution>]
20
20
  attr_reader :executions
21
21
 
22
- # Location for the event caller
22
+ # Publication context, shared by all triggered executions
23
23
  #
24
- # It's usually set by {Omnes::Bus#publish}, and it points to the caller of
25
- # that method.
26
- #
27
- # @return [Thread::Backtrace::Location]
28
- attr_reader :caller_location
29
-
30
- # Time of the event publication
31
- #
32
- # @return [Time]
33
- attr_reader :time
24
+ # @return [Omnes::PublicationContext]
25
+ attr_reader :context
34
26
 
35
27
  # @api private
36
- def initialize(event:, executions:, caller_location:, time:)
28
+ def initialize(event:, executions:, context:)
37
29
  @event = event
38
30
  @executions = executions
39
- @caller_location = caller_location
40
- @time = time
31
+ @context = context
41
32
  end
42
33
  end
43
34
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnes
4
+ # Context for an event publication
5
+ #
6
+ # An instance of this class is shared between all the executions that are
7
+ # triggered by the publication of a given event. It's provided to the
8
+ # subscriptions as their second argument when they take it.
9
+ #
10
+ # This class is useful mainly for debugging and logging purposes.
11
+ class PublicationContext
12
+ # Location for the event publisher
13
+ #
14
+ # It's set by {Omnes::Bus#publish}, and it points to the caller of that
15
+ # method.
16
+ #
17
+ # @return [Thread::Backtrace::Location]
18
+ attr_reader :caller_location
19
+
20
+ # Time of the event publication
21
+ #
22
+ # @return [Time]
23
+ attr_reader :time
24
+
25
+ # @api private
26
+ def initialize(caller_location:, time:)
27
+ @caller_location = caller_location
28
+ @time = time
29
+ end
30
+
31
+ # Serialized version of a publication context
32
+ #
33
+ # @return Hash<String, String>
34
+ def serialized
35
+ {
36
+ "caller_location" => caller_location.to_s,
37
+ "time" => time.to_s
38
+ }
39
+ end
40
+ end
41
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable"
3
+ require "omnes/configurable"
4
4
 
5
5
  module Omnes
6
6
  module Subscriber
@@ -42,7 +42,7 @@ module Omnes
42
42
  # Omnes.config.subscriber.adapter.active_job.serializer = :serialized_payload.to_proc
43
43
  # ```
44
44
  module ActiveJob
45
- extend Dry::Configurable
45
+ extend Configurable
46
46
 
47
47
  setting :serializer, default: :payload.to_proc
48
48
 
@@ -52,8 +52,8 @@ module Omnes
52
52
  end
53
53
 
54
54
  # @api private
55
- def self.call(instance, event)
56
- self.[].(instance, event)
55
+ def self.call(instance, event, publication_context)
56
+ self.[].(instance, event, publication_context)
57
57
  end
58
58
 
59
59
  # @api private
@@ -64,8 +64,12 @@ module Omnes
64
64
  @serializer = serializer
65
65
  end
66
66
 
67
- def call(instance, event)
68
- instance.class.perform_later(serializer.(event))
67
+ def call(instance, event, publication_context)
68
+ if Subscription.takes_publication_context?(instance.method(:perform))
69
+ instance.class.perform_later(serializer.(event), publication_context.serialized)
70
+ else
71
+ instance.class.perform_later(serializer.(event))
72
+ end
69
73
  end
70
74
  end
71
75
  end
@@ -7,7 +7,7 @@ module Omnes
7
7
  module Adapter
8
8
  # Builds a callback from a method of the instance
9
9
  #
10
- # You can use instance of this class as the adapter:
10
+ # You can use an instance of this class as the adapter:
11
11
  #
12
12
  # ```ruby
13
13
  # handle :foo, with: Adapter::Method.new(:foo)
@@ -29,7 +29,7 @@ module Omnes
29
29
  def call(instance)
30
30
  check_method(instance)
31
31
 
32
- ->(event) { instance.method(name).(event) }
32
+ instance.method(name)
33
33
  end
34
34
 
35
35
  private
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable"
3
+ require "omnes/configurable"
4
4
 
5
5
  module Omnes
6
6
  module Subscriber
@@ -49,7 +49,7 @@ module Omnes
49
49
  # @example
50
50
  # handle :my_event, with: Adapter::Sidekiq.in(60)
51
51
  module Sidekiq
52
- extend Dry::Configurable
52
+ extend Configurable
53
53
 
54
54
  setting :serializer, default: :payload.to_proc
55
55
 
@@ -59,8 +59,8 @@ module Omnes
59
59
  end
60
60
 
61
61
  # @api private
62
- def self.call(instance, event)
63
- self.[].(instance, event)
62
+ def self.call(instance, event, publication_context)
63
+ self.[].(instance, event, publication_context)
64
64
  end
65
65
 
66
66
  # @param seconds [Integer]
@@ -76,15 +76,29 @@ module Omnes
76
76
  @serializer = serializer
77
77
  end
78
78
 
79
- def call(instance, event)
80
- instance.class.perform_async(serializer.(event))
79
+ def call(instance, event, publication_context)
80
+ if takes_publication_context?(instance)
81
+ instance.class.perform_async(serializer.(event), publication_context.serialized)
82
+ else
83
+ instance.class.perform_async(serializer.(event))
84
+ end
81
85
  end
82
86
 
83
87
  def in(seconds)
84
- lambda do |instance, event|
85
- instance.class.perform_in(seconds, serializer.(event))
88
+ lambda do |instance, event, publication_context|
89
+ if takes_publication_context?(instance)
90
+ instance.class.perform_in(seconds, serializer.(event), publication_context.serialized)
91
+ else
92
+ instance.class.perform_in(seconds, serializer.(event))
93
+ end
86
94
  end
87
95
  end
96
+
97
+ private
98
+
99
+ def takes_publication_context?(instance)
100
+ Subscription.takes_publication_context?(instance.method(:perform))
101
+ end
88
102
  end
89
103
  end
90
104
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "omnes/configurable"
3
4
  require "omnes/subscriber/adapter/active_job"
4
5
  require "omnes/subscriber/adapter/method"
5
6
  require "omnes/subscriber/adapter/sidekiq"
@@ -14,6 +15,11 @@ module Omnes
14
15
  # Alternatively, they can be curried and only take the instance as an
15
16
  # argument, returning a one-argument callable taking the event.
16
17
  module Adapter
18
+ extend Configurable
19
+
20
+ nest_config Sidekiq
21
+ nest_config ActiveJob
22
+
17
23
  # @api private
18
24
  # TODO: Simplify when when we can take callables and Proc in a polymorphic
19
25
  # way: https://bugs.ruby-lang.org/issues/18644
@@ -10,6 +10,11 @@ module Omnes
10
10
  class State
11
11
  attr_reader :subscription_definitions, :calling_cache, :autodiscover_strategy
12
12
 
13
+ # @api private
14
+ def self.IdType(value)
15
+ value.respond_to?(:call) ? value : ->(_instance) { value }
16
+ end
17
+
13
18
  def initialize(autodiscover_strategy:, subscription_definitions: [], calling_cache: [])
14
19
  @subscription_definitions = subscription_definitions
15
20
  @calling_cache = calling_cache
@@ -19,9 +24,9 @@ module Omnes
19
24
  def call(bus, instance)
20
25
  raise MultipleSubscriberSubscriptionAttemptError if already_called?(bus, instance)
21
26
 
22
- autodiscover_subscription_definitions(bus, instance) unless autodiscover_strategy.nil?
27
+ all_subscription_definitions = subscription_definitions + autodiscovered_subscription_definitions(bus, instance)
23
28
 
24
- definitions = subscription_definitions.map { |defn| defn.(bus) }
29
+ definitions = all_subscription_definitions.map { |defn| defn.(bus, instance) }
25
30
 
26
31
  subscribe_definitions(definitions, bus, instance).tap do
27
32
  mark_as_called(bus, instance)
@@ -42,26 +47,38 @@ module Omnes
42
47
  @calling_cache << [bus, instance]
43
48
  end
44
49
 
45
- def autodiscover_subscription_definitions(bus, instance)
46
- bus.registry.event_names.each do |event_name|
47
- method_name = autodiscover_strategy.(event_name)
48
- next unless instance.respond_to?(method_name, true)
50
+ def autodiscovered_subscription_definitions(bus, instance)
51
+ return [] unless autodiscover_strategy
49
52
 
50
- add_subscription_definition do |_bus|
53
+ bus.registry.event_names.reduce([]) do |defs, event_name|
54
+ method_name = autodiscover_strategy.(event_name)
55
+ if instance.respond_to?(method_name, true)
51
56
  [
52
- Subscription::SINGLE_EVENT_MATCHER.curry[event_name],
53
- Adapter.Type(Adapter::Method.new(method_name))
57
+ *defs,
58
+ autodiscovered_subscription_definition(event_name, method_name)
54
59
  ]
60
+ else
61
+ defs
55
62
  end
56
63
  end
57
64
  end
58
65
 
66
+ def autodiscovered_subscription_definition(event_name, method_name)
67
+ lambda do |_bus, _instance|
68
+ [
69
+ Subscription::SINGLE_EVENT_MATCHER.curry[event_name],
70
+ Adapter.Type(Adapter::Method.new(method_name)),
71
+ Subscription.random_id
72
+ ]
73
+ end
74
+ end
75
+
59
76
  def subscribe_definitions(definitions, bus, instance)
60
- matcher_with_callbacks = definitions.map do |(matcher, adapter)|
61
- [matcher, adapter.curry[instance]]
77
+ matcher_with_callbacks = definitions.map do |(matcher, adapter, id)|
78
+ [matcher, adapter.curry[instance], id]
62
79
  end
63
80
 
64
- matcher_with_callbacks.map { |matcher, callback| bus.subscribe_with_matcher(matcher, callback) }
81
+ matcher_with_callbacks.map { |matcher, callback, id| bus.subscribe_with_matcher(matcher, callback, id: id) }
65
82
  end
66
83
  end
67
84
  end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry/configurable"
4
3
  require "omnes/subscriber/adapter"
5
4
  require "omnes/subscriber/state"
6
5
  require "omnes/subscription"
@@ -122,14 +121,14 @@ module Omnes
122
121
  # bus. However, you can subscribe distinct instances to the same bus or the
123
122
  # same instance to different buses.
124
123
  module Subscriber
125
- extend Dry::Configurable
124
+ extend Configurable
126
125
 
127
126
  # @api private
128
127
  ON_PREFIX_STRATEGY = ->(event_name) { :"on_#{event_name}" }
129
128
 
130
129
  setting :autodiscover, default: false
131
-
132
130
  setting :autodiscover_strategy, default: ON_PREFIX_STRATEGY
131
+ nest_config Adapter
133
132
 
134
133
  # Includes with options
135
134
  #
@@ -193,11 +192,12 @@ module Omnes
193
192
  #
194
193
  # @param event_name [Symbol]
195
194
  # @param with [Symbol, #call] Public method in the class or an adapter
196
- def handle(event_name, with:)
195
+ # @param id [Symbol] Unique identifier for the subscription
196
+ def handle(event_name, with:, id: Subscription.random_id)
197
197
  @_mutex.synchronize do
198
- @_state.add_subscription_definition do |bus|
198
+ @_state.add_subscription_definition do |bus, instance|
199
199
  bus.registry.check_event_name(event_name)
200
- [Subscription::SINGLE_EVENT_MATCHER.curry[event_name], Adapter.Type(with)]
200
+ [Subscription::SINGLE_EVENT_MATCHER.curry[event_name], Adapter.Type(with), State.IdType(id).(instance)]
201
201
  end
202
202
  end
203
203
  end
@@ -205,10 +205,11 @@ module Omnes
205
205
  # Handles all events
206
206
  #
207
207
  # @param with [Symbol, #call] Public method in the class or an adapter
208
- def handle_all(with:)
208
+ # @param id [Symbol] Unique identifier for the subscription
209
+ def handle_all(with:, id: Subscription.random_id)
209
210
  @_mutex.synchronize do
210
- @_state.add_subscription_definition do |_bus|
211
- [Subscription::ALL_EVENTS_MATCHER, Adapter.Type(with)]
211
+ @_state.add_subscription_definition do |_bus, instance|
212
+ [Subscription::ALL_EVENTS_MATCHER, Adapter.Type(with), State.IdType(id).(instance)]
212
213
  end
213
214
  end
214
215
  end
@@ -217,10 +218,11 @@ module Omnes
217
218
  #
218
219
  # @param matcher [#call]
219
220
  # @param with [Symbol, #call] Public method in the class or an adapter
220
- def handle_with_matcher(matcher, with:)
221
+ # @param id [Symbol] Unique identifier for the subscription
222
+ def handle_with_matcher(matcher, with:, id: Subscription.random_id)
221
223
  @_mutex.synchronize do
222
- @_state.add_subscription_definition do |_bus|
223
- [matcher, Adapter.Type(with)]
224
+ @_state.add_subscription_definition do |_bus, instance|
225
+ [matcher, Adapter.Type(with), State.IdType(id).(instance)]
224
226
  end
225
227
  end
226
228
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "benchmark"
4
4
  require "omnes/execution"
5
+ require "securerandom"
5
6
 
6
7
  module Omnes
7
8
  # Subscription to an event
@@ -24,19 +25,39 @@ module Omnes
24
25
  ALL_EVENTS_MATCHER = ->(_candidate) { true }
25
26
 
26
27
  # @api private
27
- attr_reader :matcher, :callback
28
+ def self.random_id
29
+ SecureRandom.uuid.to_sym
30
+ end
31
+
32
+ # @api private
33
+ def self.takes_publication_context?(callable)
34
+ callable.parameters.count == 2
35
+ end
28
36
 
29
37
  # @api private
30
- def initialize(matcher:, callback:)
38
+ attr_reader :matcher, :callback, :id
39
+
40
+ # @api private
41
+ def initialize(matcher:, callback:, id:)
42
+ raise Omnes::InvalidSubscriptionNameError.new(id: id) unless id.is_a?(Symbol)
43
+
31
44
  @matcher = matcher
32
45
  @callback = callback
46
+ @id = id
33
47
  end
34
48
 
35
49
  # @api private
36
- def call(event)
50
+ def call(event, publication_context)
37
51
  result = nil
38
52
  benchmark = Benchmark.measure do
39
- result = @callback.(event)
53
+ # work around Ruby not being able to tell remaining arity for a curried
54
+ # function (or uncurrying), because we want to be able to create subscriber
55
+ # adapters partially applying the subscriber instance
56
+ result = begin
57
+ @callback.(event, publication_context)
58
+ rescue ArgumentError
59
+ @callback.(event)
60
+ end
40
61
  end
41
62
 
42
63
  Execution.new(subscription: self, result: result, benchmark: benchmark)
data/lib/omnes/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnes
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.2"
5
5
  end
data/lib/omnes.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "omnes/bus"
4
+ require "omnes/configurable"
4
5
  require "omnes/event"
5
6
  require "omnes/subscriber"
6
7
  require "omnes/version"
@@ -33,50 +34,22 @@ require "omnes/version"
33
34
  # Refer to {Omnes::Subscriber} for how to provide event handlers through methods
34
35
  # defined in a class.
35
36
  module Omnes
36
- # Shortcut to access the configuration for different Omnes components
37
- #
38
- # TODO: Make automation for it
39
- #
40
- # @return [Omnes::Config]
41
- def self.config
42
- Config
43
- end
44
-
45
- # Wrapper for the configuration of Omnes components
46
- module Config
47
- # {Omnes::Subscriber} configuration
48
- #
49
- # @return [Dry::Configurable::Config]
50
- def self.subscriber
51
- Omnes::Subscriber.config.tap do |klass|
52
- klass.define_singleton_method(:adapter) do
53
- Module.new do
54
- def self.sidekiq
55
- Omnes::Subscriber::Adapter::Sidekiq.config
56
- end
37
+ extend Configurable
57
38
 
58
- def self.active_job
59
- Omnes::Subscriber::Adapter::ActiveJob.config
60
- end
61
- end
62
- end
63
- end
64
- end
65
-
66
- # {Omnes::Event} configuration
67
- #
68
- # @return [Dry::Configurable::Config]
69
- def self.event
70
- Omnes::Event.config
71
- end
72
- end
39
+ nest_config Subscriber
40
+ nest_config Event
73
41
 
74
42
  # @api private
75
43
  def self.included(klass)
76
44
  klass.define_method(:omnes_bus) { @omnes_bus ||= Bus.new(cal_loc_start: 2) }
77
45
  Bus.instance_methods(false).each do |method|
78
46
  klass.define_method(method) do |*args, **kwargs, &block|
79
- omnes_bus.send(method, *args, **kwargs, &block)
47
+ # TODO: Forward with ... once we deprecate ruby 2.5 & 2.6
48
+ if kwargs.any?
49
+ omnes_bus.send(method, *args, **kwargs, &block)
50
+ else
51
+ omnes_bus.send(method, *args, &block)
52
+ end
80
53
  end
81
54
  end
82
55
  end
data/omnes.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
17
17
  MSG
18
18
  spec.homepage = "https://github.com/nebulab/omnes"
19
19
  spec.license = "MIT"
20
- spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
20
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
21
21
 
22
22
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
23
23
 
@@ -36,9 +36,7 @@ Gem::Specification.new do |spec|
36
36
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
37
37
  spec.require_paths = ["lib"]
38
38
 
39
- spec.add_dependency "dry-configurable", "~> 0.14"
40
-
41
- spec.add_development_dependency "activejob", "~> 7.0"
39
+ spec.add_development_dependency "activejob"
42
40
  spec.add_development_dependency "redcarpet", "~> 3.5"
43
41
  spec.add_development_dependency "sidekiq", "~> 6.4"
44
42
  spec.add_development_dependency "yard", "~> 0.9"
metadata CHANGED
@@ -1,43 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marc Busqué
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-03-23 00:00:00.000000000 Z
11
+ date: 2022-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: dry-configurable
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '0.14'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '0.14'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: activejob
29
15
  requirement: !ruby/object:Gem::Requirement
30
16
  requirements:
31
- - - "~>"
17
+ - - ">="
32
18
  - !ruby/object:Gem::Version
33
- version: '7.0'
19
+ version: '0'
34
20
  type: :development
35
21
  prerelease: false
36
22
  version_requirements: !ruby/object:Gem::Requirement
37
23
  requirements:
38
- - - "~>"
24
+ - - ">="
39
25
  - !ruby/object:Gem::Version
40
- version: '7.0'
26
+ version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: redcarpet
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -101,7 +87,6 @@ files:
101
87
  - CHANGELOG.md
102
88
  - CODE_OF_CONDUCT.md
103
89
  - Gemfile
104
- - Gemfile.lock
105
90
  - LICENSE.txt
106
91
  - README.md
107
92
  - Rakefile
@@ -109,10 +94,12 @@ files:
109
94
  - bin/setup
110
95
  - lib/omnes.rb
111
96
  - lib/omnes/bus.rb
97
+ - lib/omnes/configurable.rb
112
98
  - lib/omnes/errors.rb
113
99
  - lib/omnes/event.rb
114
100
  - lib/omnes/execution.rb
115
101
  - lib/omnes/publication.rb
102
+ - lib/omnes/publication_context.rb
116
103
  - lib/omnes/registry.rb
117
104
  - lib/omnes/subscriber.rb
118
105
  - lib/omnes/subscriber/adapter.rb
@@ -135,7 +122,7 @@ metadata:
135
122
  source_code_uri: https://github.com/nebulab/omnes
136
123
  changelog_uri: https://github.com/nebulab/omnes/CHANGELOG.md
137
124
  rubygems_mfa_required: 'true'
138
- post_install_message:
125
+ post_install_message:
139
126
  rdoc_options: []
140
127
  require_paths:
141
128
  - lib
@@ -143,15 +130,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
143
130
  requirements:
144
131
  - - ">="
145
132
  - !ruby/object:Gem::Version
146
- version: 2.7.0
133
+ version: 2.5.0
147
134
  required_rubygems_version: !ruby/object:Gem::Requirement
148
135
  requirements:
149
136
  - - ">="
150
137
  - !ruby/object:Gem::Version
151
138
  version: '0'
152
139
  requirements: []
153
- rubygems_version: 3.2.20
154
- signing_key:
140
+ rubygems_version: 3.1.2
141
+ signing_key:
155
142
  specification_version: 4
156
143
  summary: Pub/Sub for ruby
157
144
  test_files: []