noticed 0.1.0 → 1.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +340 -16
- data/Rakefile +12 -12
- data/app/channels/noticed/notification_channel.rb +15 -0
- data/lib/generators/noticed/model_generator.rb +36 -0
- data/lib/generators/noticed/notification_generator.rb +19 -0
- data/lib/generators/noticed/templates/README +7 -0
- data/lib/generators/noticed/templates/notification.rb.tt +27 -0
- data/lib/noticed.rb +32 -2
- data/lib/noticed/base.rb +118 -0
- data/lib/noticed/coder.rb +11 -0
- data/lib/noticed/delivery_methods/action_cable.rb +27 -0
- data/lib/noticed/delivery_methods/base.rb +27 -0
- data/lib/noticed/delivery_methods/database.rb +27 -0
- data/lib/noticed/delivery_methods/email.rb +19 -0
- data/lib/noticed/delivery_methods/slack.rb +27 -0
- data/lib/noticed/delivery_methods/test.rb +21 -0
- data/lib/noticed/delivery_methods/twilio.rb +51 -0
- data/lib/noticed/delivery_methods/vonage.rb +34 -0
- data/lib/noticed/engine.rb +4 -0
- data/lib/noticed/model.rb +42 -0
- data/lib/noticed/translation.rb +21 -0
- data/lib/noticed/version.rb +1 -1
- metadata +63 -4
- data/lib/noticed/railtie.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 534fd7403e6eb6ad98672132d5c2b3ebf6de0eff7ada3a40ad9acc2bea2b7f2b
|
4
|
+
data.tar.gz: 18973d1ff94fee48cc3daadd7a1e5df9bc104153dcd6ee10d9b5cfe98df832be
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 67cc5fe29f00612c81b64f70fd0b72d264d7f9d3486b14d0892a6482ac1ca75f06303d2bfd8aebfa349d145a269295b06e9530105ca2578d85d148ac5665eefb
|
7
|
+
data.tar.gz: b66fa2922599a8edb40bdddf3a9a2a05debdfde299877140909fcb339895a0c94b2fa30c2a9d693026a4c040fcbd0c2cb1fdacaefcb328c845783694b04c4100
|
data/README.md
CHANGED
@@ -1,28 +1,352 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
<p align="center">
|
2
|
+
<h1>Noticed</h1>
|
3
|
+
</p>
|
3
4
|
|
4
|
-
|
5
|
-
How to use my plugin.
|
5
|
+
### 🎉 Notifications for your Ruby on Rails app.
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
[![Build Status](https://github.com/excid3/noticed/workflows/Tests/badge.svg)](https://github.com/excid3/noticed/actions)
|
8
|
+
|
9
|
+
Currently, we support these notification delivery methods out of the box:
|
10
|
+
|
11
|
+
* Database
|
12
|
+
* Email
|
13
|
+
* ActionCable channels
|
14
|
+
* Twilio (SMS)
|
15
|
+
* Vonage / Nexmo (SMS)
|
16
|
+
|
17
|
+
And you can easily add new notification types for any other delivery methods.
|
18
|
+
|
19
|
+
## 🚀 Installation
|
20
|
+
Run the following command to add Noticed to your Gemfile
|
9
21
|
|
10
22
|
```ruby
|
11
|
-
|
23
|
+
bundle add "noticed"
|
12
24
|
```
|
13
25
|
|
14
|
-
|
15
|
-
|
16
|
-
|
26
|
+
To save notifications to your database, use the following command to generate a Notification model.
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
rails generate noticed:model
|
30
|
+
```
|
31
|
+
|
32
|
+
This will generate a Notification model and instructions for associating User models with the notifications table.
|
33
|
+
|
34
|
+
## 📝 Usage
|
35
|
+
|
36
|
+
To generate a notification object, simply run:
|
37
|
+
|
38
|
+
`rails generate noticed:notification CommentNotification`
|
39
|
+
|
40
|
+
#### Notification Objects
|
41
|
+
|
42
|
+
Notifications inherit from `Noticed::Base`. This provides all their functionality and allows them to be delivered.
|
43
|
+
|
44
|
+
To add delivery methods, simply `include` the module for the delivery methods you would like to use.
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
class CommentNotification < Noticed::Base
|
48
|
+
deliver_by :database
|
49
|
+
deliver_by :action_cable
|
50
|
+
deliver_by :email, if: :email_notifications?
|
51
|
+
|
52
|
+
def email_notifications?
|
53
|
+
!!recipient.preferences[:email]
|
54
|
+
end
|
55
|
+
|
56
|
+
# I18n helpers
|
57
|
+
def message
|
58
|
+
t(".message")
|
59
|
+
end
|
60
|
+
|
61
|
+
# URL helpers are accessible in notifications
|
62
|
+
def url
|
63
|
+
post_path(params[:post])
|
64
|
+
end
|
65
|
+
|
66
|
+
after_deliver do
|
67
|
+
# Anything you want
|
68
|
+
end
|
69
|
+
end
|
17
70
|
```
|
18
71
|
|
19
|
-
|
20
|
-
|
21
|
-
|
72
|
+
#### Sending Notifications
|
73
|
+
|
74
|
+
To send a notification to a user:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
notification = CommentNotification.with(comment: @comment.to_gid)
|
78
|
+
|
79
|
+
# Deliver notification in background job
|
80
|
+
notification.deliver_later(@comment.post.author)
|
81
|
+
|
82
|
+
# Deliver notification immediately
|
83
|
+
notification.deliver(@comment.post.author)
|
84
|
+
|
85
|
+
# Deliver notification to multiple recipients
|
86
|
+
notification.deliver_later(User.all)
|
22
87
|
```
|
23
88
|
|
24
|
-
|
25
|
-
|
89
|
+
This will instantiate a new notification with the `comment` stored in the notification's params.
|
90
|
+
|
91
|
+
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.
|
92
|
+
|
93
|
+
**Shared Options**
|
94
|
+
|
95
|
+
* `if: :method_name` - Calls `method_name`and cancels delivery method if `false` is returned
|
96
|
+
* `unless: :method_name` - Calls `method_name`and cancels delivery method if `true` is returned
|
97
|
+
|
98
|
+
##### Helper Methods
|
99
|
+
|
100
|
+
You can define helper methods inside your Notification object to make it easier to render.
|
101
|
+
|
102
|
+
##### URL Helpers
|
103
|
+
|
104
|
+
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.
|
105
|
+
|
106
|
+
**Callbacks**
|
107
|
+
|
108
|
+
Like ActiveRecord, notifications have several different types of callbacks.
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
class CommentNotification < Noticed::Base
|
112
|
+
deliver_by :database
|
113
|
+
deliver_by :email
|
114
|
+
|
115
|
+
# Callbacks for the entire delivery
|
116
|
+
before_deliver :whatever
|
117
|
+
around_deliver :whatever
|
118
|
+
after_deliver :whatever
|
119
|
+
|
120
|
+
# Callbacks for each delivery method
|
121
|
+
before_database :whatever
|
122
|
+
around_database :whatever
|
123
|
+
after_database :whatever
|
124
|
+
|
125
|
+
before_email :whatever
|
126
|
+
around_email :whatever
|
127
|
+
after_email :whatever
|
128
|
+
end
|
129
|
+
```
|
130
|
+
|
131
|
+
When using `deliver_later` callbacks will be run around queuing the delivery method jobs (not inside the jobs as they actually execute).
|
132
|
+
|
133
|
+
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.
|
134
|
+
|
135
|
+
##### Translations
|
136
|
+
|
137
|
+
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.
|
138
|
+
|
139
|
+
For example:
|
140
|
+
|
141
|
+
`t(".message")` looks up `en.notifications.new_comment.message`
|
142
|
+
|
143
|
+
##### User Preferences
|
144
|
+
|
145
|
+
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.
|
146
|
+
|
147
|
+
For example:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class CommentNotification < Noticed::Base
|
151
|
+
deliver_by :email, if: :email_notifications?
|
152
|
+
|
153
|
+
def email_notifications?
|
154
|
+
recipient.email_notifications?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
## 🚛 Delivery Methods
|
160
|
+
|
161
|
+
The delivery methods are designed to be modular so you can customize the way each type gets delivered.
|
162
|
+
|
163
|
+
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.
|
164
|
+
|
165
|
+
### Database
|
166
|
+
|
167
|
+
Writes notification to the database.
|
168
|
+
|
169
|
+
`deliver_by :database`
|
170
|
+
|
171
|
+
**Note:** Database notifications are special in that they will run before the other delivery methods. We do this so you can reference the database record ID in other delivery methods.
|
172
|
+
|
173
|
+
##### Options
|
174
|
+
|
175
|
+
* `association` - *Optional*
|
176
|
+
|
177
|
+
The name of the database association to use. Defaults to `:notifications`
|
178
|
+
|
179
|
+
### Email
|
180
|
+
|
181
|
+
Sends an email notification. Emails will always be sent with `deliver_later`
|
182
|
+
|
183
|
+
`deliver_by :email, mailer: "UserMailer"`
|
184
|
+
|
185
|
+
##### Options
|
186
|
+
|
187
|
+
* `mailer` - **Required**
|
188
|
+
|
189
|
+
The mailer that should send the email
|
190
|
+
|
191
|
+
* `method: :invoice_paid` - *Optional*
|
192
|
+
|
193
|
+
Used to customize the method on the mailer that is called
|
194
|
+
|
195
|
+
### ActionCable
|
196
|
+
|
197
|
+
Sends a notification to the browser via websockets (ActionCable channel by default).
|
198
|
+
|
199
|
+
`deliver_by :action_cable`
|
200
|
+
|
201
|
+
##### Options
|
202
|
+
|
203
|
+
* `format: :format_for_action_cable` - *Optional*
|
204
|
+
|
205
|
+
Use a custom method to define the Hash sent through ActionCable
|
206
|
+
|
207
|
+
* `channel` - *Optional*
|
208
|
+
|
209
|
+
Override the ActionCable channel used to send notifications.
|
210
|
+
|
211
|
+
Defaults to `Noticed::NotificationChannel`
|
212
|
+
|
213
|
+
### Slack
|
214
|
+
|
215
|
+
Sends a Slack notification via webhook.
|
216
|
+
|
217
|
+
`deliver_by :slack`
|
218
|
+
|
219
|
+
##### Options
|
220
|
+
|
221
|
+
* `format: :format_for_slack` - *Optional*
|
222
|
+
|
223
|
+
Use a custom method to define the payload sent to Slack. Method should return a Hash.
|
224
|
+
|
225
|
+
* `url: :url_for_slack` - *Optional*
|
226
|
+
|
227
|
+
Use a custom method to retrieve the Slack Webhook URL. Method should return a String.
|
228
|
+
|
229
|
+
Defaults to `Rails.application.credentials.slack[:notification_url]`
|
230
|
+
|
231
|
+
### Twilio SMS
|
232
|
+
|
233
|
+
Sends an SMS notification via Twilio.
|
234
|
+
|
235
|
+
`deliver_by :twilio`
|
236
|
+
|
237
|
+
##### Options
|
238
|
+
|
239
|
+
* `credentials: :get_twilio_credentials` - *Optional*
|
240
|
+
|
241
|
+
Use a custom method to retrieve the credentials for Twilio. Method should return a Hash with `:account_sid`, `:auth_token` and `:phone_ number` keys.
|
242
|
+
|
243
|
+
Defaults to `Rails.application.credentials.twilio[:account_sid]` and `Rails.application.credentials.twilio[:auth_token]`
|
244
|
+
|
245
|
+
* `url: :get_twilio_url` - *Optional*
|
246
|
+
|
247
|
+
Use a custom method to retrieve the Twilio URL. Method should return the Twilio API url as a string.
|
248
|
+
|
249
|
+
Defaults to `"https://api.twilio.com/2010-04-01/Accounts/#{twilio_credentials(recipient)[:account_sid]}/Messages.json"`
|
250
|
+
|
251
|
+
* `format: :format_for_twilio` - *Optional*
|
252
|
+
|
253
|
+
Use a custom method to define the payload sent to Twilio. Method should return a Hash.
|
254
|
+
|
255
|
+
Defaults to:
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
{
|
259
|
+
Body: notification.params[:message],
|
260
|
+
From: twilio_credentials[:number],
|
261
|
+
To: recipient.phone_number
|
262
|
+
}
|
263
|
+
```
|
264
|
+
|
265
|
+
### Vonage SMS
|
266
|
+
|
267
|
+
Sends an SMS notification vai Vonage / Nexmo.
|
268
|
+
|
269
|
+
`deliver_by :vonage`
|
270
|
+
|
271
|
+
##### Options
|
272
|
+
|
273
|
+
* `credentials: :get_credentials` - *Optional*
|
274
|
+
|
275
|
+
Use a custom method for retrieving credentials. Method should return a Hash with `:api_key` and `:api_secret` keys.
|
276
|
+
|
277
|
+
Defaults to `Rails.application.credentials.vonage[:api_key]` and `Rails.application.credentials.vonage[:api_secret]`
|
278
|
+
|
279
|
+
* `deliver_by :vonage, format: :format_for_vonage` - *Optional*
|
280
|
+
|
281
|
+
Use a custom method to generate the params sent to Vonage. Method should return a Hash. Defaults to:
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
{
|
285
|
+
api_key: vonage_credentials[:api_key],
|
286
|
+
api_secret: vonage_credentials[:api_secret],
|
287
|
+
from: notification.params[:from],
|
288
|
+
text: notification.params[:body],
|
289
|
+
to: notification.params[:to],
|
290
|
+
type: "unicode"
|
291
|
+
}
|
292
|
+
```
|
293
|
+
|
294
|
+
### 🚚 Custom Delivery Methods
|
295
|
+
|
296
|
+
You can define a custom delivery method easily by adding a `deliver_by` line with a unique name and class option. The class will be instantiated and should inherit from `Noticed::DeliveryMethods::Base`.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
class MyNotification < Noticed::Base
|
300
|
+
deliver_by :discord, class: "DiscordNotification"
|
301
|
+
end
|
302
|
+
```
|
303
|
+
|
304
|
+
```ruby
|
305
|
+
class DiscordNotification < Noticed::DeliveryMethods::Base
|
306
|
+
def deliver
|
307
|
+
# Logic for sending a Discord notification
|
308
|
+
end
|
309
|
+
end
|
310
|
+
```
|
311
|
+
|
312
|
+
Delivery methods have access to the following methods and attributes:
|
313
|
+
|
314
|
+
* `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.
|
315
|
+
* `options` - Any configuration options on the `deliver_by` line.
|
316
|
+
* `recipient` - The object who should receive the notification. This is typically a User, Account, or other ActiveRecord model.
|
317
|
+
* `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 }`
|
318
|
+
|
319
|
+
#### Callbacks
|
320
|
+
|
321
|
+
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.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
class DiscordNotification < Noticed::DeliveryMethods::Base
|
325
|
+
after_deliver do
|
326
|
+
# Do whatever you want
|
327
|
+
end
|
328
|
+
end
|
329
|
+
```
|
330
|
+
|
331
|
+
#### Limitations
|
332
|
+
|
333
|
+
Rails 6.1+ can serialize Class and Module objects as arguments to ActiveJob. The following syntax should work for Rails 6.1+:
|
334
|
+
|
335
|
+
```ruby
|
336
|
+
deliver_by DiscordNotification
|
337
|
+
```
|
338
|
+
|
339
|
+
For Rails 6.0 and earlier, you must pass strings of the class names in the `deliver_by` options.
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
deliver_by :discord, class: "DiscordNotification"
|
343
|
+
```
|
344
|
+
|
345
|
+
We recommend the Rails 6.0 compatible options to prevent confusion.
|
346
|
+
|
347
|
+
## 🙏 Contributing
|
348
|
+
|
349
|
+
This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `standardrb` before submitting pull requests.
|
26
350
|
|
27
|
-
## License
|
351
|
+
## 📝 License
|
28
352
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -1,26 +1,26 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "bundler/setup"
|
3
3
|
rescue LoadError
|
4
|
-
puts
|
4
|
+
puts "You must `gem install bundler` and `bundle install` to run rake tasks"
|
5
5
|
end
|
6
6
|
|
7
|
-
require
|
7
|
+
require "rdoc/task"
|
8
8
|
|
9
9
|
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
-
rdoc.rdoc_dir =
|
11
|
-
rdoc.title
|
12
|
-
rdoc.options <<
|
13
|
-
rdoc.rdoc_files.include(
|
14
|
-
rdoc.rdoc_files.include(
|
10
|
+
rdoc.rdoc_dir = "rdoc"
|
11
|
+
rdoc.title = "Noticed"
|
12
|
+
rdoc.options << "--line-numbers"
|
13
|
+
rdoc.rdoc_files.include("README.md")
|
14
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
15
15
|
end
|
16
16
|
|
17
|
-
require
|
17
|
+
require "bundler/gem_tasks"
|
18
18
|
|
19
|
-
require
|
19
|
+
require "rake/testtask"
|
20
20
|
|
21
21
|
Rake::TestTask.new(:test) do |t|
|
22
|
-
t.libs <<
|
23
|
-
t.pattern =
|
22
|
+
t.libs << "test"
|
23
|
+
t.pattern = "test/**/*_test.rb"
|
24
24
|
t.verbose = false
|
25
25
|
end
|
26
26
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Noticed
|
2
|
+
class NotificationChannel < ApplicationCable::Channel
|
3
|
+
def subscribed
|
4
|
+
stream_for current_user
|
5
|
+
end
|
6
|
+
|
7
|
+
def unsubscribed
|
8
|
+
stop_all_streams
|
9
|
+
end
|
10
|
+
|
11
|
+
def mark_as_read(data)
|
12
|
+
current_user.notifications.where(id: data["ids"]).mark_as_read!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Noticed
|
6
|
+
module Generators
|
7
|
+
class ModelGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
desc "Generates a Notification model for storing notifications."
|
13
|
+
|
14
|
+
argument :name, type: :string, default: "Notification", banner: "Notification"
|
15
|
+
argument :attributes, type: :array, default: [], banner: "field:type field:type"
|
16
|
+
|
17
|
+
def generate_notification
|
18
|
+
generate :model, name, "recipient:references{polymorphic}", "type", "params:text", "read_at:datetime", *attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_noticed_model
|
22
|
+
inject_into_class model_path, class_name, " include Noticed::Model\n"
|
23
|
+
end
|
24
|
+
|
25
|
+
def done
|
26
|
+
readme "README" if behavior == :invoke
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def model_path
|
32
|
+
@model_path ||= File.join("app", "models", "#{file_path}.rb")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/named_base"
|
4
|
+
|
5
|
+
module Noticed
|
6
|
+
module Generators
|
7
|
+
class NotificationGenerator < Rails::Generators::NamedBase
|
8
|
+
include Rails::Generators::ResourceHelpers
|
9
|
+
|
10
|
+
source_root File.expand_path("../templates", __FILE__)
|
11
|
+
|
12
|
+
desc "Generates a notification with the given NAME."
|
13
|
+
|
14
|
+
def generate_notification
|
15
|
+
template "notification.rb", "app/notifications/#{singular_name}.rb"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# To deliver this notification:
|
2
|
+
#
|
3
|
+
# <%= class_name %>.with(post: @post).deliver_later(current_user)
|
4
|
+
# <%= class_name %>.with(post: @post).deliver(current_user)
|
5
|
+
|
6
|
+
class <%= class_name %> < Noticed::Base
|
7
|
+
# Add your delivery methods
|
8
|
+
#
|
9
|
+
# deliver_by :database
|
10
|
+
# deliver_by :email, mailer: "UserMailer"
|
11
|
+
# deliver_by :slack
|
12
|
+
# deliver_by :custom, class: "MyDeliveryMethod"
|
13
|
+
|
14
|
+
# Add required params
|
15
|
+
#
|
16
|
+
# param :post
|
17
|
+
|
18
|
+
# Define helper methods to make rendering easier.
|
19
|
+
#
|
20
|
+
# def message
|
21
|
+
# t(".message")
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# def url
|
25
|
+
# posts_path(params[:post])
|
26
|
+
# end
|
27
|
+
end
|
data/lib/noticed.rb
CHANGED
@@ -1,5 +1,35 @@
|
|
1
|
-
require "
|
1
|
+
require "http"
|
2
|
+
require "noticed/engine"
|
2
3
|
|
3
4
|
module Noticed
|
4
|
-
|
5
|
+
autoload :Base, "noticed/base"
|
6
|
+
autoload :Coder, "noticed/coder"
|
7
|
+
autoload :Model, "noticed/model"
|
8
|
+
autoload :Translation, "noticed/translation"
|
9
|
+
|
10
|
+
module DeliveryMethods
|
11
|
+
autoload :Base, "noticed/delivery_methods/base"
|
12
|
+
autoload :ActionCable, "noticed/delivery_methods/action_cable"
|
13
|
+
autoload :Database, "noticed/delivery_methods/database"
|
14
|
+
autoload :Email, "noticed/delivery_methods/email"
|
15
|
+
autoload :Slack, "noticed/delivery_methods/slack"
|
16
|
+
autoload :Test, "noticed/delivery_methods/test"
|
17
|
+
autoload :Twilio, "noticed/delivery_methods/twilio"
|
18
|
+
autoload :Vonage, "noticed/delivery_methods/vonage"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.notify(recipients:, notification:)
|
22
|
+
recipients.each do |recipient|
|
23
|
+
notification.notify(recipient)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Clear the recipient after sending to the group
|
27
|
+
notification.recipient = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
mattr_accessor :parent_class
|
31
|
+
@@parent_class = "ApplicationJob"
|
32
|
+
|
33
|
+
class ValidationError < StandardError
|
34
|
+
end
|
5
35
|
end
|
data/lib/noticed/base.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
module Noticed
|
2
|
+
class Base
|
3
|
+
include Translation
|
4
|
+
include Rails.application.routes.url_helpers
|
5
|
+
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
define_model_callbacks :deliver
|
8
|
+
|
9
|
+
class_attribute :delivery_methods, instance_writer: false, default: []
|
10
|
+
class_attribute :param_names, instance_writer: false, default: []
|
11
|
+
|
12
|
+
attr_accessor :record
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def deliver_by(name, options = {})
|
16
|
+
delivery_methods.push(name: name, options: options)
|
17
|
+
define_model_callbacks(name)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Copy delivery methods from parent
|
21
|
+
def inherited(base) #:nodoc:
|
22
|
+
base.delivery_methods = delivery_methods.dup
|
23
|
+
base.param_names = param_names.dup
|
24
|
+
super
|
25
|
+
end
|
26
|
+
|
27
|
+
def with(params)
|
28
|
+
new(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
def param(name)
|
32
|
+
param_names.push(name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(params = {})
|
37
|
+
@params = params
|
38
|
+
end
|
39
|
+
|
40
|
+
def deliver(recipients)
|
41
|
+
validate!
|
42
|
+
|
43
|
+
run_callbacks :deliver do
|
44
|
+
Array.wrap(recipients).uniq.each do |recipient|
|
45
|
+
run_delivery(recipient, enqueue: false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def deliver_later(recipients)
|
51
|
+
validate!
|
52
|
+
|
53
|
+
run_callbacks :deliver do
|
54
|
+
Array.wrap(recipients).uniq.each do |recipient|
|
55
|
+
run_delivery(recipient, enqueue: true)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def params
|
61
|
+
@params || {}
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# Runs all delivery methods for a notification
|
67
|
+
def run_delivery(recipient, enqueue: true)
|
68
|
+
delivery_methods = self.class.delivery_methods.dup
|
69
|
+
|
70
|
+
# Run database delivery inline first if it exists so other methods have access to the record
|
71
|
+
if (index = delivery_methods.find_index { |m| m[:name] == :database })
|
72
|
+
delivery_method = delivery_methods.delete_at(index)
|
73
|
+
@record = run_delivery_method(delivery_method, recipient: recipient, enqueue: false)
|
74
|
+
end
|
75
|
+
|
76
|
+
delivery_methods.each do |delivery_method|
|
77
|
+
run_delivery_method(delivery_method, recipient: recipient, enqueue: enqueue)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Actually runs an individual delivery
|
82
|
+
def run_delivery_method(delivery_method, recipient:, enqueue:)
|
83
|
+
return if (delivery_method_name = delivery_method.dig(:options, :if)) && !send(delivery_method_name)
|
84
|
+
return if (delivery_method_name = delivery_method.dig(:options, :unless)) && send(delivery_method_name)
|
85
|
+
|
86
|
+
args = {
|
87
|
+
notification_class: self.class.name,
|
88
|
+
options: delivery_method[:options],
|
89
|
+
params: params,
|
90
|
+
recipient: recipient,
|
91
|
+
record: record
|
92
|
+
}
|
93
|
+
|
94
|
+
run_callbacks delivery_method[:name] do
|
95
|
+
klass = get_class(delivery_method[:name], delivery_method[:options])
|
96
|
+
enqueue ? klass.perform_later(args) : klass.perform_now(args)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Retrieves the correct class for a delivery method
|
101
|
+
def get_class(name, options)
|
102
|
+
if options[:class]
|
103
|
+
options[:class].constantize
|
104
|
+
else
|
105
|
+
"Noticed::DeliveryMethods::#{name.to_s.classify}".constantize
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Validates that all params are present
|
110
|
+
def validate!
|
111
|
+
self.class.param_names.each do |param_name|
|
112
|
+
if params[param_name].nil?
|
113
|
+
raise ValidationError, "#{param_name} is missing."
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class ActionCable < Base
|
4
|
+
def deliver
|
5
|
+
channel.broadcast_to recipient, format
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def format
|
11
|
+
if (method = options[:format])
|
12
|
+
notification.send(method)
|
13
|
+
else
|
14
|
+
notification.params
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def channel
|
19
|
+
if (method = options[:channel])
|
20
|
+
notification.send(method)
|
21
|
+
else
|
22
|
+
Noticed::NotificationChannel
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Base < Noticed.parent_class.constantize
|
4
|
+
extend ActiveModel::Callbacks
|
5
|
+
define_model_callbacks :deliver
|
6
|
+
|
7
|
+
attr_reader :notification, :options, :recipient
|
8
|
+
|
9
|
+
def perform(notification_class:, options:, params:, recipient:, record:)
|
10
|
+
@notification = notification_class.constantize.new(params)
|
11
|
+
@options = options
|
12
|
+
@recipient = recipient
|
13
|
+
|
14
|
+
# Keep track of the database record for rendering
|
15
|
+
@notification.record = record
|
16
|
+
|
17
|
+
run_callbacks :deliver do
|
18
|
+
deliver
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def deliver
|
23
|
+
raise NotImplementedError, "Delivery methods must implement a deliver method"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Database < Base
|
4
|
+
# Must return the database record
|
5
|
+
def deliver
|
6
|
+
recipient.send(association_name).create!(attributes)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def association_name
|
12
|
+
options[:association] || :notifications
|
13
|
+
end
|
14
|
+
|
15
|
+
def attributes
|
16
|
+
if (method = options[:format])
|
17
|
+
notification.send(method)
|
18
|
+
else
|
19
|
+
{
|
20
|
+
type: notification.class.name,
|
21
|
+
params: notification.params
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Email < Base
|
4
|
+
def deliver
|
5
|
+
mailer.with(notification.params).send(method.to_sym).deliver_later
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def mailer
|
11
|
+
options.fetch(:mailer).constantize
|
12
|
+
end
|
13
|
+
|
14
|
+
def method
|
15
|
+
options[:method] || notification.class.name.underscore
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Slack < Base
|
4
|
+
def deliver
|
5
|
+
HTTP.post(url, json: format)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def format
|
11
|
+
if (method = options[:format])
|
12
|
+
notification.send(method)
|
13
|
+
else
|
14
|
+
params
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def url
|
19
|
+
if (method = options[:url])
|
20
|
+
notification.send(method)
|
21
|
+
else
|
22
|
+
Rails.application.credentials.slack[:notification_url]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Test < Base
|
4
|
+
class_attribute :delivered, default: []
|
5
|
+
class_attribute :callbacks, default: []
|
6
|
+
|
7
|
+
after_deliver do
|
8
|
+
self.class.callbacks << :after
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.clear!
|
12
|
+
delivered.clear
|
13
|
+
callbacks.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def deliver
|
17
|
+
self.class.delivered << notification
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Twilio < Base
|
4
|
+
def deliver
|
5
|
+
HTTP.basic_auth(user: account_sid, pass: auth_token).post(url, json: format)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def format
|
11
|
+
if (method = options[:format])
|
12
|
+
notification.send(method)
|
13
|
+
else
|
14
|
+
{
|
15
|
+
From: phone_number,
|
16
|
+
To: recipient.phone_number,
|
17
|
+
Body: notification.params[:message]
|
18
|
+
}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def url
|
23
|
+
if (method = options[:url])
|
24
|
+
notification.send(method)
|
25
|
+
else
|
26
|
+
"https://api.twilio.com/2010-04-01/Accounts/#{account_sid}/Messages.json"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def account_sid
|
31
|
+
credentials.fetch(:account_sid)
|
32
|
+
end
|
33
|
+
|
34
|
+
def auth_token
|
35
|
+
credentials.fetch(:auth_token)
|
36
|
+
end
|
37
|
+
|
38
|
+
def phone_number
|
39
|
+
credentials.fetch(:phone_number)
|
40
|
+
end
|
41
|
+
|
42
|
+
def credentials
|
43
|
+
if (method = options[:credentials])
|
44
|
+
notification.send(method)
|
45
|
+
else
|
46
|
+
Rails.application.credentials.twilio
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Noticed
|
2
|
+
module DeliveryMethods
|
3
|
+
class Vonage < Base
|
4
|
+
def deliver
|
5
|
+
HTTP.post("https://rest.nexmo.com/sms/json", json: format)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def format
|
11
|
+
if (method = options[:format])
|
12
|
+
notification.send(method)
|
13
|
+
else
|
14
|
+
{
|
15
|
+
api_key: credentials[:api_key],
|
16
|
+
api_secret: credentials[:api_secret],
|
17
|
+
from: notification.params[:from],
|
18
|
+
text: notification.params[:body],
|
19
|
+
to: notification.params[:to],
|
20
|
+
type: "unicode"
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def credentials
|
26
|
+
if (method = options[:credentials])
|
27
|
+
notification.send(method)
|
28
|
+
else
|
29
|
+
Rails.application.credentials.vonage
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Noticed
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
self.inheritance_column = nil
|
7
|
+
|
8
|
+
serialize :params, Noticed::Coder
|
9
|
+
|
10
|
+
belongs_to :recipient, polymorphic: true
|
11
|
+
|
12
|
+
scope :newest_first, -> { order(created_at: :desc) }
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
def mark_as_read!
|
17
|
+
update_all(read_at: Time.current, updated_at: Time.current)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Rehydrate the database notification into the Notification object for rendering
|
22
|
+
def to_notification
|
23
|
+
@_notification ||= begin
|
24
|
+
instance = type.constantize.with(params)
|
25
|
+
instance.record = self
|
26
|
+
instance
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def mark_as_read!
|
31
|
+
update(read_at: Time.current)
|
32
|
+
end
|
33
|
+
|
34
|
+
def unread?
|
35
|
+
!read?
|
36
|
+
end
|
37
|
+
|
38
|
+
def read?
|
39
|
+
read_at?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Translation
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
|
5
|
+
def i18n_scope
|
6
|
+
:notifications
|
7
|
+
end
|
8
|
+
|
9
|
+
def translate(key, **options)
|
10
|
+
I18n.translate(scope_translation_key(key), **options)
|
11
|
+
end
|
12
|
+
alias t translate
|
13
|
+
|
14
|
+
def scope_translation_key(key)
|
15
|
+
if key.to_s.start_with?(".")
|
16
|
+
"notifications.#{self.class.name.underscore}#{key}"
|
17
|
+
else
|
18
|
+
key
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/noticed/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: noticed
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Oliver
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -25,7 +25,49 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 6.0.0
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: http
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.0.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 4.0.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: pg
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: standard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: mocha
|
29
71
|
requirement: !ruby/object:Gem::Requirement
|
30
72
|
requirements:
|
31
73
|
- - ">="
|
@@ -49,8 +91,25 @@ files:
|
|
49
91
|
- MIT-LICENSE
|
50
92
|
- README.md
|
51
93
|
- Rakefile
|
94
|
+
- app/channels/noticed/notification_channel.rb
|
95
|
+
- lib/generators/noticed/model_generator.rb
|
96
|
+
- lib/generators/noticed/notification_generator.rb
|
97
|
+
- lib/generators/noticed/templates/README
|
98
|
+
- lib/generators/noticed/templates/notification.rb.tt
|
52
99
|
- lib/noticed.rb
|
53
|
-
- lib/noticed/
|
100
|
+
- lib/noticed/base.rb
|
101
|
+
- lib/noticed/coder.rb
|
102
|
+
- lib/noticed/delivery_methods/action_cable.rb
|
103
|
+
- lib/noticed/delivery_methods/base.rb
|
104
|
+
- lib/noticed/delivery_methods/database.rb
|
105
|
+
- lib/noticed/delivery_methods/email.rb
|
106
|
+
- lib/noticed/delivery_methods/slack.rb
|
107
|
+
- lib/noticed/delivery_methods/test.rb
|
108
|
+
- lib/noticed/delivery_methods/twilio.rb
|
109
|
+
- lib/noticed/delivery_methods/vonage.rb
|
110
|
+
- lib/noticed/engine.rb
|
111
|
+
- lib/noticed/model.rb
|
112
|
+
- lib/noticed/translation.rb
|
54
113
|
- lib/noticed/version.rb
|
55
114
|
- lib/tasks/noticed_tasks.rake
|
56
115
|
homepage: https://github.com/excid3/noticed
|
data/lib/noticed/railtie.rb
DELETED