noticed 1.6.3 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +122 -147
  3. data/app/jobs/noticed/application_job.rb +9 -0
  4. data/app/jobs/noticed/event_job.rb +19 -0
  5. data/app/models/concerns/noticed/deliverable.rb +115 -0
  6. data/app/models/concerns/noticed/notification_methods.rb +17 -0
  7. data/app/models/concerns/noticed/readable.rb +62 -0
  8. data/app/models/noticed/application_record.rb +6 -0
  9. data/app/models/noticed/deliverable/deliver_by.rb +43 -0
  10. data/app/models/noticed/event.rb +15 -0
  11. data/app/models/noticed/notification.rb +16 -0
  12. data/db/migrate/20231215190233_create_noticed_tables.rb +25 -0
  13. data/lib/generators/noticed/delivery_method_generator.rb +1 -1
  14. data/lib/generators/noticed/install_generator.rb +19 -0
  15. data/lib/generators/noticed/{notification_generator.rb → notifier_generator.rb} +2 -2
  16. data/lib/generators/noticed/templates/README +5 -4
  17. data/lib/generators/noticed/templates/notifier.rb.tt +24 -0
  18. data/lib/noticed/api_client.rb +44 -0
  19. data/lib/noticed/bulk_delivery_method.rb +46 -0
  20. data/lib/noticed/bulk_delivery_methods/discord.rb +11 -0
  21. data/lib/noticed/bulk_delivery_methods/slack.rb +17 -0
  22. data/lib/noticed/bulk_delivery_methods/webhook.rb +18 -0
  23. data/lib/noticed/coder.rb +2 -0
  24. data/lib/noticed/delivery_method.rb +50 -0
  25. data/lib/noticed/delivery_methods/action_cable.rb +7 -39
  26. data/lib/noticed/delivery_methods/discord.rb +11 -0
  27. data/lib/noticed/delivery_methods/email.rb +9 -45
  28. data/lib/noticed/delivery_methods/fcm.rb +23 -64
  29. data/lib/noticed/delivery_methods/ios.rb +25 -112
  30. data/lib/noticed/delivery_methods/microsoft_teams.rb +5 -22
  31. data/lib/noticed/delivery_methods/slack.rb +6 -16
  32. data/lib/noticed/delivery_methods/test.rb +2 -12
  33. data/lib/noticed/delivery_methods/twilio_messaging.rb +37 -0
  34. data/lib/noticed/delivery_methods/vonage_sms.rb +20 -0
  35. data/lib/noticed/delivery_methods/webhook.rb +17 -0
  36. data/lib/noticed/engine.rb +1 -9
  37. data/lib/noticed/required_options.rb +21 -0
  38. data/lib/noticed/translation.rb +7 -3
  39. data/lib/noticed/version.rb +1 -1
  40. data/lib/noticed.rb +30 -15
  41. metadata +29 -40
  42. data/lib/generators/noticed/model/base_generator.rb +0 -47
  43. data/lib/generators/noticed/model/mysql_generator.rb +0 -18
  44. data/lib/generators/noticed/model/postgresql_generator.rb +0 -18
  45. data/lib/generators/noticed/model/sqlite3_generator.rb +0 -18
  46. data/lib/generators/noticed/model_generator.rb +0 -63
  47. data/lib/generators/noticed/templates/notification.rb.tt +0 -27
  48. data/lib/noticed/base.rb +0 -160
  49. data/lib/noticed/delivery_methods/base.rb +0 -95
  50. data/lib/noticed/delivery_methods/database.rb +0 -34
  51. data/lib/noticed/delivery_methods/twilio.rb +0 -51
  52. data/lib/noticed/delivery_methods/vonage.rb +0 -40
  53. data/lib/noticed/has_notifications.rb +0 -49
  54. data/lib/noticed/model.rb +0 -85
  55. data/lib/noticed/notification_channel.rb +0 -15
  56. data/lib/noticed/text_coder.rb +0 -16
  57. data/lib/rails_6_polyfills/actioncable/test_adapter.rb +0 -70
  58. data/lib/rails_6_polyfills/actioncable/test_helper.rb +0 -143
  59. data/lib/rails_6_polyfills/activejob/serializers.rb +0 -240
  60. data/lib/rails_6_polyfills/base.rb +0 -18
  61. data/lib/tasks/noticed_tasks.rake +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dca9fddb81724156c172bcb7ac9bdcbc7d3571554dd3b906d3df02395c730b23
