noticed 2.0.0 → 2.1.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/README.md +532 -180
- data/app/channels/noticed/notification_channel.rb +19 -0
- data/app/models/concerns/noticed/deliverable.rb +39 -12
- data/app/models/concerns/noticed/readable.rb +20 -4
- 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 -3
- 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/generators/noticed/delivery_method_generator.rb +1 -0
- data/lib/generators/noticed/notifier_generator.rb +10 -1
- data/lib/generators/noticed/templates/application_delivery_method.rb.tt +2 -0
- data/lib/generators/noticed/templates/application_notifier.rb.tt +2 -0
- data/lib/generators/noticed/templates/delivery_method.rb.tt +4 -8
- data/lib/generators/noticed/templates/notifier.rb.tt +1 -1
- 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 +18 -6
- data/lib/noticed/delivery_methods/action_cable.rb +3 -3
- data/lib/noticed/delivery_methods/email.rb +10 -6
- data/lib/noticed/delivery_methods/fcm.rb +10 -1
- data/lib/noticed/delivery_methods/ios.rb +9 -4
- 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 +5 -0
- metadata +10 -3
data/README.md
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
# Noticed
|
|
2
|
-
|
|
1
|
+
# Noticed
|
|
2
|
+
|
|
3
|
+
## 🎉 Notifications for your Ruby on Rails app.
|
|
3
4
|
|
|
4
5
|
[](https://github.com/excid3/noticed/actions) [](https://badge.fury.io/rb/noticed)
|
|
5
6
|
|
|
6
|
-
|
|
7
|
+
> [!IMPORTANT]
|
|
8
|
+
> **⚠️ Upgrading from V1? Read the [Upgrade Guide](https://github.com/excid3/noticed/blob/main/UPGRADE.md)!**
|
|
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)!
|
|
11
|
+
|
|
12
|
+
Noticed implements two top-level types of delivery methods:
|
|
13
|
+
|
|
14
|
+
1. **Individual Deliveries**: Where each recipient gets their own notification
|
|
15
|
+
<details>
|
|
16
|
+
<summary> Show Example </summary>
|
|
7
17
|
|
|
8
|
-
The
|
|
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>
|
|
9
20
|
|
|
10
|
-
|
|
11
|
-
2. `Noticed::Event` - When a `Notifier` is delivered, a `Noticed::Event` record is created in the database to store params for the delivery.`Notifiers` are ActiveRecord objects inherited from `Noticed::Event` using Single Table Inheritance.
|
|
12
|
-
3. `Noticed::Notification` - Keeps track of each recipient for `Noticed::Event` and the seen & read status for each.
|
|
13
|
-
4. Delivery methods are ActiveJob instances and support the same features like wait, queue, and priority.
|
|
21
|
+
2. **Bulk Deliveries**: One notification for all recipients. This is useful for sending a notification to your Slack team, for example.
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
|
|
23
|
+
<details>
|
|
24
|
+
<summary> Show Example </summary>
|
|
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.
|
|
26
|
+
|
|
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>
|
|
29
|
+
|
|
30
|
+
Delivery methods we officially support:
|
|
17
31
|
|
|
18
32
|
* [ActionCable](docs/delivery_methods/action_cable.md)
|
|
19
33
|
* [Apple Push Notification Service](docs/delivery_methods/ios.md)
|
|
@@ -25,7 +39,7 @@ Individual Delivery methods (one notification to each recipient):
|
|
|
25
39
|
* [Vonage SMS](docs/delivery_methods/vonage_sms.md)
|
|
26
40
|
* [Test](docs/delivery_methods/test.md)
|
|
27
41
|
|
|
28
|
-
Bulk delivery methods
|
|
42
|
+
Bulk delivery methods we support:
|
|
29
43
|
|
|
30
44
|
* [Discord](docs/bulk_delivery_methods/discord.md)
|
|
31
45
|
* [Slack](docs/bulk_delivery_methods/slack.md)
|
|
@@ -33,9 +47,9 @@ Bulk delivery methods (one notification for all recipients):
|
|
|
33
47
|
|
|
34
48
|
## 🎬 Screencast
|
|
35
49
|
|
|
36
|
-
<a href="https://www.youtube.com/watch?v=
|
|
50
|
+
<a href="https://www.youtube.com/watch?v=SzX-aBEqnAc"><img src="https://i.imgur.com/UvVKWwD.png" title="How to add Notifications to Rails with Noticed" width="50%" /></a>
|
|
37
51
|
|
|
38
|
-
[Watch Screencast](https://www.youtube.com/watch?v=
|
|
52
|
+
[Watch Screencast](https://www.youtube.com/watch?v=SzX-aBEqnAc)
|
|
39
53
|
|
|
40
54
|
## 🚀 Installation
|
|
41
55
|
Run the following command to add Noticed to your Gemfile:
|
|
@@ -44,7 +58,7 @@ Run the following command to add Noticed to your Gemfile:
|
|
|
44
58
|
bundle add "noticed"
|
|
45
59
|
```
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
Generate then run the migrations:
|
|
48
62
|
|
|
49
63
|
```bash
|
|
50
64
|
rails noticed:install:migrations
|
|
@@ -53,124 +67,251 @@ rails db:migrate
|
|
|
53
67
|
|
|
54
68
|
## 📝 Usage
|
|
55
69
|
|
|
56
|
-
|
|
70
|
+
Noticed operates with a few constructs: Notifiers, delivery methods, and Notification records.
|
|
57
71
|
|
|
58
|
-
|
|
72
|
+
To start, generate a Notifier:
|
|
59
73
|
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
```sh
|
|
75
|
+
rails generate noticed:notifier NewCommentNotifier
|
|
76
|
+
```
|
|
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
|
+
|
|
91
|
+
### Notifier Objects
|
|
92
|
+
|
|
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.
|
|
94
|
+
|
|
95
|
+
Notifiers must inherit from `Noticed::Event`. This provides all of their functionality.
|
|
96
|
+
|
|
97
|
+
A Notifier exists to declare the various delivery methods that should be used for that event _and_ any notification helper methods necessary in those delivery mechanisms. In this example we’ll deliver by `:action_cable` to provide real-time UI updates to users’ browsers, `:email` if they’ve opted into email notifications, and a bulk notification to `:discord` to tell everyone on the Discord server there’s been a new comment.
|
|
62
98
|
|
|
63
99
|
```ruby
|
|
64
|
-
# app/notifiers/
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
config.
|
|
100
|
+
# ~/app/notifiers/new_comment_notifier.rb
|
|
101
|
+
|
|
102
|
+
class NewCommentNotifier < Noticed::Event
|
|
103
|
+
deliver_by :action_cable do |config|
|
|
104
|
+
config.channel = "NotificationsChannel"
|
|
105
|
+
config.stream = :some_stream
|
|
69
106
|
end
|
|
70
107
|
|
|
71
108
|
deliver_by :email do |config|
|
|
72
|
-
config.mailer = "
|
|
73
|
-
config.
|
|
109
|
+
config.mailer = "CommentMailer"
|
|
110
|
+
config.if = ->(recipient) { !!recipient.preferences[:email] }
|
|
74
111
|
end
|
|
75
|
-
end
|
|
76
|
-
```
|
|
77
112
|
|
|
78
|
-
|
|
113
|
+
bulk_deliver_by :discord do |config|
|
|
114
|
+
config.url = "https://discord.com/xyz/xyz/123"
|
|
115
|
+
config.json = -> {
|
|
116
|
+
{
|
|
117
|
+
message: message,
|
|
118
|
+
channel: :general
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
end
|
|
79
122
|
|
|
80
|
-
|
|
123
|
+
notification_methods do
|
|
124
|
+
# I18n helpers
|
|
125
|
+
def message
|
|
126
|
+
t(".message")
|
|
127
|
+
end
|
|
81
128
|
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
|
|
129
|
+
# URL helpers are accessible in notifications
|
|
130
|
+
# Don't forget to set your default_url_options so Rails knows how to generate urls
|
|
131
|
+
def url
|
|
132
|
+
user_post_path(recipient, params[:post])
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
85
136
|
```
|
|
86
137
|
|
|
87
|
-
|
|
138
|
+
For deeper specifics on setting up the `:action_cable`, `:email`, and `:discord` (bulk) delivery methods, refer to their docs: [`action_cable`](docs/delivery_methods/action_cable.md), [`email`](docs/delivery_methods/email.md), and [`discord` (bulk)](docs/bulk_delivery_methods/discord.md).
|
|
88
139
|
|
|
89
|
-
|
|
140
|
+
#### Delivery Method Configuration
|
|
90
141
|
|
|
91
|
-
|
|
142
|
+
Each delivery method can be configured with a block that yields a `config` object.
|
|
92
143
|
|
|
93
|
-
|
|
144
|
+
Procs/Lambdas will be evaluated when needed and symbols can be used to call a method.
|
|
94
145
|
|
|
95
|
-
|
|
146
|
+
When a lambda is passed, it will not pass any arguments and evaluates the Proc in the context of the Noticed::Notification
|
|
96
147
|
|
|
97
|
-
|
|
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.
|
|
149
|
+
Your method must accept a single argument. If you don't need to use the object you can just use `(*)`.
|
|
150
|
+
|
|
151
|
+
<details>
|
|
152
|
+
<summary> Show Example </summary>
|
|
98
153
|
|
|
99
154
|
```ruby
|
|
100
155
|
class CommentNotifier < Noticed::Event
|
|
101
|
-
deliver_by :
|
|
102
|
-
|
|
103
|
-
config.
|
|
104
|
-
config.
|
|
105
|
-
config.
|
|
156
|
+
deliver_by :ios do |config|
|
|
157
|
+
config.format = :ios_format
|
|
158
|
+
config.apns_key = :ios_cert
|
|
159
|
+
config.key_id = :ios_key_id
|
|
160
|
+
config.team_id = :ios_team_id
|
|
161
|
+
config.bundle_identifier = Rails.application.credentials.dig(:ios, :bundle_identifier)
|
|
162
|
+
config.device_tokens = :ios_device_tokens
|
|
163
|
+
config.if = -> { recipient.send_ios_notification? }
|
|
106
164
|
end
|
|
107
|
-
end
|
|
108
|
-
```
|
|
109
165
|
|
|
110
|
-
|
|
166
|
+
def ios_format(apn)
|
|
167
|
+
apn.alert = { title:, body: }
|
|
168
|
+
apn.mutable_content = true
|
|
169
|
+
apn.content_available = true
|
|
170
|
+
apn.sound = "notification.m4r"
|
|
171
|
+
apn.custom_payload = {
|
|
172
|
+
url:,
|
|
173
|
+
type: self.class.name,
|
|
174
|
+
id: record.id,
|
|
175
|
+
image_url: "" || image_url,
|
|
176
|
+
params: params.to_json
|
|
177
|
+
}
|
|
178
|
+
end
|
|
111
179
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
180
|
+
def ios_cert(*)
|
|
181
|
+
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :apns_token_cert)
|
|
182
|
+
end
|
|
115
183
|
|
|
116
|
-
|
|
184
|
+
def ios_key_id(*)
|
|
185
|
+
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :key_id)
|
|
186
|
+
end
|
|
117
187
|
|
|
118
|
-
|
|
188
|
+
def ios_team_id(*)
|
|
189
|
+
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :team_id)
|
|
190
|
+
end
|
|
119
191
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
192
|
+
def ios_bundle_id(*)
|
|
193
|
+
Rails.application.credentials.dig(:ios, Rails.env.to_sym, :bundle_identifier)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def ios_device_tokens(notification)
|
|
197
|
+
notification.recipient.ios_device_tokens
|
|
125
198
|
end
|
|
126
199
|
|
|
127
|
-
# URL helpers are accessible in notifications
|
|
128
|
-
# Don't forget to set your default_url_options so Rails knows how to generate urls
|
|
129
200
|
def url
|
|
130
|
-
|
|
201
|
+
comment_thread_path(record.thread)
|
|
131
202
|
end
|
|
203
|
+
end
|
|
132
204
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
137
210
|
end
|
|
138
|
-
end
|
|
139
211
|
end
|
|
140
212
|
```
|
|
213
|
+
</details>
|
|
214
|
+
|
|
215
|
+
More examples are in the docs for each delivery method.
|
|
216
|
+
|
|
217
|
+
#### Required Params
|
|
218
|
+
|
|
219
|
+
While explicit / required parameters are completely optional, Notifiers are able to opt in to required parameters via the `required_params` method:
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
class CarSaleNotifier < Noticed::Event
|
|
223
|
+
deliver_by :email { |c| c.mailer = "BranchMailer" }
|
|
224
|
+
|
|
225
|
+
# `record` is the Car record, `Branch` is the dealership
|
|
226
|
+
required_params :branch
|
|
227
|
+
|
|
228
|
+
# To validate the `:record` param, add a validation since it is an association on the Noticed::Event
|
|
229
|
+
validates :record, presence: true
|
|
230
|
+
end
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Which will validate upon any invocation that the specified parameters are present:
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
CarSaleNotifier.with(record: Car.last).deliver(Branch.last)
|
|
237
|
+
#=> Noticed::ValidationError("Param `branch` is required for CarSaleNotifier")
|
|
238
|
+
|
|
239
|
+
CarSaleNotifier.with(record: Car.last, branch: Branch.last).deliver(Branch.hq)
|
|
240
|
+
#=> OK
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
#### Helper Methods
|
|
244
|
+
|
|
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:
|
|
141
246
|
|
|
142
|
-
In your views, you can loop through notifications and access
|
|
143
247
|
```erb
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
248
|
+
<div>
|
|
249
|
+
<% @user.notifications.each do |notification| %>
|
|
250
|
+
<%= link_to notification.message, notification.url %>
|
|
251
|
+
<% end %>
|
|
252
|
+
</div>
|
|
147
253
|
```
|
|
148
254
|
|
|
149
|
-
|
|
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.
|
|
256
|
+
|
|
257
|
+
#### URL Helpers
|
|
150
258
|
|
|
151
|
-
|
|
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.
|
|
260
|
+
|
|
261
|
+
_But don't forget_, you'll need to configure `default_url_options` in order for Rails to know what host and port to use when generating URLs.
|
|
152
262
|
|
|
153
263
|
```ruby
|
|
154
264
|
Rails.application.routes.default_url_options[:host] = 'localhost:3000'
|
|
155
265
|
```
|
|
156
266
|
|
|
157
|
-
|
|
267
|
+
#### Translations
|
|
158
268
|
|
|
159
|
-
`translate` and `t` helpers
|
|
269
|
+
We've also included Rails’ `translate` and `t` helpers for you to use in your notification helper methods. This also provides an easy way of scoping translations. If the key starts with a period, it will automatically scope the key under `notifiers`, the underscored name of the notifier class, and `notification`. For example:
|
|
160
270
|
|
|
161
|
-
|
|
271
|
+
From the above Notifier...
|
|
272
|
+
|
|
273
|
+
```ruby
|
|
274
|
+
class NewCommentNotifier < Noticed::Event
|
|
275
|
+
# ...
|
|
276
|
+
|
|
277
|
+
notification_methods do
|
|
278
|
+
def message
|
|
279
|
+
t(".message")
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# ...
|
|
284
|
+
end
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Calling the `message` helper in an ERB view:
|
|
288
|
+
|
|
289
|
+
```erb
|
|
290
|
+
<%= @user.notifications.last.message %>
|
|
291
|
+
```
|
|
162
292
|
|
|
163
|
-
|
|
164
|
-
`t(".message") # in Admin::NewComment` looks up `en.notifiers.admin.new_comment.message`
|
|
293
|
+
Will look for the following translation path:
|
|
165
294
|
|
|
166
|
-
|
|
295
|
+
```yml
|
|
296
|
+
# ~/config/locales/en.yml
|
|
297
|
+
|
|
298
|
+
en:
|
|
299
|
+
notifiers:
|
|
300
|
+
new_comment_notifier:
|
|
301
|
+
notification:
|
|
302
|
+
message: "Someone posted a new comment!"
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Or, if you have your Notifier within another module, such as `Admin::NewCommentNotifier`, the resulting lookup path will be `en.notifiers.admin.new_comment_notifier.notification.message` (modules become nesting steps).
|
|
306
|
+
|
|
307
|
+
#### Tip: Capture User Preferences
|
|
167
308
|
|
|
168
309
|
You can use the `if:` and `unless: ` options on your delivery methods to check the user's preferences and skip processing if they have disabled that type of notification.
|
|
169
310
|
|
|
170
311
|
For example:
|
|
171
312
|
|
|
172
313
|
```ruby
|
|
173
|
-
class CommentNotifier < Noticed::
|
|
314
|
+
class CommentNotifier < Noticed::Event
|
|
174
315
|
deliver_by :email do |config|
|
|
175
316
|
config.mailer = 'CommentMailer'
|
|
176
317
|
config.method = :new_comment
|
|
@@ -178,183 +319,347 @@ class CommentNotifier < Noticed::Base
|
|
|
178
319
|
end
|
|
179
320
|
end
|
|
180
321
|
```
|
|
322
|
+
#### Tip: Extracting Delivery Method Configurations
|
|
181
323
|
|
|
182
|
-
|
|
324
|
+
If you want to reuse delivery method configurations across multiple Notifiers, you can extract them into a module and include them in your Notifiers.
|
|
183
325
|
|
|
184
|
-
|
|
326
|
+
```ruby
|
|
327
|
+
# /app/notifiers/notifiers/comment_notifier.rb
|
|
328
|
+
class CommentNotifier < Noticed::Event
|
|
329
|
+
include IosNotifier
|
|
330
|
+
include AndriodNotifer
|
|
331
|
+
include EmailNotifier
|
|
185
332
|
|
|
186
|
-
|
|
333
|
+
validates :record, presence: true
|
|
334
|
+
end
|
|
187
335
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
336
|
+
# /app/notifiers/concerns/ios_notifier.rb
|
|
337
|
+
module IosNotifier
|
|
338
|
+
extend ActiveSupport::Concern
|
|
339
|
+
|
|
340
|
+
included do
|
|
341
|
+
deliver_by :ios do |config|
|
|
342
|
+
config.device_tokens = ->(recipient) { recipient.notification_tokens.where(platform: :iOS).pluck(:token) }
|
|
343
|
+
config.format = ->(apn) {
|
|
344
|
+
apn.alert = "Hello world"
|
|
345
|
+
apn.custom_payload = {url: root_url(host: "example.org")}
|
|
346
|
+
}
|
|
347
|
+
config.bundle_identifier = Rails.application.credentials.dig(:ios, :bundle_id)
|
|
348
|
+
config.key_id = Rails.application.credentials.dig(:ios, :key_id)
|
|
349
|
+
config.team_id = Rails.application.credentials.dig(:ios, :team_id)
|
|
350
|
+
config.apns_key = Rails.application.credentials.dig(:ios, :apns_key)
|
|
351
|
+
config.if = ->(recipient) { recipient.ios_notifications? }
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
```
|
|
191
356
|
|
|
192
|
-
|
|
357
|
+
#### Shared Delivery Method Options
|
|
193
358
|
|
|
194
|
-
|
|
359
|
+
Each of these options are available for every delivery method (individual or bulk). The value passed may be a lambda, a symbol that represents a callable method, a symbol value, or a string value.
|
|
195
360
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
361
|
+
* `config.if` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns falsey
|
|
362
|
+
* `config.unless` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns truthy
|
|
363
|
+
* `config.wait` — (Should yield an `ActiveSupport::Duration`) Delays the job that runs this delivery method for the given duration of time
|
|
364
|
+
* `config.wait_until` — (Should yield a specific time object) Delays the job that runs this delivery method until the specific time specified
|
|
365
|
+
* `config.queue` — Sets the ActiveJob queue name to be used for the job that runs this delivery method
|
|
366
|
+
|
|
367
|
+
### 📨 Sending Notifications
|
|
368
|
+
|
|
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:
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
NewCommentNotifier.with(record: @comment, foo: "bar").deliver(@comment.thread.all_authors)
|
|
199
373
|
```
|
|
200
374
|
|
|
201
|
-
|
|
375
|
+
This instantiates a new `NewCommentNotifier` with params (similar to ActiveJob, any serializable params are permitted), then delivers notifications to all authors in the thread.
|
|
202
376
|
|
|
203
|
-
|
|
377
|
+
✨ The `record:` param is a special param that gets assigned to the `record` polymorphic association in the database. You should try to set the `record:` param where possible. This may be best understood as ‘the record/object this notification is _about_’, and allows for future queries from the record-side: “give me all notifications that were generated from this comment”.
|
|
204
378
|
|
|
205
|
-
|
|
379
|
+
This invocation will create a single `Noticed::Event` record and a `Noticed::Notification` record for each recipient. A background job will then process the Event and fire off a separate background job for each bulk delivery method _and_ each recipient + individual-delivery-method combination. In this case, that’d be the following jobs kicked off from this event:
|
|
206
380
|
|
|
207
|
-
|
|
381
|
+
- A bulk delivery job for `:discord` bulk delivery
|
|
382
|
+
- An individual delivery job for `:action_cable` method to the first thread author
|
|
383
|
+
- An individual delivery job for `:email` method to the first thread author
|
|
384
|
+
- An individual delivery job for `:action_cable` method to the second thread author
|
|
385
|
+
- An individual delivery job for `:email` method to the second thread author
|
|
386
|
+
- Etc...
|
|
208
387
|
|
|
209
|
-
|
|
388
|
+
### Custom Noticed Model Methods
|
|
210
389
|
|
|
211
|
-
|
|
390
|
+
In order to extend the Noticed models you'll need to use a concern an a to_prepare block:
|
|
212
391
|
|
|
213
392
|
```ruby
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
216
412
|
```
|
|
217
413
|
|
|
218
|
-
|
|
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
|
+
|
|
433
|
+
## ✅ Best Practices
|
|
434
|
+
|
|
435
|
+
### Renaming Notifiers
|
|
436
|
+
|
|
437
|
+
If you rename a Notifier class your existing data and Noticed setup may break. This is because Noticed serializes the class name and sets it to the `type` column on the `Noticed::Event` record and the `type` column on the `Noticed::Notification` record.
|
|
438
|
+
|
|
439
|
+
When renaming a Notifier class you will need to backfill existing Events and Notifications to reference the new name.
|
|
219
440
|
|
|
220
441
|
```ruby
|
|
221
442
|
Noticed::Event.where(type: "OldNotifierClassName").update_all(type: NewNotifierClassName.name)
|
|
222
|
-
|
|
443
|
+
# and
|
|
444
|
+
Noticed::Notification.where(type: "OldNotifierClassName::Notification").update_all(type: "#{NewNotifierClassName.name}::Notification")
|
|
223
445
|
```
|
|
224
446
|
|
|
225
447
|
## 🚛 Delivery Methods
|
|
226
448
|
|
|
227
|
-
The delivery methods are modular so you can customize the way each type gets delivered.
|
|
449
|
+
The delivery methods are designed to be modular so you can customize the way each type gets delivered.
|
|
228
450
|
|
|
229
451
|
For example, emails will require a subject, body, and email address while an SMS requires a phone number and simple message. You can define the formats for each of these in your Notifier and the delivery method will handle the processing of it.
|
|
230
452
|
|
|
231
|
-
|
|
453
|
+
Individual delivery methods:
|
|
454
|
+
|
|
455
|
+
* [ActionCable](docs/delivery_methods/action_cable.md)
|
|
456
|
+
* [Apple Push Notification Service](docs/delivery_methods/ios.md)
|
|
457
|
+
* [Email](docs/delivery_methods/email.md)
|
|
458
|
+
* [Firebase Cloud Messaging](docs/delivery_methods/fcm.md) (iOS, Android, and web clients)
|
|
459
|
+
* [Microsoft Teams](docs/delivery_methods/microsoft_teams.md)
|
|
460
|
+
* [Slack](docs/delivery_methods/slack.md)
|
|
461
|
+
* [Twilio Messaging](docs/delivery_methods/twilio_messaging.md) - SMS, Whatsapp
|
|
462
|
+
* [Vonage SMS](docs/delivery_methods/vonage_sms.md)
|
|
463
|
+
* [Test](docs/delivery_methods/test.md)
|
|
232
464
|
|
|
233
|
-
|
|
465
|
+
Bulk delivery methods:
|
|
234
466
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
467
|
+
* [Discord](docs/bulk_delivery_methods/discord.md)
|
|
468
|
+
* [Slack](docs/bulk_delivery_methods/slack.md)
|
|
469
|
+
* [Webhook](docs/bulk_delivery_methods/webhook.md)
|
|
470
|
+
|
|
471
|
+
### No Delivery Methods
|
|
472
|
+
|
|
473
|
+
It’s worth pointing out that you can have a fully-functional and useful Notifier that has _no_ delivery methods. This means that invoking the Notifier and ‘sending’ the notification will only create new database records (no external surfaces like email, sms, etc.). This is still useful as it’s the database records that allow your app to render a user’s (or other object’s) notifications in your web UI.
|
|
474
|
+
|
|
475
|
+
So even with no delivery methods set, this example is still perfectly available and helpful:
|
|
476
|
+
|
|
477
|
+
```erb
|
|
478
|
+
<div>
|
|
479
|
+
<% @user.notifications.each do |notification| %>
|
|
480
|
+
<%= link_to notification.message, notification.url %>
|
|
481
|
+
<% end %>
|
|
482
|
+
</div>
|
|
240
483
|
```
|
|
241
484
|
|
|
242
|
-
|
|
485
|
+
Sending a notification is entirely an internal-to-your-app function. Delivery methods just get the word out! But many apps may be fully satisfied without that extra layer.
|
|
243
486
|
|
|
244
|
-
|
|
487
|
+
### Fallback Notifications
|
|
488
|
+
|
|
489
|
+
A common pattern is to deliver a notification via a real (or real-ish)-time service, then, after some time has passed, email the user if they have not yet read the notification. You can implement this functionality by combining multiple delivery methods, the `wait` option, and the conditional `if` / `unless` option.
|
|
245
490
|
|
|
246
491
|
```ruby
|
|
247
|
-
class
|
|
248
|
-
deliver_by :
|
|
249
|
-
deliver_by :
|
|
250
|
-
|
|
251
|
-
|
|
492
|
+
class NewCommentNotifier< Noticed::Event
|
|
493
|
+
deliver_by :action_cable
|
|
494
|
+
deliver_by :email do |config|
|
|
495
|
+
config.mailer = "CommentMailer"
|
|
496
|
+
config.wait = 15.minutes
|
|
497
|
+
config.unless = -> { read? }
|
|
498
|
+
end
|
|
252
499
|
end
|
|
253
500
|
```
|
|
254
501
|
|
|
255
|
-
|
|
502
|
+
Here a notification will be created immediately in the database (for display directly in your app’s web interface) and sent via ActionCable. If the notification has not been marked `read` after 15 minutes, the email notification will be sent. If the notification has already been read in the app, the email will be skipped.
|
|
256
503
|
|
|
257
|
-
|
|
258
|
-
- Immediately issues a ping in Slack.
|
|
259
|
-
- If the notification remains unread after 10 minutes, it emails the team.
|
|
260
|
-
- If the notification remains unread after 20 minutes, it sends an SMS to the on-call phone.
|
|
504
|
+
_A note here: notifications expose a `#mark_as_read` method, but your app must choose when and where to call that method._
|
|
261
505
|
|
|
262
506
|
You can mix and match the options and delivery methods to suit your application specific needs.
|
|
263
507
|
|
|
264
|
-
Please note that to implement this pattern, it is essential `deliver_by :database` is one among the different delivery methods specified. Without this, a database record of the notification will not be created.
|
|
265
|
-
|
|
266
508
|
### 🚚 Custom Delivery Methods
|
|
267
509
|
|
|
268
|
-
To generate a custom delivery method, simply run
|
|
510
|
+
If you want to build your own delivery method to deliver notifications to a specific service or medium that Noticed doesn’t (or doesn’t _yet_) support, you’re welcome to do so! To generate a custom delivery method, simply run
|
|
269
511
|
|
|
270
512
|
`rails generate noticed:delivery_method Discord`
|
|
271
513
|
|
|
272
|
-
This will generate a new `DeliveryMethods::Discord` class inside the `app/
|
|
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.
|
|
273
515
|
|
|
274
516
|
```ruby
|
|
275
|
-
class DeliveryMethods::Discord <
|
|
517
|
+
class DeliveryMethods::Discord < ApplicationDeliveryMethod
|
|
518
|
+
# Specify the config options your delivery method requires in its config block
|
|
519
|
+
required_options # :foo, :bar
|
|
520
|
+
|
|
276
521
|
def deliver
|
|
277
|
-
# Logic for sending
|
|
522
|
+
# Logic for sending the notification
|
|
278
523
|
end
|
|
279
524
|
end
|
|
525
|
+
|
|
280
526
|
```
|
|
281
527
|
|
|
282
528
|
You can use the custom delivery method thus created by adding a `deliver_by` line with a unique name and `class` option in your notification class.
|
|
283
529
|
|
|
284
530
|
```ruby
|
|
285
|
-
class MyNotifier < Noticed::
|
|
531
|
+
class MyNotifier < Noticed::Event
|
|
286
532
|
deliver_by :discord, class: "DeliveryMethods::Discord"
|
|
287
533
|
end
|
|
288
534
|
```
|
|
289
535
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
* `record` - The instance of the Notification. You can call methods on the notification to let the user easily override formatting and other functionality of the delivery method.
|
|
293
|
-
* `options` - Any configuration options on the `deliver_by` line.
|
|
294
|
-
* `recipient` - The object who should receive the notification. This is typically a User, Account, or other ActiveRecord model.
|
|
295
|
-
* `params` - The params passed into the notification. This is details about the event that happened. For example, a user commenting on a post would have params of `{ user: User.first }`
|
|
536
|
+
<details>
|
|
537
|
+
<summary>Turbo Stream Custom Delivery Method Example</summary>
|
|
296
538
|
|
|
297
|
-
|
|
539
|
+
A common custom delivery method in the Rails world might be to Delivery to the web via turbo stream.
|
|
298
540
|
|
|
299
|
-
|
|
541
|
+
Note: This example users custom methods that extend the `Noticed::Notification` class.
|
|
300
542
|
|
|
301
|
-
|
|
543
|
+
See the [Custom Noticed Model Methods](#custom-noticed-model-methods) section for more information.
|
|
302
544
|
|
|
303
545
|
```ruby
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
546
|
+
# app/notifiers/delivery_methods/turbo_stream.rb
|
|
547
|
+
class DeliveryMethods::TurboStream < ApplicationDeliveryMethod
|
|
307
548
|
def deliver
|
|
308
|
-
|
|
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
|
|
309
554
|
end
|
|
555
|
+
end
|
|
556
|
+
```
|
|
310
557
|
|
|
311
|
-
|
|
312
|
-
|
|
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
|
|
313
571
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
+
)
|
|
318
579
|
end
|
|
319
|
-
end
|
|
320
580
|
|
|
321
|
-
|
|
322
|
-
|
|
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
|
|
323
589
|
end
|
|
324
590
|
```
|
|
591
|
+
</details>
|
|
325
592
|
|
|
326
|
-
|
|
593
|
+
Delivery methods have access to the following methods and attributes:
|
|
594
|
+
|
|
595
|
+
* `event` — The `Noticed::Event` record that spawned the notification object currently being delivered
|
|
596
|
+
* `record` — The object originally passed into the Notifier as the `record:` param (see the ✨ note above)
|
|
597
|
+
* `notification` — The `Noticed::Notification` instance being delivered. All notification helper methods are available on this object
|
|
598
|
+
* `recipient` — The individual recipient object being delivered to for this notification (remember that each recipient gets their own instance of the Delivery Method `#deliver`)
|
|
599
|
+
* `config` — The hash of configuration options declared by the Notifier that generated this notification and delivery
|
|
600
|
+
* `params` — The parameters given to the Notifier in the invocation (via `.with()`)
|
|
601
|
+
|
|
602
|
+
#### Validating config options passed to Custom Delivery methods
|
|
327
603
|
|
|
328
|
-
|
|
604
|
+
The presence of delivery method config options are automatically validated when declaring them with the `required_options` method. In the following example, Noticed will ensure that any Notifier using `deliver_by :email` will specify the `mailer` and `method` config keys:
|
|
329
605
|
|
|
330
606
|
```ruby
|
|
331
|
-
class
|
|
332
|
-
|
|
607
|
+
class DeliveryMethods::Email < Noticed::DeliveryMethod
|
|
608
|
+
required_options :mailer, :method
|
|
609
|
+
|
|
610
|
+
def deliver
|
|
611
|
+
# ...
|
|
612
|
+
method = config.method
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
If you’d like your config options to support dynamic resolution (set `config.foo` to a lambda or symbol of a method name etc.), you can use `evaluate_option`:
|
|
618
|
+
|
|
619
|
+
```ruby
|
|
620
|
+
class NewSaleNotifier < Noticed::Event
|
|
621
|
+
deliver_by :whats_app do |config|
|
|
622
|
+
config.day = -> { is_tuesday? "Tuesday" : "Not Tuesday" }
|
|
623
|
+
end
|
|
624
|
+
end
|
|
625
|
+
|
|
626
|
+
class DeliveryMethods::WhatsApp < Noticed::DeliveryMethod
|
|
627
|
+
required_options :day
|
|
628
|
+
|
|
629
|
+
def deliver
|
|
630
|
+
# ...
|
|
631
|
+
config.day #=> #<Proc:0x000f7c8 (lambda)>
|
|
632
|
+
evaluate_option(config.day) #=> "Tuesday"
|
|
633
|
+
end
|
|
333
634
|
end
|
|
334
635
|
```
|
|
335
636
|
|
|
336
637
|
#### Callbacks
|
|
337
638
|
|
|
338
|
-
Callbacks for delivery methods wrap the
|
|
639
|
+
Callbacks for delivery methods wrap the _actual_ delivery of the notification. You can use `before_deliver`, `around_deliver` and `after_deliver` in your custom delivery methods.
|
|
339
640
|
|
|
340
641
|
```ruby
|
|
341
|
-
class DeliveryMethods::Discord < Noticed::
|
|
642
|
+
class DeliveryMethods::Discord < Noticed::DeliveryMethod
|
|
342
643
|
after_deliver do
|
|
343
644
|
# Do whatever you want
|
|
344
645
|
end
|
|
345
646
|
end
|
|
346
647
|
```
|
|
347
648
|
|
|
348
|
-
|
|
649
|
+
## 📦 Database Model
|
|
650
|
+
|
|
651
|
+
The Noticed database models include several helpful features to make working with notifications easier.
|
|
349
652
|
|
|
350
|
-
|
|
653
|
+
### Notification
|
|
351
654
|
|
|
352
|
-
#### Class methods
|
|
655
|
+
#### Class methods/scopes
|
|
656
|
+
|
|
657
|
+
(Assuming your user `has_many :notifications, as: :recipient, class_name: "Noticed::Notification"`)
|
|
353
658
|
|
|
354
659
|
Sorting notifications by newest first:
|
|
355
660
|
|
|
356
661
|
```ruby
|
|
357
|
-
user.notifications.newest_first
|
|
662
|
+
@user.notifications.newest_first
|
|
358
663
|
```
|
|
359
664
|
|
|
360
665
|
Query for read or unread notifications:
|
|
@@ -364,12 +669,11 @@ user.notifications.read
|
|
|
364
669
|
user.notifications.unread
|
|
365
670
|
```
|
|
366
671
|
|
|
367
|
-
|
|
368
672
|
Marking all notifications as read or unread:
|
|
369
673
|
|
|
370
674
|
```ruby
|
|
371
|
-
user.notifications.mark_as_read
|
|
372
|
-
user.notifications.mark_as_unread
|
|
675
|
+
user.notifications.mark_as_read
|
|
676
|
+
user.notifications.mark_as_unread
|
|
373
677
|
```
|
|
374
678
|
|
|
375
679
|
#### Instance methods
|
|
@@ -383,7 +687,9 @@ Convert back into a Noticed notifier object:
|
|
|
383
687
|
Mark notification as read / unread:
|
|
384
688
|
|
|
385
689
|
```ruby
|
|
690
|
+
@notification.mark_as_read
|
|
386
691
|
@notification.mark_as_read!
|
|
692
|
+
@notification.mark_as_unread
|
|
387
693
|
@notification.mark_as_unread!
|
|
388
694
|
```
|
|
389
695
|
|
|
@@ -396,41 +702,45 @@ Check if read / unread:
|
|
|
396
702
|
|
|
397
703
|
#### Associating Notifications
|
|
398
704
|
|
|
399
|
-
Adding notification associations to your models makes querying and
|
|
705
|
+
Adding notification associations to your models makes querying, rendering, and managing notifications easy (and is a pretty critical feature of most applications).
|
|
400
706
|
|
|
401
|
-
|
|
707
|
+
There are two ways to associate your models to notifications:
|
|
402
708
|
|
|
403
|
-
|
|
709
|
+
1. Where your object `has_many` notifications as the recipient (who you sent the notification to)
|
|
710
|
+
2. Where your object `has_many` notifications as the `record` (what the notifications were about)
|
|
404
711
|
|
|
405
|
-
|
|
406
|
-
2. Notifications where the record is in the notification params
|
|
712
|
+
In the former, we’ll use a `has_many` to `:notifications`. In the latter, we’ll actually `has_many` to `:events`, since `record`s generate notifiable _events_ (and events generate notifications).
|
|
407
713
|
|
|
408
|
-
|
|
714
|
+
We can illustrate that in the following:
|
|
409
715
|
|
|
410
716
|
```ruby
|
|
411
|
-
class
|
|
412
|
-
|
|
413
|
-
|
|
717
|
+
class User < ApplicationRecord
|
|
718
|
+
has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"
|
|
719
|
+
end
|
|
414
720
|
|
|
415
|
-
|
|
416
|
-
|
|
721
|
+
# All of the notifications the user has been sent
|
|
722
|
+
# @user.notifications.each { |n| render(n) }
|
|
417
723
|
|
|
418
|
-
|
|
419
|
-
|
|
724
|
+
class Post < ApplicationRecord
|
|
725
|
+
has_many :noticed_events, as: :record, dependent: :destroy, class_name: "Noticed::Event"
|
|
726
|
+
has_many :notifications, through: :noticed_events, class_name: "Noticed::Notification"
|
|
420
727
|
end
|
|
421
728
|
|
|
422
|
-
#
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
729
|
+
# All of the notification events this post generated
|
|
730
|
+
# @post.notifications
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
#### ActiveJob Parent Class
|
|
734
|
+
|
|
735
|
+
Noticed uses its own `Noticed::ApplicationJob` as the base job for all notifications. In the event that you would like to customize the parent job class, there is a `parent_class` attribute that can be overridden with your own class. This should be done in a `noticed.rb` initializer.
|
|
426
736
|
|
|
427
|
-
|
|
428
|
-
|
|
737
|
+
```ruby
|
|
738
|
+
Noticed.parent_class = "ApplicationJob"
|
|
429
739
|
```
|
|
430
740
|
|
|
431
741
|
#### Handling Deleted Records
|
|
432
742
|
|
|
433
|
-
|
|
743
|
+
Generally we recommend using a `dependent: ___` relationship on your models to avoid cases where Noticed Events or Notifications are left lingering when your models are destroyed. In the case that they are or data becomes mis-matched, you’ll likely run into deserialization issues. That may be globally alleviated with the following snippet, but use with caution.
|
|
434
744
|
|
|
435
745
|
```ruby
|
|
436
746
|
class ApplicationJob < ActiveJob::Base
|
|
@@ -438,6 +748,48 @@ class ApplicationJob < ActiveJob::Base
|
|
|
438
748
|
end
|
|
439
749
|
```
|
|
440
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
|
+
|
|
441
793
|
## 🙏 Contributing
|
|
442
794
|
|
|
443
795
|
This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `standardrb` before submitting pull requests.
|