active_delivery 1.0.0.rc2 → 1.1.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.
@@ -1,25 +1,59 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbstractNotifier
4
- # Notificaiton payload wrapper which contains
4
+ # NotificationDelivery payload wrapper which contains
5
5
  # information about the current notifier class
6
6
  # and knows how to trigger the delivery
7
- class Notification
8
- attr_reader :payload, :owner
7
+ class NotificationDelivery
8
+ attr_reader :action_name, :notifier_class
9
+
10
+ def initialize(notifier_class, action_name, params: {}, args: [], kwargs: {})
11
+ @notifier_class = notifier_class
12
+ @action_name = action_name
13
+ @params = params
14
+ @args = args
15
+ @kwargs = kwargs
16
+ end
9
17
 
10
- def initialize(owner, payload)
11
- @owner = owner
12
- @payload = payload
18
+ def processed
19
+ return @processed if instance_variable_defined?(:@processed)
20
+
21
+ @processed = notifier.process_action(action_name, *args, **kwargs) || Notification.new(nil)
13
22
  end
14
23
 
15
- def notify_later
16
- return if AbstractNotifier.noop?
17
- owner.async_adapter.enqueue owner, payload
24
+ alias_method :notification, :processed
25
+
26
+ def notify_later(**opts)
27
+ if notifier_class.async_adapter.respond_to?(:enqueue_delivery)
28
+ notifier_class.async_adapter.enqueue_delivery(self, **opts)
29
+ else
30
+ notifier_class.async_adapter.enqueue(notifier_class.name, action_name, params:, args:, kwargs:)
31
+ end
18
32
  end
19
33
 
20
34
  def notify_now
21
- return if AbstractNotifier.noop?
22
- owner.driver.call(payload)
35
+ return unless notification.payload
36
+
37
+ notifier.deliver!(notification)
38
+ end
39
+
40
+ def delivery_params = {params:, args:, kwargs:}
41
+
42
+ private
43
+
44
+ attr_reader :params, :args, :kwargs
45
+
46
+ def notifier
47
+ @notifier ||= notifier_class.new(action_name, **params)
48
+ end
49
+ end
50
+
51
+ # Notification object contains the compiled payload to be delivered
52
+ class Notification
53
+ attr_reader :payload
54
+
55
+ def initialize(payload)
56
+ @payload = payload
23
57
  end
24
58
  end
25
59
 
@@ -35,11 +69,7 @@ module AbstractNotifier
35
69
 
36
70
  # rubocop:disable Style/MethodMissingSuper
37
71
  def method_missing(method_name, *args, **kwargs)
38
- if kwargs.empty?
39
- notifier_class.new(method_name, **params).public_send(method_name, *args)
40
- else
41
- notifier_class.new(method_name, **params).public_send(method_name, *args, **kwargs)
42
- end
72
+ NotificationDelivery.new(notifier_class, method_name, params:, args:, kwargs:)
43
73
  end
44
74
  # rubocop:enable Style/MethodMissingSuper
45
75
 
@@ -112,9 +142,9 @@ module AbstractNotifier
112
142
  end
113
143
  end
114
144
 
115
- def method_missing(method_name, *args)
145
+ def method_missing(method_name, *args, **kwargs)
116
146
  if action_methods.include?(method_name.to_s)
117
- new(method_name).public_send(method_name, *args)
147
+ NotificationDelivery.new(self, method_name, args:, kwargs:)
118
148
  else
119
149
  super
120
150
  end
@@ -152,16 +182,31 @@ module AbstractNotifier
152
182
  @params = params.freeze
153
183
  end
154
184
 
185
+ def process_action(...)
186
+ public_send(...)
187
+ end
188
+
189
+ def deliver!(notification)
190
+ self.class.driver.call(notification.payload)
191
+ end
192
+
155
193
  def notification(**payload)
156
194
  merge_defaults!(payload)
157
195
 
196
+ payload[:body] = implicit_payload_body unless payload.key?(:body)
197
+
158
198
  raise ArgumentError, "Notification body must be present" if
159
199
  payload[:body].nil? || payload[:body].empty?
160
- Notification.new(self.class, payload)
200
+
201
+ @notification = Notification.new(payload)
161
202
  end