4
- data.tar.gz: 5a8676aa3224c61b6623b81db22aecde7fa175c5514da465dfd735269b91394a
3
+ metadata.gz: f2466298cbeef0bde1ae34d07ed5317032659ba413cc15797e211012c9d7847c
4
+ data.tar.gz: b4edc8f7a819331a2d66a39c04d491f8c534f44fae8c14edbc0ce2b366556c8f
5
5
  SHA512:
6
- metadata.gz: 582f1a4e7282af29b7a66fcf322f13e2af6880757a3871a7cb74a44b3cc5fb9de6e404e5a62d78349cd7283caab14445bb63a508595878d98cadc352ea8bcbc1
7
- data.tar.gz: 3764af8b5240a7b7de561512086497ea60ff9fecb1ff49d85ce2ea42509b97188519a97d9e05c884e0d48aebbfdca3cb27022b88347160e066942fd00b191ec5
6
+ metadata.gz: bf817631a96cf372b864e6df07c38f3c0a4e97f288ae63b934af36fa9102e22408802176e9642f6a1c3a22b46854c764dfd6a25d19da00c3da5c0eeeec778694
7
+ data.tar.gz: 4d7a596f35331bc8f38e1337398f72fdf38af34b631bae27db48c32b4f58f702023c3a5d44e00ebb9f7e8e990b1053d8bc76c5941db8c9b7a296e03e9f4c2a96
data/README.md CHANGED
@@ -1,88 +1,124 @@
1
- <p align="center">
2
- <h1>Noticed</h1>
3
- </p>
4
-
1
+ # Noticed
5
2
  ### 🎉 Notifications for your Ruby on Rails app.
6
3
 
7
4
  [![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)
8
5
 
9
- Currently, we support these notification delivery methods out of the box:
6
+ Noticed helps you send notifications in your Rails apps. Notifications can be sent to any number of recipients. You might want a Slack notification with 0 recipients to let your team know when something happens. A notification can also be sent to 1+ recipients with individual deliveries (like an email to each recipient).
7
+
8
+ The core concepts of Noticed are:
9
+
10
+ 1. `Notifier` - Classes that define how notifications are delivered and when.
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.
14
+
15
+ ## Delivery Methods
16
+ Individual Delivery methods (one notification to each recipient):
17
+
18
+ * [ActionCable](docs/delivery_methods/action_cable.md)
19
+ * [Apple Push Notification Service](docs/delivery_methods/ios.md)
20
+ * [Email](docs/delivery_methods/email.md)
21
+ * [Firebase Cloud Messaging](docs/delivery_methods/fcm.md) (iOS, Android, and web clients)
22
+ * [Microsoft Teams](docs/delivery_methods/microsoft_teams.md)
23
+ * [Slack](docs/delivery_methods/slack.md)
24
+ * [Twilio Messaging](docs/delivery_methods/twilio_messaging.md) - SMS, Whatsapp
25
+ * [Vonage SMS](docs/delivery_methods/vonage_sms.md)
26
+ * [Test](docs/delivery_methods/test.md)
10
27
 
11
- * Database
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)
28
+ Bulk delivery methods (one notification for all recipients):
20
29
 
21
- And you can easily add new notification types for any other delivery methods.
30
+ * [Discord](docs/bulk_delivery_methods/discord.md)
31
+ * [Slack](docs/bulk_delivery_methods/slack.md)
32
+ * [Webhook](docs/bulk_delivery_methods/webhook.md)
22
33
 
23
34
  ## 🎬 Screencast
24
35
 
25
- <div style="width:50%">
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>
36
+ <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" width="50%" /></a>
28
37
 
