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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +51 -23
- data/README.md +110 -7
- data/lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb +36 -0
- data/lib/.rbnext/3.0/abstract_notifier/base.rb +223 -0
- data/lib/.rbnext/3.0/active_delivery/base.rb +143 -18
- data/lib/.rbnext/3.0/active_delivery/callbacks.rb +21 -17
- data/lib/.rbnext/3.0/active_delivery/lines/base.rb +53 -15
- data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +6 -1
- data/lib/.rbnext/3.0/active_delivery/testing.rb +62 -0
- data/lib/.rbnext/3.1/abstract_notifier/async_adapters/active_job.rb +36 -0
- data/lib/.rbnext/3.1/abstract_notifier/base.rb +223 -0
- data/lib/.rbnext/3.1/active_delivery/base.rb +141 -16
- data/lib/.rbnext/3.1/active_delivery/lines/base.rb +53 -15
- data/lib/abstract_notifier/async_adapters/active_job.rb +15 -6
- data/lib/abstract_notifier/base.rb +64 -19
- data/lib/abstract_notifier/callbacks.rb +94 -0
- data/lib/abstract_notifier/testing.rb +9 -5
- data/lib/abstract_notifier/version.rb +1 -1
- data/lib/abstract_notifier.rb +1 -0
- data/lib/active_delivery/base.rb +6 -5
- data/lib/active_delivery/lines/base.rb +34 -3
- data/lib/active_delivery/lines/mailer.rb +4 -0
- data/lib/active_delivery/lines/notifier.rb +5 -1
- data/lib/active_delivery/version.rb +1 -1
- metadata +12 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dfc96638e6c019dd1b13f57eef8ab3b9bf2a34df2b58ca7f7ea8ea167886c59e
|
4
|
+
data.tar.gz: b50d556edef4a01fc7d1a8af1659a05f99a821584c2d4818418401f3a03350dd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 518d226591f5114383c2d138c163b4bac13e62666cce4f7edde7d2df5bca7e770c17576f00fee378b9a8733cc72372960efa2692839a6dc4d274d130787fcb41
|
7
|
+
data.tar.gz: 2bd5eed0775fd32d83ca45364d8ecdc4f28c275eb2ffbbcad768ef98d8581b1cd0e66eb72f7e012c9d4dbd0a18e8a72cb40a858f0459614e18a5ee3e24fb0420
|
data/CHANGELOG.md
CHANGED
@@ -2,47 +2,75 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 1.1.0 (2023-12-01) ❄️
|
6
|
+
|
7
|
+
- Support delayed delivery options (e.g, `wait_until`). ([@palkan][])
|
8
|
+
|
9
|
+
## 📬 1.0.0 (2023-08-29)
|
10
|
+
|
11
|
+
- Add `resolver_pattern` option to specify naming pattern for notifiers without using Procs. ([@palkan][])
|
12
|
+
|
13
|
+
- [!IMPORTANT] Notifier's `#notify_later` now do not process the action right away, only enqueue the job. ([@palkan][]).
|
14
|
+
|
15
|
+
This matches the Action Mailer behaviour. Now, the action is only invoked before the delivery attempt.
|
16
|
+
|
17
|
+
- Add callbacks support to Abstract Notifier (`before_action`, `after_deliver`, etc.). ([@palkan][])
|
18
|
+
|
5
19
|
- **Merge in abstract_notifier** ([@palkan][])
|
6
20
|
|
7
|
-
[Abstract Notifier](https://github.com/palkan/abstract_notifier) is now a part of Active Delivery.
|
21
|
+
[Abstract Notifier](https://github.com/palkan/abstract_notifier) is now a part of Active Delivery.
|
8
22
|
|
9
23
|
- Add ability to specify delivery actions explicitly and disable implicit proxying. ([@palkan][])
|
10
24
|
|
11
|
-
You can disable default Active Delivery behaviour of proxying action methods to underlying lines via the `ActiveDelivery.deliver_actions_required = true` configuration option. Then, in each delivery class, you can specify the available actions via the `.delivers` method:
|
25
|
+
You can disable default Active Delivery behaviour of proxying action methods to underlying lines via the `ActiveDelivery.deliver_actions_required = true` configuration option. Then, in each delivery class, you can specify the available actions via the `.delivers` method:
|
12
26
|
|
13
|
-
```ruby
|
14
|
-
class PostMailer < ApplicationMailer
|
15
|
-
|
16
|
-
|
17
|
-
|
27
|
+
```ruby
|
28
|
+
class PostMailer < ApplicationMailer
|
29
|
+
def published(post)
|
30
|
+
# ...
|
31
|
+
end
|
18
32
|
|
19
|
-
|
20
|
-
|
33
|
+
def whatever(post)
|
34
|
+
# ...
|
35
|
+
end
|
21
36
|
end
|
22
|
-
end
|
23
37
|
|
24
|
-
ActiveDelivery.deliver_actions_required = true
|
38
|
+
ActiveDelivery.deliver_actions_required = true
|
25
39
|
|
26
|
-
class PostDelivery < ApplicationDelivery
|
27
|
-
|
28
|
-
end
|
40
|
+
class PostDelivery < ApplicationDelivery
|
41
|
+
delivers :published
|
42
|
+
end
|
29
43
|
|
30
|
-
PostDelivery.published(post) #=> ok
|
31
|
-
PostDelivery.whatever(post) #=> raises NoMethodError
|
32
|
-
```
|
44
|
+
PostDelivery.published(post) #=> ok
|
45
|
+
PostDelivery.whatever(post) #=> raises NoMethodError
|
46
|
+
```
|
33
47
|
|
34
48
|
- Add `#deliver_via(*lines)` RSpec matcher. ([@palkan][])
|
35
49
|
|
50
|
+
- **BREAKING** The `#resolve_class` method in Line classes now receive a delivery class instead of a name:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
# before
|
54
|
+
def resolve_class(name)
|
55
|
+
name.gsub(/Delivery$/, "Channel").safe_constantize
|
56
|
+
end
|
57
|
+
|
58
|
+
# after
|
59
|
+
def resolve_class(name)
|
60
|
+
name.to_s.gsub(/Delivery$/, "Channel").safe_constantize
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
36
64
|
- Provide ActionMailer-like interface to trigger notifications. ([@palkan][])
|
37
65
|
|
38
|
-
Now you can send notifications as follows:
|
66
|
+
Now you can send notifications as follows:
|
39
67
|
|
40
|
-
```ruby
|
41
|
-
MyDelivery.with(user:).new_notification(payload).deliver_later
|
68
|
+
```ruby
|
69
|
+
MyDelivery.with(user:).new_notification(payload).deliver_later
|
42
70
|
|
43
|
-
# Equals to the old (and still supported)
|
44
|
-
MyDelivery.with(user:).notify(:new_notification, payload)
|
45
|
-
```
|
71
|
+
# Equals to the old (and still supported)
|
72
|
+
MyDelivery.with(user:).notify(:new_notification, payload)
|
73
|
+
```
|
46
74
|
|
47
75
|
- Support passing a string class name as a handler class. ([@palkan][])
|
48
76
|
|
data/README.md
CHANGED
@@ -79,8 +79,16 @@ class ApplicationDelivery < ActiveDelivery::Base
|
|
79
79
|
# Mailers are enabled by default, everything else must be declared explicitly
|
80
80
|
|
81
81
|
# For example, you can use a notifier line (see below) with a custom resolver
|
82
|
+
# (the argument is the delivery class)
|
82
83
|
register_line :sms, ActiveDelivery::Lines::Notifier,
|
83
|
-
resolver: -> { _1.name.gsub(/Delivery$/, "SMSNotifier").safe_constantize }
|
84
|
+
resolver: -> { _1.name.gsub(/Delivery$/, "SMSNotifier").safe_constantize } #=> PostDelivery -> PostSMSNotifier
|
85
|
+
|
86
|
+
# Or you can use a name pattern to resolve notifier classes for delivery classes
|
87
|
+
# Available placeholders are:
|
88
|
+
# - delivery_class — full delivery class name
|
89
|
+
# - delivery_name — full delivery class name without the "Delivery" suffix
|
90
|
+
register_line :webhook, ActiveDelivery::Lines::Notifier,
|
91
|
+
resolver_pattern: "%{delivery_name}WebhookNotifier" #=> PostDelivery -> PostWebhookNotifier
|
84
92
|
|
85
93
|
register_line :cable, ActionCableDeliveryLine
|
86
94
|
# and more
|
@@ -105,6 +113,9 @@ PostsDelivery.published(user, post).deliver_later
|
|
105
113
|
PostsMailer.published(user, post).deliver_later
|
106
114
|
PostsSMSNotifier.published(user, post).notify_later
|
107
115
|
|
116
|
+
# You can also pass options supported by your async executor (such as ActiveJob)
|
117
|
+
PostsDelivery.published(user, post).deliver_later(wait_until: 1.day.from_now)
|
118
|
+
|
108
119
|
# and whaterver your ActionCableDeliveryLine does
|
109
120
|
# under the hood.
|
110
121
|
```
|
@@ -153,6 +164,45 @@ PostDelivery.published(post) #=> ok
|
|
153
164
|
PostDelivery.whatever(post) #=> raises NoMethodError
|
154
165
|
```
|
155
166
|
|
167
|
+
### Organizing delivery and notifier classes
|
168
|
+
|
169
|
+
There are two common ways to organize delivery and notifier classes in your codebase:
|
170
|
+
|
171
|
+
```txt
|
172
|
+
app/
|
173
|
+
deliveries/ deliveries/
|
174
|
+
application_delivery.rb application_delivery.rb
|
175
|
+
post_delivery.rb post_delivery/
|
176
|
+
user_delivery.rb post_mailer.rb
|
177
|
+
mailers/ post_sms_notifier.rb
|
178
|
+
application_mailer.rb post_webhook_notifier.rb
|
179
|
+
post_mailer.rb post_delivery.rb
|
180
|
+
user_mailer.rb user_delivery/
|
181
|
+
notifiers/ user_mailer.rb
|
182
|
+
application_notifier.rb user_sms_notifier.rb
|
183
|
+
post_sms_notifier.rb user_webhook_notifier.rb
|
184
|
+
post_webhook_notifier.rb user_delivery.rb
|
185
|
+
user_sms_notifier.rb
|
186
|
+
user_webhook_notifier.rb
|
187
|
+
```
|
188
|
+
|
189
|
+
The left side is a _flat_ structure, more typical for classic Rails applications. The right side follows the _sidecar pattern_ and aims to localize all the code related to a specific delivery class in a single directory. To use the sidecar version, you need to configure your delivery lines as follows:
|
190
|
+
|
191
|
+
```ruby
|
192
|
+
class ApplicationDelivery < ActiveDelivery::Base
|
193
|
+
self.abstract_class = true
|
194
|
+
|
195
|
+
register_line :mailer, ActiveDelivery::Lines::Mailer,
|
196
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_mailer"
|
197
|
+
register_line :sms,
|
198
|
+
notifier: true,
|
199
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_sms_notifier"
|
200
|
+
register_line :webhook,
|
201
|
+
notifier: true,
|
202
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_webhook_notifier"
|
203
|
+
end
|
204
|
+
```
|
205
|
+
|
156
206
|
### Customizing delivery handlers
|
157
207
|
|
158
208
|
You can specify a mailer class explicitly:
|
@@ -659,7 +709,7 @@ end
|
|
659
709
|
|
660
710
|
### Background jobs / async notifications
|
661
711
|
|
662
|
-
To use `#notify_later` you **must** configure an async adapter for Abstract Notifier.
|
712
|
+
To use `#notify_later(**delivery_options)` you **must** configure an async adapter for Abstract Notifier.
|
663
713
|
|
664
714
|
We provide an Active Job adapter out of the box and enable it if Active Job is found.
|
665
715
|
|
@@ -671,11 +721,14 @@ class MyAsyncAdapter
|
|
671
721
|
def initialize(options = {})
|
672
722
|
end
|
673
723
|
|
674
|
-
# `
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
#
|
724
|
+
# `enqueue_delivery` method accepts notifier class, action name and notification parameters
|
725
|
+
def enqueue_delivery(delivery, **options)
|
726
|
+
# <Your implementation here>
|
727
|
+
# To trigger the notification delivery, you can use the following snippet:
|
728
|
+
#
|
729
|
+
# AbstractNotifier::NotificationDelivery.new(
|
730
|
+
# delivery.notifier_class, delivery.action_name, **delivery.delivery_params
|
731
|
+
# ).notify_now
|
679
732
|
end
|
680
733
|
end
|
681
734
|
|
@@ -688,6 +741,53 @@ class EventsNotifier < AbstractNotifier::Base
|
|
688
741
|
end
|
689
742
|
```
|
690
743
|
|
744
|
+
### Action and Delivery Callbacks
|
745
|
+
|
746
|
+
**NOTE:** callbacks are only available if ActiveSupport is present in the application's runtime.
|
747
|
+
|
748
|
+
```ruby
|
749
|
+
# Run method before building a notification payload
|
750
|
+
# NOTE: when `false` is returned the execution is halted
|
751
|
+
before_action :do_something
|
752
|
+
|
753
|
+
# Run method before delivering notification
|
754
|
+
# NOTE: when `false` is returned the execution is halted
|
755
|
+
before_deliver :do_something
|
756
|
+
|
757
|
+
# Run method after the notification payload was build but before delivering
|
758
|
+
after_action :verify_notification_payload
|
759
|
+
|
760
|
+
# Run method after the actual delivery was performed
|
761
|
+
after_deliver :mark_user_as_notified, if: -> { params[:user].present? }
|
762
|
+
|
763
|
+
# after_ and around_ callbacks are also supported
|
764
|
+
after_action_ :cleanup
|
765
|
+
|
766
|
+
around_deliver :set_context
|
767
|
+
|
768
|
+
# You can also skip callbacks in sub-classes
|
769
|
+
skip_before_action :do_something, only: %i[some_reminder]
|
770
|
+
```
|
771
|
+
|
772
|
+
Example:
|
773
|
+
|
774
|
+
```ruby
|
775
|
+
class MyNotifier < AbstractNotifier::Base
|
776
|
+
# Log sent notifications
|
777
|
+
after_deliver do
|
778
|
+
# You can access the notification name within the instance or
|
779
|
+
MyLogger.info "Notification sent: #{notification_name}"
|
780
|
+
end
|
781
|
+
|
782
|
+
def some_event(body)
|
783
|
+
notification(body:)
|
784
|
+
end
|
785
|
+
end
|
786
|
+
|
787
|
+
MyNotifier.some_event("hello")
|
788
|
+
#=> Notification sent: some_event
|
789
|
+
```
|
790
|
+
|
691
791
|
### Delivery modes
|
692
792
|
|
693
793
|
For test/development purposes there are two special _global_ delivery modes:
|
@@ -772,6 +872,9 @@ class ApplicationDelivery < ActiveDelivery::Base
|
|
772
872
|
# `*Delivery` -> `*CustomNotifier`
|
773
873
|
register_line :custom_notifier, notifier: true, suffix: "CustomNotifier"
|
774
874
|
|
875
|
+
# Or using a custom pattern
|
876
|
+
register_line :custom_notifier, notifier: true, resolver_pattern: "%{delivery_name}CustomNotifier"
|
877
|
+
|
775
878
|
# Or you can specify a Proc object to do custom resolution:
|
776
879
|
register_line :some_notifier, notifier: true,
|
777
880
|
resolver: ->(delivery_class) { resolve_somehow(delivery_class) }
|
@@ -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, *__rest__, &__block__)
|
8
|
+
AbstractNotifier::NotificationDelivery.new(notifier_class.constantize, *__rest__, &__block__).notify_now
|
9
|
+
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :perform)
|
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; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :enqueue)
|
24
|
+
|
25
|
+
def enqueue_delivery(delivery, **opts)
|
26
|
+
job.set(queue: queue, **opts).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(**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: 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?(*args)
|
77
|
+
notifier_class.respond_to_missing?(*args)
|
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
|