active_delivery 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +60 -25
- data/lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb +12 -3
- data/lib/.rbnext/3.0/abstract_notifier/base.rb +223 -0
- data/lib/.rbnext/3.0/active_delivery/base.rb +14 -13
- data/lib/.rbnext/3.0/active_delivery/lines/base.rb +14 -2
- data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +4 -0
- data/lib/.rbnext/3.0/active_delivery/testing/minitest.rb +58 -0
- data/lib/.rbnext/3.0/active_delivery/testing.rb +1 -0
- data/lib/.rbnext/3.1/abstract_notifier/async_adapters/active_job.rb +36 -0
- data/lib/.rbnext/3.1/abstract_notifier/base.rb +16 -10
- data/lib/.rbnext/3.1/active_delivery/base.rb +14 -13
- data/lib/.rbnext/3.1/active_delivery/lines/base.rb +14 -2
- data/lib/.rbnext/3.1/active_delivery/testing/minitest.rb +58 -0
- data/lib/.rbnext/3.2/abstract_notifier/async_adapters/active_job.rb +36 -0
- data/lib/.rbnext/3.2/abstract_notifier/base.rb +223 -0
- data/lib/.rbnext/3.2/abstract_notifier/testing/rspec.rb +164 -0
- data/lib/.rbnext/3.2/abstract_notifier/testing.rb +53 -0
- data/lib/.rbnext/3.2/active_delivery/base.rb +249 -0
- data/lib/.rbnext/3.2/active_delivery/lines/base.rb +101 -0
- data/lib/.rbnext/3.2/active_delivery/lines/notifier.rb +57 -0
- data/lib/.rbnext/3.2/active_delivery/testing/rspec.rb +222 -0
- data/lib/abstract_notifier/async_adapters/active_job.rb +12 -3
- data/lib/abstract_notifier/base.rb +15 -9
- data/lib/abstract_notifier/testing/minitest.rb +1 -1
- data/lib/abstract_notifier/testing/rspec.rb +4 -4
- data/lib/abstract_notifier/testing.rb +2 -2
- data/lib/active_delivery/base.rb +14 -13
- data/lib/active_delivery/lines/base.rb +14 -2
- data/lib/active_delivery/lines/mailer.rb +4 -0
- data/lib/active_delivery/lines/notifier.rb +8 -4
- data/lib/active_delivery/testing/minitest.rb +58 -0
- data/lib/active_delivery/testing/rspec.rb +4 -4
- data/lib/active_delivery/testing.rb +1 -0
- data/lib/active_delivery/version.rb +1 -1
- metadata +20 -7
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveDelivery
|
4
|
+
module TestHelper
|
5
|
+
def assert_deliveries(count)
|
6
|
+
TestDelivery.enable { yield }
|
7
|
+
|
8
|
+
assert_equal TestDelivery.store.count, count, "Expected #{count} deliveries, got #{TestDelivery.store.count}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_no_deliveries(&__block__) ; assert_deliveries(0, &__block__); end
|
12
|
+
|
13
|
+
def assert_delivery_enqueued(delivery_class, event, count: 1, params: nil, with: nil)
|
14
|
+
TestDelivery.enable { yield }
|
15
|
+
|
16
|
+
deliveries = TestDelivery.store
|
17
|
+
|
18
|
+
if with
|
19
|
+
args = with
|
20
|
+
kwargs = args.pop if args.last.is_a?(Hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
matching_deliveries, _unmatching_deliveries =
|
24
|
+
deliveries.partition do |(delivery, options)|
|
25
|
+
next false if delivery_class != delivery.owner.class
|
26
|
+
|
27
|
+
next false if event != delivery.notification
|
28
|
+
|
29
|
+
next false if params && !hash_include?(delivery.owner.params, params)
|
30
|
+
|
31
|
+
next true unless with
|
32
|
+
|
33
|
+
actual_args = delivery.params
|
34
|
+
actual_kwargs = delivery.options
|
35
|
+
|
36
|
+
next false unless args.each.with_index.all? do |arg, i|
|
37
|
+
arg === actual_args[i]
|
38
|
+
end
|
39
|
+
|
40
|
+
next false unless kwargs.all? do |k, v|
|
41
|
+
v === actual_kwargs[k]
|
42
|
+
end
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_equal count, matching_deliveries.count, "Expected #{count} deliveries, got #{deliveries.count}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def hash_include?(haystack, needle)
|
53
|
+
needle.all? do |k, v|
|
54
|
+
haystack.key?(k) && haystack[k] == v
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
module AsyncAdapters
|
5
|
+
class ActiveJob
|
6
|
+
class DeliveryJob < ::ActiveJob::Base
|
7
|
+
def perform(notifier_class, ...)
|
8
|
+
AbstractNotifier::NotificationDelivery.new(notifier_class.constantize, ...).notify_now
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
DEFAULT_QUEUE = "notifiers"
|
13
|
+
|
14
|
+
attr_reader :job, :queue
|
15
|
+
|
16
|
+
def initialize(queue: DEFAULT_QUEUE, job: DeliveryJob)
|
17
|
+
@job = job
|
18
|
+
@queue = queue
|
19
|
+
end
|
20
|
+
|
21
|
+
def enqueue(...)
|
22
|
+
job.set(queue: queue).perform_later(...)
|
23
|
+
end
|
24
|
+
|
25
|
+
def enqueue_delivery(delivery, **__kwrest__)
|
26
|
+
job.set(queue: queue, **__kwrest__).perform_later(
|
27
|
+
delivery.notifier_class.name,
|
28
|
+
delivery.action_name,
|
29
|
+
**delivery.delivery_params
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
AbstractNotifier.async_adapter ||= :active_job
|
@@ -5,10 +5,10 @@ module AbstractNotifier
|
|
5
5
|
# information about the current notifier class
|
6
6
|
# and knows how to trigger the delivery
|
7
7
|
class NotificationDelivery
|
8
|
-
attr_reader :action_name
|
8
|
+
attr_reader :action_name, :notifier_class
|
9
9
|
|
10
|
-
def initialize(
|
11
|
-
@
|
10
|
+
def initialize(notifier_class, action_name, params: {}, args: [], kwargs: {})
|
11
|
+
@notifier_class = notifier_class
|
12
12
|
@action_name = action_name
|
13
13
|
@params = params
|
14
14
|
@args = args
|
@@ -23,8 +23,12 @@ module AbstractNotifier
|
|
23
23
|
|
24
24
|
alias_method :notification, :processed
|
25
25
|
|
26
|
-
def notify_later
|
27
|
-
|
26
|
+
def notify_later(**__kwrest__)
|
27
|
+
if notifier_class.async_adapter.respond_to?(:enqueue_delivery)
|
28
|
+
notifier_class.async_adapter.enqueue_delivery(self, **__kwrest__)
|
29
|
+
else
|
30
|
+
notifier_class.async_adapter.enqueue(notifier_class.name, action_name, params: params, args: args, kwargs: kwargs)
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
def notify_now
|
@@ -33,12 +37,14 @@ module AbstractNotifier
|
|
33
37
|
notifier.deliver!(notification)
|
34
38
|
end
|
35
39
|
|
40
|
+
def delivery_params ; {params: params, args: args, kwargs: kwargs}; end
|
41
|
+
|
36
42
|
private
|
37
43
|
|
38
|
-
attr_reader :
|
44
|
+
attr_reader :params, :args, :kwargs
|
39
45
|
|
40
46
|
def notifier
|
41
|
-
@notifier ||=
|
47
|
+
@notifier ||= notifier_class.new(action_name, **params)
|
42
48
|
end
|
43
49
|
end
|
44
50
|
|
@@ -67,8 +73,8 @@ module AbstractNotifier
|
|
67
73
|
end
|
68
74
|
# rubocop:enable Style/MethodMissingSuper
|
69
75
|
|
70
|
-
def respond_to_missing?(*
|
71
|
-
notifier_class.respond_to_missing?(*
|
76
|
+
def respond_to_missing?(*__rest__)
|
77
|
+
notifier_class.respond_to_missing?(*__rest__)
|
72
78
|
end
|
73
79
|
end
|
74
80
|
|
@@ -178,7 +184,7 @@ module AbstractNotifier
|
|
178
184
|
|
179
185
|
def process_action(...)
|
180
186
|
public_send(...)
|
181
|
-
end
|
187
|
+
end
|
182
188
|
|
183
189
|
def deliver!(notification)
|
184
190
|
self.class.driver.call(notification.payload)
|
@@ -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
|
@@ -77,8 +77,8 @@ module ActiveDelivery
|
|
77
77
|
|
78
78
|
# The same as .notify but delivers synchronously
|
79
79
|
# (i.e. #deliver_now for mailers)
|
80
|
-
def notify!(mid, *
|
81
|
-
notify(mid, *
|
80
|
+
def notify!(mid, *__rest__, **hargs)
|
81
|
+
notify(mid, *__rest__, **hargs, sync: true)
|
82
82
|
end
|
83
83
|
|
84
84
|
alias_method :notify_now, :notify!
|
@@ -93,7 +93,7 @@ module ActiveDelivery
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
-
def register_line(line_id, line_class = nil, notifier: nil, **
|
96
|
+
def register_line(line_id, line_class = nil, notifier: nil, **__kwrest__)
|
97
97
|
raise ArgumentError, "A line class or notifier configuration must be provided" if line_class.nil? && notifier.nil?
|
98
98
|
|
99
99
|
# Configure Notifier
|
@@ -101,7 +101,7 @@ module ActiveDelivery
|
|
101
101
|
line_class = ActiveDelivery::Lines::Notifier
|
102
102
|
end
|
103
103
|
|
104
|
-
delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **
|
104
|
+
delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **__kwrest__)
|
105
105
|
|
106
106
|
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
107
107
|
def #{line_id}(val)
|
@@ -152,13 +152,13 @@ module ActiveDelivery
|
|
152
152
|
super
|
153
153
|
end
|
154
154
|
|
155
|
-
def method_missing(mid, *
|
155
|
+
def method_missing(mid, *__rest__, **__kwrest__)
|
156
156
|
return super unless respond_to_missing?(mid)
|
157
157
|
|
158
158
|
# Lazily define a class method to avoid lookups
|
159
159
|
delivers(mid)
|
160
160
|
|
161
|
-
public_send(mid, *
|
161
|
+
public_send(mid, *__rest__, **__kwrest__)
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
@@ -197,7 +197,7 @@ module ActiveDelivery
|
|
197
197
|
super
|
198
198
|
end
|
199
199
|
|
200
|
-
def method_missing(mid, *
|
200
|
+
def method_missing(mid, *__rest__, **__kwrest__)
|
201
201
|
return super unless respond_to_missing?(mid)
|
202
202
|
|
203
203
|
# Lazily define a method to avoid future lookups
|
@@ -211,27 +211,28 @@ module ActiveDelivery
|
|
211
211
|
end
|
212
212
|
CODE
|
213
213
|
|
214
|
-
public_send(mid, *
|
214
|
+
public_send(mid, *__rest__, **__kwrest__)
|
215
215
|
end
|
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: sync)
|
223
|
+
notify_line(type, line, delivery, sync: sync, enqueue_options: 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: params,
|
234
234
|
sync: sync,
|
235
|
+
enqueue_options: enqueue_options,
|
235
236
|
**delivery.options
|
236
237
|
)
|
237
238
|
true
|
@@ -37,9 +37,21 @@ module ActiveDelivery
|
|
37
37
|
def notify_later(handler, mid, ...)
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
40
|
+
def notify_later_with_options(handler, enqueue_options, mid, ...)
|
41
|
+
notify_later(handler, mid, ...)
|
42
|
+
end
|
43
|
+
|
44
|
+
def notify(mid, *__rest__, params:, sync:, enqueue_options:, **__kwrest__)
|
41
45
|
clazz = params.empty? ? handler_class : handler_class.with(**params)
|
42
|
-
sync
|
46
|
+
if sync
|
47
|
+
return notify_now(clazz, mid, *__rest__, **__kwrest__)
|
48
|
+
end
|
49
|
+
|
50
|
+
if enqueue_options.empty?
|
51
|
+
notify_later(clazz, mid, *__rest__, **__kwrest__)
|
52
|
+
else
|
53
|
+
notify_later_with_options(clazz, enqueue_options, mid, *__rest__, **__kwrest__)
|
54
|
+
end
|
43
55
|
end
|
44
56
|
|
45
57
|
def handler_class
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveDelivery
|
4
|
+
module TestHelper
|
5
|
+
def assert_deliveries(count)
|
6
|
+
TestDelivery.enable { yield }
|
7
|
+
|
8
|
+
assert_equal TestDelivery.store.count, count, "Expected #{count} deliveries, got #{TestDelivery.store.count}"
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_no_deliveries(&__block__) = assert_deliveries(0, &__block__)
|
12
|
+
|
13
|
+
def assert_delivery_enqueued(delivery_class, event, count: 1, params: nil, with: nil)
|
14
|
+
TestDelivery.enable { yield }
|
15
|
+
|
16
|
+
deliveries = TestDelivery.store
|
17
|
+
|
18
|
+
if with
|
19
|
+
args = with
|
20
|
+
kwargs = args.pop if args.last.is_a?(Hash)
|
21
|
+
end
|
22
|
+
|
23
|
+
matching_deliveries, _unmatching_deliveries =
|
24
|
+
deliveries.partition do |(delivery, options)|
|
25
|
+
next false if delivery_class != delivery.owner.class
|
26
|
+
|
27
|
+
next false if event != delivery.notification
|
28
|
+
|
29
|
+
next false if params && !hash_include?(delivery.owner.params, params)
|
30
|
+
|
31
|
+
next true unless with
|
32
|
+
|
33
|
+
actual_args = delivery.params
|
34
|
+
actual_kwargs = delivery.options
|
35
|
+
|
36
|
+
next false unless args.each.with_index.all? do |arg, i|
|
37
|
+
arg === actual_args[i]
|
38
|
+
end
|
39
|
+
|
40
|
+
next false unless kwargs.all? do |k, v|
|
41
|
+
v === actual_kwargs[k]
|
42
|
+
end
|
43
|
+
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
assert_equal count, matching_deliveries.count, "Expected #{count} deliveries, got #{deliveries.count}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def hash_include?(haystack, needle)
|
53
|
+
needle.all? do |k, v|
|
54
|
+
haystack.key?(k) && haystack[k] == v
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
module AsyncAdapters
|
5
|
+
class ActiveJob
|
6
|
+
class DeliveryJob < ::ActiveJob::Base
|
7
|
+
def perform(notifier_class, ...)
|
8
|
+
AbstractNotifier::NotificationDelivery.new(notifier_class.constantize, ...).notify_now
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
DEFAULT_QUEUE = "notifiers"
|
13
|
+
|
14
|
+
attr_reader :job, :queue
|
15
|
+
|
16
|
+
def initialize(queue: DEFAULT_QUEUE, job: DeliveryJob)
|
17
|
+
@job = job
|
18
|
+
@queue = queue
|
19
|
+
end
|
20
|
+
|
21
|
+
def enqueue(...)
|
22
|
+
job.set(queue:).perform_later(...)
|
23
|
+
end
|
24
|
+
|
25
|
+
def enqueue_delivery(delivery, **__kwrest__)
|
26
|
+
job.set(queue:, **__kwrest__).perform_later(
|
27
|
+
delivery.notifier_class.name,
|
28
|
+
delivery.action_name,
|
29
|
+
**delivery.delivery_params
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
AbstractNotifier.async_adapter ||= :active_job
|
@@ -0,0 +1,223 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
# NotificationDelivery payload wrapper which contains
|
5
|
+
# information about the current notifier class
|
6
|
+
# and knows how to trigger the delivery
|
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
|
17
|
+
|
18
|
+
def processed
|
19
|
+
return @processed if instance_variable_defined?(:@processed)
|
20
|
+
|
21
|
+
@processed = notifier.process_action(action_name, *args, **kwargs) || Notification.new(nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
alias_method :notification, :processed
|
25
|
+
|
26
|
+
def notify_later(**__kwrest__)
|
27
|
+
if notifier_class.async_adapter.respond_to?(:enqueue_delivery)
|
28
|
+
notifier_class.async_adapter.enqueue_delivery(self, **__kwrest__)
|
29
|
+
else
|
30
|
+
notifier_class.async_adapter.enqueue(notifier_class.name, action_name, params:, args:, kwargs:)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def notify_now
|
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
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Base class for notifiers
|
61
|
+
class Base
|
62
|
+
class ParamsProxy
|
63
|
+
attr_reader :notifier_class, :params
|
64
|
+
|
65
|
+
def initialize(notifier_class, params)
|
66
|
+
@notifier_class = notifier_class
|
67
|
+
@params = params
|
68
|
+
end
|
69
|
+
|
70
|
+
# rubocop:disable Style/MethodMissingSuper
|
71
|
+
def method_missing(method_name, *args, **kwargs)
|
72
|
+
NotificationDelivery.new(notifier_class, method_name, params:, args:, kwargs:)
|
73
|
+
end
|
74
|
+
# rubocop:enable Style/MethodMissingSuper
|
75
|
+
|
76
|
+
def respond_to_missing?(*__rest__)
|
77
|
+
notifier_class.respond_to_missing?(*__rest__)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class << self
|
82
|
+
attr_writer :driver
|
83
|
+
|
84
|
+
def driver
|
85
|
+
return @driver if instance_variable_defined?(:@driver)
|
86
|
+
|
87
|
+
@driver =
|
88
|
+
if superclass.respond_to?(:driver)
|
89
|
+
superclass.driver
|
90
|
+
else
|
91
|
+
raise "Driver not found for #{name}. " \
|
92
|
+
"Please, specify driver via `self.driver = MyDriver`"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def async_adapter=(args)
|
97
|
+
adapter, options = Array(args)
|
98
|
+
@async_adapter = AsyncAdapters.lookup(adapter, options)
|
99
|
+
end
|
100
|
+
|
101
|
+
def async_adapter
|
102
|
+
return @async_adapter if instance_variable_defined?(:@async_adapter)
|
103
|
+
|
104
|
+
@async_adapter =
|
105
|
+
if superclass.respond_to?(:async_adapter)
|
106
|
+
superclass.async_adapter
|
107
|
+
else
|
108
|
+
AbstractNotifier.async_adapter
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def default(method_name = nil, **hargs, &block)
|
113
|
+
return @defaults_generator = block if block
|
114
|
+
|
115
|
+
return @defaults_generator = proc { send(method_name) } unless method_name.nil?
|
116
|
+
|
117
|
+
@default_params =
|
118
|
+
if superclass.respond_to?(:default_params)
|
119
|
+
superclass.default_params.merge(hargs).freeze
|
120
|
+
else
|
121
|
+
hargs.freeze
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def defaults_generator
|
126
|
+
return @defaults_generator if instance_variable_defined?(:@defaults_generator)
|
127
|
+
|
128
|
+
@defaults_generator =
|
129
|
+
if superclass.respond_to?(:defaults_generator)
|
130
|
+
superclass.defaults_generator
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def default_params
|
135
|
+
return @default_params if instance_variable_defined?(:@default_params)
|
136
|
+
|
137
|
+
@default_params =
|
138
|
+
if superclass.respond_to?(:default_params)
|
139
|
+
superclass.default_params.dup
|
140
|
+
else
|
141
|
+
{}
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def method_missing(method_name, *args, **kwargs)
|
146
|
+
if action_methods.include?(method_name.to_s)
|
147
|
+
NotificationDelivery.new(self, method_name, args:, kwargs:)
|
148
|
+
else
|
149
|
+
super
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def with(params)
|
154
|
+
ParamsProxy.new(self, params)
|
155
|
+
end
|
156
|
+
|
157
|
+
def respond_to_missing?(method_name, _include_private = false)
|
158
|
+
action_methods.include?(method_name.to_s) || super
|
159
|
+
end
|
160
|
+
|
161
|
+
# See https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/actionpack/lib/abstract_controller/base.rb#L74
|
162
|
+
def action_methods
|
163
|
+
@action_methods ||= begin
|
164
|
+
# All public instance methods of this class, including ancestors
|
165
|
+
methods = (public_instance_methods(true) -
|
166
|
+
# Except for public instance methods of Base and its ancestors
|
167
|
+
Base.public_instance_methods(true) +
|
168
|
+
# Be sure to include shadowed public instance methods of this class
|
169
|
+
public_instance_methods(false))
|
170
|
+
|
171
|
+
methods.map!(&:to_s)
|
172
|
+
|
173
|
+
methods.to_set
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
attr_reader :params, :notification_name
|
179
|
+
|
180
|
+
def initialize(notification_name, **params)
|
181
|
+
@notification_name = notification_name
|
182
|
+
@params = params.freeze
|
183
|
+
end
|
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
|
+
|
193
|
+
def notification(**payload)
|
194
|
+
merge_defaults!(payload)
|
195
|
+
|
196
|
+
payload[:body] = implicit_payload_body unless payload.key?(:body)
|
197
|
+
|
198
|
+
raise ArgumentError, "Notification body must be present" if
|
199
|
+
payload[:body].nil? || payload[:body].empty?
|
200
|
+
|
201
|
+
@notification = Notification.new(payload)
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def implicit_payload_body
|
207
|
+
# no-op — override to provide custom logic
|
208
|
+
end
|
209
|
+
|
210
|
+
def merge_defaults!(payload)
|
211
|
+
defaults =
|
212
|
+
if self.class.defaults_generator
|
213
|
+
instance_exec(&self.class.defaults_generator)
|
214
|
+
else
|
215
|
+
self.class.default_params
|
216
|
+
end
|
217
|
+
|
218
|
+
defaults.each do |k, v|
|
219
|
+
payload[k] = v unless payload.key?(k)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|