29
38
  [Watch Screencast](https://www.youtube.com/watch?v=Scffi4otlFc)
30
39
 
31
40
  ## 🚀 Installation
32
- Run the following command to add Noticed to your Gemfile
41
+ Run the following command to add Noticed to your Gemfile:
33
42
 
34
43
  ```ruby
35
44
  bundle add "noticed"
36
45
  ```
37
46
 
38
- To save notifications to your database, use the following command to generate a Notification model.
47
+ Add the migrations:
39
48
 
40
- ```ruby
41
- rails generate noticed:model
49
+ ```bash
50
+ rails noticed:install:migrations
51
+ rails db:migrate
42
52
  ```
43
53
 
44
- This will generate a Notification model and instructions for associating User models with the notifications table.
45
-
46
54
  ## 📝 Usage
47
55
 
48
- To generate a notification object, simply run:
56
+ To generate a Notifier, simply run:
49
57
 
50
- `rails generate noticed:notification CommentNotification`
58
+ `rails generate noticed:notifier CommentNotifier`
51
59
 
52
- #### Sending Notifications
53
-
54
- To send a notification to a user:
60
+ #### Add Delivery Methods
61
+ Then add delivery methods to the Notifier. See [docs/delivery_methods](docs/) for a full list.
55
62
 
56
63
  ```ruby
57
- # Instantiate a new notification
58
- notification = CommentNotification.with(comment: @comment)
64
+ # app/notifiers/comment_notifier.rb
65
+ class CommentNotifier < Noticed::Event
66
+ bulk_deliver_by :webhook do |config|
67
+ config.url = "https://example.org..."
68
+ config.json = ->{ text: "New comment: #{record.body}" }
69
+ end
59
70
 
60
- # Deliver notification in background job
61
- notification.deliver_later(@comment.post.author)
71
+ deliver_by :email do |config|
72
+ config.mailer = "UserMailer"
73
+ config.method = :new_comment
74
+ end
75
+ end
76
+ ```
62
77
 
63
- # Deliver notification immediately
64
- notification.deliver(@comment.post.author)
78
+ #### Sending Notifications
65
79
 
66
- # Deliver notification to multiple recipients
67
- notification.deliver_later(User.all)
80
+ To send a notification to user(s):
81
+
82
+ ```ruby
83
+ # Instantiate a new notifier
84
+ CommentNotifier.with(record: @comment, foo: "bar").deliver_later(User.all)
68
85
  ```
69
86
 
70
- This will instantiate a new notification with the `comment` stored in the notification's params.
87
+ This instantiates a new `CommentNotifier` with params. Similar to ActiveJob, you can pass any params can be serialized. `record:` is a special param that gets assigned to the `record` polymorphic association in the database.
88
+
89
+ Delivering will create a `Noticed::Event` record and associated `Noticed::Notification` records for each recipient.
71
90
 
72
- Each delivery method is able to transform this metadata that's best for the format. For example, the database may simply store the comment so it can be linked when rendering in the navbar. The websocket mechanism may transform this into a browser notification or insert it into the navbar.
91
+ After saving, a job will be enqueued for processing this notification and delivering it to all recipients.
73
92
 
74
- #### Notification Objects
93
+ Each delivery method also spawns its own job. This allows you to skip email notifications if the user had already opened a push notification, for example.
75
94
 
76
- Notifications inherit from `Noticed::Base`. This provides all their functionality and allows them to be delivered.
95
+ #### Notifier Objects
77
96
 
78
- To add delivery methods, simply `include` the module for the delivery methods you would like to use.
97
+ Notifiers inherit from `Noticed::Event`. This provides all their functionality and allows them to be delivered.
79
98
 
80
99
  ```ruby
81
- class CommentNotification < Noticed::Base
82
- deliver_by :database
100
+ class CommentNotifier < Noticed::Event
83
101
  deliver_by :action_cable
84
- deliver_by :email, mailer: 'CommentMailer', if: :email_notifications?
102
+ deliver_by :email do |config|
103
+ config.mailer = "UserMailer"
104
+ config.if = ->(recipient) { !!recipient.preferences[:email] }
105
+ config.wait = 5.minutes
106
+ end
107
+ end
108
+ ```
109
+
110
+ **Shared Options**
85
111
 
112
+ * `if: :method_name` - Calls `method_name` and cancels delivery method if `false` is returned. This can also be specified as a Proc / lambda.
113
+ * `unless: :method_name` - Calls `method_name` and cancels delivery method if `true` is returned
114
+ * `wait:` - Delays the delivery for the given duration of time. Can be an `ActiveSupport::Duration`, Proc / lambda, or Symbol.
115
+
116
+ ##### Helper Methods
117
+
118
+ You can define helper methods inside your Notifier object to make it easier to render.
119
+
120
+ ```ruby
121
+ class CommentNotifier < Noticed::Event
86
122
  # I18n helpers
87
123
  def message
88
124
  t(".message")
@@ -94,77 +130,38 @@ class CommentNotification < Noticed::Base
94
130
  post_path(params[:post])
95
131
  end
96
132
 
97
- def email_notifications?
98
- !!recipient.preferences[:email]
99
- end
100
-
101
- after_deliver do
102
- # Anything you want
133
+ # Defines methods added to the Noticed::Notification
134
+ notification_methods do
135
+ def personalized_welcome
136
+ "Hello #{recipient.first_name}."
137
+ end
103
138
  end
104
139
  end
105
140
  ```
106
141
 
107
- **Shared Options**
108
-
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
-
114
- ##### Helper Methods
115
-
116
- You can define helper methods inside your Notification object to make it easier to render.
142
+ In your views, you can loop through notifications and access
143
+ ```erb
144
+ <%= current_user.notifications.includes(:event).each do |notification| %>
145
+ <%= link_to notification.personalized_welcome, notification.event.url %>
146
+ <% end %>
147
+ ```
117
148
 
118
149
  ##### URL Helpers
119
150
 
120
- Rails url helpers are included in notification classes by default so you have full access to them just like you would in your controllers and views.
121
-
122
- 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.
151
+ URL helpers are included in Notifier classes so you have full access to them just like in your controllers and views. Configure `default_url_options` in order for Rails to know what host and port to use when generating URLs.
123
152
 
124
153
  ```ruby
125
154
  Rails.application.routes.default_url_options[:host] = 'localhost:3000'
126
155
  ```
127
156
 
128
- **Callbacks**
129
-
130
- Like ActiveRecord, notifications have several different types of callbacks.
131
-
132
- ```ruby
133
- class CommentNotification < Noticed::Base
134
- deliver_by :database
135
- deliver_by :email, mailer: 'CommentMailer'
136
-
137
- # Callbacks for the entire delivery
138
- before_deliver :whatever
139
- around_deliver :whatever
140
- after_deliver :whatever
141
-
142
- # Callbacks for each delivery method
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
150
- end
151
- ```
152
-
153
- When using `deliver_later` callbacks will be run around queuing the delivery method jobs (not inside the jobs as they actually execute).
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
157
  ##### Translations
158
158
 
159
- We've added `translate` and `t` helpers like Rails has to provide an easy way of scoping translations. If the key starts with a period, it will automatically scope the key under `notifications` and the underscored name of the notification class it is used in.
159
+ `translate` and `t` helpers are available in Notifiers. If the key starts with a period, it will automatically scope the key under `notifiers` and the underscored name of the notification class it is used in.
160
160
 
161
161
  For example:
162
162
 
163
- `t(".message")` looks up `en.notifications.new_comment.message`
164
-
165
- Or when notification class is in module:
166
-
167
- `t(".message") # in Admin::NewComment` looks up `en.notifications.admin.new_comment.message`
163
+ `t(".message")` looks up `en.notifiers.new_comment.message`
164
+ `t(".message") # in Admin::NewComment` looks up `en.notifiers.admin.new_comment.message`
168
165
 
169
166
  ##### User Preferences
170
167
 
@@ -173,30 +170,20 @@ You can use the `if:` and `unless: ` options on your delivery methods to check t
173
170
  For example:
174
171
 
175
172
  ```ruby
176
- class CommentNotification < Noticed::Base
177
- deliver_by :email, mailer: 'CommentMailer', if: :email_notifications?
178
-
179
- def email_notifications?
180
- recipient.email_notifications?
173
+ class CommentNotifier < Noticed::Base
174
+ deliver_by :email do |config|
175
+ config.mailer = 'CommentMailer'
176
+ config.method = :new_comment
177
+ config.if = ->{ recipient.email_notifications? }
181
178
  end
182
179
  end
183
180
  ```
184
181
 
185
- ## 🐞 Debugging
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.
188
-
189
- Example:
190
-
191
- ```ruby
192
- deliver_by :slack, debug: true
193
- ```
194
-
195
182
  ## ✅ Best Practices
196
183
 
197
184
  ### Creating a notification from an Active Record callback
198
185
 
199
- A common use case is to trigger a notification when a record is created. For example,
186
+ Always use `after_commit` hooks to send notifications from ActiveRecord callbacks. For example, to send a notification automatically after a message is created:
200
187
 
201
188
  ```ruby
202
189
  class Message < ApplicationRecord
@@ -207,58 +194,46 @@ class Message < ApplicationRecord
207
194
  private
208
195
 
209
196
  def notify_recipient
210
- NewMessageNotification.with(message: self).deliver_later(recipient)
197
+ NewMessageNotifier.with(message: self).deliver_later(recipient)
211
198
  end
212
199
  ```
213
200
 
214
- If you are creating the notification on a background job (i.e. via `#deliver_later`), make sure you use a `commit` hook such as `after_create_commit` or `after_commit`.
215
-
216
201
  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.
217
202
 
218
203
  A common symptom of this problem is undelivered notifications and the following error in your logs.
219
204
 
220
205
  > `Discarded Noticed::DeliveryMethods::Email due to a ActiveJob::DeserializationError.`
221
206
 
222
- ### Renaming notifications
207
+ ### Renaming Notifiers
223
208
 
224
- If you rename the class of a notification object your existing queries can break. This is because Noticed serializes the class name and sets it to the `type` column on the `Notification` record.
209
+ If you rename the class of a notification object your existing queries can break. This is because ActiveRecord serializes the class name and sets it to the `type` column on the Noticed records.
225
210
 
226
- You can catch these errors at runtime by using `YourNotificationClassName.name` instead of hardcoding the string when performing a query.
211
+ You can catch these errors at runtime by using `YourNotifierClassName.name` instead of hardcoding the string when performing a query.
227
212
 
228
213
  ```ruby
229
- Notification.where(type: YourNotificationClassName.name) # good
230
- Notification.where(type: "YourNotificationClassName") # bad
214
+ Noticed::Event.where(type: YourNotifierClassName.name) # good
215
+ Noticed::Event.where(type: "YourNotifierClassName") # bad
231
216
  ```
232
217
 
233
218
  When renaming a notification class you will need to backfill existing notifications to reference the new name.
234
219
 
235
220
  ```ruby
236
- Notification.where(type: "OldNotificationClassName").update_all(type: NewNotificationClassName.name)
221
+ Noticed::Event.where(type: "OldNotifierClassName").update_all(type: NewNotifierClassName.name)
222
+ Noticed::Notification.where(type: "OldNotifierClassName::Notification").update_all(type: NewNotifierClassName::Notification.name)
237
223
  ```
238
224
 
239
225
  ## 🚛 Delivery Methods
240
226
 
241
- The delivery methods are designed to be modular so you can customize the way each type gets delivered.
227
+ The delivery methods are modular so you can customize the way each type gets delivered.
242
228
 
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 Notification and the delivery method will handle the processing of it.
244
-
245
- * [Database](docs/delivery_methods/database.md)
246
- * [Email](docs/delivery_methods/email.md)
247
- * [ActionCable](docs/delivery_methods/action_cable.md)
248
- * [iOS Apple Push Notifications](docs/delivery_methods/ios.md)
249
- * [Microsoft Teams](docs/delivery_methods/microsoft_teams.md)
250
- * [Slack](docs/delivery_methods/slack.md)
251
- * [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)
229
+ 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.
255
230
 
256
231
  ### Fallback Notifications
257
232
 
258
233
  A common pattern is to deliver a notification via the database and 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 `delay` option, and the conditional `if` / `unless` option.
259
234
 
260
235
  ```ruby
261
- class CommentNotification < Noticed::Base
236
+ class CommentNotifier< Noticed::Base
262
237
  deliver_by :database
263
238
  deliver_by :email, mailer: 'CommentMailer', delay: 15.minutes, unless: :read?
264
239
  end
@@ -269,7 +244,7 @@ Here a notification will be created immediately in the database (for display dir
269
244
  You can also configure multiple fallback options:
270
245
 
271
246
  ```ruby
272
- class CriticalSystemNotification < Noticed::Base
247
+ class CriticalSystemNotifier < Noticed::Base
273
248
  deliver_by :database
274
249
  deliver_by :slack
275
250
  deliver_by :email, mailer: 'CriticalSystemMailer', delay: 10.minutes, if: :unread?
@@ -307,14 +282,14 @@ end
307
282
  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
283
 
309
284
  ```ruby
310
- class MyNotification < Noticed::Base
285
+ class MyNotifier < Noticed::Base
311
286
  deliver_by :discord, class: "DeliveryMethods::Discord"
312
287
  end
313
288
  ```
314
289
 
315
290
  Delivery methods have access to the following methods and attributes:
316
291
 
317
- * `notification` - 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.
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.
318
293
  * `options` - Any configuration options on the `deliver_by` line.
319
294
  * `recipient` - The object who should receive the notification. This is typically a User, Account, or other ActiveRecord model.
320
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 }`
@@ -343,7 +318,7 @@ class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
343
318
  end
344
319
  end
345
320
 
346
- class CommentNotification < Noticed::Base
321
+ class CommentNotifier < Noticed::Base
347
322
  deliver_by :discord, class: 'DeliveryMethods::Discord'
348
323
  end
349
324
  ```
