active_delivery 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/README.md +60 -25
  4. data/lib/.rbnext/3.0/abstract_notifier/async_adapters/active_job.rb +12 -3
  5. data/lib/.rbnext/3.0/abstract_notifier/base.rb +223 -0
  6. data/lib/.rbnext/3.0/active_delivery/base.rb +14 -13
  7. data/lib/.rbnext/3.0/active_delivery/lines/base.rb +14 -2
  8. data/lib/.rbnext/3.0/active_delivery/lines/mailer.rb +4 -0
  9. data/lib/.rbnext/3.0/active_delivery/testing/minitest.rb +58 -0
  10. data/lib/.rbnext/3.0/active_delivery/testing.rb +1 -0
  11. data/lib/.rbnext/3.1/abstract_notifier/async_adapters/active_job.rb +36 -0
  12. data/lib/.rbnext/3.1/abstract_notifier/base.rb +16 -10
  13. data/lib/.rbnext/3.1/active_delivery/base.rb +14 -13
  14. data/lib/.rbnext/3.1/active_delivery/lines/base.rb +14 -2
  15. data/lib/.rbnext/3.1/active_delivery/testing/minitest.rb +58 -0
  16. data/lib/.rbnext/3.2/abstract_notifier/async_adapters/active_job.rb +36 -0
  17. data/lib/.rbnext/3.2/abstract_notifier/base.rb +223 -0
  18. data/lib/.rbnext/3.2/abstract_notifier/testing/rspec.rb +164 -0
  19. data/lib/.rbnext/3.2/abstract_notifier/testing.rb +53 -0
  20. data/lib/.rbnext/3.2/active_delivery/base.rb +249 -0
  21. data/lib/.rbnext/3.2/active_delivery/lines/base.rb +101 -0
  22. data/lib/.rbnext/3.2/active_delivery/lines/notifier.rb +57 -0
  23. data/lib/.rbnext/3.2/active_delivery/testing/rspec.rb +222 -0
  24. data/lib/abstract_notifier/async_adapters/active_job.rb +12 -3
  25. data/lib/abstract_notifier/base.rb +15 -9
  26. data/lib/abstract_notifier/testing/minitest.rb +1 -1
  27. data/lib/abstract_notifier/testing/rspec.rb +4 -4
  28. data/lib/abstract_notifier/testing.rb +2 -2
  29. data/lib/active_delivery/base.rb +14 -13
  30. data/lib/active_delivery/lines/base.rb +14 -2
  31. data/lib/active_delivery/lines/mailer.rb +4 -0
  32. data/lib/active_delivery/lines/notifier.rb +8 -4
  33. data/lib/active_delivery/testing/minitest.rb +58 -0
  34. data/lib/active_delivery/testing/rspec.rb +4 -4
  35. data/lib/active_delivery/testing.rb +1 -0
  36. data/lib/active_delivery/version.rb +1 -1
  37. metadata +20 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34afeceddc78be6864aed136f68bc0a0f0eb43c203ef1520260fe94a8c934b39
4
- data.tar.gz: dab4270f8b7a4c5e14b18c3fd36ceb081587425f26a50060191778bab768d688
3
+ metadata.gz: d05087cb016af8444f7668b81681675248b7b63f41f77a1d9fdb22b65ccb6845
4
+ data.tar.gz: d60851ccb9d9ea6557d131289942edf57b39c79a499e37d41f8f1d06b480446f
5
5
  SHA512:
6
- metadata.gz: 67992b515155ae977dd2f839448b3fe89d8c1391906e3c20bd425782241378f70d0b933834433a5e30904a7f3a1176c1313cd690267f001258f9584b078f0367
7
- data.tar.gz: c811abf777c5c0c7552749fcedd228e7914bc80ded1a14129f4194097d74049f72e80c229c59549ac09141fed3145ab4c4199706ca29055ce6a405c3c16e13a2
6
+ metadata.gz: 5a94a2fd3e2f19d9e99c0df6494752b23649802b064b390ea50d65506b01beb57650f31f5cd140495d6befa847a65423cc141ec8c313a6a116de69a4cd5576df
7
+ data.tar.gz: 4febe7f49e314d27f370642fa43085c5020dae8bac7fa9a3512514a6b861d17b726d4685ed91f081eab22f4c2af68a4117d37f8f29a576c78fe323f2c73b58e2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 1.2.0 (2024-02-05)
6
+
7
+ - Add Minitest assertions (`assert_deliveries`, `assert_no_deliveries`, `assert_delivery_enqueued`). ([@palkan][])
8
+
9
+ ## 1.1.0 (2023-12-01) ❄️
10
+
11
+ - Support delayed delivery options (e.g, `wait_until`). ([@palkan][])
12
+
5
13
  ## 📬 1.0.0 (2023-08-29)