162
203
 
163
204
  private
164
205
 
206
+ def implicit_payload_body
207
+ # no-op — override to provide custom logic
208
+ end
209
+
165
210
  def merge_defaults!(payload)
166
211
  defaults =
167
212
  if self.class.defaults_generator
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/version"
4
+ require "active_support/callbacks"
5
+ require "active_support/concern"
6
+
7
+ module AbstractNotifier
8
+ # Add callbacks support to Abstract Notifier (requires ActiveSupport::Callbacks)
9
+ #
10
+ # # Run method before seding notification
11
+ # # NOTE: when `false` is returned the execution is halted
12
+ # before_action :do_something
13
+ #
14
+ # # after_ and around_ callbacks are also supported
15
+ # after_action :cleanup
16
+ #
17
+ # around_action :set_context
18
+ #
19
+ # # Deliver callbacks are also available
20
+ # before_deliver :do_something
21
+ #
22
+ # # after_ and around_ callbacks are also supported
23
+ # after_deliver :cleanup
24
+ #
25
+ # around_deliver :set_context
26
+ module Callbacks
27
+ extend ActiveSupport::Concern
28
+
29
+ include ActiveSupport::Callbacks
30
+
31
+ CALLBACK_TERMINATOR = ->(_target, result) { result.call == false }
32
+
33
+ included do
34
+ define_callbacks :action,
35
+ terminator: CALLBACK_TERMINATOR,
36
+ skip_after_callbacks_if_terminated: true
37
+
38
+ define_callbacks :deliver,
39
+ terminator: CALLBACK_TERMINATOR,
40
+ skip_after_callbacks_if_terminated: true
41
+
42
+ prepend InstanceExt
43
+ end
44
+
45
+ module InstanceExt
46
+ def process_action(...)
47
+ run_callbacks(:action) { super(...) }
48
+ end
49
+
50
+ def deliver!(...)
51
+ run_callbacks(:deliver) { super(...) }
52
+ end
53
+ end
54
+
55
+ class_methods do
56
+ def _normalize_callback_options(options)
57
+ _normalize_callback_option(options, :only, :if)
58
+ _normalize_callback_option(options, :except, :unless)
59
+ end
60
+
61
+ def _normalize_callback_option(options, from, to)
62
+ if (from = options[from])
63
+ from_set = Array(from).map(&:to_s).to_set
64
+ from = proc { |c| from_set.include? c.notification_name.to_s }
65
+ options[to] = Array(options[to]).unshift(from)
66
+ end
67
+ end
68
+
69
+ %i[before after around].each do |kind|
70
+ %i[action deliver].each do |event|
71
+ define_method "#{kind}_#{event}" do |*names, on: event, **options, &block|
72
+ _normalize_callback_options(options)
73
+
74
+ names.each do |name|
75
+ set_callback on, kind, name, options
76
+ end
77
+
78
+ set_callback on, kind, block, options if block
79
+ end
80
+
81
+ define_method "skip_#{kind}_#{event}" do |*names, on: event, **options|
82
+ _normalize_callback_options(options)
83
+
84
+ names.each do |name|
85
+ skip_callback(on, kind, name, options)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ AbstractNotifier::Base.include AbstractNotifier::Callbacks
@@ -27,23 +27,27 @@ module AbstractNotifier
27
27
  end
28
28
  end
29
29
 
30
- module Notification
30
+ module NotificationDelivery
31
31
  def notify_now
32
32
  return super unless AbstractNotifier.test?
33
33
 
34
- Driver.send_notification payload.merge(via: owner)
34
+ payload = notification.payload
35
+
36
+ Driver.send_notification payload.merge(via: notifier.class)
35
37
  end
36
38
 
37
- def notify_later
39
+ def notify_later(**opts)
38
40
  return super unless AbstractNotifier.test?
39
41
 
40
- Driver.enqueue_notification payload.merge(via: owner)
42
+ payload = notification.payload
43
+
44
+ Driver.enqueue_notification payload.merge(via: notifier.class, **opts)
41
45
  end
42
46
  end
43
47
  end
44
48
  end
45
49
 