@@ -353,7 +328,7 @@ Now it will raise an error because a required argument is missing.
353
328
  To fix the error, the argument has to be passed correctly. For example:
354
329
 
355
330
  ```ruby
356
- class CommentNotification < Noticed::Base
331
+ class CommentNotifier < Noticed::Base
357
332
  deliver_by :discord, class: 'DeliveryMethods::Discord', username: User.admin.username
358
333
  end
359
334
  ```
@@ -399,10 +374,10 @@ user.notifications.mark_as_unread!
399
374
 
400
375
  #### Instance methods
401
376
 
402
- Convert back into a Noticed notification object:
377
+ Convert back into a Noticed notifier object:
403
378
 
404
379
  ```ruby
405
- @notification.to_notification
380
+ @notification.to_notifier
406
381
  ```
407
382
 
408
383
  Mark notification as read / unread:
@@ -445,11 +420,11 @@ class Post < ApplicationRecord
445
420
  end
446
421
 
447
422
  # Create a CommentNotification with a post param
448
- CommentNotification.with(post: @post).deliver(user)
423
+ CommentNotifier.with(post: @post).deliver(user)
449
424
  # Lookup Notifications where params: {post: @post}
450
425
  @post.notifications_as_post
451
426
 
452
- CommentNotification.with(parent: @post).deliver(user)
427
+ CommentNotifier.with(parent: @post).deliver(user)
453
428
  @post.notifications_as_parent
