active_delivery 1.0.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d05087cb016af8444f7668b81681675248b7b63f41f77a1d9fdb22b65ccb6845
|
4
|
+
data.tar.gz: d60851ccb9d9ea6557d131289942edf57b39c79a499e37d41f8f1d06b480446f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5a94a2fd3e2f19d9e99c0df6494752b23649802b064b390ea50d65506b01beb57650f31f5cd140495d6befa847a65423cc141ec8c313a6a116de69a4cd5576df
|
7
|
+
data.tar.gz: 4febe7f49e314d27f370642fa43085c5020dae8bac7fa9a3512514a6b861d17b726d4685ed91f081eab22f4c2af68a4117d37f8f29a576c78fe323f2c73b58e2
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.2.0 (2024-02-05)
|
6
|
+
|
7
|
+
- Add Minitest assertions (`assert_deliveries`, `assert_no_deliveries`, `assert_delivery_enqueued`). ([@palkan][])
|
8
|
+
|
9
|
+
## 1.1.0 (2023-12-01) ❄️
|
10
|
+
|
11
|
+
- Support delayed delivery options (e.g, `wait_until`). ([@palkan][])
|
12
|
+
|
5
13
|
## 📬 1.0.0 (2023-08-29)
|
6
14
|
|
7
15
|
- Add `resolver_pattern` option to specify naming pattern for notifiers without using Procs. ([@palkan][])
|
@@ -43,6 +51,20 @@
|
|
43
51
|
|
44
52
|
- Add `#deliver_via(*lines)` RSpec matcher. ([@palkan][])
|
45
53
|
|
54
|
+
- **BREAKING** The `#resolve_class` method in Line classes now receive a delivery class instead of a name:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
# before
|
58
|
+
def resolve_class(name)
|
59
|
+
name.gsub(/Delivery$/, "Channel").safe_constantize
|
60
|
+
end
|
61
|
+
|
62
|
+
# after
|
63
|
+
def resolve_class(name)
|
64
|
+
name.to_s.gsub(/Delivery$/, "Channel").safe_constantize
|
65
|
+
end
|
66
|
+
```
|
67
|
+
|
46
68
|
- Provide ActionMailer-like interface to trigger notifications. ([@palkan][])
|
47
69
|
|
48
70
|
Now you can send notifications as follows:
|
data/README.md
CHANGED
@@ -10,6 +10,8 @@ Since v1.0, Active Delivery is bundled with [Abstract Notifier](https://github.c
|
|
10
10
|
|
11
11
|
📖 Read the introduction post: ["Crafting user notifications in Rails with Active Delivery"](https://evilmartians.com/chronicles/crafting-user-notifications-in-rails-with-active-delivery)
|
12
12
|
|
13
|
+
📖 Read more about designing notifications layer in Ruby on Rails applications in the [Layered design for Ruby on Rails applications](https://www.packtpub.com/product/layered-design-for-ruby-on-rails-applications/9781801813785) book.
|
14
|
+
|
13
15
|
<a href="https://evilmartians.com/?utm_source=action_policy">
|
14
16
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
15
17
|
|
@@ -56,7 +58,7 @@ end
|
|
56
58
|
Add this line to your application's Gemfile:
|
57
59
|
|
58
60
|
```ruby
|
59
|
-
gem "active_delivery", "1.0
|
61
|
+
gem "active_delivery", "~> 1.0"
|
60
62
|
```
|
61
63
|
|
62
64
|
And then execute:
|
@@ -113,6 +115,9 @@ PostsDelivery.published(user, post).deliver_later
|
|
113
115
|
PostsMailer.published(user, post).deliver_later
|
114
116
|
PostsSMSNotifier.published(user, post).notify_later
|
115
117
|
|
118
|
+
# You can also pass options supported by your async executor (such as ActiveJob)
|
119
|
+
PostsDelivery.published(user, post).deliver_later(wait_until: 1.day.from_now)
|
120
|
+
|
116
121
|
# and whaterver your ActionCableDeliveryLine does
|
117
122
|
# under the hood.
|
118
123
|
```
|
@@ -288,17 +293,36 @@ MyDeliver.notify(:something_wicked_this_way_comes)
|
|
288
293
|
|
289
294
|
## Testing
|
290
295
|
|
291
|
-
|
296
|
+
### Setup
|
297
|
+
|
298
|
+
Test mode is activated automatically if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". Otherwise, add `require "active_delivery/testing/rspec"` to your `spec_helper.rb` / `rails_helper.rb` manually or `require "active_delivery/testing/minitest"`. This is also required if you're using Spring in the test environment (e.g. with help of [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)).
|
299
|
+
|
300
|
+
For Minitest, you also MUST include the test helper into your test class. For example:
|
301
|
+
|
302
|
+
```ruby
|
303
|
+
class ActiveSupport::TestCase
|
304
|
+
# ...
|
305
|
+
include ActiveDelivery::TestHelper
|
306
|
+
end
|
307
|
+
```
|
292
308
|
|
293
309
|
### Deliveries
|
294
310
|
|
295
|
-
Active Delivery provides an elegant way to test deliveries in your code (i.e., when you want to check whether a notification has been sent) through a `have_delivered_to` matcher:
|
311
|
+
Active Delivery provides an elegant way to test deliveries in your code (i.e., when you want to check whether a notification has been sent) through a `have_delivered_to` RSpec matcher or `assert_delivery_enqueued` Minitest assertion:
|
296
312
|
|
297
313
|
```ruby
|
314
|
+
# RSpec
|
298
315
|
it "delivers notification" do
|
299
316
|
expect { subject }.to have_delivered_to(Community::EventsDelivery, :modified, event)
|
300
317
|
.with(profile: profile)
|
301
318
|
end
|
319
|
+
|
320
|
+
# Minitest
|
321
|
+
def test_delivers_notification
|
322
|
+
assert_delivery_enqueued(Community::EventsDelivery, :modified, with: [event]) do
|
323
|
+
some_action
|
324
|
+
end
|
325
|
+
end
|
302
326
|
```
|
303
327
|
|
304
328
|
You can also use such RSpec features as compound expectations and composed matchers:
|
@@ -317,14 +341,27 @@ end
|
|
317
341
|
If you want to test that no notification is delivered you can use negation
|
318
342
|
|
319
343
|
```ruby
|
344
|
+
# RSpec
|
320
345
|
specify "when event is not found" do
|
321
346
|
expect do
|
322
347
|
described_class.perform_now(profile.id, "123", "one_hour_before")
|
323
348
|
end.not_to have_delivered_to(Community::EventsDelivery)
|
324
349
|
end
|
350
|
+
|
351
|
+
# Minitest
|
352
|
+
def test_no_notification_if_event_is_not_found
|
353
|
+
assert_no_deliveries do
|
354
|
+
some_action
|
355
|
+
end
|
356
|
+
|
357
|
+
# Alternatively, you can use the positive assertion
|
358
|
+
assert_deliveries(0) do
|
359
|
+
some_action
|
360
|
+
end
|
361
|
+
end
|
325
362
|
```
|
326
363
|
|
327
|
-
|
364
|
+
With RSpec, you can also use the `#have_not_delivered_to` matcher:
|
328
365
|
|
329
366
|
```ruby
|
330
367
|
specify "when event is not found" do
|
@@ -357,7 +394,7 @@ describe PostsDelivery do
|
|
357
394
|
end
|
358
395
|
```
|
359
396
|
|
360
|
-
You can also use the `#deliver_via`
|
397
|
+
You can also use the `#deliver_via` RSpec matcher as follows:
|
361
398
|
|
362
399
|
```ruby
|
363
400
|
describe PostsDelivery, type: :delivery do
|
@@ -384,8 +421,6 @@ describe PostsDelivery, type: :delivery do
|
|
384
421
|
end
|
385
422
|
```
|
386
423
|
|
387
|
-
**NOTE:** test mode activated automatically if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". Otherwise, add `require "active_delivery/testing/rspec"` to your `spec_helper.rb` / `rails_helper.rb` manually. This is also required if you're using Spring in the test environment (e.g. with help of [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)).
|
388
|
-
|
389
424
|
## Custom "lines"
|
390
425
|
|
391
426
|
The _Line_ class describes the way you want to _transfer_ your deliveries.
|
@@ -407,8 +442,8 @@ class EventPigeon
|
|
407
442
|
alias_method :with, :new
|
408
443
|
|
409
444
|
# delegate delivery action to the instance
|
410
|
-
def message_arrived(*
|
411
|
-
new.message_arrived(*
|
445
|
+
def message_arrived(*)
|
446
|
+
new.message_arrived(*)
|
412
447
|
end
|
413
448
|
end
|
414
449
|
|
@@ -444,17 +479,17 @@ class PigeonLine < ActiveDelivery::Lines::Base
|
|
444
479
|
# Called when we want to send message synchronously
|
445
480
|
# `sender` here either `sender_class` or `sender_class.with(params)`
|
446
481
|
# if params passed.
|
447
|
-
def notify_now(sender, delivery_action,
|
482
|
+
def notify_now(sender, delivery_action, *, **)
|
448
483
|
# For example, our EventPigeon class returns some `Pigeon` object
|
449
|
-
pigeon = sender.public_send(delivery_action,
|
484
|
+
pigeon = sender.public_send(delivery_action, *, **)
|
450
485
|
# PigeonLaunchService do all the sending job
|
451
486
|
PigeonService.launch pigeon
|
452
487
|
end
|
453
488
|
|
454
489
|
# Called when we want to send a message asynchronously.
|
455
490
|
# For example, you can use a background job here.
|
456
|
-
def notify_later(sender, delivery_action,
|
457
|
-
pigeon = sender.public_send(delivery_action,
|
491
|
+
def notify_later(sender, delivery_action, *, **)
|
492
|
+
pigeon = sender.public_send(delivery_action, *, **)
|
458
493
|
# PigeonLaunchService do all the sending job
|
459
494
|
PigeonLaunchJob.perform_later pigeon
|
460
495
|
end
|
@@ -472,8 +507,8 @@ class EventPigeon
|
|
472
507
|
alias_method :with, :new
|
473
508
|
|
474
509
|
# delegate delivery action to the instance
|
475
|
-
def message_arrived(*
|
476
|
-
new.message_arrived(*
|
510
|
+
def message_arrived(*)
|
511
|
+
new.message_arrived(*)
|
477
512
|
end
|
478
513
|
end
|
479
514
|
|
@@ -488,18 +523,18 @@ class EventPigeon
|
|
488
523
|
end
|
489
524
|
|
490
525
|
class PigeonLine < ActiveDelivery::Lines::Base
|
491
|
-
def notify_later(sender, delivery_action,
|
526
|
+
def notify_later(sender, delivery_action, *, **kwargs)
|
492
527
|
# `to_s` is important for serialization. Unless you might have error
|
493
|
-
PigeonLaunchJob.perform_later
|
528
|
+
PigeonLaunchJob.perform_later(sender.class.to_s, delivery_action, *, **kwargs.merge(params: line.params))
|
494
529
|
end
|
495
530
|
end
|
496
531
|
|
497
532
|
class PigeonLaunchJob < ActiveJob::Base
|
498
|
-
def perform(sender, delivery_action,
|
533
|
+
def perform(sender, delivery_action, *, params: nil, **)
|
499
534
|
klass = sender.safe_constantize
|
500
535
|
handler = params ? klass.with(**params) : klass.new
|
501
536
|
|
502
|
-
handler.public_send(delivery_action,
|
537
|
+
handler.public_send(delivery_action, *, **)
|
503
538
|
end
|
504
539
|
end
|
505
540
|
```
|
@@ -588,12 +623,12 @@ class ActionCableDeliveryLine < ActiveDelivery::Line::Base
|
|
588
623
|
# We want to broadcast all notifications
|
589
624
|
def notify?(...) = true
|
590
625
|
|
591
|
-
def notify_now(context, delivery_action,
|
626
|
+
def notify_now(context, delivery_action, *, **)
|
592
627
|
# Skip if no user provided
|
593
628
|
return unless context.user
|
594
629
|
|
595
630
|
payload = {event: [context.scope, delivery_action].join(".")}
|
596
|
-
payload.merge!(serialized_args(
|
631
|
+
payload.merge!(serialized_args(*, **))
|
597
632
|
|
598
633
|
DeliveryChannel.broadcast_to context.user, payload
|
599
634
|
end
|
@@ -706,7 +741,7 @@ end
|
|
706
741
|
|
707
742
|
### Background jobs / async notifications
|
708
743
|
|
709
|
-
To use `#notify_later` you **must** configure an async adapter for Abstract Notifier.
|
744
|
+
To use `#notify_later(**delivery_options)` you **must** configure an async adapter for Abstract Notifier.
|
710
745
|
|
711
746
|
We provide an Active Job adapter out of the box and enable it if Active Job is found.
|
712
747
|
|
@@ -718,13 +753,13 @@ class MyAsyncAdapter
|
|
718
753
|
def initialize(options = {})
|
719
754
|
end
|
720
755
|
|
721
|
-
# `
|
722
|
-
def
|
756
|
+
# `enqueue_delivery` method accepts notifier class, action name and notification parameters
|
757
|
+
def enqueue_delivery(delivery, **options)
|
723
758
|
# <Your implementation here>
|
724
759
|
# To trigger the notification delivery, you can use the following snippet:
|
725
760
|
#
|
726
761
|
# AbstractNotifier::NotificationDelivery.new(
|
727
|
-
# notifier_class
|
762
|
+
# delivery.notifier_class, delivery.action_name, **delivery.delivery_params
|
728
763
|
# ).notify_now
|
729
764
|
end
|
730
765
|
end
|
@@ -11,15 +11,24 @@ module AbstractNotifier
|
|
11
11
|
|
12
12
|
DEFAULT_QUEUE = "notifiers"
|
13
13
|
|
14
|
-
attr_reader :job
|
14
|
+
attr_reader :job, :queue
|
15
15
|
|
16
16
|
def initialize(queue: DEFAULT_QUEUE, job: DeliveryJob)
|
17
|
-
@job = job
|
17
|
+
@job = job
|
18
|
+
@queue = queue
|
18
19
|
end
|
19
20
|
|
20
21
|
def enqueue(...)
|
21
|
-
job.perform_later(...)
|
22
|
+
job.set(queue: queue).perform_later(...)
|
22
23
|
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :enqueue)
|
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
|
23
32
|
end
|
24
33
|
end
|
25
34
|
end
|
@@ -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: params, args: args, kwargs: 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: params, args: args, kwargs: kwargs}; end
|
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: params, args: args, kwargs: 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: args, kwargs: 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; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :process_action)
|
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
|
@@ -12,9 +12,9 @@ module ActiveDelivery
|
|
12
12
|
@metadata = metadata.freeze
|
13
13
|
end
|
14
14
|
|
15
|
-
def deliver_later ; owner.perform_notify(self); end
|
15
|
+
def deliver_later(**opts) ; owner.perform_notify(self, enqueue_options: opts); end
|
16
16
|
|
17
|
-
def deliver_now ; owner.perform_notify(self, sync: true); end
|
17
|
+
def deliver_now(**opts) ; owner.perform_notify(self, sync: true); end
|
18
18
|
|
19
19
|
def delivery_class ; owner.class; end
|
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, *__rest__, &__block__)
|
38
38
|
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later)
|
39
39
|
|
40
|
-
def
|
40
|
+
def notify_later_with_options(handler, enqueue_options, mid, *__rest__, &__block__)
|
41
|
+
notify_later(handler, mid, *__rest__, &__block__)
|
42
|
+
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later_with_options)
|
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
|
@@ -19,6 +19,10 @@ module ActiveDelivery
|
|
19
19
|
def notify_later(mailer, mid, *__rest__, &__block__)
|
20
20
|
mailer.public_send(mid, *__rest__, &__block__).deliver_later
|
21
21
|
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later)
|
22
|
+
|
23
|
+
def notify_later_with_options(mailer, enqueue_options, mid, *__rest__, &__block__)
|
24
|
+
mailer.public_send(mid, *__rest__, &__block__).deliver_later(**enqueue_options)
|
25
|
+
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later_with_options)
|
22
26
|
end
|
23
27
|
|
24
28
|
ActiveDelivery::Base.register_line :mailer, Mailer, resolver: Mailer::DEFAULT_RESOLVER
|