6
14
 
7
15
  - Add `resolver_pattern` option to specify naming pattern for notifiers without using Procs. ([@palkan][])
@@ -43,6 +51,20 @@
43
51
 
44
52
  - Add `#deliver_via(*lines)` RSpec matcher. ([@palkan][])
45
53
 
54
+ - **BREAKING** The `#resolve_class` method in Line classes now receive a delivery class instead of a name:
55
+
56
+ ```ruby
57
+ # before
58
+ def resolve_class(name)
59
+ name.gsub(/Delivery$/, "Channel").safe_constantize
60
+ end
61
+
62
+ # after
63
+ def resolve_class(name)
64
+ name.to_s.gsub(/Delivery$/, "Channel").safe_constantize
65
+ end
66
+ ```
67
+
46
68
  - Provide ActionMailer-like interface to trigger notifications. ([@palkan][])
47
69
 
48
70
  Now you can send notifications as follows:
data/README.md CHANGED
@@ -10,6 +10,8 @@ Since v1.0, Active Delivery is bundled with [Abstract Notifier](https://github.c
10
10
 
11
11
  📖 Read the introduction post: ["Crafting user notifications in Rails with Active Delivery"](https://evilmartians.com/chronicles/crafting-user-notifications-in-rails-with-active-delivery)
12
12
 
13
+ 📖 Read more about designing notifications layer in Ruby on Rails applications in the [Layered design for Ruby on Rails applications](https://www.packtpub.com/product/layered-design-for-ruby-on-rails-applications/9781801813785) book.
14
+
13
15
  <a href="https://evilmartians.com/?utm_source=action_policy">
14
16
  <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
15
17
 
@@ -56,7 +58,7 @@ end
56
58
  Add this line to your application's Gemfile:
57
59
 
58
60
  ```ruby
59
- gem "active_delivery", "1.0.0.rc2"
61
+ gem "active_delivery", "~> 1.0"
60
62
  ```
61
63
 
62
64
  And then execute:
@@ -113,6 +115,9 @@ PostsDelivery.published(user, post).deliver_later
113
115
  PostsMailer.published(user, post).deliver_later
114
116
  PostsSMSNotifier.published(user, post).notify_later
115
117
 
118
+ # You can also pass options supported by your async executor (such as ActiveJob)
119
+ PostsDelivery.published(user, post).deliver_later(wait_until: 1.day.from_now)
120
+
116
121
  # and whaterver your ActionCableDeliveryLine does
117
122
  # under the hood.
118
123
  ```
@@ -288,17 +293,36 @@ MyDeliver.notify(:something_wicked_this_way_comes)
288
293
 
289
294
  ## Testing
290
295
 
291
- **NOTE:** Currently, only RSpec matchers are provided.
296
+ ### Setup
297
+
298
+ Test mode is activated automatically if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". Otherwise, add `require "active_delivery/testing/rspec"` to your `spec_helper.rb` / `rails_helper.rb` manually or `require "active_delivery/testing/minitest"`. This is also required if you're using Spring in the test environment (e.g. with help of [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)).
299
+
300
+ For Minitest, you also MUST include the test helper into your test class. For example:
301
+
302
+ ```ruby
303
+ class ActiveSupport::TestCase
304
+ # ...
305
+ include ActiveDelivery::TestHelper
306
+ end
307
+ ```
292
308
 
293
309
  ### Deliveries
294
310
 
295
- Active Delivery provides an elegant way to test deliveries in your code (i.e., when you want to check whether a notification has been sent) through a `have_delivered_to` matcher:
311
+ Active Delivery provides an elegant way to test deliveries in your code (i.e., when you want to check whether a notification has been sent) through a `have_delivered_to` RSpec matcher or `assert_delivery_enqueued` Minitest assertion:
296
312
 
297
313
  ```ruby
314
+ # RSpec
298
315
  it "delivers notification" do
299
316
  expect { subject }.to have_delivered_to(Community::EventsDelivery, :modified, event)
300
317
  .with(profile: profile)
301
318
  end
319
+
320
+ # Minitest
321
+ def test_delivers_notification
322
+ assert_delivery_enqueued(Community::EventsDelivery, :modified, with: [event]) do
323
+ some_action
324
+ end
325
+ end
302
326
  ```
303
327
 
304
328
  You can also use such RSpec features as compound expectations and composed matchers:
@@ -317,14 +341,27 @@ end
317
341
  If you want to test that no notification is delivered you can use negation
318
342
 
319
343
  ```ruby
344
+ # RSpec
320
345
  specify "when event is not found" do
321
346
  expect do
322
347
  described_class.perform_now(profile.id, "123", "one_hour_before")
323
348
  end.not_to have_delivered_to(Community::EventsDelivery)
324
349
  end
350
+
351
+ # Minitest
352
+ def test_no_notification_if_event_is_not_found
353
+ assert_no_deliveries do
354
+ some_action
355
+ end
356
+
357
+ # Alternatively, you can use the positive assertion
358
+ assert_deliveries(0) do
359
+ some_action
360
+ end
361
+ end
325
362
  ```
326
363
 
327
- or use the `#have_not_delivered_to` matcher:
364
+ With RSpec, you can also use the `#have_not_delivered_to` matcher:
328
365
 
329
366
  ```ruby
330
367
  specify "when event is not found" do
@@ -357,7 +394,7 @@ describe PostsDelivery do
357
394
  end
358
395
  ```
359
396
 
360
- You can also use the `#deliver_via` matchers as follows:
397
+ You can also use the `#deliver_via` RSpec matcher as follows:
361
398
 
362
399
  ```ruby
363
400
  describe PostsDelivery, type: :delivery do
@@ -384,8 +421,6 @@ describe PostsDelivery, type: :delivery do
384
421
  end
385
422
  ```
386
423
 
387
- **NOTE:** test mode activated automatically if `RAILS_ENV` or `RACK_ENV` env variable is equal to "test". Otherwise, add `require "active_delivery/testing/rspec"` to your `spec_helper.rb` / `rails_helper.rb` manually. This is also required if you're using Spring in the test environment (e.g. with help of [spring-commands-rspec](https://github.com/jonleighton/spring-commands-rspec)).
388
-
389
424
  ## Custom "lines"
390
425
 
391
426
  The _Line_ class describes the way you want to _transfer_ your deliveries.
@@ -407,8 +442,8 @@ class EventPigeon
407
442
  alias_method :with, :new
408
443
 
409
444
  # delegate delivery action to the instance
410
- def message_arrived(*args)
411
- new.message_arrived(*args)
445
+ def message_arrived(*)
446
+ new.message_arrived(*)
412
447
  end
413
448
  end
414
449
 
@@ -444,17 +479,17 @@ class PigeonLine < ActiveDelivery::Lines::Base
444
479
  # Called when we want to send message synchronously
445
480
  # `sender` here either `sender_class` or `sender_class.with(params)`
446
481
  # if params passed.
447
- def notify_now(sender, delivery_action, *args, **kwargs)
482
+ def notify_now(sender, delivery_action, *, **)
448
483
  # For example, our EventPigeon class returns some `Pigeon` object
449
- pigeon = sender.public_send(delivery_action, *args, **kwargs)
484
+ pigeon = sender.public_send(delivery_action, *, **)
450
485
  # PigeonLaunchService do all the sending job
451
486
  PigeonService.launch pigeon
452
487
  end
453
488
 
454
489
  # Called when we want to send a message asynchronously.
455
490
  # For example, you can use a background job here.
456
- def notify_later(sender, delivery_action, *args, **kwargs)
457
- pigeon = sender.public_send(delivery_action, *args, **kwargs)
491
+ def notify_later(sender, delivery_action, *, **)
492
+ pigeon = sender.public_send(delivery_action, *, **)
458
493
  # PigeonLaunchService do all the sending job
459
494
  PigeonLaunchJob.perform_later pigeon
460
495
  end
@@ -472,8 +507,8 @@ class EventPigeon
472
507
  alias_method :with, :new
473
508
 
474
509
  # delegate delivery action to the instance
475
- def message_arrived(*args)
476
- new.message_arrived(*args)
510
+ def message_arrived(*)
511
+ new.message_arrived(*)
477
512
  end
478
513
  end
479
514
 
@@ -488,18 +523,18 @@ class EventPigeon
488
523
  end
489
524
 
490
525
  class PigeonLine < ActiveDelivery::Lines::Base
491
- def notify_later(sender, delivery_action, *args, **kwargs)
526
+ def notify_later(sender, delivery_action, *, **kwargs)
492
527
  # `to_s` is important for serialization. Unless you might have error
493
- PigeonLaunchJob.perform_later sender.class.to_s, delivery_action, *args, **kwargs.merge(params: line.params)
528
+ PigeonLaunchJob.perform_later(sender.class.to_s, delivery_action, *, **kwargs.merge(params: line.params))
494
529
  end
495
530
  end
496
531
 
497
532
  class PigeonLaunchJob < ActiveJob::Base
498
- def perform(sender, delivery_action, *args, params: nil, **kwargs)
533
+ def perform(sender, delivery_action, *, params: nil, **)
499
534
  klass = sender.safe_constantize
500
535
  handler = params ? klass.with(**params) : klass.new
501
536
 
502
- handler.public_send(delivery_action, *args, **kwargs)
537
+ handler.public_send(delivery_action, *, **)
503
538
  end
504
539
  end
505
540
  ```
@@ -588,12 +623,12 @@ class ActionCableDeliveryLine < ActiveDelivery::Line::Base
588
623
  # We want to broadcast all notifications
589
624
  def notify?(...) = true
590
625
 
591
- def notify_now(context, delivery_action, *args, **kwargs)
626
+ def notify_now(context, delivery_action, *, **)
592
627
  # Skip if no user provided
593
628
  return unless context.user
594
629
 
595
630
  payload = {event: [context.scope, delivery_action].join(".")}
596
- payload.merge!(serialized_args(*args, **kwargs))
631
+ payload.merge!(serialized_args(*, **))
597
632
 
598
633
  DeliveryChannel.broadcast_to context.user, payload
599
634
  end
@@ -706,7 +741,7 @@ end
706
741
 
707
742
  ### Background jobs / async notifications
708
743
 
709
- To use `#notify_later` you **must** configure an async adapter for Abstract Notifier.
744
+ To use `#notify_later(**delivery_options)` you **must** configure an async adapter for Abstract Notifier.
710
745
 
711
746
  We provide an Active Job adapter out of the box and enable it if Active Job is found.
712
747
 
@@ -718,13 +753,13 @@ class MyAsyncAdapter
718
753
  def initialize(options = {})
719
754
  end
720
755
 
721
- # `enqueue` method accepts notifier class, action name and notification parameters
722
- def enqueue(notifier_class, action_name, params:, args:, kwargs:)
756
+ # `enqueue_delivery` method accepts notifier class, action name and notification parameters
757
+ def enqueue_delivery(delivery, **options)
723
758
  # <Your implementation here>
724
759
  # To trigger the notification delivery, you can use the following snippet:
725
760
  #
726
761
  # AbstractNotifier::NotificationDelivery.new(
727
- # notifier_class.constantize, action_name, params:, args:, kwargs:
762
+ # delivery.notifier_class, delivery.action_name, **delivery.delivery_params
728
763
  # ).notify_now
729
764
  end
730
765
  end
@@ -11,15 +11,24 @@ module AbstractNotifier
11
11
 
12
12
  DEFAULT_QUEUE = "notifiers"
13
13
 
14
- attr_reader :job
14
+ attr_reader :job, :queue
15
15
 
16
16
  def initialize(queue: DEFAULT_QUEUE, job: DeliveryJob)
17
- @job = job.set(queue: queue)
17
+ @job = job
18
+ @queue = queue
18
19
  end
19
20
 
20
21
  def enqueue(...)
21
- job.perform_later(...)
22
+ job.set(queue: queue).perform_later(...)
22
23
  end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :enqueue)
24
+
25
+ def enqueue_delivery(delivery, **__kwrest__)
26
+ job.set(queue: queue, **__kwrest__).perform_later(
27
+ delivery.notifier_class.name,
28
+ delivery.action_name,
29
+ **delivery.delivery_params
30
+ )
31
+ end
23
32
  end
24
33
  end
25
34
  end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AbstractNotifier
4
+ # NotificationDelivery payload wrapper which contains
5
+ # information about the current notifier class
6
+ # and knows how to trigger the delivery
7
+ class NotificationDelivery
8
+ attr_reader :action_name, :notifier_class
9
+
10
+ def initialize(notifier_class, action_name, params: {}, args: [], kwargs: {})
11
+ @notifier_class = notifier_class
12
+ @action_name = action_name
13
+ @params = params
14
+ @args = args
15
+ @kwargs = kwargs
16
+ end
17
+
18
+ def processed
19
+ return @processed if instance_variable_defined?(:@processed)
20
+
21
+ @processed = notifier.process_action(action_name, *args, **kwargs) || Notification.new(nil)
22
+ end
23
+
24
+ alias_method :notification, :processed
25
+
26
+ def notify_later(**__kwrest__)
27
+ if notifier_class.async_adapter.respond_to?(:enqueue_delivery)
28
+ notifier_class.async_adapter.enqueue_delivery(self, **__kwrest__)
29
+ else
30
+ notifier_class.async_adapter.enqueue(notifier_class.name, action_name, params: params, args: args, kwargs: kwargs)
31
+ end
32
+ end
33
+
34
+ def notify_now
35
+ return unless notification.payload
36
+
37
+ notifier.deliver!(notification)
38
+ end
39
+
40
+ def delivery_params ; {params: params, args: args, kwargs: kwargs}; end
41
+
42
+ private
43
+
44
+ attr_reader :params, :args, :kwargs
45
+
46
+ def notifier
47
+ @notifier ||= notifier_class.new(action_name, **params)
48
+ end
49
+ end
50
+
51
+ # Notification object contains the compiled payload to be delivered
52
+ class Notification
53
+ attr_reader :payload
54
+
55
+ def initialize(payload)
56
+ @payload = payload
57
+ end
58
+ end
59
+
60
+ # Base class for notifiers
61
+ class Base
62
+ class ParamsProxy
63
+ attr_reader :notifier_class, :params
64
+
65
+ def initialize(notifier_class, params)
66
+ @notifier_class = notifier_class
67
+ @params = params
68
+ end
69
+
70
+ # rubocop:disable Style/MethodMissingSuper
71
+ def method_missing(method_name, *args, **kwargs)
72
+ NotificationDelivery.new(notifier_class, method_name, params: params, args: args, kwargs: kwargs)
73
+ end
74
+ # rubocop:enable Style/MethodMissingSuper
75
+
76
+ def respond_to_missing?(*__rest__)
77
+ notifier_class.respond_to_missing?(*__rest__)
78
+ end
79
+ end
80
+
81
+ class << self
82
+ attr_writer :driver
83
+
84
+ def driver
85
+ return @driver if instance_variable_defined?(:@driver)
86
+
87
+ @driver =
88
+ if superclass.respond_to?(:driver)
89
+ superclass.driver
90
+ else
91
+ raise "Driver not found for #{name}. " \
92
+ "Please, specify driver via `self.driver = MyDriver`"
93
+ end
94
+ end
95
+
96
+ def async_adapter=(args)
97
+ adapter, options = Array(args)
98
+ @async_adapter = AsyncAdapters.lookup(adapter, options)
99
+ end
100
+
101
+ def async_adapter
102
+ return @async_adapter if instance_variable_defined?(:@async_adapter)
103
+
104
+ @async_adapter =
105
+ if superclass.respond_to?(:async_adapter)
106
+ superclass.async_adapter
107
+ else
108
+ AbstractNotifier.async_adapter
109
+ end
110
+ end
111
+
112
+ def default(method_name = nil, **hargs, &block)
113
+ return @defaults_generator = block if block
114
+
115
+ return @defaults_generator = proc { send(method_name) } unless method_name.nil?
116
+
117
+ @default_params =
118
+ if superclass.respond_to?(:default_params)
119
+ superclass.default_params.merge(hargs).freeze
120
+ else
121
+ hargs.freeze
122
+ end
123
+ end
124
+
125
+ def defaults_generator
126
+ return @defaults_generator if instance_variable_defined?(:@defaults_generator)
127
+
128
+ @defaults_generator =
129
+ if superclass.respond_to?(:defaults_generator)
130
+ superclass.defaults_generator
131
+ end
132
+ end
133
+
134
+ def default_params
135
+ return @default_params if instance_variable_defined?(:@default_params)
136
+
137
+ @default_params =
138
+ if superclass.respond_to?(:default_params)
139
+ superclass.default_params.dup
140
+ else
141
+ {}
142
+ end
143
+ end
144
+
145
+ def method_missing(method_name, *args, **kwargs)
146
+ if action_methods.include?(method_name.to_s)
147
+ NotificationDelivery.new(self, method_name, args: args, kwargs: kwargs)
148
+ else
149
+ super
150
+ end
151
+ end
152
+
153
+ def with(params)
154
+ ParamsProxy.new(self, params)
155
+ end
156
+
157
+ def respond_to_missing?(method_name, _include_private = false)
158
+ action_methods.include?(method_name.to_s) || super
159
+ end
160
+
161
+ # See https://github.com/rails/rails/blob/b13a5cb83ea00d6a3d71320fd276ca21049c2544/actionpack/lib/abstract_controller/base.rb#L74
162
+ def action_methods
163
+ @action_methods ||= begin
164
+ # All public instance methods of this class, including ancestors
165
+ methods = (public_instance_methods(true) -
166
+ # Except for public instance methods of Base and its ancestors
167
+ Base.public_instance_methods(true) +
168
+ # Be sure to include shadowed public instance methods of this class
169
+ public_instance_methods(false))
170
+
171
+ methods.map!(&:to_s)
172
+
173
+ methods.to_set
174
+ end
175
+ end
176
+ end
177
+
178
+ attr_reader :params, :notification_name
179
+
180
+ def initialize(notification_name, **params)
181
+ @notification_name = notification_name
182
+ @params = params.freeze
183
+ end
184
+
185
+ def process_action(...)
186
+ public_send(...)
187
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :process_action)
188
+
189
+ def deliver!(notification)
190
+ self.class.driver.call(notification.payload)
191
+ end
192
+
193
+ def notification(**payload)
194
+ merge_defaults!(payload)
195
+
196
+ payload[:body] = implicit_payload_body unless payload.key?(:body)
197
+
198
+ raise ArgumentError, "Notification body must be present" if
199
+ payload[:body].nil? || payload[:body].empty?
200
+
201
+ @notification = Notification.new(payload)
202
+ end
203
+
204
+ private
205
+
206
+ def implicit_payload_body
207
+ # no-op — override to provide custom logic
208
+ end
209
+
210
+ def merge_defaults!(payload)
211
+ defaults =
212
+ if self.class.defaults_generator
213
+ instance_exec(&self.class.defaults_generator)
214
+ else
215
+ self.class.default_params
216
+ end
217
+
218
+ defaults.each do |k, v|
219
+ payload[k] = v unless payload.key?(k)
220
+ end
221
+ end
222
+ end
223
+ end
@@ -12,9 +12,9 @@ module ActiveDelivery
12
12
  @metadata = metadata.freeze
13
13
  end
14
14
 
15
- def deliver_later ; owner.perform_notify(self); end
15
+ def deliver_later(**opts) ; owner.perform_notify(self, enqueue_options: opts); end
16
16
 
17
- def deliver_now ; owner.perform_notify(self, sync: true); end
17
+ def deliver_now(**opts) ; owner.perform_notify(self, sync: true); end
18
18
 
19
19
  def delivery_class ; owner.class; end
20
20
  end
@@ -77,8 +77,8 @@ module ActiveDelivery
77
77
 
78
78
  # The same as .notify but delivers synchronously
79
79
  # (i.e. #deliver_now for mailers)
80
- def notify!(mid, *args, **hargs)
81
- notify(mid, *args, **hargs, sync: true)
80
+ def notify!(mid, *__rest__, **hargs)
81
+ notify(mid, *__rest__, **hargs, sync: true)
82
82
  end
83
83
 
84
84
  alias_method :notify_now, :notify!
@@ -93,7 +93,7 @@ module ActiveDelivery
93
93
  end
94
94
  end
95
95
 
96
- def register_line(line_id, line_class = nil, notifier: nil, **options)
96
+ def register_line(line_id, line_class = nil, notifier: nil, **__kwrest__)
97
97
  raise ArgumentError, "A line class or notifier configuration must be provided" if line_class.nil? && notifier.nil?
98
98
 
99
99
  # Configure Notifier
@@ -101,7 +101,7 @@ module ActiveDelivery
101
101
  line_class = ActiveDelivery::Lines::Notifier
102
102
  end
103
103
 
104
- delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **options)
104
+ delivery_lines[line_id] = line_class.new(id: line_id, owner: self, **__kwrest__)
105
105
 