454
429
  ```
455
430
 
@@ -0,0 +1,9 @@
1
+ module Noticed
2
+ class ApplicationJob < ActiveJob::Base
3
+ # Automatically retry jobs that encountered a deadlock
4
+ # retry_on ActiveRecord::Deadlocked
5
+
6
+ # Most jobs are safe to ignore if the underlying records are no longer available
7
+ discard_on ActiveJob::DeserializationError
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module Noticed
2
+ class EventJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(event)
6
+ # Enqueue bulk deliveries
7
+ event.bulk_delivery_methods.each do |_, deliver_by|
8
+ deliver_by.perform_later(event)
9
+ end
10
+
11
+ # Enqueue individual deliveries
12
+ event.notifications.each do |notification|
13
+ event.delivery_methods.each do |_, deliver_by|
14
+ deliver_by.perform_later(notification)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,115 @@
1
+ module Noticed
2
+ module Deliverable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :bulk_delivery_methods, instance_writer: false, default: {}
7
+ class_attribute :delivery_methods, instance_writer: false, default: {}
8
+ class_attribute :required_param_names, instance_writer: false, default: []
9
+
10
+ attribute :params, default: {}
11
+
12
+ if Rails.gem_version >= Gem::Version.new("7.1.0.alpha")
13
+ serialize :params, coder: Coder
14
+ else
15
+ serialize :params, Coder
16
+ end
17
+ end
18
+
19
+ class_methods do
20
+ def inherited(base)
21
+ base.bulk_delivery_methods = bulk_delivery_methods.dup
22
+ base.delivery_methods = delivery_methods.dup
23
+ base.required_param_names = required_param_names.dup
24
+ super
25
+ end
26
+
27
+ def bulk_deliver_by(name, options = {})
28
+ raise NameError, "#{name} has already been used for this Notifier." if bulk_delivery_methods.has_key?(name)
29
+
30
+ config = ActiveSupport::OrderedOptions.new.merge(options)
31
+ yield config if block_given?
32
+ bulk_delivery_methods[name] = DeliverBy.new(name, config, bulk: true)
33
+ end
34
+
35
+ def deliver_by(name, options = {})
36
+ raise NameError, "#{name} has already been used for this Notifier." if delivery_methods.has_key?(name)
37
+
38
+ config = ActiveSupport::OrderedOptions.new.merge(options)
39
+ yield config if block_given?
40
+ delivery_methods[name] = DeliverBy.new(name, config)
41
+ end
42
+
43
+ def required_params(*names)
44
+ required_param_names.concat names
45
+ end
46
+ alias_method :required_param, :required_params
47
+
48
+ def with(params)
49
+ record = params.delete(:record)
50
+ new(params: params, record: record)
51
+ end
52
+
53
+ def deliver(recipients = nil, options = {})
54
+ new.deliver(recipients, options)
55
+ end
56
+ end
57
+
58
+ # CommentNotifier.deliver(User.all)
59
+ # CommentNotifier.deliver(User.all, priority: 10)
60
+ # CommentNotifier.deliver(User.all, queue: :low_priority)
61
+ # CommentNotifier.deliver(User.all, wait: 5.minutes)
62
+ # CommentNotifier.deliver(User.all, wait_until: 1.hour.from_now)
63
+ def deliver(recipients = nil, options = {})
64
+ validate!
65
+
66
+ transaction do
67
+ save!
68
+
69
+ recipients_attributes = Array.wrap(recipients).map do |recipient|
70
+ recipient_attributes_for(recipient)
71
+ end
72
+
73
+ if Rails.gem_version >= Gem::Version.new("7.0.0.alpha1")
74
+ notifications.insert_all!(recipients_attributes, record_timestamps: true) if recipients_attributes.any?
75
+ else
76
+ time = Time.current
77
+ recipients_attributes.each do |attributes|
78
+ attributes[:created_at] = time
79
+ attributes[:updated_at] = time
80
+ end
81
+ notifications.insert_all!(recipients_attributes) if recipients_attributes.any?
82
+ end
83
+ end
84
+
85
+ # Enqueue delivery job
86
+ EventJob.set(options).perform_later(self)
87
+
88
+ self
89
+ end
90
+
91
+ def recipient_attributes_for(recipient)
92
+ {
93
+ type: "#{self.class.name}::Notification",
94
+ recipient_type: recipient.class.name,
95
+ recipient_id: recipient.id
96
+ }
97
+ end
98
+
99
+ def validate!
100
+ validate_params!
101
+ validate_delivery_methods!
102
+ end
103
+
104
+ def validate_params!
105
+ required_param_names.each do |param_name|
106
+ raise ValidationError, "Param `#{param_name}` is required for #{self.class.name}." unless params[param_name].present?
107
+ end
108
+ end
109
+
110
+ def validate_delivery_methods!
111
+ bulk_delivery_methods.values.each(&:validate!)
112
+ delivery_methods.values.each(&:validate!)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,17 @@
1
+ module Noticed
2
+ module NotificationMethods
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ # Generate a Notification class each time a Notifier is defined
7
+ def inherited(notifier)
8
+ super
9
+ notifier.const_set :Notification, Class.new(Noticed::Notification)
10
+ end
11
+
12
+ def notification_methods(&block)
13
+ const_get(:Notification).class_eval(&block)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,62 @@
1
+ module Noticed
2
+ module Readable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ scope :read, -> { where.not(read_at: nil) }
7
+ scope :unread, -> { where(read_at: nil) }
8
+ scope :seen, -> { where.not(seen_at: nil) }
9
+ scope :unseen, -> { where(seen_at: nil) }
10
+ end
11
+
12
+ class_methods do
13
+ def mark_as_read
14
+ update_all(read_at: Time.current)
15
+ end
16
+
17
+ def mark_as_unread
18
+ update_all(read_at: nil)
19
+ end
20
+
21
+ def mark_as_seen
22
+ update_all(seen_at: Time.current)
23
+ end
24
+
25
+ def mark_as_unseen
26
+ update_all(seen_at: nil)
27
+ end
28
+ end
29
+
30
+ def mark_as_read
31
+ update(read_at: Time.current)
32
+ end
33
+
34
+ def mark_as_unread
35
+ update(read_at: nil)
36
+ end
37
+
38
+ def mark_as_seen
39
+ update(seen_at: Time.current)
40
+ end
41
+
42
+ def mark_as_unseen
43
+ update(seen_at: nil)
44
+ end
45
+
46
+ def read?
47
+ read_at?
48
+ end
49
+
50
+ def unread?
51
+ !read_at?
52
+ end
53
+
54
+ def seen?
55
+ seen_at?
56
+ end
57
+
58
+ def unseen?
59
+ !seen_at?
60
+ end
61
+ end
62
+ end