noticed 1.6.3 → 2.0.1
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 +269 -237
- data/app/jobs/noticed/application_job.rb +9 -0
- data/app/jobs/noticed/event_job.rb +19 -0
- data/app/models/concerns/noticed/deliverable.rb +115 -0
- data/app/models/concerns/noticed/notification_methods.rb +17 -0
- data/app/models/concerns/noticed/readable.rb +78 -0
- data/app/models/noticed/application_record.rb +6 -0
- data/app/models/noticed/deliverable/deliver_by.rb +43 -0
- data/app/models/noticed/event.rb +15 -0
- data/app/models/noticed/notification.rb +14 -0
- data/db/migrate/20231215190233_create_noticed_tables.rb +25 -0
- data/lib/generators/noticed/delivery_method_generator.rb +1 -1
- data/lib/generators/noticed/install_generator.rb +19 -0
- data/lib/generators/noticed/{notification_generator.rb → notifier_generator.rb} +2 -2
- data/lib/generators/noticed/templates/README +5 -4
- data/lib/generators/noticed/templates/delivery_method.rb.tt +4 -8
- data/lib/generators/noticed/templates/notifier.rb.tt +24 -0
- data/lib/noticed/api_client.rb +44 -0
- data/lib/noticed/bulk_delivery_method.rb +46 -0
- data/lib/noticed/bulk_delivery_methods/discord.rb +11 -0
- data/lib/noticed/bulk_delivery_methods/slack.rb +17 -0
- data/lib/noticed/bulk_delivery_methods/webhook.rb +18 -0
- data/lib/noticed/coder.rb +2 -0
- data/lib/noticed/delivery_method.rb +51 -0
- data/lib/noticed/delivery_methods/action_cable.rb +7 -39
- data/lib/noticed/delivery_methods/discord.rb +11 -0
- data/lib/noticed/delivery_methods/email.rb +13 -45
- data/lib/noticed/delivery_methods/fcm.rb +23 -64
- data/lib/noticed/delivery_methods/ios.rb +25 -112
- data/lib/noticed/delivery_methods/microsoft_teams.rb +5 -22
- data/lib/noticed/delivery_methods/slack.rb +6 -16
- data/lib/noticed/delivery_methods/test.rb +2 -12
- data/lib/noticed/delivery_methods/twilio_messaging.rb +37 -0
- data/lib/noticed/delivery_methods/vonage_sms.rb +20 -0
- data/lib/noticed/delivery_methods/webhook.rb +17 -0
- data/lib/noticed/engine.rb +1 -9
- data/lib/noticed/required_options.rb +21 -0
- data/lib/noticed/translation.rb +7 -3
- data/lib/noticed/version.rb +1 -1
- data/lib/noticed.rb +30 -15
- metadata +29 -40
- data/lib/generators/noticed/model/base_generator.rb +0 -47
- data/lib/generators/noticed/model/mysql_generator.rb +0 -18
- data/lib/generators/noticed/model/postgresql_generator.rb +0 -18
- data/lib/generators/noticed/model/sqlite3_generator.rb +0 -18
- data/lib/generators/noticed/model_generator.rb +0 -63
- data/lib/generators/noticed/templates/notification.rb.tt +0 -27
- data/lib/noticed/base.rb +0 -160
- data/lib/noticed/delivery_methods/base.rb +0 -95
- data/lib/noticed/delivery_methods/database.rb +0 -34
- data/lib/noticed/delivery_methods/twilio.rb +0 -51
- data/lib/noticed/delivery_methods/vonage.rb +0 -40
- data/lib/noticed/has_notifications.rb +0 -49
- data/lib/noticed/model.rb +0 -85
- data/lib/noticed/notification_channel.rb +0 -15
- data/lib/noticed/text_coder.rb +0 -16
- data/lib/rails_6_polyfills/actioncable/test_adapter.rb +0 -70
- data/lib/rails_6_polyfills/actioncable/test_helper.rb +0 -143
- data/lib/rails_6_polyfills/activejob/serializers.rb +0 -240
- data/lib/rails_6_polyfills/base.rb +0 -18
- data/lib/tasks/noticed_tasks.rake +0 -4
data/README.md
CHANGED
@@ -1,385 +1,419 @@
|
|
1
|
-
|
2
|
-
<h1>Noticed</h1>
|
3
|
-
</p>
|
1
|
+
# Noticed
|
4
2
|
|
5
3
|
### 🎉 Notifications for your Ruby on Rails app.
|
6
4
|
|
7
5
|
[](https://github.com/excid3/noticed/actions) [](https://badge.fury.io/rb/noticed)
|
8
6
|
|
9
|
-
|
7
|
+
**⚠️⚠️ Upgrading from V1? Read the [Upgrade Guide](https://github.com/excid3/noticed/blob/main/UPGRADE.md)!**
|
10
8
|
|
11
|
-
|
12
|
-
* Email
|
13
|
-
* ActionCable channels
|
14
|
-
* Slack
|
15
|
-
* Microsoft Teams
|
16
|
-
* Twilio (SMS)
|
17
|
-
* Vonage / Nexmo (SMS)
|
18
|
-
* iOS Apple Push Notifications
|
19
|
-
* Firebase Cloud Messaging (Android and more)
|
9
|
+
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)!
|
20
10
|
|
21
|
-
|
11
|
+
Noticed implements two top-level types of delivery methods:
|
12
|
+
|
13
|
+
1. Individual Deliveries: Where each recipient gets their own notification
|
14
|
+
|
15
|
+
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.
|
16
|
+
|
17
|
+
2. Bulk Deliveries - one notification for all recipients. This is useful for sending a notification to your Slack team, for example.
|
18
|
+
|
19
|
+
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
|
+
|
21
|
+
Bulk deliveries are typically used to push notifications to other platforms where users are managed (Slack, Discord, etc.) instead of your own.
|
22
|
+
|
23
|
+
Delivery methods we officially support:
|
24
|
+
|
25
|
+
* [ActionCable](docs/delivery_methods/action_cable.md)
|
26
|
+
* [Apple Push Notification Service](docs/delivery_methods/ios.md)
|
27
|
+
* [Email](docs/delivery_methods/email.md)
|
28
|
+
* [Firebase Cloud Messaging](docs/delivery_methods/fcm.md) (iOS, Android, and web clients)
|
29
|
+
* [Microsoft Teams](docs/delivery_methods/microsoft_teams.md)
|
30
|
+
* [Slack](docs/delivery_methods/slack.md)
|
31
|
+
* [Twilio Messaging](docs/delivery_methods/twilio_messaging.md) - SMS, Whatsapp
|
32
|
+
* [Vonage SMS](docs/delivery_methods/vonage_sms.md)
|
33
|
+
* [Test](docs/delivery_methods/test.md)
|
34
|
+
|
35
|
+
Bulk delivery methods we support:
|
36
|
+
|
37
|
+
* [Discord](docs/bulk_delivery_methods/discord.md)
|
38
|
+
* [Slack](docs/bulk_delivery_methods/slack.md)
|
39
|
+
* [Webhook](docs/bulk_delivery_methods/webhook.md)
|
22
40
|
|
23
41
|
## 🎬 Screencast
|
24
42
|
|
25
|
-
<
|
26
|
-
<a href="https://www.youtube.com/watch?v=Scffi4otlFc"><img src="https://i.imgur.com/UvVKWwD.png" title="How to add Notifications to Rails with Noticed" /></a>
|
27
|
-
</div>
|
43
|
+
<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>
|
28
44
|
|
29
|
-
[Watch Screencast](https://www.youtube.com/watch?v=
|
45
|
+
[Watch Screencast](https://www.youtube.com/watch?v=SzX-aBEqnAc)
|
30
46
|
|
31
47
|
## 🚀 Installation
|
32
|
-
Run the following command to add Noticed to your Gemfile
|
48
|
+
Run the following command to add Noticed to your Gemfile:
|
33
49
|
|
34
50
|
```ruby
|
35
51
|
bundle add "noticed"
|
36
52
|
```
|
37
53
|
|
38
|
-
|
54
|
+
Generate then run the migrations:
|
39
55
|
|
40
|
-
```
|
41
|
-
rails
|
56
|
+
```bash
|
57
|
+
rails noticed:install:migrations
|
58
|
+
rails db:migrate
|
42
59
|
```
|
43
60
|
|
44
|
-
This will generate a Notification model and instructions for associating User models with the notifications table.
|
45
|
-
|
46
61
|
## 📝 Usage
|
47
62
|
|
48
|
-
|
63
|
+
Noticed operates with a few constructs: Notifiers, delivery methods, and Notification records.
|
49
64
|
|
50
|
-
|
65
|
+
To start, generate a Notifier:
|
51
66
|
|
52
|
-
|
67
|
+
```sh
|
68
|
+
rails generate noticed:notifier NewCommentNotifier
|
69
|
+
```
|
70
|
+
|
71
|
+
#### Notifier Objects
|
53
72
|
|
54
|
-
|
73
|
+
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.
|
74
|
+
|
75
|
+
Notifiers must inherit from `Noticed::Event`. This provides all of their functionality.
|
76
|
+
|
77
|
+
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.
|
55
78
|
|
56
79
|
```ruby
|
57
|
-
#
|
58
|
-
notification = CommentNotification.with(comment: @comment)
|
80
|
+
# ~/app/notifiers/new_comment_notifier.rb
|
59
81
|
|
60
|
-
|
61
|
-
|
82
|
+
class NewCommentNotifier < Noticed::Event
|
83
|
+
deliver_by :action_cable do |config|
|
84
|
+
config.channel = "NotificationsChannel"
|
85
|
+
config.stream = :some_stream
|
86
|
+
end
|
62
87
|
|
63
|
-
|
64
|
-
|
88
|
+
deliver_by :email do |config|
|
89
|
+
config.mailer = "CommentMailer"
|
90
|
+
config.if = ->(recipient) { !!recipient.preferences[:email] }
|
91
|
+
end
|
65
92
|
|
66
|
-
|
67
|
-
|
68
|
-
|
93
|
+
bulk_deliver_by :discord do |config|
|
94
|
+
config.url = "https://discord.com/xyz/xyz/123"
|
95
|
+
config.json = -> {
|
96
|
+
{
|
97
|
+
message: message,
|
98
|
+
channel: :general
|
99
|
+
}
|
100
|
+
}
|
101
|
+
end
|
69
102
|
|
70
|
-
|
103
|
+
notification_methods do
|
104
|
+
# I18n helpers
|
105
|
+
def message
|
106
|
+
t(".message")
|
107
|
+
end
|
71
108
|
|
72
|
-
|
109
|
+
# URL helpers are accessible in notifications
|
110
|
+
# Don't forget to set your default_url_options so Rails knows how to generate urls
|
111
|
+
def url
|
112
|
+
user_post_path(recipient, params[:post])
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
73
117
|
|
74
|
-
|
118
|
+
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).
|
75
119
|
|
76
|
-
|
120
|
+
##### Required Params
|
77
121
|
|
78
|
-
|
122
|
+
While explicit / required parameters are completely optional, Notifiers are able to opt in to required parameters via the `required_params` method:
|
79
123
|
|
80
124
|
```ruby
|
81
|
-
class
|
82
|
-
deliver_by :
|
83
|
-
deliver_by :action_cable
|
84
|
-
deliver_by :email, mailer: 'CommentMailer', if: :email_notifications?
|
125
|
+
class CarSaleNotifier < Noticed::Event
|
126
|
+
deliver_by :email { |c| c.mailer = "BranchMailer" }
|
85
127
|
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
128
|
+
# `record` is the Car record, `Branch` is the dealership
|
129
|
+
required_params :record, :branch
|
130
|
+
end
|
131
|
+
```
|
90
132
|
|
91
|
-
|
92
|
-
# Don't forget to set your default_url_options so Rails knows how to generate urls
|
93
|
-
def url
|
94
|
-
post_path(params[:post])
|
95
|
-
end
|
133
|
+
Which will validate upon any invocation that the specified parameters are present:
|
96
134
|
|
97
|
-
|
98
|
-
|
99
|
-
|
135
|
+
```ruby
|
136
|
+
CarSaleNotifier.with(record: Car.last).deliver(Branch.last)
|
137
|
+
#=> Noticed::ValidationError("Param `branch` is required for CarSaleNotifier")
|
100
138
|
|
101
|
-
|
102
|
-
|
103
|
-
end
|
104
|
-
end
|
139
|
+
CarSaleNotifier.with(record: Car.last, branch: Branch.last).deliver(Branch.hq)
|
140
|
+
#=> OK
|
105
141
|
```
|
106
142
|
|
107
|
-
**Shared Options**
|
108
143
|
|
109
|
-
* `if: :method_name` - Calls `method_name` and cancels delivery method if `false` is returned
|
110
|
-
* `unless: :method_name` - Calls `method_name` and cancels delivery method if `true` is returned
|
111
|
-
* `delay: ActiveSupport::Duration` - Delays the delivery for the given duration of time
|
112
|
-
* `delay: :method_name` - Calls `method_name` which should return an `ActiveSupport::Duration` and delays the delivery for the given duration of time
|
113
144
|
|
114
145
|
##### Helper Methods
|
115
146
|
|
116
|
-
|
147
|
+
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:
|
148
|
+
|
149
|
+
```erb
|
150
|
+
<div>
|
151
|
+
<% @user.notifications.each do |notification| %>
|
152
|
+
<%= link_to notification.message, notification.url %>
|
153
|
+
<% end %>
|
154
|
+
</div>
|
155
|
+
```
|
117
156
|
|
118
|
-
|
157
|
+
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.
|
119
158
|
|
120
|
-
|
159
|
+
###### URL Helpers
|
121
160
|
|
122
|
-
|
161
|
+
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.
|
162
|
+
|
163
|
+
_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.
|
123
164
|
|
124
165
|
```ruby
|
125
166
|
Rails.application.routes.default_url_options[:host] = 'localhost:3000'
|
126
167
|
```
|
127
168
|
|
128
|
-
|
169
|
+
###### Translations
|
170
|
+
|
171
|
+
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:
|
129
172
|
|
130
|
-
|
173
|
+
From the above Notifier...
|
131
174
|
|
132
175
|
```ruby
|
133
|
-
class
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
#
|
143
|
-
before_database :whatever
|
144
|
-
around_database :whatever
|
145
|
-
after_database :whatever
|
146
|
-
|
147
|
-
before_email :whatever
|
148
|
-
around_email :whatever
|
149
|
-
after_email :whatever
|
176
|
+
class NewCommentNotifier < Noticed::Event
|
177
|
+
# ...
|
178
|
+
|
179
|
+
notification_methods do
|
180
|
+
def message
|
181
|
+
t(".message")
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# ...
|
150
186
|
end
|
151
187
|
```
|
152
188
|
|
153
|
-
|
154
|
-
|
155
|
-
Defining custom delivery methods allows you to add callbacks that run inside the background job as each individual delivery is executed. See the Custom Delivery Methods section for more information.
|
156
|
-
|
157
|
-
##### Translations
|
189
|
+
Calling the `message` helper in an ERB view:
|
158
190
|
|
159
|
-
|
191
|
+
```erb
|
192
|
+
<%= @user.notifications.last.message %>
|
193
|
+
```
|
160
194
|
|
161
|
-
|
195
|
+
Will look for the following translation path:
|
162
196
|
|
163
|
-
|
197
|
+
```yml
|
198
|
+
# ~/config/locales/en.yml
|
164
199
|
|
165
|
-
|
200
|
+
en:
|
201
|
+
notifiers:
|
202
|
+
new_comment_notifier:
|
203
|
+
notification:
|
204
|
+
message: "Someone posted a new comment!"
|
205
|
+
```
|
166
206
|
|
167
|
-
|
207
|
+
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).
|
168
208
|
|
169
|
-
##### User Preferences
|
209
|
+
##### Tip: Capture User Preferences
|
170
210
|
|
171
211
|
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.
|
172
212
|
|
173
213
|
For example:
|
174
214
|
|
175
215
|
```ruby
|
176
|
-
class
|
177
|
-
deliver_by :email
|
178
|
-
|
179
|
-
|
180
|
-
recipient.email_notifications?
|
216
|
+
class CommentNotifier < Noticed::Event
|
217
|
+
deliver_by :email do |config|
|
218
|
+
config.mailer = 'CommentMailer'
|
219
|
+
config.method = :new_comment
|
220
|
+
config.if = ->{ recipient.email_notifications? }
|
181
221
|
end
|
182
222
|
end
|
183
223
|
```
|
184
224
|
|
185
|
-
|
186
|
-
|
187
|
-
In order to figure out what's up when you run in to errors, you can set the `debug` parameter to `true` in your notification, which will give you a more detailed error message about what went wrong.
|
225
|
+
**Shared Delivery Method Options**
|
188
226
|
|
189
|
-
|
227
|
+
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.
|
190
228
|
|
191
|
-
|
192
|
-
|
193
|
-
|
229
|
+
* `config.if` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns falsey
|
230
|
+
* `config.unless` — Intended for a lambda or method; runs after the `wait` if configured; cancels the delivery method if returns truthy
|
231
|
+
* `config.wait` — (Should yield an `ActiveSupport::Duration`) Delays the job that runs this delivery method for the given duration of time
|
232
|
+
* `config.wait_until` — (Should yield a specific time object) Delays the job that runs this delivery method until the specific time specified
|
233
|
+
* `config.queue` — Sets the ActiveJob queue name to be used for the job that runs this delivery method
|
194
234
|
|
195
|
-
|
196
|
-
|
197
|
-
### Creating a notification from an Active Record callback
|
235
|
+
#### Sending Notifications
|
198
236
|
|
199
|
-
|
237
|
+
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:
|
200
238
|
|
201
239
|
```ruby
|
202
|
-
|
203
|
-
belongs_to :recipient, class_name: "User"
|
204
|
-
|
205
|
-
after_create_commit :notify_recipient
|
206
|
-
|
207
|
-
private
|
208
|
-
|
209
|
-
def notify_recipient
|
210
|
-
NewMessageNotification.with(message: self).deliver_later(recipient)
|
211
|
-
end
|
240
|
+
NewCommentNotifier.with(record: @comment, foo: "bar").deliver(@comment.thread.all_authors)
|
212
241
|
```
|
213
242
|
|
214
|
-
|
215
|
-
|
216
|
-
Using `after_create` might cause the notification delivery methods to fail. This is because the job was enqueued while inside a database transaction, and the `Message` record might not yet be saved to the database.
|
243
|
+
This instantiates a new `NewCommentNotifier` with params (similar to ActiveJob, any serializable params are permitted), then delivers notifications to all authors in the thread.
|
217
244
|
|
218
|
-
|
245
|
+
✨ 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”.
|
219
246
|
|
220
|
-
|
247
|
+
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:
|
221
248
|
|
222
|
-
|
249
|
+
- A bulk delivery job for `:discord` bulk delivery
|
250
|
+
- An individual delivery job for `:action_cable` method to the first thread author
|
251
|
+
- An individual delivery job for `:email` method to the first thread author
|
252
|
+
- An individual delivery job for `:action_cable` method to the second thread author
|
253
|
+
- An individual delivery job for `:email` method to the second thread author
|
254
|
+
- Etc...
|
223
255
|
|
224
|
-
|
256
|
+
## ✅ Best Practices
|
225
257
|
|
226
|
-
|
258
|
+
### Renaming Notifiers
|
227
259
|
|
228
|
-
|
229
|
-
Notification.where(type: YourNotificationClassName.name) # good
|
230
|
-
Notification.where(type: "YourNotificationClassName") # bad
|
231
|
-
```
|
260
|
+
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.
|
232
261
|
|
233
|
-
When renaming a
|
262
|
+
When renaming a Notifier class you will need to backfill existing Events and Notifications to reference the new name.
|
234
263
|
|
235
264
|
```ruby
|
236
|
-
|
265
|
+
Noticed::Event.where(type: "OldNotifierClassName").update_all(type: NewNotifierClassName.name)
|
266
|
+
# and
|
267
|
+
Noticed::Notification.where(type: "OldNotifierClassName::Notification").update_all(type: "#{NewNotifierClassName.name}::Notification")
|
237
268
|
```
|
238
269
|
|
239
270
|
## 🚛 Delivery Methods
|
240
271
|
|
241
272
|
The delivery methods are designed to be modular so you can customize the way each type gets delivered.
|
242
273
|
|
243
|
-
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
|
274
|
+
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.
|
275
|
+
|
276
|
+
Individual delivery methods:
|
244
277
|
|
245
|
-
* [Database](docs/delivery_methods/database.md)
|
246
|
-
* [Email](docs/delivery_methods/email.md)
|
247
278
|
* [ActionCable](docs/delivery_methods/action_cable.md)
|
248
|
-
* [
|
279
|
+
* [Apple Push Notification Service](docs/delivery_methods/ios.md)
|
280
|
+
* [Email](docs/delivery_methods/email.md)
|
281
|
+
* [Firebase Cloud Messaging](docs/delivery_methods/fcm.md) (iOS, Android, and web clients)
|
249
282
|
* [Microsoft Teams](docs/delivery_methods/microsoft_teams.md)
|
250
283
|
* [Slack](docs/delivery_methods/slack.md)
|
284
|
+
* [Twilio Messaging](docs/delivery_methods/twilio_messaging.md) - SMS, Whatsapp
|
285
|
+
* [Vonage SMS](docs/delivery_methods/vonage_sms.md)
|
251
286
|
* [Test](docs/delivery_methods/test.md)
|
252
|
-
* [Twilio](docs/delivery_methods/twilio.md)
|
253
|
-
* [Vonage](docs/delivery_methods/vonage.md)
|
254
|
-
* [Firebase Cloud Messaging](docs/delivery_methods/fcm.md)
|
255
287
|
|
256
|
-
|
288
|
+
Bulk delivery methods:
|
257
289
|
|
258
|
-
|
290
|
+
* [Discord](docs/bulk_delivery_methods/discord.md)
|
291
|
+
* [Slack](docs/bulk_delivery_methods/slack.md)
|
292
|
+
* [Webhook](docs/bulk_delivery_methods/webhook.md)
|
259
293
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
294
|
+
### No Delivery Methods
|
295
|
+
|
296
|
+
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.
|
297
|
+
|
298
|
+
So even with no delivery methods set, this example is still perfectly available and helpful:
|
299
|
+
|
300
|
+
```erb
|
301
|
+
<div>
|
302
|
+
<% @user.notifications.each do |notification| %>
|
303
|
+
<%= link_to notification.message, notification.url %>
|
304
|
+
<% end %>
|
305
|
+
</div>
|
265
306
|
```
|
266
307
|
|
267
|
-
|
308
|
+
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.
|
309
|
+
|
310
|
+
### Fallback Notifications
|
268
311
|
|
269
|
-
You can
|
312
|
+
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.
|
270
313
|
|
271
314
|
```ruby
|
272
|
-
class
|
273
|
-
deliver_by :
|
274
|
-
deliver_by :
|
275
|
-
|
276
|
-
|
315
|
+
class NewCommentNotifier< Noticed::Event
|
316
|
+
deliver_by :action_cable
|
317
|
+
deliver_by :email do |config|
|
318
|
+
config.mailer = "CommentMailer"
|
319
|
+
config.wait = 15.minutes
|
320
|
+
config.unless = -> { read? }
|
321
|
+
end
|
277
322
|
end
|
278
323
|
```
|
279
324
|
|
280
|
-
|
325
|
+
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.
|
281
326
|
|
282
|
-
|
283
|
-
- Immediately issues a ping in Slack.
|
284
|
-
- If the notification remains unread after 10 minutes, it emails the team.
|
285
|
-
- If the notification remains unread after 20 minutes, it sends an SMS to the on-call phone.
|
327
|
+
_A note here: notifications expose a `#mark_as_read` method, but your app must choose when and where to call that method._
|
286
328
|
|
287
329
|
You can mix and match the options and delivery methods to suit your application specific needs.
|
288
330
|
|
289
|
-
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.
|
290
|
-
|
291
331
|
### 🚚 Custom Delivery Methods
|
292
332
|
|
293
|
-
To generate a custom delivery method, simply run
|
333
|
+
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
|
294
334
|
|
295
335
|
`rails generate noticed:delivery_method Discord`
|
296
336
|
|
297
|
-
This will generate a new `DeliveryMethods::Discord` class inside the `app/
|
337
|
+
This will generate a new `DeliveryMethods::Discord` class inside the `app/notifiers/delivery_methods` folder, which can be used to deliver notifications to Discord.
|
298
338
|
|
299
339
|
```ruby
|
300
|
-
class DeliveryMethods::Discord < Noticed::
|
340
|
+
class DeliveryMethods::Discord < Noticed::DeliveryMethod
|
341
|
+
# Specify the config options your delivery method requires in its config block
|
342
|
+
required_options # :foo, :bar
|
343
|
+
|
301
344
|
def deliver
|
302
|
-
# Logic for sending
|
345
|
+
# Logic for sending the notification
|
303
346
|
end
|
304
347
|
end
|
348
|
+
|
305
349
|
```
|
306
350
|
|
307
351
|
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.
|
308
352
|
|
309
353
|
```ruby
|
310
|
-
class
|
354
|
+
class MyNotifier < Noticed::Event
|
311
355
|
deliver_by :discord, class: "DeliveryMethods::Discord"
|
312
356
|
end
|
313
357
|
```
|
314
358
|
|
315
359
|
Delivery methods have access to the following methods and attributes:
|
316
360
|
|
317
|
-
* `
|
318
|
-
* `
|
319
|
-
* `
|
320
|
-
* `
|
321
|
-
|
322
|
-
|
361
|
+
* `event` — The `Noticed::Event` record that spawned the notification object currently being delivered
|
362
|
+
* `record` — The object originally passed into the Notifier as the `record:` param (see the ✨ note above)
|
363
|
+
* `notification` — The `Noticed::Notification` instance being delivered. All notification helper methods are available on this object
|
364
|
+
* `recipient` — The individual recipient object being delivered to for this notification (remember that each recipient gets their own instance of the Delivery Method `#deliver`)
|
365
|
+
* `config` — The hash of configuration options declared by the Notifier that generated this notification and delivery
|
366
|
+
* `params` — The parameters given to the Notifier in the invocation (via `.with()`)
|
323
367
|
|
324
|
-
|
368
|
+
#### Validating config options passed to Custom Delivery methods
|
325
369
|
|
326
|
-
|
370
|
+
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:
|
327
371
|
|
328
372
|
```ruby
|
329
|
-
class DeliveryMethods::
|
330
|
-
|
373
|
+
class DeliveryMethods::Email < Noticed::DeliveryMethod
|
374
|
+
required_options :mailer, :method
|
331
375
|
|
332
376
|
def deliver
|
333
|
-
#
|
377
|
+
# ...
|
378
|
+
method = config.method
|
334
379
|
end
|
335
|
-
|
336
|
-
def self.validate!(delivery_method_options)
|
337
|
-
super # Don't forget to call super, otherwise option presence won't be validated
|
338
|
-
|
339
|
-
# Custom validations
|
340
|
-
if delivery_method_options[:username].blank?
|
341
|
-
raise Noticed::ValidationError, 'the `username` option must be present'
|
342
|
-
end
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
class CommentNotification < Noticed::Base
|
347
|
-
deliver_by :discord, class: 'DeliveryMethods::Discord'
|
348
380
|
end
|
349
381
|
```
|
350
382
|
|
351
|
-
|
352
|
-
|
353
|
-
To fix the error, the argument has to be passed correctly. For example:
|
383
|
+
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`:
|
354
384
|
|
355
385
|
```ruby
|
356
|
-
class
|
357
|
-
deliver_by :
|
386
|
+
class NewSaleNotifier < Noticed::Event
|
387
|
+
deliver_by :whats_app do |config|
|
388
|
+
config.day = -> { is_tuesday? "Tuesday" : "Not Tuesday" }
|
389
|
+
end
|
358
390
|
end
|
359
|
-
```
|
360
|
-
|
361
|
-
#### Callbacks
|
362
391
|
|
363
|
-
|
392
|
+
class DeliveryMethods::WhatsApp < Noticed::DeliveryMethod
|
393
|
+
required_options :day
|
364
394
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
395
|
+
def deliver
|
396
|
+
# ...
|
397
|
+
config.day #=> #<Proc:0x000f7c8 (lambda)>
|
398
|
+
evaluate_option(config.day) #=> "Tuesday"
|
369
399
|
end
|
370
400
|
end
|
371
401
|
```
|
372
402
|
|
373
403
|
### 📦 Database Model
|
374
404
|
|
375
|
-
The
|
405
|
+
The Noticed database models include several helpful features to make working with notifications easier.
|
406
|
+
|
407
|
+
#### Notification
|
376
408
|
|
377
|
-
|
409
|
+
##### Class methods/scopes
|
410
|
+
|
411
|
+
(Assuming your user `has_many :notifications, as: :recipient, class_name: "Noticed::Notification"`)
|
378
412
|
|
379
413
|
Sorting notifications by newest first:
|
380
414
|
|
381
415
|
```ruby
|
382
|
-
user.notifications.newest_first
|
416
|
+
@user.notifications.newest_first
|
383
417
|
```
|
384
418
|
|
385
419
|
Query for read or unread notifications:
|
@@ -399,16 +433,18 @@ user.notifications.mark_as_unread!
|
|
399
433
|
|
400
434
|
#### Instance methods
|
401
435
|
|
402
|
-
Convert back into a Noticed
|
436
|
+
Convert back into a Noticed notifier object:
|
403
437
|
|
404
438
|
```ruby
|
405
|
-
@notification.
|
439
|
+
@notification.to_notifier
|
406
440
|
```
|
407
441
|
|
408
442
|
Mark notification as read / unread:
|
409
443
|
|
410
444
|
```ruby
|
445
|
+
@notification.mark_as_read
|
411
446
|
@notification.mark_as_read!
|
447
|
+
@notification.mark_as_unread
|
412
448
|
@notification.mark_as_unread!
|
413
449
|
```
|
414
450
|
|
@@ -421,41 +457,36 @@ Check if read / unread:
|
|
421
457
|
|
422
458
|
#### Associating Notifications
|
423
459
|
|
424
|
-
Adding notification associations to your models makes querying and
|
460
|
+
Adding notification associations to your models makes querying, rendering, and managing notifications easy (and is a pretty critical feature of most applications).
|
425
461
|
|
426
|
-
|
462
|
+
There are two ways to associate your models to notifications:
|
427
463
|
|
428
|
-
|
464
|
+
1. Where your object `has_many` notifications as the recipient (who you sent the notification to)
|
465
|
+
2. Where your object `has_many` notifications as the `record` (what the notifications were about)
|
429
466
|
|
430
|
-
|
431
|
-
2. Notifications where the record is in the notification params
|
467
|
+
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).
|
432
468
|
|
433
|
-
|
469
|
+
We can illustrate that in the following:
|
434
470
|
|
435
471
|
```ruby
|
436
|
-
class
|
437
|
-
|
438
|
-
|
472
|
+
class User < ApplicationRecord
|
473
|
+
has_many :notifications, as: :recipient, dependent: :destroy, class_name: "Noticed::Notification"
|
474
|
+
end
|
439
475
|
|
440
|
-
|
441
|
-
|
476
|
+
# All of the notifications the user has been sent
|
477
|
+
# @user.notifications.each { |n| render(n) }
|
442
478
|
|
443
|
-
|
444
|
-
|
479
|
+
class Post < ApplicationRecord
|
480
|
+
has_many :noticed_events, as: :record, dependent: :destroy, class_name: "Noticed::Event"
|
445
481
|
end
|
446
482
|
|
447
|
-
#
|
448
|
-
|
449
|
-
# Lookup Notifications where params: {post: @post}
|
450
|
-
@post.notifications_as_post
|
451
|
-
|
452
|
-
CommentNotification.with(parent: @post).deliver(user)
|
453
|
-
@post.notifications_as_parent
|
483
|
+
# All of the notification events this post generated
|
484
|
+
# @post.noticed_events.each { |ne| ne.notifications... }
|
454
485
|
```
|
455
486
|
|
456
487
|
#### Handling Deleted Records
|
457
488
|
|
458
|
-
|
489
|
+
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.
|
459
490
|
|
460
491
|
```ruby
|
461
492
|
class ApplicationJob < ActiveJob::Base
|
@@ -477,3 +508,4 @@ DATABASE_URL=postgres://127.0.0.1/noticed_test rails test
|
|
477
508
|
|
478
509
|
## 📝 License
|
479
510
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
511
|
+
|