46
- AbstractNotifier::Notification.prepend AbstractNotifier::Testing::Notification
50
+ AbstractNotifier::NotificationDelivery.prepend AbstractNotifier::Testing::NotificationDelivery
47
51
 
48
52
  require "abstract_notifier/testing/rspec" if defined?(RSpec::Core)
49
53
  require "abstract_notifier/testing/minitest" if defined?(Minitest::Assertions)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AbstractNotifier
4
- VERSION = "0.3.2"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -69,6 +69,7 @@ end
69
69
  require "abstract_notifier/base"
70
70
  require "abstract_notifier/async_adapters"
71
71
 
72
+ require "abstract_notifier/callbacks" if defined?(ActiveSupport)
72
73
  require "abstract_notifier/async_adapters/active_job" if defined?(ActiveJob)
73
74
 
74
75
  require "abstract_notifier/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
@@ -12,9 +12,9 @@ module ActiveDelivery
12
12
  @metadata = metadata.freeze
13
13
  end
14
14
 
15
- def deliver_later = owner.perform_notify(self)
15
+ def deliver_later(**opts) = owner.perform_notify(self, enqueue_options: opts)
16
16
 
17
- def deliver_now = owner.perform_notify(self, sync: true)
17
+ def deliver_now(**opts) = owner.perform_notify(self, sync: true)
18
18
 
19
19
  def delivery_class = owner.class
20
20
  end
@@ -216,22 +216,23 @@ module ActiveDelivery
216
216
 
217
217
  protected
218
218
 
219
- def perform_notify(delivery, sync: false)
219
+ def perform_notify(delivery, sync: false, enqueue_options: {})
220
220
  delivery_lines.each do |type, line|
221
221
  next unless line.notify?(delivery.notification)
222
222
 
223
- notify_line(type, line, delivery, sync:)
223
+ notify_line(type, line, delivery, sync:, enqueue_options:)
224
224
  end
225
225
  end
226
226
 
227
227
  private
228
228
 
229
- def notify_line(type, line, delivery, sync:)
229
+ def notify_line(type, line, delivery, sync:, enqueue_options:)
230
230
  line.notify(
231
231
  delivery.notification,
232
232
  *delivery.params,
233
233
  params:,
234
234
  sync:,
235
+ enqueue_options:,
235
236
  **delivery.options
236
237
  )
237
238
  true
@@ -16,7 +16,7 @@ module ActiveDelivery
16
16
  @id = id
17
17
  @owner = owner
18
18
  @options = options.tap(&:freeze)
19
- @resolver = options[:resolver]
19
+ @resolver = options[:resolver] || build_pattern_resolver(options[:resolver_pattern])
20
20
  end
21
21
 
22
22
  def dup_for(new_owner)
@@ -37,9 +37,21 @@ module ActiveDelivery
37
37
  def notify_later(handler, mid, ...)
38
38
  end
39
39
 
40
- def notify(mid, *args, params:, sync:, **kwargs)
40
+ def notify_later_with_options(handler, enqueue_options, mid, ...)
41
+ notify_later(handler, mid, ...)
42
+ end
43
+
44
+ def notify(mid, *args, params:, sync:, enqueue_options:, **kwargs)
41
45
  clazz = params.empty? ? handler_class : handler_class.with(**params)
42
- sync ? notify_now(clazz, mid, *args, **kwargs) : notify_later(clazz, mid, *args, **kwargs)
46
+ if sync
47
+ return notify_now(clazz, mid, *args, **kwargs)
48
+ end
49
+
50
+ if enqueue_options.empty?
51
+ notify_later(clazz, mid, *args, **kwargs)
52
+ else
53
+ notify_later_with_options(clazz, enqueue_options, mid, *args, **kwargs)
54
+ end
43
55
  end
44
56
 
45
57
  def handler_class
@@ -65,6 +77,25 @@ module ActiveDelivery
65
77
  private
66
78
 
67
79
  attr_reader :resolver