106
106
  instance_eval <<~CODE, __FILE__, __LINE__ + 1
107
107
  def #{line_id}(val)
@@ -152,13 +152,13 @@ module ActiveDelivery
152
152
  super
153
153
  end
154
154
 
155
- def method_missing(mid, *args, **kwargs)
155
+ def method_missing(mid, *__rest__, **__kwrest__)
156
156
  return super unless respond_to_missing?(mid)
157
157
 
158
158
  # Lazily define a class method to avoid lookups
159
159
  delivers(mid)
160
160
 
161
- public_send(mid, *args, **kwargs)
161
+ public_send(mid, *__rest__, **__kwrest__)
162
162
  end
163
163
  end
164
164
 
@@ -197,7 +197,7 @@ module ActiveDelivery
197
197
  super
198
198
  end
199
199
 
200
- def method_missing(mid, *args, **kwargs)
200
+ def method_missing(mid, *__rest__, **__kwrest__)
201
201
  return super unless respond_to_missing?(mid)
202
202
 
203
203
  # Lazily define a method to avoid future lookups
@@ -211,27 +211,28 @@ module ActiveDelivery
211
211
  end
212
212
  CODE
213
213
 
214
- public_send(mid, *args, **kwargs)
214
+ public_send(mid, *__rest__, **__kwrest__)
215
215
  end
