active_delivery 1.0.0.rc2 → 1.0.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 +33 -23
- data/README.md +106 -6
- data/lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb +27 -0
- data/lib/.rbnext/3.0/active_delivery/base.rb +142 -18
- data/lib/.rbnext/3.0/active_delivery/callbacks.rb +21 -17
- data/lib/.rbnext/3.0/active_delivery/lines/base.rb +39 -13
- data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +2 -1
- data/lib/.rbnext/3.0/active_delivery/testing.rb +62 -0
- data/lib/.rbnext/3.1/abstract_notifier/base.rb +217 -0
- data/lib/.rbnext/3.1/active_delivery/base.rb +140 -16
- data/lib/.rbnext/3.1/active_delivery/lines/base.rb +39 -13
- data/lib/abstract_notifier/async_adapters/active_job.rb +4 -4
- data/lib/abstract_notifier/base.rb +57 -18
- data/lib/abstract_notifier/callbacks.rb +94 -0
- data/lib/abstract_notifier/testing.rb +8 -4
- data/lib/abstract_notifier/version.rb +1 -1
- data/lib/abstract_notifier.rb +1 -0
- data/lib/active_delivery/lines/base.rb +20 -1
- data/lib/active_delivery/lines/notifier.rb +1 -1
- data/lib/active_delivery/version.rb +1 -1
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 34afeceddc78be6864aed136f68bc0a0f0eb43c203ef1520260fe94a8c934b39
|
4
|
+
data.tar.gz: dab4270f8b7a4c5e14b18c3fd36ceb081587425f26a50060191778bab768d688
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67992b515155ae977dd2f839448b3fe89d8c1391906e3c20bd425782241378f70d0b933834433a5e30904a7f3a1176c1313cd690267f001258f9584b078f0367
|
7
|
+
data.tar.gz: c811abf777c5c0c7552749fcedd228e7914bc80ded1a14129f4194097d74049f72e80c229c59549ac09141fed3145ab4c4199706ca29055ce6a405c3c16e13a2
|
data/CHANGELOG.md
CHANGED
@@ -2,47 +2,57 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 📬 1.0.0 (2023-08-29)
|
6
|
+
|
7
|
+
- Add `resolver_pattern` option to specify naming pattern for notifiers without using Procs. ([@palkan][])
|
8
|
+
|
9
|
+
- [!IMPORTANT] Notifier's `#notify_later` now do not process the action right away, only enqueue the job. ([@palkan][]).
|
10
|
+
|
11
|
+
This matches the Action Mailer behaviour. Now, the action is only invoked before the delivery attempt.
|
12
|
+
|
13
|
+
- Add callbacks support to Abstract Notifier (`before_action`, `after_deliver`, etc.). ([@palkan][])
|
14
|
+
|
5
15
|
- **Merge in abstract_notifier** ([@palkan][])
|
6
16
|
|
7
|
-
[Abstract Notifier](https://github.com/palkan/abstract_notifier) is now a part of Active Delivery.
|
17
|
+
[Abstract Notifier](https://github.com/palkan/abstract_notifier) is now a part of Active Delivery.
|
8
18
|
|
9
19
|
- Add ability to specify delivery actions explicitly and disable implicit proxying. ([@palkan][])
|
10
20
|
|
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:
|
21
|
+
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
22
|
|
13
|
-
```ruby
|
14
|
-
class PostMailer < ApplicationMailer
|
15
|
-
|
16
|
-
|
17
|
-
|
23
|
+
```ruby
|
24
|
+
class PostMailer < ApplicationMailer
|
25
|
+
def published(post)
|
26
|
+
# ...
|
27
|
+
end
|
18
28
|
|
19
|
-
|
20
|
-
|
29
|
+
def whatever(post)
|
30
|
+
# ...
|
31
|
+
end
|
21
32
|
end
|
22
|
-
end
|
23
33
|
|
24
|
-
ActiveDelivery.deliver_actions_required = true
|
34
|
+
ActiveDelivery.deliver_actions_required = true
|
25
35
|
|
26
|
-
class PostDelivery < ApplicationDelivery
|
27
|
-
|
28
|
-
end
|
36
|
+
class PostDelivery < ApplicationDelivery
|
37
|
+
delivers :published
|
38
|
+
end
|
29
39
|
|
30
|
-
PostDelivery.published(post) #=> ok
|
31
|
-
PostDelivery.whatever(post) #=> raises NoMethodError
|
32
|
-
```
|
40
|
+
PostDelivery.published(post) #=> ok
|
41
|
+
PostDelivery.whatever(post) #=> raises NoMethodError
|
42
|
+
```
|
33
43
|
|
34
44
|
- Add `#deliver_via(*lines)` RSpec matcher. ([@palkan][])
|
35
45
|
|
36
46
|
- Provide ActionMailer-like interface to trigger notifications. ([@palkan][])
|
37
47
|
|
38
|
-
Now you can send notifications as follows:
|
48
|
+
Now you can send notifications as follows:
|
39
49
|
|
40
|
-
```ruby
|
41
|
-
MyDelivery.with(user:).new_notification(payload).deliver_later
|
50
|
+
```ruby
|
51
|
+
MyDelivery.with(user:).new_notification(payload).deliver_later
|
42
52
|
|
43
|
-
# Equals to the old (and still supported)
|
44
|
-
MyDelivery.with(user:).notify(:new_notification, payload)
|
45
|
-
```
|
53
|
+
# Equals to the old (and still supported)
|
54
|
+
MyDelivery.with(user:).notify(:new_notification, payload)
|
55
|
+
```
|
46
56
|
|
47
57
|
- Support passing a string class name as a handler class. ([@palkan][])
|
48
58
|
|
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
|
@@ -153,6 +161,45 @@ PostDelivery.published(post) #=> ok
|
|
153
161
|
PostDelivery.whatever(post) #=> raises NoMethodError
|
154
162
|
```
|
155
163
|
|
164
|
+
### Organizing delivery and notifier classes
|
165
|
+
|
166
|
+
There are two common ways to organize delivery and notifier classes in your codebase:
|
167
|
+
|
168
|
+
```txt
|
169
|
+
app/
|
170
|
+
deliveries/ deliveries/
|
171
|
+
application_delivery.rb application_delivery.rb
|
172
|
+
post_delivery.rb post_delivery/
|
173
|
+
user_delivery.rb post_mailer.rb
|
174
|
+
mailers/ post_sms_notifier.rb
|
175
|
+
application_mailer.rb post_webhook_notifier.rb
|
176
|
+
post_mailer.rb post_delivery.rb
|
177
|
+
user_mailer.rb user_delivery/
|
178
|
+
notifiers/ user_mailer.rb
|
179
|
+
application_notifier.rb user_sms_notifier.rb
|
180
|
+
post_sms_notifier.rb user_webhook_notifier.rb
|
181
|
+
post_webhook_notifier.rb user_delivery.rb
|
182
|
+
user_sms_notifier.rb
|
183
|
+
user_webhook_notifier.rb
|
184
|
+
```
|
185
|
+
|
186
|
+
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:
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
class ApplicationDelivery < ActiveDelivery::Base
|
190
|
+
self.abstract_class = true
|
191
|
+
|
192
|
+
register_line :mailer, ActiveDelivery::Lines::Mailer,
|
193
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_mailer"
|
194
|
+
register_line :sms,
|
195
|
+
notifier: true,
|
196
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_sms_notifier"
|
197
|
+
register_line :webhook,
|
198
|
+
notifier: true,
|
199
|
+
resolver_pattern: "%{delivery_class}::%{delivery_name}_webhook_notifier"
|
200
|
+
end
|
201
|
+
```
|
202
|
+
|
156
203
|
### Customizing delivery handlers
|
157
204
|
|
158
205
|
You can specify a mailer class explicitly:
|
@@ -671,11 +718,14 @@ class MyAsyncAdapter
|
|
671
718
|
def initialize(options = {})
|
672
719
|
end
|
673
720
|
|
674
|
-
# `enqueue` method accepts notifier class and notification
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
#
|
721
|
+
# `enqueue` method accepts notifier class, action name and notification parameters
|
722
|
+
def enqueue(notifier_class, action_name, params:, args:, kwargs:)
|
723
|
+
# <Your implementation here>
|
724
|
+
# To trigger the notification delivery, you can use the following snippet:
|
725
|
+
#
|
726
|
+
# AbstractNotifier::NotificationDelivery.new(
|
727
|
+
# notifier_class.constantize, action_name, params:, args:, kwargs:
|
728
|
+
# ).notify_now
|
679
729
|
end
|
680
730
|
end
|
681
731
|
|
@@ -688,6 +738,53 @@ class EventsNotifier < AbstractNotifier::Base
|
|
688
738
|
end
|
689
739
|
```
|
690
740
|
|
741
|
+
### Action and Delivery Callbacks
|
742
|
+
|
743
|
+
**NOTE:** callbacks are only available if ActiveSupport is present in the application's runtime.
|
744
|
+
|
745
|
+
```ruby
|
746
|
+
# Run method before building a notification payload
|
747
|
+
# NOTE: when `false` is returned the execution is halted
|
748
|
+
before_action :do_something
|
749
|
+
|
750
|
+
# Run method before delivering notification
|
751
|
+
# NOTE: when `false` is returned the execution is halted
|
752
|
+
before_deliver :do_something
|
753
|
+
|
754
|
+
# Run method after the notification payload was build but before delivering
|
755
|
+
after_action :verify_notification_payload
|
756
|
+
|
757
|
+
# Run method after the actual delivery was performed
|
758
|
+
after_deliver :mark_user_as_notified, if: -> { params[:user].present? }
|
759
|
+
|
760
|
+
# after_ and around_ callbacks are also supported
|
761
|
+
after_action_ :cleanup
|
762
|
+
|
763
|
+
around_deliver :set_context
|
764
|
+
|
765
|
+
# You can also skip callbacks in sub-classes
|
766
|
+
skip_before_action :do_something, only: %i[some_reminder]
|
767
|
+
```
|
768
|
+
|
769
|
+
Example:
|
770
|
+
|
771
|
+
```ruby
|
772
|
+
class MyNotifier < AbstractNotifier::Base
|
773
|
+
# Log sent notifications
|
774
|
+
after_deliver do
|
775
|
+
# You can access the notification name within the instance or
|
776
|
+
MyLogger.info "Notification sent: #{notification_name}"
|
777
|
+
end
|
778
|
+
|
779
|
+
def some_event(body)
|
780
|
+
notification(body:)
|
781
|
+
end
|
782
|
+
end
|
783
|
+
|
784
|
+
MyNotifier.some_event("hello")
|
785
|
+
#=> Notification sent: some_event
|
786
|
+
```
|
787
|
+
|
691
788
|
### Delivery modes
|
692
789
|
|
693
790
|
For test/development purposes there are two special _global_ delivery modes:
|
@@ -772,6 +869,9 @@ class ApplicationDelivery < ActiveDelivery::Base
|
|
772
869
|
# `*Delivery` -> `*CustomNotifier`
|
773
870
|
register_line :custom_notifier, notifier: true, suffix: "CustomNotifier"
|
774
871
|
|
872
|
+
# Or using a custom pattern
|
873
|
+
register_line :custom_notifier, notifier: true, resolver_pattern: "%{delivery_name}CustomNotifier"
|
874
|
+
|
775
875
|
# Or you can specify a Proc object to do custom resolution:
|
776
876
|
register_line :some_notifier, notifier: true,
|
777
877
|
resolver: ->(delivery_class) { resolve_somehow(delivery_class) }
|
@@ -0,0 +1,27 @@
|
|
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
|
15
|
+
|
16
|
+
def initialize(queue: DEFAULT_QUEUE, job: DeliveryJob)
|
17
|
+
@job = job.set(queue: queue)
|
18
|
+
end
|
19
|
+
|
20
|
+
def enqueue(...)
|
21
|
+
job.perform_later(...)
|
22
|
+
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :enqueue)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
AbstractNotifier.async_adapter ||= :active_job
|
@@ -1,6 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveDelivery
|
4
|
+
class Delivery # :nodoc:
|
5
|
+
attr_reader :params, :options, :metadata, :notification, :owner
|
6
|
+
|
7
|
+
def initialize(owner, notification:, params:, options:, metadata:)
|
8
|
+
@owner = owner
|
9
|
+
@notification = notification
|
10
|
+
@params = params.freeze
|
11
|
+
@options = options.freeze
|
12
|
+
@metadata = metadata.freeze
|
13
|
+
end
|
14
|
+
|
15
|
+
def deliver_later ; owner.perform_notify(self); end
|
16
|
+
|
17
|
+
def deliver_now ; owner.perform_notify(self, sync: true); end
|
18
|
+
|
19
|
+
def delivery_class ; owner.class; end
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Whether to memoize resolved handler classes or not.
|
24
|
+
# Set to false if you're using a code reloader (e.g., Zeitwerk).
|
25
|
+
#
|
26
|
+
# Defaults to true (i.e. memoization is enabled
|
27
|
+
attr_accessor :cache_classes
|
28
|
+
# Whether to enforce specifying available delivery actions via .delivers in the
|
29
|
+
# delivery classes
|
30
|
+
attr_accessor :deliver_actions_required
|
31
|
+
end
|
32
|
+
|
33
|
+
self.cache_classes = true
|
34
|
+
self.deliver_actions_required = false
|
35
|
+
|
4
36
|
# Base class for deliveries.
|
5
37
|
#
|
6
38
|
# Delivery object describes how to notify a user about
|
@@ -10,6 +42,8 @@ module ActiveDelivery
|
|
10
42
|
# (i.e. mailers, notifiers). That means that calling a method on delivery class invokes the
|
11
43
|
# same method on the corresponding class, e.g.:
|
12
44
|
#
|
45
|
+
# EventsDelivery.one_hour_before(profile, event).deliver_later
|
46
|
+
# # or
|
13
47
|
# EventsDelivery.notify(:one_hour_before, profile, event)
|
14
48
|
#
|
15
49
|
# # under the hood it calls
|
@@ -20,14 +54,14 @@ module ActiveDelivery
|
|
20
54
|
#
|
21
55
|
# Delivery also supports _parameterized_ calling:
|
22
56
|
#
|
23
|
-
# EventsDelivery.with(profile: profile).
|
57
|
+
# EventsDelivery.with(profile: profile).canceled(event).deliver_later
|
24
58
|
#
|
25
59
|
# The parameters could be accessed through `params` instance method (e.g.
|
26
60
|
# to implement guard-like logic).
|
27
61
|
#
|
28
62
|
# When params are presents the parametrized mailer is used, i.e.:
|
29
63
|
#
|
30
|
-
# EventsMailer.with(profile: profile).canceled(event)
|
64
|
+
# EventsMailer.with(profile: profile).canceled(event).deliver_later
|
31
65
|
#
|
32
66
|
# See https://api.rubyonrails.org/classes/ActionMailer/Parameterized.html
|
33
67
|
class Base
|
@@ -47,6 +81,8 @@ module ActiveDelivery
|
|
47
81
|
notify(mid, *args, **hargs, sync: true)
|
48
82
|
end
|
49
83
|
|
84
|
+
alias_method :notify_now, :notify!
|
85
|
+
|
50
86
|
def delivery_lines
|
51
87
|
@lines ||= if superclass.respond_to?(:delivery_lines)
|
52
88
|
superclass.delivery_lines.each_with_object({}) do |(key, val), acc|
|
@@ -57,12 +93,19 @@ module ActiveDelivery
|
|
57
93
|
end
|
58
94
|
end
|
59
95
|
|
60
|
-
def register_line(line_id, line_class, **options)
|
96
|
+
def register_line(line_id, line_class = nil, notifier: nil, **options)
|
97
|
+
raise ArgumentError, "A line class or notifier configuration must be provided" if line_class.nil? && notifier.nil?
|
98
|
+
|
99
|
+
# Configure Notifier
|
100
|
+
if line_class.nil?
|
101
|
+
line_class = ActiveDelivery::Lines::Notifier
|
102
|
+
end
|
103
|
+
|
61
104
|
delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **options)
|
62
105
|
|
63
106
|
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
64
107
|
def #{line_id}(val)
|
65
|
-
delivery_lines[:#{line_id}].
|
108
|
+
delivery_lines[:#{line_id}].handler_class_name = val
|
66
109
|
end
|
67
110
|
|
68
111
|
def #{line_id}_class
|
@@ -81,8 +124,46 @@ module ActiveDelivery
|
|
81
124
|
end
|
82
125
|
|
83
126
|
def abstract_class? ; abstract_class == true; end
|
127
|
+
|
128
|
+
# Specify explicitly which actions are supported by the delivery.
|
129
|
+
def delivers(*actions)
|
130
|
+
actions.each do |mid|
|
131
|
+
class_eval <<~CODE, __FILE__, __LINE__ + 1
|
132
|
+
def self.#{mid}(...)
|
133
|
+
new.#{mid}(...)
|
134
|
+
end
|
135
|
+
|
136
|
+
def #{mid}(*args, **kwargs)
|
137
|
+
delivery(
|
138
|
+
notification: :#{mid},
|
139
|
+
params: args,
|
140
|
+
options: kwargs
|
141
|
+
)
|
142
|
+
end
|
143
|
+
CODE
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def respond_to_missing?(mid, include_private = false)
|
148
|
+
unless ActiveDelivery.deliver_actions_required
|
149
|
+
return true if delivery_lines.any? { |_, line| line.notify?(mid) }
|
150
|
+
end
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
155
|
+
def method_missing(mid, *args, **kwargs)
|
156
|
+
return super unless respond_to_missing?(mid)
|
157
|
+
|
158
|
+
# Lazily define a class method to avoid lookups
|
159
|
+
delivers(mid)
|
160
|
+
|
161
|
+
public_send(mid, *args, **kwargs)
|
162
|
+
end
|
84
163
|
end
|
85
164
|
|
165
|
+
self.abstract_class = true
|
166
|
+
|
86
167
|
attr_reader :params, :notification_name
|
87
168
|
|
88
169
|
def initialize(**params)
|
@@ -91,31 +172,74 @@ module ActiveDelivery
|
|
91
172
|
end
|
92
173
|
|
93
174
|
# Enqueues delivery (i.e. uses #deliver_later for mailers)
|
94
|
-
def notify(mid, *
|
95
|
-
|
96
|
-
|
97
|
-
|
175
|
+
def notify(mid, *args, **kwargs)
|
176
|
+
perform_notify(
|
177
|
+
delivery(notification: mid, params: args, options: kwargs)
|
178
|
+
)
|
179
|
+
end
|
98
180
|
|
99
181
|
# The same as .notify but delivers synchronously
|
100
182
|
# (i.e. #deliver_now for mailers)
|
101
|
-
def notify!(mid, *args, **
|
102
|
-
|
183
|
+
def notify!(mid, *args, **kwargs)
|
184
|
+
perform_notify(
|
185
|
+
delivery(notification: mid, params: args, options: kwargs),
|
186
|
+
sync: true
|
187
|
+
)
|
103
188
|
end
|
104
189
|
|
105
|
-
|
190
|
+
alias_method :notify_now, :notify!
|
191
|
+
|
192
|
+
def respond_to_missing?(mid, include_private = false)
|
193
|
+
unless ActiveDelivery.deliver_actions_required
|
194
|
+
return true if delivery_lines.any? { |_, line| line.notify?(mid) }
|
195
|
+
end
|
196
|
+
|
197
|
+
super
|
198
|
+
end
|
106
199
|
|
107
|
-
def
|
200
|
+
def method_missing(mid, *args, **kwargs)
|
201
|
+
return super unless respond_to_missing?(mid)
|
202
|
+
|
203
|
+
# Lazily define a method to avoid future lookups
|
204
|
+
self.class.class_eval <<~CODE, __FILE__, __LINE__ + 1
|
205
|
+
def #{mid}(*args, **kwargs)
|
206
|
+
delivery(
|
207
|
+
notification: :#{mid},
|
208
|
+
params: args,
|
209
|
+
options: kwargs
|
210
|
+
)
|
211
|
+
end
|
212
|
+
CODE
|
213
|
+
|
214
|
+
public_send(mid, *args, **kwargs)
|
215
|
+
end
|
216
|
+
|
217
|
+
protected
|
218
|
+
|
219
|
+
def perform_notify(delivery, sync: false)
|
108
220
|
delivery_lines.each do |type, line|
|
109
|
-
next
|
110
|
-
next unless line.notify?(notification_name)
|
221
|
+
next unless line.notify?(delivery.notification)
|
111
222
|
|
112
|
-
notify_line(type,
|
223
|
+
notify_line(type, line, delivery, sync: sync)
|
113
224
|
end
|
114
225
|
end
|
115
226
|
|
116
|
-
|
117
|
-
|
118
|
-
|
227
|
+
private
|
228
|
+
|
229
|
+
def notify_line(type, line, delivery, sync:)
|
230
|
+
line.notify(
|
231
|
+
delivery.notification,
|
232
|
+
*delivery.params,
|
233
|
+
params: params,
|
234
|
+
sync: sync,
|
235
|
+
**delivery.options
|
236
|
+
)
|
237
|
+
true
|
238
|
+
end
|
239
|
+
|
240
|
+
def delivery(notification:, params: nil, options: nil, metadata: nil)
|
241
|
+
Delivery.new(self, notification: notification, params: params, options: options, metadata: metadata)
|
242
|
+
end
|
119
243
|
|
120
244
|
def delivery_lines
|
121
245
|
self.class.delivery_lines
|
@@ -37,9 +37,11 @@ module ActiveDelivery
|
|
37
37
|
end
|
38
38
|
|
39
39
|
module InstanceExt
|
40
|
-
def
|
41
|
-
|
42
|
-
|
40
|
+
def perform_notify(delivery, *__rest__, &__block__)
|
41
|
+
# We need to store the notification name to be able to use it in callbacks if/unless
|
42
|
+
@notification_name = delivery.notification
|
43
|
+
run_callbacks(:notify) { super(delivery, *__rest__, &__block__) }
|
44
|
+
end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :perform_notify)
|
43
45
|
|
44
46
|
def notify_line(kind, *__rest__, &__block__)
|
45
47
|
run_callbacks(kind) { super(kind, *__rest__, &__block__) }
|
@@ -73,22 +75,24 @@ module ActiveDelivery
|
|
73
75
|
skip_after_callbacks_if_terminated: true
|
74
76
|
end
|
75
77
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
set_callback on, :before, method_or_block, options
|
80
|
-
end
|
78
|
+
%i[before after around].each do |kind|
|
79
|
+
define_method "#{kind}_notify" do |*names, on: :notify, **options, &block|
|
80
|
+
_normalize_callback_options(options)
|
81
81
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
82
|
+
names.each do |name|
|
83
|
+
set_callback on, kind, name, options
|
84
|
+
end
|
85
|
+
|
86
|
+
set_callback on, kind, block, options if block
|
87
|
+
end
|
88
|
+
|
89
|
+
define_method "skip_#{kind}_notify" do |*names, on: :notify, **options|
|
90
|
+
_normalize_callback_options(options)
|
87
91
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
+
names.each do |name|
|
93
|
+
skip_callback(on, kind, name, options)
|
94
|
+
end
|
95
|
+
end
|
92
96
|
end
|
93
97
|
end
|
94
98
|
end
|
@@ -1,17 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
unless "".respond_to?(:safe_constantize)
|
4
|
+
require "active_delivery/ext/string_constantize"
|
5
|
+
using ActiveDelivery::Ext::StringConstantize
|
6
|
+
end
|
7
|
+
|
3
8
|
module ActiveDelivery
|
4
9
|
module Lines
|
5
10
|
class Base
|
6
11
|
attr_reader :id, :options
|
7
12
|
attr_accessor :owner
|
8
|
-
|
13
|
+
attr_accessor :handler_class_name
|
9
14
|
|
10
15
|
def initialize(id:, owner:, **options)
|
11
16
|
@id = id
|
12
17
|
@owner = owner
|
13
18
|
@options = options.tap(&:freeze)
|
14
|
-
@resolver = options[:resolver]
|
19
|
+
@resolver = options[:resolver] || build_pattern_resolver(options[:resolver_pattern])
|
15
20
|
end
|
16
21
|
|
17
22
|
def dup_for(new_owner)
|
@@ -23,7 +28,7 @@ module ActiveDelivery
|
|
23
28
|
end
|
24
29
|
|
25
30
|
def notify?(method_name)
|
26
|
-
handler_class
|
31
|
+
handler_class&.respond_to?(method_name)
|
27
32
|
end
|
28
33
|
|
29
34
|
def notify_now(handler, mid, *__rest__, &__block__)
|
@@ -38,26 +43,47 @@ module ActiveDelivery
|
|
38
43
|
end
|
39
44
|
|
40
45
|
def handler_class
|
41
|
-
|
46
|
+
if ::ActiveDelivery.cache_classes
|
47
|
+
return @handler_class if instance_variable_defined?(:@handler_class)
|
48
|
+
end
|
42
49
|
|
43
50
|
return @handler_class = nil if owner.abstract_class?
|
44
51
|
|
45
|
-
|
46
|
-
|
52
|
+
superline = owner.superclass.delivery_lines[id] if owner.superclass.respond_to?(:delivery_lines) && owner.superclass.delivery_lines[id]
|
53
|
+
|
54
|
+
# If an explicit class name has been specified somewhere in the ancestor chain, use it.
|
55
|
+
class_name = @handler_class_name || superline&.handler_class_name
|
56
|
+
|
57
|
+
@handler_class =
|
58
|
+
if class_name
|
59
|
+
class_name.is_a?(Class) ? class_name : class_name.safe_constantize
|
60
|
+
else
|
61
|
+
resolve_class(owner) || superline&.handler_class
|
62
|
+
end
|
47
63
|
end
|
48
64
|
|
49
65
|
private
|
50
66
|
|
51
|
-
|
52
|
-
handler_method = "#{id}_class"
|
67
|
+
attr_reader :resolver
|
53
68
|
|
54
|
-
|
55
|
-
return unless
|
69
|
+
def build_pattern_resolver(pattern)
|
70
|
+
return unless pattern
|
56
71
|
|
57
|
-
|
58
|
-
|
72
|
+
proc do |delivery|
|
73
|
+
delivery_class = delivery.name
|
59
74
|
|
60
|
-
|
75
|
+
next unless delivery_class
|
76
|
+
|
77
|
+
*namespace, delivery_name = delivery_class.split("::")
|
78
|
+
|
79
|
+
delivery_namespace = ""
|
80
|
+
delivery_namespace = "#{namespace.join("::")}::" unless namespace.empty?
|
81
|
+
|
82
|
+
delivery_name = delivery_name.sub(/Delivery$/, "")
|
83
|
+
|
84
|
+
(pattern % {delivery_class: delivery_class, delivery_name: delivery_name, delivery_namespace: delivery_namespace}).safe_constantize
|
85
|
+
end
|
86
|
+
end
|
61
87
|
end
|
62
88
|
end
|
63
89
|
end
|
@@ -5,9 +5,10 @@ module ActiveDelivery
|
|
5
5
|
class Mailer < Base
|
6
6
|
alias_method :mailer_class, :handler_class
|
7
7
|
|
8
|
-
DEFAULT_RESOLVER = ->(
|
8
|
+
DEFAULT_RESOLVER = ->(klass) { klass.name&.gsub(/Delivery$/, "Mailer")&.safe_constantize }
|
9
9
|
|
10
10
|
def notify?(method_name)
|
11
|
+
return unless mailer_class
|
11
12
|
mailer_class.action_methods.include?(method_name.to_s)
|
12
13
|
end
|
13
14
|
|