active_delivery 1.0.0.rc2 → 1.1.0

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