omnes 0.1.0 → 0.2.2

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