noticed 2.0.6 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +193 -21
- data/app/models/concerns/noticed/deliverable.rb +14 -10
- data/app/models/noticed/deliverable/deliver_by.rb +13 -4
- data/app/models/noticed/ephemeral.rb +53 -0
- data/app/models/noticed/notification.rb +1 -1
- data/db/migrate/20231215190233_create_noticed_tables.rb +1 -0
- data/db/migrate/20240129184740_add_notifications_count_to_noticed_event.rb +5 -0
- data/lib/noticed/bulk_delivery_method.rb +9 -3
- data/lib/noticed/bulk_delivery_methods/test.rb +11 -0
- data/lib/noticed/delivery_method.rb +9 -3
- data/lib/noticed/delivery_methods/ios.rb +6 -2
- data/lib/noticed/engine.rb +6 -0
- data/lib/noticed/has_notifications.rb +49 -0
- data/lib/noticed/version.rb +1 -1
- data/lib/noticed.rb +2 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 457449f2026b5ae977841ac9c0c0d30373fe1f8715670290ff49b75e021fea9a
|
4
|
+
data.tar.gz: 51adcf9c865b36de52eed8066a8d622867a897caad615879632c63939b5aebf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d57c184f5c1b72848795ab0350e3fe2f6a05a4dc7698e26acb8bda186ac7f8e9ef5c1451fd9bba77e6afa5b118288a1bdc39a293b269d8645491fb9d026ea23
|
7
|
+
data.tar.gz: daf3bb3225f9273821d9918da31700e7c0c65e89c441e8340847d085b9f76bdfde347e920f861dff3eb697e3083fbbfe0fb7a3ee6e0759172d89988b90c68381
|
data/README.md
CHANGED
@@ -1,24 +1,31 @@
|
|
1
1
|
# Noticed
|
2
2
|
|
3
|
-
|
3
|
+
## 🎉 Notifications for your Ruby on Rails app.
|
4
4
|
|
5
5
|
[![Build Status](https://github.com/excid3/noticed/workflows/Tests/badge.svg)](https://github.com/excid3/noticed/actions) [![Gem Version](https://badge.fury.io/rb/noticed.svg)](https://badge.fury.io/rb/noticed)
|
6
6
|
|
7
|
-
|
7
|
+
> [!IMPORTANT]
|
8
|
+
> **⚠️ Upgrading from V1? Read the [Upgrade Guide](https://github.com/excid3/noticed/blob/main/UPGRADE.md)!**
|
8
9
|
|
9
10
|
Noticed is a gem that allows your application to send notifications of varying types, over various mediums, to various recipients. Be it a Slack notification to your own team when some internal event occurs or a notification to your user, sent as a text message, email, and real-time UI element in the browser, Noticed supports all of the above (at the same time)!
|
10
11
|
|
11
12
|
Noticed implements two top-level types of delivery methods:
|
12
13
|
|
13
|
-
1. Individual Deliveries
|
14
|
+
1. **Individual Deliveries**: Where each recipient gets their own notification
|
15
|
+
<details>
|
16
|
+
<summary> Show Example </summary>
|
14
17
|
|
15
18
|
Let’s use a car dealership as an example here. When someone purchases a car, a notification will be sent to the buyer with some contract details (“Congrats on your new 2024 XYZ Model...”), another to the car sales-person with different details (“You closed X deal; your commission is Y”), and another to the bank handling the loan with financial details (“New loan issued; amount $20,000...”). The event (the car being sold) necessitates multiple notifications being sent out to different recipients, but each contains its own unique information and should be separate from the others. These are individual deliveries.
|
19
|
+
</details>
|
16
20
|
|
17
|
-
2. Bulk Deliveries
|
21
|
+
2. **Bulk Deliveries**: One notification for all recipients. This is useful for sending a notification to your Slack team, for example.
|
18
22
|
|
23
|
+
<details>
|
24
|
+
<summary> Show Example </summary>
|
19
25
|
Let’s continue with the car-sale example here. Consider that your development team created the car-sales application that processed the deal above and sent out the notifications to the three parties. For the sake of team morale and feeling the ‘wins’, you may want to implement a notification that notifies your internal development team whenever a car sells through your platform. In this case, you’ll be notifying many people (your development team, maybe others at your company) but with the same content (“someone just bought a car through our platform!”). This is a bulk delivery. It’s generally a single notification that many people just need to be made aware of.
|
20
26
|
|
21
27
|
Bulk deliveries are typically used to push notifications to other platforms where users are managed (Slack, Discord, etc.) instead of your own.
|
28
|
+
</details>
|
22
29
|
|
23
30
|
Delivery methods we officially support:
|
24
31
|
|
@@ -68,6 +75,19 @@ To start, generate a Notifier:
|
|
68
75
|
rails generate noticed:notifier NewCommentNotifier
|
69
76
|
```
|
70
77
|
|
78
|
+
### Usage Contents
|
79
|
+
- [Notifier Objects](#notifier-objects)
|
80
|
+
- [Delivery Method Configuration](#delivery-method-configuration)
|
81
|
+
- [Required Params](#required-params)
|
82
|
+
- [Helper Methods](#helper-methods)
|
83
|
+
- [URL Helpers](#url-helpers)
|
84
|
+
- [Translations](#translations)
|
85
|
+
- [Tip: Capture User Preferences](#tip-capture-user-preferences)
|
86
|
+
- [Tip: Extracting Delivery Method Configurations](#tip-extracting-delivery-method-configurations)
|
87
|
+
- [Shared Delivery Method Options](#shared-delivery-method-options)
|
88
|
+
- [Sending Notifications](#sending-notifications)
|
89
|
+
- [Custom Noticed Model Methods](#custom-noticed-model-methods)
|
90
|
+
|
71
91
|
### Notifier Objects
|
72
92
|
|
73
93
|
Notifiers are essentially the controllers of the Noticed ecosystem and represent an Event. As such, we recommend naming them with the event they model in mind — be it a `NewSaleNotifier,` `ChargeFailureNotifier`, etc.
|
@@ -123,9 +143,14 @@ Each delivery method can be configured with a block that yields a `config` objec
|
|
123
143
|
|
124
144
|
Procs/Lambdas will be evaluated when needed and symbols can be used to call a method.
|
125
145
|
|
146
|
+
When a lambda is passed, it will not pass any arguments and evaluates the Proc in the context of the Noticed::Notification
|
147
|
+
|
126
148
|
If you are using a symbol to call a method, we pass the notification object as an argument to the method. This allows you to access the notification object within the method.
|
127
149
|
Your method must accept a single argument. If you don't need to use the object you can just use `(*)`.
|
128
150
|
|
151
|
+
<details>
|
152
|
+
<summary> Show Example </summary>
|
153
|
+
|
129
154
|
```ruby
|
130
155
|
class CommentNotifier < Noticed::Event
|
131
156
|
deliver_by :ios do |config|
|
@@ -135,7 +160,7 @@ class CommentNotifier < Noticed::Event
|
|
135
160
|
config.team_id = :ios_team_id
|
136
161
|
config.bundle_identifier = Rails.application.credentials.dig(:ios, :bundle_identifier)
|
137
162
|
config.device_tokens = :ios_device_tokens
|
138
|
-
config.if = ->
|
163
|
+
config.if = -> { recipient.send_ios_notification? }
|
139
164
|
end
|
140
165
|
|
141
166
|
def ios_format(apn)
|
@@ -172,18 +197,22 @@ class CommentNotifier < Noticed::Event
|
|
172
197
|
notification.recipient.ios_device_tokens
|
173
198
|
end
|
174
199
|
|
175
|
-
def send_ios_notification?(notification)
|
176
|
-
recipient = notification.recipient
|
177
|
-
return false unless recipient.is_a?(User)
|
178
|
-
|
179
|
-
recipient.send_notifications?
|
180
|
-
end
|
181
|
-
|
182
200
|
def url
|
183
201
|
comment_thread_path(record.thread)
|
184
202
|
end
|
185
203
|
end
|
204
|
+
|
205
|
+
class Recipent < ApplicationRecord # or whatever your recipient model is
|
206
|
+
has_many :ios_device_tokens
|
207
|
+
|
208
|
+
def send_ios_notification?
|
209
|
+
# some logic
|
210
|
+
end
|
211
|
+
end
|
186
212
|
```
|
213
|
+
</details>
|
214
|
+
|
215
|
+
More examples are in the docs for each delivery method.
|
187
216
|
|
188
217
|
#### Required Params
|
189
218
|
|
@@ -211,7 +240,6 @@ CarSaleNotifier.with(record: Car.last, branch: Branch.last).deliver(Branch.hq)
|
|
211
240
|
#=> OK
|
212
241
|
```
|
213
242
|
|
214
|
-
|
215
243
|
#### Helper Methods
|
216
244
|
|
217
245
|
Notifiers can implement various helper methods, within a `notification_methods` block, that make it easier to render the resulting notification directly. These helpers can be helpful depending on where and how you choose to render notifications. A common use is rendering a user’s notifications in your web UI as standard ERB. These notification helper methods make that rendering much simpler:
|
@@ -226,7 +254,7 @@ Notifiers can implement various helper methods, within a `notification_methods`
|
|
226
254
|
|
227
255
|
On the other hand, if you’re using email delivery, ActionMailer has its own full stack for setting up objects and rendering. Your notification helper methods will always be available from the notification object, but using ActionMailer’s own paradigms may fit better for that particular delivery method. YMMV.
|
228
256
|
|
229
|
-
####
|
257
|
+
#### URL Helpers
|
230
258
|
|
231
259
|
Rails url helpers are included in Notifiers by default so you have full access to them in your notification helper methods, just like you would in your controllers and views.
|
232
260
|
|
@@ -336,7 +364,7 @@ Each of these options are available for every delivery method (individual or bul
|
|
336
364
|
* `config.wait_until` — (Should yield a specific time object) Delays the job that runs this delivery method until the specific time specified
|
337
365
|
* `config.queue` — Sets the ActiveJob queue name to be used for the job that runs this delivery method
|
338
366
|
|
339
|
-
### Sending Notifications
|
367
|
+
### 📨 Sending Notifications
|
340
368
|
|
341
369
|
Following the `NewCommentNotifier` example above, here’s how we might invoke the Notifier to send notifications to every author in the thread about a new comment being added:
|
342
370
|
|
@@ -357,6 +385,51 @@ This invocation will create a single `Noticed::Event` record and a `Noticed::Not
|
|
357
385
|
- An individual delivery job for `:email` method to the second thread author
|
358
386
|
- Etc...
|
359
387
|
|
388
|
+
### Custom Noticed Model Methods
|
389
|
+
|
390
|
+
In order to extend the Noticed models you'll need to use a concern an a to_prepare block:
|
391
|
+
|
392
|
+
```ruby
|
393
|
+
# config/initializers/noticed.rb
|
394
|
+
module NotificationExtensions
|
395
|
+
extend ActiveSupport::Concern
|
396
|
+
|
397
|
+
included do
|
398
|
+
belongs_to :organisation
|
399
|
+
|
400
|
+
scope :filter_by_type, ->(type) { where(type:) }
|
401
|
+
scope :exclude_type, ->(type) { where.not(type:) }
|
402
|
+
end
|
403
|
+
|
404
|
+
# You can also add instance methods here
|
405
|
+
end
|
406
|
+
|
407
|
+
Rails.application.config.to_prepare do
|
408
|
+
# You can extend Noticed::Event or Noticed::Notification here
|
409
|
+
Noticed::Event.include EventExtensions
|
410
|
+
Noticed::Notification.include NotificationExtensions
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
The `NotificationExtensions` class could be separated into it's own file and live somewhere like `app/models/concerns/notification_extensions.rb`.
|
415
|
+
|
416
|
+
If you do this, the `to_prepare` block will need to be in `application.rb` instead of an initializer.
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
# config/application.rb
|
420
|
+
module MyApp
|
421
|
+
class Application < Rails::Application
|
422
|
+
|
423
|
+
# ...
|
424
|
+
|
425
|
+
config.to_prepare do
|
426
|
+
Noticed::Event.include Noticed::EventExtensions
|
427
|
+
Noticed::Notification.include Noticed::NotificationExtensions
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
```
|
432
|
+
|
360
433
|
## ✅ Best Practices
|
361
434
|
|
362
435
|
### Renaming Notifiers
|
@@ -441,7 +514,7 @@ If you want to build your own delivery method to deliver notifications to a spec
|
|
441
514
|
This will generate a new `DeliveryMethods::Discord` class inside the `app/notifiers/delivery_methods` folder, which can be used to deliver notifications to Discord.
|
442
515
|
|
443
516
|
```ruby
|
444
|
-
class DeliveryMethods::Discord <
|
517
|
+
class DeliveryMethods::Discord < ApplicationDeliveryMethod
|
445
518
|
# Specify the config options your delivery method requires in its config block
|
446
519
|
required_options # :foo, :bar
|
447
520
|
|
@@ -460,6 +533,63 @@ class MyNotifier < Noticed::Event
|
|
460
533
|
end
|
461
534
|
```
|
462
535
|
|
536
|
+
<details>
|
537
|
+
<summary>Turbo Stream Custom Delivery Method Example</summary>
|
538
|
+
|
539
|
+
A common custom delivery method in the Rails world might be to Delivery to the web via turbo stream.
|
540
|
+
|
541
|
+
Note: This example users custom methods that extend the `Noticed::Notification` class.
|
542
|
+
|
543
|
+
See the [Custom Noticed Model Methods](#custom-noticed-model-methods) section for more information.
|
544
|
+
|
545
|
+
```ruby
|
546
|
+
# app/notifiers/delivery_methods/turbo_stream.rb
|
547
|
+
class DeliveryMethods::TurboStream < ApplicationDeliveryMethod
|
548
|
+
def deliver
|
549
|
+
return unless recipient.is_a?(User)
|
550
|
+
|
551
|
+
notification.broadcast_update_to_bell
|
552
|
+
notification.broadcast_replace_to_index_count
|
553
|
+
notification.broadcast_prepend_to_index_list
|
554
|
+
end
|
555
|
+
end
|
556
|
+
```
|
557
|
+
|
558
|
+
```ruby
|
559
|
+
# app/models/concerns/noticed/notification_extensions.rb
|
560
|
+
module Noticed::NotificationExtensions
|
561
|
+
extend ActiveSupport::Concern
|
562
|
+
|
563
|
+
def broadcast_update_to_bell
|
564
|
+
broadcast_update_to(
|
565
|
+
"notifications_#{recipient.id}",
|
566
|
+
target: "notification_bell",
|
567
|
+
partial: "navbar/notifications/bell",
|
568
|
+
locals: { user: recipient }
|
569
|
+
)
|
570
|
+
end
|
571
|
+
|
572
|
+
def broadcast_replace_to_index_count
|
573
|
+
broadcast_replace_to(
|
574
|
+
"notifications_index_#{recipient.id}",
|
575
|
+
target: "notification_index_count",
|
576
|
+
partial: "notifications/notifications_count",
|
577
|
+
locals: { count: recipient.reload.notifications_count, unread: recipient.reload.unread_notifications_count }
|
578
|
+
)
|
579
|
+
end
|
580
|
+
|
581
|
+
def broadcast_prepend_to_index_list
|
582
|
+
broadcast_prepend_to(
|
583
|
+
"notifications_index_list_#{recipient.id}",
|
584
|
+
target: "notifications",
|
585
|
+
partial: "notifications/notification",
|
586
|
+
locals: { notification: self }
|
587
|
+
)
|
588
|
+
end
|
589
|
+
end
|
590
|
+
```
|
591
|
+
</details>
|
592
|
+
|
463
593
|
Delivery methods have access to the following methods and attributes:
|
464
594
|
|
465
595
|
* `event` — The `Noticed::Event` record that spawned the notification object currently being delivered
|
@@ -516,13 +646,13 @@ class DeliveryMethods::Discord < Noticed::DeliveryMethod
|
|
516
646
|
end
|
517
647
|
```
|
518
648
|
|
519
|
-
|
649
|
+
## 📦 Database Model
|
520
650
|
|
521
651
|
The Noticed database models include several helpful features to make working with notifications easier.
|
522
652
|
|
523
|
-
|
653
|
+
### Notification
|
524
654
|
|
525
|
-
|
655
|
+
#### Class methods/scopes
|
526
656
|
|
527
657
|
(Assuming your user `has_many :notifications, as: :recipient, class_name: "Noticed::Notification"`)
|
528
658
|
|
@@ -539,7 +669,6 @@ user.notifications.read
|
|
539
669
|
user.notifications.unread
|
540
670
|
```
|
541
671
|
|
542
|
-
|
543
672
|
Marking all notifications as read or unread:
|
544
673
|
|
545
674
|
```ruby
|
@@ -594,10 +723,11 @@ end
|
|
594
723
|
|
595
724
|
class Post < ApplicationRecord
|
596
725
|
has_many :noticed_events, as: :record, dependent: :destroy, class_name: "Noticed::Event"
|
726
|
+
has_many :notifications, through: :noticed_events, class_name: "Noticed::Notification"
|
597
727
|
end
|
598
728
|
|
599
729
|
# All of the notification events this post generated
|
600
|
-
# @post.
|
730
|
+
# @post.notifications
|
601
731
|
```
|
602
732
|
|
603
733
|
#### ActiveJob Parent Class
|
@@ -618,6 +748,48 @@ class ApplicationJob < ActiveJob::Base
|
|
618
748
|
end
|
619
749
|
```
|
620
750
|
|
751
|
+
### Customizing the Database Models
|
752
|
+
|
753
|
+
You can modify the database models by editing the generated migrations.
|
754
|
+
|
755
|
+
One common adjustment is to change the IDs to UUIDs (if you're using UUIDs in your app).
|
756
|
+
|
757
|
+
You can also add additional columns to the `Noticed::Event` and `Noticed::Notification` models.
|
758
|
+
|
759
|
+
```ruby
|
760
|
+
# This migration comes from noticed (originally 20231215190233)
|
761
|
+
class CreateNoticedTables < ActiveRecord::Migration[7.1]
|
762
|
+
def change
|
763
|
+
create_table :noticed_events, id: :uuid do |t|
|
764
|
+
t.string :type
|
765
|
+
t.belongs_to :record, polymorphic: true, type: :uuid
|
766
|
+
t.jsonb :params
|
767
|
+
|
768
|
+
# Custom Fields
|
769
|
+
t.string :organisation_id, type: :uuid, as: "((params ->> 'organisation_id')::uuid)", stored: true
|
770
|
+
t.virtual :action_type, type: :string, as: "((params ->> 'action_type'))", stored: true
|
771
|
+
t.virtual :url, type: :string, as: "((params ->> 'url'))", stored: true
|
772
|
+
|
773
|
+
t.timestamps
|
774
|
+
end
|
775
|
+
|
776
|
+
create_table :noticed_notifications, id: :uuid do |t|
|
777
|
+
t.string :type
|
778
|
+
t.belongs_to :event, null: false, type: :uuid
|
779
|
+
t.belongs_to :recipient, polymorphic: true, null: false, type: :uuid
|
780
|
+
t.datetime :read_at
|
781
|
+
t.datetime :seen_at
|
782
|
+
|
783
|
+
t.timestamps
|
784
|
+
end
|
785
|
+
|
786
|
+
add_index :noticed_notifications, :read_at
|
787
|
+
end
|
788
|
+
end
|
789
|
+
```
|
790
|
+
|
791
|
+
The custom fields in the above example are stored as virtual columns. These are populated from values passed in the `params` hash when creating the notifier.
|
792
|
+
|
621
793
|
## 🙏 Contributing
|
622
794
|
|
623
795
|
This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `standardrb` before submitting pull requests.
|
@@ -9,10 +9,13 @@ module Noticed
|
|
9
9
|
|
10
10
|
attribute :params, default: {}
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
# Ephemeral notifiers cannot serialize params since they aren't ActiveRecord backed
|
13
|
+
if respond_to? :serialize
|
14
|
+
if Rails.gem_version >= Gem::Version.new("7.1.0.alpha")
|
15
|
+
serialize :params, coder: Coder
|
16
|
+
else
|
17
|
+
serialize :params, Coder
|
18
|
+
end
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
@@ -71,8 +74,8 @@ module Noticed
|
|
71
74
|
new(params: params, record: record)
|
72
75
|
end
|
73
76
|
|
74
|
-
def deliver(recipients = nil, options
|
75
|
-
new.deliver(recipients, options)
|
77
|
+
def deliver(recipients = nil, **options)
|
78
|
+
new.deliver(recipients, **options)
|
76
79
|
end
|
77
80
|
alias_method :deliver_later, :deliver
|
78
81
|
end
|
@@ -82,16 +85,17 @@ module Noticed
|
|
82
85
|
# CommentNotifier.deliver(User.all, queue: :low_priority)
|
83
86
|
# CommentNotifier.deliver(User.all, wait: 5.minutes)
|
84
87
|
# CommentNotifier.deliver(User.all, wait_until: 1.hour.from_now)
|
85
|
-
def deliver(recipients = nil,
|
88
|
+
def deliver(recipients = nil, enqueue_job: true, **options)
|
86
89
|
validate!
|
87
90
|
|
88
91
|
transaction do
|
89
|
-
save!
|
90
|
-
|
91
92
|
recipients_attributes = Array.wrap(recipients).map do |recipient|
|
92
93
|
recipient_attributes_for(recipient)
|
93
94
|
end
|
94
95
|
|
96
|
+
self.notifications_count = recipients_attributes.size
|
97
|
+
save!
|
98
|
+
|
95
99
|
if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1")
|
96
100
|
notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any?
|
97
101
|
else
|
@@ -105,7 +109,7 @@ module Noticed
|
|
105
109
|
end
|
106
110
|
|
107
111
|
# Enqueue delivery job
|
108
|
-
EventJob.set(options).perform_later(self)
|
112
|
+
EventJob.set(options).perform_later(self) if enqueue_job
|
109
113
|
|
110
114
|
self
|
111
115
|
end
|
@@ -19,14 +19,23 @@ module Noticed
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def perform_later(event_or_notification, options = {})
|
22
|
-
options[:wait]
|
23
|
-
options[:wait_until]
|
24
|
-
options[:queue]
|
25
|
-
options[:priority]
|
22
|
+
options[:wait] ||= evaluate_option(:wait, event_or_notification) if config.has_key?(:wait)
|
23
|
+
options[:wait_until] ||= evaluate_option(:wait_until, event_or_notification) if config.has_key?(:wait_until)
|
24
|
+
options[:queue] ||= evaluate_option(:queue, event_or_notification) if config.has_key?(:queue)
|
25
|
+
options[:priority] ||= evaluate_option(:priority, event_or_notification) if config.has_key?(:priority)
|
26
26
|
|
27
27
|
constant.set(options).perform_later(name, event_or_notification)
|
28
28
|
end
|
29
29
|
|
30
|
+
def ephemeral_perform_later(notifier, recipient, params, options = {})
|
31
|
+
options[:wait] ||= evaluate_option(:wait, recipient) if config.has_key?(:wait)
|
32
|
+
options[:wait_until] ||= evaluate_option(:wait_until, recipient) if config.has_key?(:wait_until)
|
33
|
+
options[:queue] ||= evaluate_option(:queue, recipient) if config.has_key?(:queue)
|
34
|
+
options[:priority] ||= evaluate_option(:priority, recipient) if config.has_key?(:priority)
|
35
|
+
|
36
|
+
constant.set(options).perform_later(name, "#{notifier}::Notification", recipient: recipient, params: params)
|
37
|
+
end
|
38
|
+
|
30
39
|
def evaluate_option(name, context)
|
31
40
|
option = config[name]
|
32
41
|
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Noticed
|
2
|
+
class Ephemeral
|
3
|
+
include ActiveModel::Model
|
4
|
+
include ActiveModel::Attributes
|
5
|
+
include Noticed::Deliverable
|
6
|
+
|
7
|
+
attribute :record
|
8
|
+
attribute :params, default: {}
|
9
|
+
|
10
|
+
class Notification
|
11
|
+
include ActiveModel::Model
|
12
|
+
include ActiveModel::Attributes
|
13
|
+
|
14
|
+
attribute :recipient
|
15
|
+
attribute :event
|
16
|
+
|
17
|
+
delegate :params, :record, to: :event
|
18
|
+
|
19
|
+
def self.new_with_params(recipient, params)
|
20
|
+
instance = new(recipient: recipient)
|
21
|
+
instance.event = module_parent.new(params: params)
|
22
|
+
instance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Dynamically define Notification on each Ephemeral Notifier
|
27
|
+
def self.inherited(notifier)
|
28
|
+
super
|
29
|
+
notifier.const_set :Notification, Class.new(Noticed::Ephemeral::Notification)
|
30
|
+
end
|
31
|
+
|
32
|
+
def deliver(recipients)
|
33
|
+
recipients = Array.wrap(recipients)
|
34
|
+
bulk_delivery_methods.each do |_, deliver_by|
|
35
|
+
deliver_by.ephemeral_perform_later(self.class.name, recipients, params)
|
36
|
+
end
|
37
|
+
|
38
|
+
recipients.each do |recipient|
|
39
|
+
delivery_methods.each do |_, deliver_by|
|
40
|
+
deliver_by.ephemeral_perform_later(self.class.name, recipient, params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def record
|
46
|
+
params[:record]
|
47
|
+
end
|
48
|
+
|
49
|
+
def notification_methods(&block)
|
50
|
+
const_get(:Notification).class_eval(&block)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -7,9 +7,15 @@ module Noticed
|
|
7
7
|
|
8
8
|
attr_reader :config, :event
|
9
9
|
|
10
|
-
def perform(delivery_method_name, event)
|
11
|
-
|
12
|
-
|
10
|
+
def perform(delivery_method_name, event, recipients: nil, params: {}, overrides: {})
|
11
|
+
# Ephemeral notifications
|
12
|
+
if event.is_a? String
|
13
|
+
@event = @notification.event
|
14
|
+
@config = overrides
|
15
|
+
else
|
16
|
+
@event = event
|
17
|
+
@config = event.bulk_delivery_methods.fetch(delivery_method_name).config.merge(overrides)
|
18
|
+
end
|
13
19
|
|
14
20
|
return false if config.has_key?(:if) && !evaluate_option(:if)
|
15
21
|
return false if config.has_key?(:unless) && evaluate_option(:unless)
|
@@ -12,9 +12,15 @@ module Noticed
|
|
12
12
|
delegate :recipient, to: :notification
|
13
13
|
delegate :record, :params, to: :event
|
14
14
|
|
15
|
-
def perform(delivery_method_name, notification, overrides: {})
|
16
|
-
|
17
|
-
|
15
|
+
def perform(delivery_method_name, notification, recipient: nil, params: {}, overrides: {})
|
16
|
+
# Ephemeral notifications
|
17
|
+
if notification.is_a? String
|
18
|
+
@notification = notification.constantize.new_with_params(recipient, params)
|
19
|
+
@event = @notification.event
|
20
|
+
else
|
21
|
+
@notification = notification
|
22
|
+
@event = notification.event
|
23
|
+
end
|
18
24
|
|
19
25
|
# Look up config from Notifier and merge overrides
|
20
26
|
@config = event.delivery_methods.fetch(delivery_method_name).config.merge(overrides)
|
@@ -32,8 +32,12 @@ module Noticed
|
|
32
32
|
def format_notification(apn)
|
33
33
|
apn.topic = evaluate_option(:bundle_identifier)
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
method = config[:format]
|
36
|
+
# Call method on Notifier if defined
|
37
|
+
if method&.is_a?(Symbol) && event.respond_to?(method)
|
38
|
+
event.send(method, apn)
|
39
|
+
# If Proc, evaluate it on the Notification
|
40
|
+
elsif method&.respond_to?(:call)
|
37
41
|
notification.instance_exec(apn, &method)
|
38
42
|
elsif notification.params.try(:has_key?, :message)
|
39
43
|
apn.alert = notification.params[:message]
|
data/lib/noticed/engine.rb
CHANGED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Noticed
|
2
|
+
module HasNotifications
|
3
|
+
# Defines a method for the association and a before_destroy callback to remove notifications
|
4
|
+
# where this record is a param
|
5
|
+
#
|
6
|
+
# class User < ApplicationRecord
|
7
|
+
# has_noticed_notifications
|
8
|
+
# has_noticed_notifications param_name: :owner, destroy: false, model: "Notification"
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# @user.notifications_as_user
|
12
|
+
# @user.notifications_as_owner
|
13
|
+
|
14
|
+
extend ActiveSupport::Concern
|
15
|
+
|
16
|
+
class_methods do
|
17
|
+
def has_noticed_notifications(param_name: model_name.singular, **options)
|
18
|
+
define_method :"notifications_as_#{param_name}" do
|
19
|
+
model = options.fetch(:model_name, "Noticed::Event").constantize
|
20
|
+
case current_adapter
|
21
|
+
when "postgresql", "postgis"
|
22
|
+
model.where("params @> ?", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
23
|
+
when "mysql2"
|
24
|
+
model.where("JSON_CONTAINS(params, ?)", Noticed::Coder.dump(param_name.to_sym => self).to_json)
|
25
|
+
when "sqlite3"
|
26
|
+
model.where("json_extract(params, ?) = ?", "$.#{param_name}", Noticed::Coder.dump(self).to_json)
|
27
|
+
else
|
28
|
+
# This will perform an exact match which isn't ideal
|
29
|
+
model.where(params: {param_name.to_sym => self})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
if options.fetch(:destroy, true)
|
34
|
+
before_destroy do
|
35
|
+
send(:"notifications_as_#{param_name}").destroy_all
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def current_adapter
|
42
|
+
if ActiveRecord::Base.respond_to?(:connection_db_config)
|
43
|
+
ActiveRecord::Base.connection_db_config.adapter
|
44
|
+
else
|
45
|
+
ActiveRecord::Base.connection_config[:adapter]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/noticed/version.rb
CHANGED
data/lib/noticed.rb
CHANGED
@@ -14,12 +14,14 @@ module Noticed
|
|
14
14
|
autoload :BulkDeliveryMethod, "noticed/bulk_delivery_method"
|
15
15
|
autoload :Coder, "noticed/coder"
|
16
16
|
autoload :DeliveryMethod, "noticed/delivery_method"
|
17
|
+
autoload :HasNotifications, "noticed/has_notifications"
|
17
18
|
autoload :RequiredOptions, "noticed/required_options"
|
18
19
|
autoload :Translation, "noticed/translation"
|
19
20
|
|
20
21
|
module BulkDeliveryMethods
|
21
22
|
autoload :Discord, "noticed/bulk_delivery_methods/discord"
|
22
23
|
autoload :Slack, "noticed/bulk_delivery_methods/slack"
|
24
|
+
autoload :Test, "noticed/bulk_delivery_methods/test"
|
23
25
|
autoload :Webhook, "noticed/bulk_delivery_methods/webhook"
|
24
26
|
end
|
25
27
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: noticed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-01-
|
11
|
+
date: 2024-01-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -43,9 +43,11 @@ files:
|
|
43
43
|
- app/models/concerns/noticed/readable.rb
|
44
44
|
- app/models/noticed/application_record.rb
|
45
45
|
- app/models/noticed/deliverable/deliver_by.rb
|
46
|
+
- app/models/noticed/ephemeral.rb
|
46
47
|
- app/models/noticed/event.rb
|
47
48
|
- app/models/noticed/notification.rb
|
48
49
|
- db/migrate/20231215190233_create_noticed_tables.rb
|
50
|
+
- db/migrate/20240129184740_add_notifications_count_to_noticed_event.rb
|
49
51
|
- lib/generators/noticed/delivery_method_generator.rb
|
50
52
|
- lib/generators/noticed/install_generator.rb
|
51
53
|
- lib/generators/noticed/notifier_generator.rb
|
@@ -59,6 +61,7 @@ files:
|
|
59
61
|
- lib/noticed/bulk_delivery_method.rb
|
60
62
|
- lib/noticed/bulk_delivery_methods/discord.rb
|
61
63
|
- lib/noticed/bulk_delivery_methods/slack.rb
|
64
|
+
- lib/noticed/bulk_delivery_methods/test.rb
|
62
65
|
- lib/noticed/bulk_delivery_methods/webhook.rb
|
63
66
|
- lib/noticed/coder.rb
|
64
67
|
- lib/noticed/delivery_method.rb
|
@@ -74,6 +77,7 @@ files:
|
|
74
77
|
- lib/noticed/delivery_methods/vonage_sms.rb
|
75
78
|
- lib/noticed/delivery_methods/webhook.rb
|
76
79
|
- lib/noticed/engine.rb
|
80
|
+
- lib/noticed/has_notifications.rb
|
77
81
|
- lib/noticed/required_options.rb
|
78
82
|
- lib/noticed/translation.rb
|
79
83
|
- lib/noticed/version.rb
|
@@ -96,7 +100,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
100
|
- !ruby/object:Gem::Version
|
97
101
|
version: '0'
|
98
102
|
requirements: []
|
99
|
-
rubygems_version: 3.5.
|
103
|
+
rubygems_version: 3.5.4
|
100
104
|
signing_key:
|
101
105
|
specification_version: 4
|
102
106
|
summary: Notifications for Ruby on Rails applications
|