216
216
 
217
217
  protected
218
218
 
219
- def perform_notify(delivery, sync: false)
219
+ def perform_notify(delivery, sync: false, enqueue_options: {})
220
220
  delivery_lines.each do |type, line|
221
221
  next unless line.notify?(delivery.notification)
222
222
 
223
- notify_line(type, line, delivery, sync: sync)
223
+ notify_line(type, line, delivery, sync: sync, enqueue_options: enqueue_options)
224
224
  end
225
225
  end
226
226
 
227
227
  private
228
228
 
229
- def notify_line(type, line, delivery, sync:)
229
+ def notify_line(type, line, delivery, sync:, enqueue_options:)
230
230
  line.notify(
231
231
  delivery.notification,
232
232
  *delivery.params,
233
233
  params: params,
234
234
  sync: sync,
235
+ enqueue_options: enqueue_options,
235
236
  **delivery.options
236
237
  )
237
238
  true
@@ -37,9 +37,21 @@ module ActiveDelivery
37
37
  def notify_later(handler, mid, *__rest__, &__block__)
38
38
  end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later)
39
39
 
40
- def notify(mid, *args, params:, sync:, **kwargs)
40
+ def notify_later_with_options(handler, enqueue_options, mid, *__rest__, &__block__)
41
+ notify_later(handler, mid, *__rest__, &__block__)
42
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later_with_options)
43
+
44
+ def notify(mid, *__rest__, params:, sync:, enqueue_options:, **__kwrest__)
41
45
  clazz = params.empty? ? handler_class : handler_class.with(**params)
