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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e60a18149fe412035d57b7a8866bc47551d51429fe8ceba299b8320da5a03a5f
4
- data.tar.gz: 489cdf6fac9316e3dc18529f0e79899a133d5e20de5a08e6752c99aafe6ac7c7
3
+ metadata.gz: 534fd7403e6eb6ad98672132d5c2b3ebf6de0eff7ada3a40ad9acc2bea2b7f2b
4
+ data.tar.gz: 18973d1ff94fee48cc3daadd7a1e5df9bc104153dcd6ee10d9b5cfe98df832be
5
5
  SHA512:
6
- metadata.gz: f68d1913f07d23f591d581f88538ec4c45ca4a8f0ed60a31c48eee6fba9d4b11dd490281fe4092e78150e75a6f9b29a25a5a3b8c9616afb6d45a7cd8306a52b5
7
- data.tar.gz: 0517dbddf042732465661203139391f2444732d451f4262f11bd2d6415ba78d38d713e21eb3bb5d05ee2314cf31eeafedd5c26fd5c9a3d6cd66d520e9ed6cf7e
6
+ metadata.gz: 67cc5fe29f00612c81b64f70fd0b72d264d7f9d3486b14d0892a6482ac1ca75f06303d2bfd8aebfa349d145a269295b06e9530105ca2578d85d148ac5665eefb
7
+ data.tar.gz: b66fa2922599a8edb40bdddf3a9a2a05debdfde299877140909fcb339895a0c94b2fa30c2a9d693026a4c040fcbd0c2cb1fdacaefcb328c845783694b04c4100
data/README.md CHANGED
@@ -1,28 +1,352 @@
1
- # Noticed
2
- Short description and motivation.
1
+ <p align="center">
2
+ <h1>Noticed</h1>
3
+ </p>
3
4
 
4
- ## Usage
5
- How to use my plugin.
5
+ ### 🎉 Notifications for your Ruby on Rails app.
6
6
 
7
- ## Installation
8
- Add this line to your application's Gemfile:
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
- gem 'noticed'
23
+ bundle add "noticed"
12
24
  ```
13
25
 
14
- And then execute:
15
- ```bash
16
- $ bundle
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
- Or install it yourself as:
20
- ```bash
21
- $ gem install noticed
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
- ## Contributing
25
- Contribution directions go here.
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 'bundler/setup'
2
+ require "bundler/setup"
3
3
  rescue LoadError
4
- puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
4
+ puts "You must `gem install bundler` and `bundle install` to run rake tasks"
5
5
  end
6
6
 
7
- require 'rdoc/task'
7
+ require "rdoc/task"
8
8
 
9
9
  RDoc::Task.new(:rdoc) do |rdoc|
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')
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 'bundler/gem_tasks'
17
+ require "bundler/gem_tasks"
18
18
 
19
- require 'rake/testtask'
19
+ require "rake/testtask"
20
20
 
21
21
  Rake::TestTask.new(:test) do |t|
22
- t.libs << 'test'
23
- t.pattern = 'test/**/*_test.rb'
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,7 @@
1
+
2
+ 🚚 Your notifications database model has been generated!
3
+
4
+ Next steps:
5
+ 1. Run "rails db:migrate"
6
+ 2. Add "has_many :notifications, as: :recipient" to your User model(s).
7
+ 3. Generate notifications with "rails g noticed:notification"
@@ -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
@@ -1,5 +1,35 @@
1
- require "noticed/railtie"
1
+ require "http"
2
+ require "noticed/engine"
2
3
 
3
4
  module Noticed
4
- # Your code goes here...
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
@@ -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,11 @@
1
+ module Noticed
2
+ class Coder
3
+ def self.load(data)
4
+ ActiveJob::Arguments.send(:deserialize_argument, data)
5
+ end
6
+
7
+ def self.dump(data)
8
+ ActiveJob::Arguments.send(:serialize_argument, data)
9
+ end
10
+ end
11
+ 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,4 @@
1
+ module Noticed
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ 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
@@ -1,3 +1,3 @@
1
1
  module Noticed
2
- VERSION = '0.1.0'
2
+ VERSION = "1.2.2"
3
3
  end
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: 0.1.0
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-07-27 00:00:00.000000000 Z
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: sqlite3
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/railtie.rb
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
@@ -1,4 +0,0 @@
1
- module Noticed
2
- class Railtie < ::Rails::Railtie
3
- end
4
- end