80
+
81
+ def build_pattern_resolver(pattern)
82
+ return unless pattern
83
+
84
+ proc do |delivery|
85
+ delivery_class = delivery.name
86
+
87
+ next unless delivery_class
88
+
89
+ *namespace, delivery_name = delivery_class.split("::")
90
+
91
+ delivery_namespace = ""
92
+ delivery_namespace = "#{namespace.join("::")}::" unless namespace.empty?
93
+
94
+ delivery_name = delivery_name.sub(/Delivery$/, "")
95
+
96
+ (pattern % {delivery_class:, delivery_name:, delivery_namespace:}).safe_constantize
97
+ end
98
+ end
68
99
  end
69
100
  end
70
101
  end
@@ -19,6 +19,10 @@ module ActiveDelivery
19
19
  def notify_later(mailer, mid, ...)
20
20
  mailer.public_send(mid, ...).deliver_later
21
21
  end
22
+
23
+ def notify_later_with_options(mailer, enqueue_options, mid, ...)
24
+ mailer.public_send(mid, ...).deliver_later(**enqueue_options)
25
+ end
22
26
  end
23
27
 
24
28
  ActiveDelivery::Base.register_line :mailer, Mailer, resolver: Mailer::DEFAULT_RESOLVER
@@ -18,7 +18,7 @@ module ActiveDelivery
18
18
 
19
19
  def initialize(**opts)
20
20
  super
21
- @resolver = opts.fetch(:resolver, build_resolver(options.fetch(:suffix, DEFAULT_SUFFIX)))
21
+ @resolver ||= build_resolver(options.fetch(:suffix, DEFAULT_SUFFIX))
22
22
  end
23
23
 
24
24
  def resolve_class(klass)
@@ -38,6 +38,10 @@ module ActiveDelivery
38
38
  handler.public_send(mid, *args).notify_later
39
39
  end
40
40
 
41
+ def notify_later_with_options(handler, enqueue_options, mid, *args)
42
+ handler.public_send(mid, *args).notify_later(**enqueue_options)
43
+ end
44
+
41
45
  private
42
46
 
43
47
  attr_reader :resolver
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveDelivery
4
- VERSION = "1.0.0.rc2"
4
+ VERSION = "1.1.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_delivery
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-05 00:00:00.000000000 Z
11
+ date: 2023-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '4.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: ruby-next
70
+ name: ruby-next-core
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -93,16 +93,22 @@ files:
93
93
  - README.md
94
94
  - bin/console
95
95
  - bin/setup
96
+ - lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb
97
+ - lib/.rbnext/3.0/abstract_notifier/base.rb
96
98
  - lib/.rbnext/3.0/active_delivery/base.rb
97
99
  - lib/.rbnext/3.0/active_delivery/callbacks.rb
98
100
  - lib/.rbnext/3.0/active_delivery/lines/base.rb
99
101
  - lib/.rbnext/3.0/active_delivery/lines/mailer.rb
102
+ - lib/.rbnext/3.0/active_delivery/testing.rb
103
+ - lib/.rbnext/3.1/abstract_notifier/async_adapters/active_job.rb
104
+ - lib/.rbnext/3.1/abstract_notifier/base.rb
100
105
  - lib/.rbnext/3.1/active_delivery/base.rb
101
106
  - lib/.rbnext/3.1/active_delivery/lines/base.rb
102
107
  - lib/abstract_notifier.rb
103
108
  - lib/abstract_notifier/async_adapters.rb
104
109
  - lib/abstract_notifier/async_adapters/active_job.rb
105
110
  - lib/abstract_notifier/base.rb
111
+ - lib/abstract_notifier/callbacks.rb
106
112
  - lib/abstract_notifier/testing.rb
107
113
  - lib/abstract_notifier/testing/minitest.rb
108
114
  - lib/abstract_notifier/testing/rspec.rb
@@ -138,11 +144,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
138
144
  version: '2.7'
139
145
  required_rubygems_version: !ruby/object:Gem::Requirement
140
146
  requirements:
141
- - - ">"
147
+ - - ">="
142
148
  - !ruby/object:Gem::Version
143
- version: 1.3.1
149
+ version: '0'
144
150
  requirements: []
145
- rubygems_version: 3.4.8
151
+ rubygems_version: 3.4.20
146
152
  signing_key:
147
153
  specification_version: 4
148
154
  summary: Ruby and Rails framework for managing all types of notifications in one place