42
- sync ? notify_now(clazz, mid, *args, **kwargs) : notify_later(clazz, mid, *args, **kwargs)
46
+ if sync
47
+ return notify_now(clazz, mid, *__rest__, **__kwrest__)
48
+ end
49
+
50
+ if enqueue_options.empty?
51
+ notify_later(clazz, mid, *__rest__, **__kwrest__)
52
+ else
53
+ notify_later_with_options(clazz, enqueue_options, mid, *__rest__, **__kwrest__)
54
+ end
43
55
  end
44
56
 
45
57
  def handler_class
@@ -19,6 +19,10 @@ module ActiveDelivery
19
19
  def notify_later(mailer, mid, *__rest__, &__block__)
20
20
  mailer.public_send(mid, *__rest__, &__block__).deliver_later
21
21
  end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later)
22
+
23
+ def notify_later_with_options(mailer, enqueue_options, mid, *__rest__, &__block__)
24
+ mailer.public_send(mid, *__rest__, &__block__).deliver_later(**enqueue_options)
25
+ end; respond_to?(:ruby2_keywords, true) && (ruby2_keywords :notify_later_with_options)
22
26
  end
23
27
 
24
28
  ActiveDelivery::Base.register_line :mailer, Mailer, resolver: Mailer::DEFAULT_RESOLVER