active_delivery 0.4.3 → 1.0.0.rc2
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 +58 -1
- data/LICENSE.txt +19 -17
- data/README.md +503 -32
- data/lib/.rbnext/3.0/active_delivery/base.rb +124 -0
- data/lib/.rbnext/3.0/active_delivery/callbacks.rb +97 -0
- data/lib/.rbnext/3.0/active_delivery/lines/base.rb +63 -0
- data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +25 -0
- data/lib/.rbnext/3.1/active_delivery/base.rb +124 -0
- data/lib/.rbnext/3.1/active_delivery/lines/base.rb +63 -0
- data/lib/abstract_notifier/async_adapters/active_job.rb +27 -0
- data/lib/abstract_notifier/async_adapters.rb +16 -0
- data/lib/abstract_notifier/base.rb +178 -0
- data/lib/abstract_notifier/testing/minitest.rb +51 -0
- data/lib/abstract_notifier/testing/rspec.rb +164 -0
- data/lib/abstract_notifier/testing.rb +49 -0
- data/lib/abstract_notifier/version.rb +5 -0
- data/lib/abstract_notifier.rb +74 -0
- data/lib/active_delivery/base.rb +156 -27
- data/lib/active_delivery/callbacks.rb +25 -25
- data/lib/active_delivery/ext/string_constantize.rb +24 -0
- data/lib/active_delivery/lines/base.rb +24 -17
- data/lib/active_delivery/lines/mailer.rb +7 -18
- data/lib/active_delivery/lines/notifier.rb +53 -0
- data/lib/active_delivery/raitie.rb +9 -0
- data/lib/active_delivery/testing/rspec.rb +59 -12
- data/lib/active_delivery/testing.rb +19 -5
- data/lib/active_delivery/version.rb +1 -1
- data/lib/active_delivery.rb +8 -0
- metadata +61 -56
- data/.gem_release.yml +0 -3
- data/.github/ISSUE_TEMPLATE/bug_report.md +0 -24
- data/.github/ISSUE_TEMPLATE/feature_request.md +0 -24
- data/.github/PULL_REQUEST_TEMPLATE.md +0 -23
- data/.github/workflows/docs-lint.yml +0 -72
- data/.github/workflows/rspec-jruby.yml +0 -35
- data/.github/workflows/rspec.yml +0 -51
- data/.github/workflows/rubocop.yml +0 -21
- data/.gitignore +0 -43
- data/.mdlrc +0 -1
- data/.rspec +0 -2
- data/.rubocop-md.yml +0 -16
- data/.rubocop.yml +0 -28
- data/Gemfile +0 -17
- data/RELEASING.md +0 -43
- data/Rakefile +0 -20
- data/active_delivery.gemspec +0 -35
- data/forspell.dict +0 -8
- data/gemfiles/jruby.gemfile +0 -5
- data/gemfiles/rails42.gemfile +0 -8
- data/gemfiles/rails5.gemfile +0 -5
- data/gemfiles/rails50.gemfile +0 -8
- data/gemfiles/rails6.gemfile +0 -5
- data/gemfiles/railsmaster.gemfile +0 -6
- data/gemfiles/rubocop.gemfile +0 -4
- data/lefthook.yml +0 -18
- data/lib/active_delivery/action_mailer/parameterized.rb +0 -92
data/lib/active_delivery/base.rb
CHANGED
@@ -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)
|
16
|
+
|
17
|
+
def deliver_now = owner.perform_notify(self, sync: true)
|
18
|
+
|
19
|
+
def delivery_class = owner.class
|
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,25 +54,25 @@ 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
|
34
68
|
class << self
|
35
69
|
attr_accessor :abstract_class
|
36
70
|
|
37
|
-
|
71
|
+
alias_method :with, :new
|
38
72
|
|
39
73
|
# Enqueues delivery (i.e. uses #deliver_later for mailers)
|
40
|
-
def notify(
|
41
|
-
new.notify(
|
74
|
+
def notify(...)
|
75
|
+
new.notify(...)
|
42
76
|
end
|
43
77
|
|
44
78
|
# The same as .notify but delivers synchronously
|
@@ -47,24 +81,31 @@ 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
|
-
@lines ||=
|
52
|
-
|
53
|
-
|
54
|
-
acc[key] = val.dup_for(self)
|
55
|
-
end
|
56
|
-
else
|
57
|
-
{}
|
87
|
+
@lines ||= if superclass.respond_to?(:delivery_lines)
|
88
|
+
superclass.delivery_lines.each_with_object({}) do |(key, val), acc|
|
89
|
+
acc[key] = val.dup_for(self)
|
58
90
|
end
|
91
|
+
else
|
92
|
+
{}
|
59
93
|
end
|
60
94
|
end
|
61
95
|
|
62
|
-
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
|
+
|
63
104
|
delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **options)
|
64
105
|
|
65
106
|
instance_eval <<~CODE, __FILE__, __LINE__ + 1
|
66
107
|
def #{line_id}(val)
|
67
|
-
delivery_lines[:#{line_id}].
|
108
|
+
delivery_lines[:#{line_id}].handler_class_name = val
|
68
109
|
end
|
69
110
|
|
70
111
|
def #{line_id}_class
|
@@ -73,11 +114,56 @@ module ActiveDelivery
|
|
73
114
|
CODE
|
74
115
|
end
|
75
116
|
|
76
|
-
def
|
77
|
-
|
117
|
+
def unregister_line(line_id)
|
118
|
+
removed_line = delivery_lines.delete(line_id)
|
119
|
+
|
120
|
+
return if removed_line.nil?
|
121
|
+
|
122
|
+
singleton_class.undef_method line_id
|
123
|
+
singleton_class.undef_method "#{line_id}_class"
|
124
|
+
end
|
125
|
+
|
126
|
+
def abstract_class? = abstract_class == true
|
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)
|
78
162
|
end
|
79
163
|
end
|
80
164
|
|
165
|
+
self.abstract_class = true
|
166
|
+
|
81
167
|
attr_reader :params, :notification_name
|
82
168
|
|
83
169
|
def initialize(**params)
|
@@ -87,29 +173,72 @@ module ActiveDelivery
|
|
87
173
|
|
88
174
|
# Enqueues delivery (i.e. uses #deliver_later for mailers)
|
89
175
|
def notify(mid, *args, **kwargs)
|
90
|
-
|
91
|
-
|
176
|
+
perform_notify(
|
177
|
+
delivery(notification: mid, params: args, options: kwargs)
|
178
|
+
)
|
92
179
|
end
|
93
180
|
|
94
181
|
# The same as .notify but delivers synchronously
|
95
182
|
# (i.e. #deliver_now for mailers)
|
96
|
-
def notify!(mid, *args, **
|
97
|
-
|
183
|
+
def notify!(mid, *args, **kwargs)
|
184
|
+
perform_notify(
|
185
|
+
delivery(notification: mid, params: args, options: kwargs),
|
186
|
+
sync: true
|
187
|
+
)
|
98
188
|
end
|
99
189
|
|
100
|
-
|
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
|
101
196
|
|
102
|
-
|
197
|
+
super
|
198
|
+
end
|
199
|
+
|
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)
|
103
220
|
delivery_lines.each do |type, line|
|
104
|
-
next
|
105
|
-
next unless line.notify?(notification_name)
|
221
|
+
next unless line.notify?(delivery.notification)
|
106
222
|
|
107
|
-
notify_line(type,
|
223
|
+
notify_line(type, line, delivery, sync:)
|
108
224
|
end
|
109
225
|
end
|
110
226
|
|
111
|
-
|
112
|
-
|
227
|
+
private
|
228
|
+
|
229
|
+
def notify_line(type, line, delivery, sync:)
|
230
|
+
line.notify(
|
231
|
+
delivery.notification,
|
232
|
+
*delivery.params,
|
233
|
+
params:,
|
234
|
+
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:, params:, options:, metadata:)
|
113
242
|
end
|
114
243
|
|
115
244
|
def delivery_lines
|
@@ -8,7 +8,7 @@ module ActiveDelivery
|
|
8
8
|
# Add callbacks support to Active Delivery (requires ActiveSupport::Callbacks)
|
9
9
|
#
|
10
10
|
# # Run method before delivering notification
|
11
|
-
# # NOTE: when `false` is returned the
|
11
|
+
# # NOTE: when `false` is returned the execution is halted
|
12
12
|
# before_notify :do_something
|
13
13
|
#
|
14
14
|
# # You can specify a notification method (to run callback only for that method)
|
@@ -26,11 +26,7 @@ module ActiveDelivery
|
|
26
26
|
|
27
27
|
include ActiveSupport::Callbacks
|
28
28
|
|
29
|
-
CALLBACK_TERMINATOR =
|
30
|
-
->(_target, result) { result.call == false }
|
31
|
-
else
|
32
|
-
->(_target, result) { result == false }
|
33
|
-
end
|
29
|
+
CALLBACK_TERMINATOR = ->(_target, result) { result.call == false }
|
34
30
|
|
35
31
|
included do
|
36
32
|
# Define "global" callbacks
|
@@ -41,17 +37,19 @@ module ActiveDelivery
|
|
41
37
|
end
|
42
38
|
|
43
39
|
module InstanceExt
|
44
|
-
def
|
45
|
-
|
40
|
+
def perform_notify(delivery, ...)
|
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, ...) }
|
46
44
|
end
|
47
45
|
|
48
|
-
def notify_line(
|
49
|
-
run_callbacks(
|
46
|
+
def notify_line(kind, ...)
|
47
|
+
run_callbacks(kind) { super(kind, ...) }
|
50
48
|
end
|
51
49
|
end
|
52
50
|
|
53
51
|
module SingltonExt
|
54
|
-
def register_line(line_id,
|
52
|
+
def register_line(line_id, ...)
|
55
53
|
super
|
56
54
|
define_line_callbacks line_id
|
57
55
|
end
|
@@ -77,22 +75,24 @@ module ActiveDelivery
|
|
77
75
|
skip_after_callbacks_if_terminated: true
|
78
76
|
end
|
79
77
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
set_callback on, :before, method_or_block, options
|
84
|
-
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)
|
85
81
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
set_callback on, :after, method_or_block, options
|
90
|
-
end
|
82
|
+
names.each do |name|
|
83
|
+
set_callback on, kind, name, options
|
84
|
+
end
|
91
85
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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)
|
91
|
+
|
92
|
+
names.each do |name|
|
93
|
+
skip_callback(on, kind, name, options)
|
94
|
+
end
|
95
|
+
end
|
96
96
|
end
|
97
97
|
end
|
98
98
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveDelivery
|
4
|
+
module Ext
|
5
|
+
# Add simple safe_constantize method to String
|
6
|
+
module StringConstantize
|
7
|
+
refine String do
|
8
|
+
def safe_constantize
|
9
|
+
names = split("::")
|
10
|
+
|
11
|
+
return nil if names.empty?
|
12
|
+
|
13
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
14
|
+
names.shift if names.size > 1 && names.first.empty?
|
15
|
+
|
16
|
+
names.inject(Object) do |constant, name|
|
17
|
+
break if constant.nil?
|
18
|
+
constant.const_get(name, false) if constant.const_defined?(name, false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,11 +1,16 @@
|
|
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
|
@@ -15,7 +20,7 @@ module ActiveDelivery
|
|
15
20
|
end
|
16
21
|
|
17
22
|
def dup_for(new_owner)
|
18
|
-
self.class.new(id
|
23
|
+
self.class.new(id:, **options, owner: new_owner)
|
19
24
|
end
|
20
25
|
|
21
26
|
def resolve_class(name)
|
@@ -23,13 +28,13 @@ 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
|
-
def notify_now(handler, mid,
|
34
|
+
def notify_now(handler, mid, ...)
|
30
35
|
end
|
31
36
|
|
32
|
-
def notify_later(handler, mid,
|
37
|
+
def notify_later(handler, mid, ...)
|
33
38
|
end
|
34
39
|
|
35
40
|
def notify(mid, *args, params:, sync:, **kwargs)
|
@@ -38,25 +43,27 @@ 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
|
-
superclass_handler
|
47
|
-
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def superclass_handler
|
52
|
-
handler_method = "#{id}_class"
|
52
|
+
superline = owner.superclass.delivery_lines[id] if owner.superclass.respond_to?(:delivery_lines) && owner.superclass.delivery_lines[id]
|
53
53
|
|
54
|
-
|
55
|
-
|
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
56
|
|
57
|
-
|
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
|
58
63
|
end
|
59
64
|
|
65
|
+
private
|
66
|
+
|
60
67
|
attr_reader :resolver
|
61
68
|
end
|
62
69
|
end
|
@@ -1,34 +1,23 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
if ActionMailer::VERSION::MAJOR < 5 || (ActionMailer::VERSION::MAJOR == 5 && ActionMailer::VERSION::MINOR < 2)
|
4
|
-
require "active_delivery/action_mailer/parameterized"
|
5
|
-
end
|
6
|
-
|
7
3
|
module ActiveDelivery
|
8
4
|
module Lines
|
9
5
|
class Mailer < Base
|
10
|
-
|
6
|
+
alias_method :mailer_class, :handler_class
|
11
7
|
|
12
|
-
DEFAULT_RESOLVER = ->(
|
8
|
+
DEFAULT_RESOLVER = ->(klass) { klass.name&.gsub(/Delivery$/, "Mailer")&.safe_constantize }
|
13
9
|
|
14
10
|
def notify?(method_name)
|
11
|
+
return unless mailer_class
|
15
12
|
mailer_class.action_methods.include?(method_name.to_s)
|
16
13
|
end
|
17
14
|
|
18
|
-
def notify_now(mailer, mid,
|
19
|
-
|
20
|
-
mailer.public_send(mid, *args).deliver_now
|
21
|
-
else
|
22
|
-
mailer.public_send(mid, *args, **kwargs).deliver_now
|
23
|
-
end
|
15
|
+
def notify_now(mailer, mid, ...)
|
16
|
+
mailer.public_send(mid, ...).deliver_now
|
24
17
|
end
|
25
18
|
|
26
|
-
def notify_later(mailer, mid,
|
27
|
-
|
28
|
-
mailer.public_send(mid, *args).deliver_later
|
29
|
-
else
|
30
|
-
mailer.public_send(mid, *args, **kwargs).deliver_later
|
31
|
-
end
|
19
|
+
def notify_later(mailer, mid, ...)
|
20
|
+
mailer.public_send(mid, ...).deliver_later
|
32
21
|
end
|
33
22
|
end
|
34
23
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless "".respond_to?(:safe_constantize)
|
4
|
+
require "active_delivery/ext/string_constantize"
|
5
|
+
using ActiveDelivery::Ext::StringConstantize
|
6
|
+
end
|
7
|
+
|
8
|
+
module ActiveDelivery
|
9
|
+
module Lines
|
10
|
+
# AbstractNotifier line for Active Delivery.
|
11
|
+
#
|
12
|
+
# You must provide custom `resolver` to infer notifier class
|
13
|
+
# (if String#safe_constantize is defined, we convert "*Delivery" -> "*Notifier").
|
14
|
+
#
|
15
|
+
# Resolver is a callable object.
|
16
|
+
class Notifier < ActiveDelivery::Lines::Base
|
17
|
+
DEFAULT_SUFFIX = "Notifier"
|
18
|
+
|
19
|
+
def initialize(**opts)
|
20
|
+
super
|
21
|
+
@resolver = opts.fetch(:resolver, build_resolver(options.fetch(:suffix, DEFAULT_SUFFIX)))
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_class(klass)
|
25
|
+
resolver&.call(klass)
|
26
|
+
end
|
27
|
+
|
28
|
+
def notify?(method_name)
|
29
|
+
return unless handler_class
|
30
|
+
handler_class.action_methods.include?(method_name.to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def notify_now(handler, mid, *args)
|
34
|
+
handler.public_send(mid, *args).notify_now
|
35
|
+
end
|
36
|
+
|
37
|
+
def notify_later(handler, mid, *args)
|
38
|
+
handler.public_send(mid, *args).notify_later
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :resolver
|
44
|
+
|
45
|
+
def build_resolver(suffix)
|
46
|
+
lambda do |klass|
|
47
|
+
klass_name = klass.name
|
48
|
+
klass_name&.sub(/Delivery\z/, suffix)&.safe_constantize
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -2,12 +2,13 @@
|
|
2
2
|
|
3
3
|
module ActiveDelivery
|
4
4
|
class HaveDeliveredTo < RSpec::Matchers::BuiltIn::BaseMatcher
|
5
|
-
attr_reader :delivery_class, :event, :args, :params, :sync_value
|
5
|
+
attr_reader :delivery_class, :event, :args, :kwargs, :params, :sync_value
|
6
6
|
|
7
|
-
def initialize(delivery_class, event = nil, *args)
|
7
|
+
def initialize(delivery_class, event = nil, *args, **kwargs)
|
8
8
|
@delivery_class = delivery_class
|
9
9
|
@event = event
|
10
10
|
@args = args
|
11
|
+
@kwargs = kwargs
|
11
12
|
set_expected_number(:exactly, 1)
|
12
13
|
end
|
13
14
|
|
@@ -64,17 +65,25 @@ module ActiveDelivery
|
|
64
65
|
actual_deliveries = TestDelivery.store
|
65
66
|
|
66
67
|
@matching_deliveries, @unmatching_deliveries =
|
67
|
-
actual_deliveries.partition do |(delivery,
|
68
|
-
next false unless delivery_class
|
68
|
+
actual_deliveries.partition do |(delivery, options)|
|
69
|
+
next false unless delivery_class == delivery.owner.class
|
69
70
|
|
70
|
-
next false
|
71
|
-
|
71
|
+
next false if !sync_value.nil? && (options.fetch(:sync, false) != sync_value)
|
72
|
+
|
73
|
+
next false unless params.nil? || params === delivery.owner.params
|
74
|
+
|
75
|
+
next false unless event.nil? || event == delivery.notification
|
76
|
+
|
77
|
+
actual_args = delivery.params
|
78
|
+
actual_kwargs = delivery.options
|
72
79
|
|
73
80
|
next false unless args.each.with_index.all? do |arg, i|
|
74
81
|
arg === actual_args[i]
|
75
82
|
end
|
76
83
|
|
77
|
-
next false
|
84
|
+
next false unless kwargs.all? do |k, v|
|
85
|
+
v === actual_kwargs[k]
|
86
|
+
end
|
78
87
|
|
79
88
|
true
|
80
89
|
end
|
@@ -127,7 +136,7 @@ module ActiveDelivery
|
|
127
136
|
end
|
128
137
|
|
129
138
|
def message_expectation_modifier
|
130
|
-
number_modifier = @expected_number == 1 ? "once" : "#{@expected_number} times"
|
139
|
+
number_modifier = (@expected_number == 1) ? "once" : "#{@expected_number} times"
|
131
140
|
case @expectation_type
|
132
141
|
when :exactly then "exactly #{number_modifier}"
|
133
142
|
when :at_most then "at most #{number_modifier}"
|
@@ -145,12 +154,13 @@ module ActiveDelivery
|
|
145
154
|
end
|
146
155
|
|
147
156
|
def deliveries_description(deliveries)
|
148
|
-
deliveries.each.with_object(+"") do |(delivery,
|
149
|
-
msg << "\n :#{
|
157
|
+
deliveries.each.with_object(+"") do |(delivery, options), msg|
|
158
|
+
msg << "\n :#{delivery.notification} via #{delivery.owner.class}" \
|
150
159
|
"#{options[:sync] ? " (sync)" : ""}" \
|
151
160
|
" with:" \
|
152
|
-
"\n - params: #{delivery.params.empty? ? "<none>" : delivery.params.
|
153
|
-
"\n - args: #{
|
161
|
+
"\n - params: #{delivery.owner.params.empty? ? "<none>" : delivery.owner.params.inspect}" \
|
162
|
+
"\n - args: #{delivery.params}" \
|
163
|
+
"\n - kwargs: #{delivery.options}"
|
154
164
|
end
|
155
165
|
end
|
156
166
|
|
@@ -162,6 +172,37 @@ module ActiveDelivery
|
|
162
172
|
end
|
163
173
|
end
|
164
174
|
end
|
175
|
+
|
176
|
+
class DeliverVia < RSpec::Matchers::BuiltIn::BaseMatcher
|
177
|
+
attr_reader :lines
|
178
|
+
|
179
|
+
def initialize(*lines)
|
180
|
+
@actual_lines = []
|
181
|
+
@lines = lines.sort
|
182
|
+
end
|
183
|
+
|
184
|
+
def supports_block_expectations?
|
185
|
+
true
|
186
|
+
end
|
187
|
+
|
188
|
+
def matches?(proc)
|
189
|
+
raise ArgumentError, "deliver_via only supports block expectations" unless Proc === proc
|
190
|
+
|
191
|
+
TestDelivery.lines.clear
|
192
|
+
|
193
|
+
proc.call
|
194
|
+
|
195
|
+
@actual_lines = TestDelivery.lines.sort
|
196
|
+
|
197
|
+
lines == @actual_lines
|
198
|
+
end
|
199
|
+
|
200
|
+
private
|
201
|
+
|
202
|
+
def failure_message
|
203
|
+
"expected to deliver via #{lines.join(", ")} lines, but delivered to #{@actual_lines.any? ? @actual_lines.join(", ") : "none"}"
|
204
|
+
end
|
205
|
+
end
|
165
206
|
end
|
166
207
|
|
167
208
|
RSpec.configure do |config|
|
@@ -170,6 +211,12 @@ RSpec.configure do |config|
|
|
170
211
|
ActiveDelivery::HaveDeliveredTo.new(*args)
|
171
212
|
end
|
172
213
|
end)
|
214
|
+
|
215
|
+
config.include(Module.new do
|
216
|
+
def deliver_via(*args)
|
217
|
+
ActiveDelivery::DeliverVia.new(*args)
|
218
|
+
end
|
219
|
+
end, type: :delivery)
|
173
220
|
end
|
174
221
|
|
175
222
|
RSpec::Matchers.define_negated_matcher :have_not_delivered_to, :have_delivered_to
|