active_delivery 0.4.4 → 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 +62 -0
- data/LICENSE.txt +19 -17
- data/README.md +595 -33
- data/lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb +27 -0
- data/lib/.rbnext/3.0/active_delivery/base.rb +248 -0
- data/lib/.rbnext/3.0/active_delivery/callbacks.rb +101 -0
- data/lib/.rbnext/3.0/active_delivery/lines/base.rb +89 -0
- data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +26 -0
- 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 +248 -0
- data/lib/.rbnext/3.1/active_delivery/lines/base.rb +89 -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 +217 -0
- data/lib/abstract_notifier/callbacks.rb +94 -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 +53 -0
- data/lib/abstract_notifier/version.rb +5 -0
- data/lib/abstract_notifier.rb +75 -0
- data/lib/active_delivery/base.rb +147 -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 +42 -16
- 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 +63 -54
- 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
@@ -0,0 +1,217 @@
|
|
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
|
9
|
+
|
10
|
+
def initialize(owner_class, action_name, params: {}, args: [], kwargs: {})
|
11
|
+
@owner_class = owner_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
|
27
|
+
owner_class.async_adapter.enqueue(owner_class.name, action_name, params:, args:, kwargs:)
|
28
|
+
end
|
29
|
+
|
30
|
+
def notify_now
|
31
|
+
return unless notification.payload
|
32
|
+
|
33
|
+
notifier.deliver!(notification)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :owner_class, :params, :args, :kwargs
|
39
|
+
|
40
|
+
def notifier
|
41
|
+
@notifier ||= owner_class.new(action_name, **params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Notification object contains the compiled payload to be delivered
|
46
|
+
class Notification
|
47
|
+
attr_reader :payload
|
48
|
+
|
49
|
+
def initialize(payload)
|
50
|
+
@payload = payload
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Base class for notifiers
|
55
|
+
class Base
|
56
|
+
class ParamsProxy
|
57
|
+
attr_reader :notifier_class, :params
|
58
|
+
|
59
|
+
def initialize(notifier_class, params)
|
60
|
+
@notifier_class = notifier_class
|
61
|
+
@params = params
|
62
|
+
end
|
63
|
+
|
64
|
+
# rubocop:disable Style/MethodMissingSuper
|
65
|
+
def method_missing(method_name, *args, **kwargs)
|
66
|
+
NotificationDelivery.new(notifier_class, method_name, params:, args:, kwargs:)
|
67
|
+
end
|
68
|
+
# rubocop:enable Style/MethodMissingSuper
|
69
|
+
|
70
|
+
def respond_to_missing?(*args)
|
71
|
+
notifier_class.respond_to_missing?(*args)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class << self
|
76
|
+
attr_writer :driver
|
77
|
+
|
78
|
+
def driver
|
79
|
+
return @driver if instance_variable_defined?(:@driver)
|
80
|
+
|
81
|
+
@driver =
|
82
|
+
if superclass.respond_to?(:driver)
|
83
|
+
superclass.driver
|
84
|
+
else
|
85
|
+
raise "Driver not found for #{name}. " \
|
86
|
+
"Please, specify driver via `self.driver = MyDriver`"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def async_adapter=(args)
|
91
|
+
adapter, options = Array(args)
|
92
|
+
@async_adapter = AsyncAdapters.lookup(adapter, options)
|
93
|
+
end
|
94
|
+
|
95
|
+
def async_adapter
|
96
|
+
return @async_adapter if instance_variable_defined?(:@async_adapter)
|
97
|
+
|
98
|
+
@async_adapter =
|
99
|
+
if superclass.respond_to?(:async_adapter)
|
100
|
+
superclass.async_adapter
|
101
|
+
else
|
102
|
+
AbstractNotifier.async_adapter
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def default(method_name = nil, **hargs, &block)
|
107
|
+
return @defaults_generator = block if block
|
108
|
+
|
109
|
+
return @defaults_generator = proc { send(method_name) } unless method_name.nil?
|
110
|
+
|
111
|
+
@default_params =
|
112
|
+
if superclass.respond_to?(:default_params)
|
113
|
+
superclass.default_params.merge(hargs).freeze
|
114
|
+
else
|
115
|
+
hargs.freeze
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def defaults_generator
|
120
|
+
return @defaults_generator if instance_variable_defined?(:@defaults_generator)
|
121
|
+
|
122
|
+
@defaults_generator =
|
123
|
+
if superclass.respond_to?(:defaults_generator)
|
124
|
+
superclass.defaults_generator
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def default_params
|
129
|
+
return @default_params if instance_variable_defined?(:@default_params)
|
130
|
+
|
131
|
+
@default_params =
|
132
|
+
if superclass.respond_to?(:default_params)
|
133
|
+
superclass.default_params.dup
|
134
|
+
else
|
135
|
+
{}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def method_missing(method_name, *args, **kwargs)
|
140
|
+
if action_methods.include?(method_name.to_s)
|
141
|
+
NotificationDelivery.new(self, method_name, args:, kwargs:)
|
142
|
+
else
|
143
|
+
super
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def with(params)
|
148
|
+
ParamsProxy.new(self, params)
|
149
|
+
end
|
150
|
+
|
151
|
+
def respond_to_missing?(method_name, _include_private = false)
|
152
|
+
action_methods.include?(method_name.to_s) || super
|
153
|
+
end
|
154
|
+
|
155
|
+
# See https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/actionpack/lib/abstract_controller/base.rb#L74
|
156
|
+
def action_methods
|
157
|
+
@action_methods ||= begin
|
158
|
+
# All public instance methods of this class, including ancestors
|
159
|
+
methods = (public_instance_methods(true) -
|
160
|
+
# Except for public instance methods of Base and its ancestors
|
161
|
+
Base.public_instance_methods(true) +
|
162
|
+
# Be sure to include shadowed public instance methods of this class
|
163
|
+
public_instance_methods(false))
|
164
|
+
|
165
|
+
methods.map!(&:to_s)
|
166
|
+
|
167
|
+
methods.to_set
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
attr_reader :params, :notification_name
|
173
|
+
|
174
|
+
def initialize(notification_name, **params)
|
175
|
+
@notification_name = notification_name
|
176
|
+
@params = params.freeze
|
177
|
+
end
|
178
|
+
|
179
|
+
def process_action(...)
|
180
|
+
public_send(...)
|
181
|
+
end
|
182
|
+
|
183
|
+
def deliver!(notification)
|
184
|
+
self.class.driver.call(notification.payload)
|
185
|
+
end
|
186
|
+
|
187
|
+
def notification(**payload)
|
188
|
+
merge_defaults!(payload)
|
189
|
+
|
190
|
+
payload[:body] = implicit_payload_body unless payload.key?(:body)
|
191
|
+
|
192
|
+
raise ArgumentError, "Notification body must be present" if
|
193
|
+
payload[:body].nil? || payload[:body].empty?
|
194
|
+
|
195
|
+
@notification = Notification.new(payload)
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def implicit_payload_body
|
201
|
+
# no-op — override to provide custom logic
|
202
|
+
end
|
203
|
+
|
204
|
+
def merge_defaults!(payload)
|
205
|
+
defaults =
|
206
|
+
if self.class.defaults_generator
|
207
|
+
instance_exec(&self.class.defaults_generator)
|
208
|
+
else
|
209
|
+
self.class.default_params
|
210
|
+
end
|
211
|
+
|
212
|
+
defaults.each do |k, v|
|
213
|
+
payload[k] = v unless payload.key?(k)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/version"
|
4
|
+
require "active_support/callbacks"
|
5
|
+
require "active_support/concern"
|
6
|
+
|
7
|
+
module AbstractNotifier
|
8
|
+
# Add callbacks support to Abstract Notifier (requires ActiveSupport::Callbacks)
|
9
|
+
#
|
10
|
+
# # Run method before seding notification
|
11
|
+
# # NOTE: when `false` is returned the execution is halted
|
12
|
+
# before_action :do_something
|
13
|
+
#
|
14
|
+
# # after_ and around_ callbacks are also supported
|
15
|
+
# after_action :cleanup
|
16
|
+
#
|
17
|
+
# around_action :set_context
|
18
|
+
#
|
19
|
+
# # Deliver callbacks are also available
|
20
|
+
# before_deliver :do_something
|
21
|
+
#
|
22
|
+
# # after_ and around_ callbacks are also supported
|
23
|
+
# after_deliver :cleanup
|
24
|
+
#
|
25
|
+
# around_deliver :set_context
|
26
|
+
module Callbacks
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
include ActiveSupport::Callbacks
|
30
|
+
|
31
|
+
CALLBACK_TERMINATOR = ->(_target, result) { result.call == false }
|
32
|
+
|
33
|
+
included do
|
34
|
+
define_callbacks :action,
|
35
|
+
terminator: CALLBACK_TERMINATOR,
|
36
|
+
skip_after_callbacks_if_terminated: true
|
37
|
+
|
38
|
+
define_callbacks :deliver,
|
39
|
+
terminator: CALLBACK_TERMINATOR,
|
40
|
+
skip_after_callbacks_if_terminated: true
|
41
|
+
|
42
|
+
prepend InstanceExt
|
43
|
+
end
|
44
|
+
|
45
|
+
module InstanceExt
|
46
|
+
def process_action(...)
|
47
|
+
run_callbacks(:action) { super(...) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def deliver!(...)
|
51
|
+
run_callbacks(:deliver) { super(...) }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class_methods do
|
56
|
+
def _normalize_callback_options(options)
|
57
|
+
_normalize_callback_option(options, :only, :if)
|
58
|
+
_normalize_callback_option(options, :except, :unless)
|
59
|
+
end
|
60
|
+
|
61
|
+
def _normalize_callback_option(options, from, to)
|
62
|
+
if (from = options[from])
|
63
|
+
from_set = Array(from).map(&:to_s).to_set
|
64
|
+
from = proc { |c| from_set.include? c.notification_name.to_s }
|
65
|
+
options[to] = Array(options[to]).unshift(from)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
%i[before after around].each do |kind|
|
70
|
+
%i[action deliver].each do |event|
|
71
|
+
define_method "#{kind}_#{event}" do |*names, on: event, **options, &block|
|
72
|
+
_normalize_callback_options(options)
|
73
|
+
|
74
|
+
names.each do |name|
|
75
|
+
set_callback on, kind, name, options
|
76
|
+
end
|
77
|
+
|
78
|
+
set_callback on, kind, block, options if block
|
79
|
+
end
|
80
|
+
|
81
|
+
define_method "skip_#{kind}_#{event}" do |*names, on: event, **options|
|
82
|
+
_normalize_callback_options(options)
|
83
|
+
|
84
|
+
names.each do |name|
|
85
|
+
skip_callback(on, kind, name, options)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
AbstractNotifier::Base.include AbstractNotifier::Callbacks
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
module TestHelper
|
5
|
+
def assert_notifications_sent(count, params)
|
6
|
+
yield
|
7
|
+
assert_equal deliveries.count, count
|
8
|
+
count.times do |i|
|
9
|
+
delivery = deliveries[0 - i]
|
10
|
+
if !params[:via]
|
11
|
+
delivery = delivery.dup
|
12
|
+
delivery.delete(:via)
|
13
|
+
end
|
14
|
+
|
15
|
+
msg = message(msg) { "Expected #{mu_pp(delivery)} to include #{mu_pp(params)}" }
|
16
|
+
assert hash_include?(delivery, params), msg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def assert_notifications_enqueued(count, params)
|
21
|
+
yield
|
22
|
+
assert_equal enqueued_deliveries.count, count
|
23
|
+
count.times do |i|
|
24
|
+
delivery = enqueued_deliveries[0 - i]
|
25
|
+
if !params[:via]
|
26
|
+
delivery = delivery.dup
|
27
|
+
delivery.delete(:via)
|
28
|
+
end
|
29
|
+
|
30
|
+
msg = message(msg) { "Expected #{mu_pp(delivery)} to include #{mu_pp(params)}" }
|
31
|
+
assert hash_include?(delivery, params), msg
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def deliveries
|
38
|
+
AbstractNotifier::Testing::Driver.deliveries
|
39
|
+
end
|
40
|
+
|
41
|
+
def enqueued_deliveries
|
42
|
+
AbstractNotifier::Testing::Driver.enqueued_deliveries
|
43
|
+
end
|
44
|
+
|
45
|
+
def hash_include?(haystack, needle)
|
46
|
+
needle.all? do |k, v|
|
47
|
+
haystack.key?(k) && haystack[k] == v
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
class HaveSentNotification < RSpec::Matchers::BuiltIn::BaseMatcher
|
5
|
+
attr_reader :payload
|
6
|
+
|
7
|
+
def initialize(payload = nil)
|
8
|
+
@payload = payload
|
9
|
+
set_expected_number(:exactly, 1)
|
10
|
+
end
|
11
|
+
|
12
|
+
def exactly(count)
|
13
|
+
set_expected_number(:exactly, count)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def at_least(count)
|
18
|
+
set_expected_number(:at_least, count)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def at_most(count)
|
23
|
+
set_expected_number(:at_most, count)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def times
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def once
|
32
|
+
exactly(:once)
|
33
|
+
end
|
34
|
+
|
35
|
+
def twice
|
36
|
+
exactly(:twice)
|
37
|
+
end
|
38
|
+
|
39
|
+
def thrice
|
40
|
+
exactly(:thrice)
|
41
|
+
end
|
42
|
+
|
43
|
+
def supports_block_expectations?
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def matches?(proc)
|
48
|
+
raise ArgumentError, "have_sent_notification only supports block expectations" unless Proc === proc
|
49
|
+
|
50
|
+
raise "You can only use have_sent_notification matcher in :test delivery mode" unless AbstractNotifier.test?
|
51
|
+
|
52
|
+
original_deliveries_count = deliveries.count
|
53
|
+
proc.call
|
54
|
+
in_block_deliveries = deliveries.drop(original_deliveries_count)
|
55
|
+
|
56
|
+
@matching_deliveries, @unmatching_deliveries =
|
57
|
+
in_block_deliveries.partition do |actual_payload|
|
58
|
+
next true if payload.nil?
|
59
|
+
|
60
|
+
if payload.is_a?(::Hash) && !payload[:via]
|
61
|
+
actual_payload = actual_payload.dup
|
62
|
+
actual_payload.delete(:via)
|
63
|
+
end
|
64
|
+
|
65
|
+
payload === actual_payload
|
66
|
+
end
|
67
|
+
|
68
|
+
@matching_count = @matching_deliveries.size
|
69
|
+
|
70
|
+
case @expectation_type
|
71
|
+
when :exactly then @expected_number == @matching_count
|
72
|
+
when :at_most then @expected_number >= @matching_count
|
73
|
+
when :at_least then @expected_number <= @matching_count
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def deliveries
|
78
|
+
AbstractNotifier::Testing::Driver.deliveries
|
79
|
+
end
|
80
|
+
|
81
|
+
def set_expected_number(relativity, count)
|
82
|
+
@expectation_type = relativity
|
83
|
+
@expected_number =
|
84
|
+
case count
|
85
|
+
when :once then 1
|
86
|
+
when :twice then 2
|
87
|
+
when :thrice then 3
|
88
|
+
else Integer(count)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def failure_message
|
93
|
+
(+"expected to #{verb_present} notification: #{payload_description}").tap do |msg|
|
94
|
+
msg << " #{message_expectation_modifier}, but"
|
95
|
+
|
96
|
+
if @unmatching_deliveries.any?
|
97
|
+
msg << " #{verb_past} the following notifications:"
|
98
|
+
@unmatching_deliveries.each do |unmatching_payload|
|
99
|
+
msg << "\n #{unmatching_payload}"
|
100
|
+
end
|
101
|
+
else
|
102
|
+
msg << " haven't #{verb_past} anything"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def failure_message_when_negated
|
108
|
+
"expected not to #{verb_present} #{payload}"
|
109
|
+
end
|
110
|
+
|
111
|
+
def message_expectation_modifier
|
112
|
+
number_modifier = (@expected_number == 1) ? "once" : "#{@expected_number} times"
|
113
|
+
case @expectation_type
|
114
|
+
when :exactly then "exactly #{number_modifier}"
|
115
|
+
when :at_most then "at most #{number_modifier}"
|
116
|
+
when :at_least then "at least #{number_modifier}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def payload_description
|
121
|
+
if payload.is_a?(RSpec::Matchers::Composable)
|
122
|
+
payload.description
|
123
|
+
else
|
124
|
+
payload
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def verb_past
|
129
|
+
"sent"
|
130
|
+
end
|
131
|
+
|
132
|
+
def verb_present
|
133
|
+
"send"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class HaveEnqueuedNotification < HaveSentNotification
|
138
|
+
private
|
139
|
+
|
140
|
+
def deliveries
|
141
|
+
AbstractNotifier::Testing::Driver.enqueued_deliveries
|
142
|
+
end
|
143
|
+
|
144
|
+
def verb_past
|
145
|
+
"enqueued"
|
146
|
+
end
|
147
|
+
|
148
|
+
def verb_present
|
149
|
+
"enqueue"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
RSpec.configure do |config|
|
155
|
+
config.include(Module.new do
|
156
|
+
def have_sent_notification(*args)
|
157
|
+
AbstractNotifier::HaveSentNotification.new(*args)
|
158
|
+
end
|
159
|
+
|
160
|
+
def have_enqueued_notification(*args)
|
161
|
+
AbstractNotifier::HaveEnqueuedNotification.new(*args)
|
162
|
+
end
|
163
|
+
end)
|
164
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AbstractNotifier
|
4
|
+
module Testing
|
5
|
+
module Driver
|
6
|
+
class << self
|
7
|
+
def deliveries
|
8
|
+
Thread.current[:notifier_deliveries] ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def enqueued_deliveries
|
12
|
+
Thread.current[:notifier_enqueued_deliveries] ||= []
|
13
|
+
end
|
14
|
+
|
15
|
+
def clear
|
16
|
+
deliveries.clear
|
17
|
+
enqueued_deliveries.clear
|
18
|
+
end
|
19
|
+
|
20
|
+
def send_notification(data)
|
21
|
+
deliveries << data
|
22
|
+
end
|
23
|
+
|
24
|
+
def enqueue_notification(data)
|
25
|
+
enqueued_deliveries << data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module NotificationDelivery
|
31
|
+
def notify_now
|
32
|
+
return super unless AbstractNotifier.test?
|
33
|
+
|
34
|
+
payload = notification.payload
|
35
|
+
|
36
|
+
Driver.send_notification payload.merge(via: notifier.class)
|
37
|
+
end
|
38
|
+
|
39
|
+
def notify_later
|
40
|
+
return super unless AbstractNotifier.test?
|
41
|
+
|
42
|
+
payload = notification.payload
|
43
|
+
|
44
|
+
Driver.enqueue_notification payload.merge(via: notifier.class)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
AbstractNotifier::NotificationDelivery.prepend AbstractNotifier::Testing::NotificationDelivery
|
51
|
+
|
52
|
+
require "abstract_notifier/testing/rspec" if defined?(RSpec::Core)
|
53
|
+
require "abstract_notifier/testing/minitest" if defined?(Minitest::Assertions)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "abstract_notifier/version"
|
4
|
+
|
5
|
+
# Abstract Notifier is responsible for generating and triggering text-based notifications
|
6
|
+
# (like Action Mailer for email notifications).
|
7
|
+
#
|
8
|
+
# Example:
|
9
|
+
#
|
10
|
+
# class ApplicationNotifier < AbstractNotifier::Base
|
11
|
+
# self.driver = NotifyService.new
|
12
|
+
#
|
13
|
+
# def profile
|
14
|
+
# params[:profile] if params
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# class EventsNotifier < ApplicationNotifier
|
19
|
+
# def canceled(event)
|
20
|
+
# notification(
|
21
|
+
# # the only required option is `body`
|
22
|
+
# body: "Event #{event.title} has been canceled",
|
23
|
+
# # all other options are passed to delivery driver
|
24
|
+
# identity: profile.notification_service_id
|
25
|
+
# )
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# EventsNotifier.with(profile: profile).canceled(event).notify_later
|
30
|
+
#
|
31
|
+
module AbstractNotifier
|
32
|
+
DELIVERY_MODES = %i[test noop normal].freeze
|
33
|
+
|
34
|
+
class << self
|
35
|
+
attr_reader :delivery_mode
|
36
|
+
attr_reader :async_adapter
|
37
|
+
|
38
|
+
def delivery_mode=(val)
|
39
|
+
unless DELIVERY_MODES.include?(val)
|
40
|
+
raise ArgumentError, "Unsupported delivery mode: #{val}. " \
|
41
|
+
"Supported values: #{DELIVERY_MODES.join(", ")}"
|
42
|
+
end
|
43
|
+
|
44
|
+
@delivery_mode = val
|
45
|
+
end
|
46
|
+
|
47
|
+
def async_adapter=(args)
|
48
|
+
adapter, options = Array(args)
|
49
|
+
@async_adapter = AsyncAdapters.lookup(adapter, options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def noop?
|
53
|
+
delivery_mode == :noop
|
54
|
+
end
|
55
|
+
|
56
|
+
def test?
|
57
|
+
delivery_mode == :test
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
self.delivery_mode =
|
62
|
+
if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
|
63
|
+
:test
|
64
|
+
else
|
65
|
+
:normal
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
require "abstract_notifier/base"
|
70
|
+
require "abstract_notifier/async_adapters"
|
71
|
+
|
72
|
+
require "abstract_notifier/callbacks" if defined?(ActiveSupport)
|
73
|
+
require "abstract_notifier/async_adapters/active_job" if defined?(ActiveJob)
|
74
|
+
|
75
|
+
require "abstract_notifier/testing" if ENV["RACK_ENV"] == "test" || ENV["RAILS_ENV"] == "test"
|