noticed 1.2.7 → 1.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcd5afdca1ab2862bca9259b3015f86d93ab323eb64b6f8de666f9067216b202
4
- data.tar.gz: 8c6a53d533eb2667c44804b4a7a5e518b4ab7938b345c6105490145bb4fdb75a
3
+ metadata.gz: 6fca882449d4696c1551badc82383f789610f19898dec345a64f33a225ca9f81
4
+ data.tar.gz: 8751afe5063970449573719d7bedb957d7c88e52f1efef96357d8754d48cec21
5
5
  SHA512:
6
- metadata.gz: 21ea1e549dcbd0857fea6538a0fc2d17ba5f0d249b7f39d1e1d33a56cbece35a3413605d8c1a6e71a381a424b6db93c82bfe7de19d87e3e669c569926fa4a42b
7
- data.tar.gz: 1e85eb2edfbbac61fa9bcacf91a4fe041adc945c54ad029a6e8ab75e3d4fc64d52fbcf8527e43da9d0fd3c30e267c2c1e13bafbdf10662f6f826758a1a4f3f41
6
+ metadata.gz: 8ab4f29e4611ee0e5c3f548562e0265dc991ca45cb4789fbf7eb780430b21eddaef3407fbc7946f21bbff411149dd8e796197d9d0b334d85d8085aaea7beb54a
7
+ data.tar.gz: 7c6ce9b53092a182e405fc26274daae30e8ec6e2149c5f5794d3fa5c3728210f013f632c844dd00f9ad5f1d64cba12b09de6c311b9d367b580d91f899affb4c4
data/README.md CHANGED
@@ -256,7 +256,7 @@ Sends an SMS notification via Twilio.
256
256
 
257
257
  * `credentials: :get_twilio_credentials` - *Optional*
258
258
 
259
- Use a custom method to retrieve the credentials for Twilio. Method should return a Hash with `:account_sid`, `:auth_token` and `:phone_ number` keys.
259
+ Use a custom method to retrieve the credentials for Twilio. Method should return a Hash with `:account_sid`, `:auth_token` and `:phone_number` keys.
260
260
 
261
261
  Defaults to `Rails.application.credentials.twilio[:account_sid]` and `Rails.application.credentials.twilio[:auth_token]`
262
262
 
@@ -311,22 +311,28 @@ Sends an SMS notification via Vonage / Nexmo.
311
311
 
312
312
  ### 🚚 Custom Delivery Methods
313
313
 
314
- 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`.
314
+ To generate a custom delivery method, simply run
315
315
 
316
- ```ruby
317
- class MyNotification < Noticed::Base
318
- deliver_by :discord, class: "DiscordNotification"
319
- end
320
- ```
316
+ `rails generate noticed:delivery_method Discord`
317
+
318
+ This will generate a new `DeliveryMethods::Discord` class inside the `app/notifications/delivery_methods` folder, which can be used to deliver notifications to Discord.
321
319
 
322
320
  ```ruby
323
- class DiscordNotification < Noticed::DeliveryMethods::Base
321
+ class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
324
322
  def deliver
325
323
  # Logic for sending a Discord notification
326
324
  end
327
325
  end
328
326
  ```
329
327
 
328
+ 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.
329
+
330
+ ```ruby
331
+ class MyNotification < Noticed::Base
332
+ deliver_by :discord, class: "DeliveryMethods::Discord"
333
+ end
334
+ ```
335
+
330
336
  Delivery methods have access to the following methods and attributes:
331
337
 
332
338
  * `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.
@@ -339,7 +345,7 @@ Delivery methods have access to the following methods and attributes:
339
345
  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.
340
346
 
341
347
  ```ruby
342
- class DiscordNotification < Noticed::DeliveryMethods::Base
348
+ class DeliveryMethods::Discord < Noticed::DeliveryMethods::Base
343
349
  after_deliver do
344
350
  # Do whatever you want
345
351
  end
@@ -351,13 +357,13 @@ end
351
357
  Rails 6.1+ can serialize Class and Module objects as arguments to ActiveJob. The following syntax should work for Rails 6.1+:
352
358
 
353
359
  ```ruby
354
- deliver_by DiscordNotification
360
+ deliver_by DeliveryMethods::Discord
355
361
  ```
356
362
 
357
363
  For Rails 6.0 and earlier, you must pass strings of the class names in the `deliver_by` options.
358
364
 
359
365
  ```ruby
360
- deliver_by :discord, class: "DiscordNotification"
366
+ deliver_by :discord, class: "DeliveryMethods::Discord"
361
367
  ```
362
368
 
363
369
  We recommend the Rails 6.0 compatible options to prevent confusion.
@@ -401,6 +407,60 @@ Check if read / unread:
401
407
  @notification.unread?
402
408
  ```
403
409
 
410
+ #### Associating Notifications
411
+
412
+ Adding notification associations to your models makes querying and deleting notifications easy and is a pretty critical feature of most applications.
413
+
414
+ For example, in most cases, you'll want to delete notifications for records that are destroyed.
415
+
416
+ ##### JSON Columns
417
+
418
+ If you're using MySQL or Postgresql, the `params` column on the notifications table is in `json` or `jsonb` format and can be queried against directly.
419
+
420
+ For example, we can query the notifications and delete them on destroy like so:
421
+
422
+ ```ruby
423
+ class Post < ApplicationRecord
424
+ def notifications
425
+ # Exact match
426
+ @notifications ||= Notification.where(params: { post: self })
427
+
428
+ # Or Postgres syntax to query the post key in the JSON column
429
+ # @notifications ||= Notification.where("params->'post' = ?", Noticed::Coder.dump(self).to_json)
430
+ end
431
+
432
+ before_destroy :destroy_notifications
433
+
434
+ def destroy_notifications
435
+ notifications.destroy_all
436
+ end
437
+ end
438
+ ```
439
+
440
+ ##### Polymorphic Assocation
441
+
442
+ If your notification is only associated with one model or you're using a `text` column for your params column , then a polymorphic association is what you'll want to use.
443
+
444
+ 1. Add a polymorphic association to the Notification model. `rails g migration AddNotifiableToNotifications notifiable:belongs_to{polymorphic}`
445
+
446
+ 2. Add `has_many :notifications, as: :notifiable, dependent: :destroy` to each model
447
+
448
+ 3. Customize database `format: ` option to write the `notifiable` attribute(s) when saving the notification
449
+
450
+ ```ruby
451
+ class ExampleNotification < Noticed::Base
452
+ deliver_by :database, format: :format_for_database
453
+
454
+ def format_for_database
455
+ {
456
+ notifiable: params.delete(:post),
457
+ type: self.class.name,
458
+ params: params
459
+ }
460
+ end
461
+ end
462
+ ```
463
+
404
464
  ## 🙏 Contributing
405
465
 
406
466
  This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `standardrb` before submitting pull requests.
@@ -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 DeliveryMethodGenerator < Rails::Generators::NamedBase
8
+ include Rails::Generators::ResourceHelpers
9
+
10
+ source_root File.expand_path("../templates", __FILE__)
11
+
12
+ desc "Generates a class for a custom delivery method with the given NAME."
13
+
14
+ def generate_notification
15
+ template "delivery_method.rb", "app/notifications/delivery_methods/#{singular_name}.rb"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -15,7 +15,7 @@ module Noticed
15
15
  argument :attributes, type: :array, default: [], banner: "field:type field:type"
16
16
 
17
17
  def generate_notification
18
- generate :model, name, "recipient:references{polymorphic}", "type", "params:text", "read_at:datetime", *attributes
18
+ generate :model, name, "recipient:references{polymorphic}", "type", params_column, "read_at:datetime", *attributes
19
19
  end
20
20
 
21
21
  def add_noticed_model
@@ -31,6 +31,17 @@ module Noticed
31
31
  def model_path
32
32
  @model_path ||= File.join("app", "models", "#{file_path}.rb")
33
33
  end
34
+
35
+ def params_column
36
+ case ActiveRecord::Base.configurations.configs_for(spec_name: "primary").config["adapter"]
37
+ when "mysql"
38
+ "params:json"
39
+ when "postgresql"
40
+ "params:jsonb"
41
+ else
42
+ "params:text"
43
+ end
44
+ end
34
45
  end
35
46
  end
36
47
  end
@@ -12,7 +12,7 @@ module Noticed
12
12
  desc "Generates a notification with the given NAME."
13
13
 
14
14
  def generate_notification
15
- template "notification.rb", "app/notifications/#{singular_name}.rb"
15
+ template "notification.rb", "app/notifications/#{file_path}.rb"
16
16
  end
17
17
  end
18
18
  end
@@ -0,0 +1,5 @@
1
+ class DeliveryMethods::<%= class_name %> < Noticed::DeliveryMethods::Base
2
+ def deliver
3
+ # Logic for sending the notification
4
+ end
5
+ end
@@ -6,6 +6,7 @@ module Noticed
6
6
  autoload :Base, "noticed/base"
7
7
  autoload :Coder, "noticed/coder"
8
8
  autoload :Model, "noticed/model"
9
+ autoload :TextCoder, "noticed/text_coder"
9
10
  autoload :Translation, "noticed/translation"
10
11
 
11
12
  module DeliveryMethods
@@ -33,4 +34,12 @@ module Noticed
33
34
 
34
35
  class ValidationError < StandardError
35
36
  end
37
+
38
+ class ResponseUnsuccessful < StandardError
39
+ attr_reader :response
40
+
41
+ def initialize(response)
42
+ @response = response
43
+ end
44
+ end
36
45
  end
@@ -69,6 +69,9 @@ module Noticed
69
69
  def run_delivery(recipient, enqueue: true)
70
70
  delivery_methods = self.class.delivery_methods.dup
71
71
 
72
+ # Set recipient to instance var so it is available to Notification class
73
+ @recipient = recipient
74
+
72
75
  # Run database delivery inline first if it exists so other methods have access to the record
73
76
  if (index = delivery_methods.find_index { |m| m[:name] == :database })
74
77
  delivery_method = delivery_methods.delete_at(index)
@@ -2,18 +2,12 @@ module Noticed
2
2
  class Coder
3
3
  def self.load(data)
4
4
  return if data.nil?
5
-
6
- # Text columns need JSON parsing
7
- if data.is_a?(String)
8
- data = JSON.parse(data)
9
- end
10
-
11
5
  ActiveJob::Arguments.send(:deserialize_argument, data)
12
6
  end
13
7
 
14
8
  def self.dump(data)
15
9
  return if data.nil?
16
- ActiveJob::Arguments.send(:serialize_argument, data).to_json
10
+ ActiveJob::Arguments.send(:serialize_argument, data)
17
11
  end
18
12
  end
19
13
  end
@@ -17,16 +17,16 @@ module Noticed
17
17
 
18
18
  def channel
19
19
  @channel ||= begin
20
- value = options[:channel]
21
- case value
22
- when String
23
- value.constantize
24
- when Symbol
25
- notification.send(value)
26
- when Class
27
- value
28
- else
29
- Noticed::NotificationChannel
20
+ value = options[:channel]
21
+ case value
22
+ when String
23
+ value.constantize
24
+ when Symbol
25
+ notification.send(value)
26
+ when Class
27
+ value
28
+ else
29
+ Noticed::NotificationChannel
30
30
  end
31
31
  end
32
32
  end
@@ -6,15 +6,15 @@ module Noticed
6
6
 
7
7
  attr_reader :notification, :options, :recipient, :record
8
8
 
9
- def perform(notification_class:, options:, params:, recipient:, record:)
10
- @notification = notification_class.constantize.new(params)
11
- @options = options
12
- @recipient = recipient
13
- @record = record
9
+ def perform(args)
10
+ @notification = args[:notification_class].constantize.new(args[:params])
11
+ @options = args[:options]
12
+ @recipient = args[:recipient]
13
+ @record = args[:record]
14
14
 
15
15
  # Make notification aware of database record and recipient during delivery
16
- @notification.record = record
17
- @notification.recipient = recipient
16
+ @notification.record = args[:record]
17
+ @notification.recipient = args[:recipient]
18
18
 
19
19
  run_callbacks :deliver do
20
20
  deliver
@@ -24,6 +24,36 @@ module Noticed
24
24
  def deliver
25
25
  raise NotImplementedError, "Delivery methods must implement a deliver method"
26
26
  end
27
+
28
+ private
29
+
30
+ # Helper method for making POST requests from delivery methods
31
+ #
32
+ # Usage:
33
+ # post("http://example.com", basic_auth: {user:, pass:}, json: {}, form: {})
34
+ #
35
+ def post(url, args = {})
36
+ basic_auth = args.delete(:basic_auth)
37
+
38
+ request = if basic_auth
39
+ HTTP.basic_auth(user: basic_auth[:user], pass: basic_auth[:pass])
40
+ else
41
+ HTTP
42
+ end
43
+
44
+ response = request.post(url, args)
45
+
46
+ if options[:debug]
47
+ Rails.logger.debug("POST #{url}")
48
+ Rails.logger.debug("Response: #{response.code}: #{response}")
49
+ end
50
+
51
+ if !options[:ignore_failure] && !response.status.success?
52
+ raise ResponseUnsuccessful.new(response)
53
+ end
54
+
55
+ response
56
+ end
27
57
  end
28
58
  end
29
59
  end
@@ -2,7 +2,7 @@ module Noticed
2
2
  module DeliveryMethods
3
3
  class Slack < Base
4
4
  def deliver
5
- HTTP.post(url, json: format)
5
+ post(url, json: format)
6
6
  end
7
7
 
8
8
  private
@@ -2,7 +2,7 @@ module Noticed
2
2
  module DeliveryMethods
3
3
  class Twilio < Base
4
4
  def deliver
5
- HTTP.basic_auth(user: account_sid, pass: auth_token).post(url, json: format)
5
+ post(url, basic_auth: {user: account_sid, pass: auth_token}, form: format)
6
6
  end
7
7
 
8
8
  private
@@ -2,7 +2,11 @@ module Noticed
2
2
  module DeliveryMethods
3
3
  class Vonage < Base
4
4
  def deliver
5
- HTTP.post("https://rest.nexmo.com/sms/json", json: format)
5
+ response = post("https://rest.nexmo.com/sms/json", json: format)
6
+ status = response.parse.dig("messages", 0, "status")
7
+ if !options[:ignore_failure] && status != "0"
8
+ raise ResponseUnsuccessful.new(response)
9
+ end
6
10
  end
7
11
 
8
12
  private
@@ -5,17 +5,28 @@ module Noticed
5
5
  included do
6
6
  self.inheritance_column = nil
7
7
 
8
- serialize :params, Noticed::Coder
8
+ serialize :params, noticed_coder
9
9
 
10
10
  belongs_to :recipient, polymorphic: true
11
11
 
12
12
  scope :newest_first, -> { order(created_at: :desc) }
13
+ scope :unread, -> { where(read_at: nil) }
14
+ scope :read, -> { where.not(read_at: nil) }
13
15
  end
14
16
 
15
17
  module ClassMethods
16
18
  def mark_as_read!
17
19
  update_all(read_at: Time.current, updated_at: Time.current)
18
20
  end
21
+
22
+ def noticed_coder
23
+ case attribute_types["params"].type
24
+ when :json, :jsonb
25
+ Noticed::Coder
26
+ else
27
+ Noticed::TextCoder
28
+ end
29
+ end
19
30
  end
20
31
 
21
32
  # Rehydrate the database notification into the Notification object for rendering
@@ -0,0 +1,16 @@
1
+ module Noticed
2
+ class TextCoder
3
+ def self.load(data)
4
+ return if data.nil?
5
+
6
+ # Text columns need JSON parsing
7
+ data = JSON.parse(data)
8
+ ActiveJob::Arguments.send(:deserialize_argument, data)
9
+ end
10
+
11
+ def self.dump(data)
12
+ return if data.nil?
13
+ ActiveJob::Arguments.send(:serialize_argument, data).to_json
14
+ end
15
+ end
16
+ end
@@ -1,21 +1,23 @@
1
- module Translation
2
- extend ActiveSupport::Concern
1
+ module Noticed
2
+ module Translation
3
+ extend ActiveSupport::Concern
3
4
 
4
- # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
5
- def i18n_scope
6
- :notifications
7
- end
5
+ # Returns the +i18n_scope+ for the class. Overwrite if you want custom lookup.
6
+ def i18n_scope
7
+ :notifications
8
+ end
8
9
 
9
- def translate(key, **options)
10
- I18n.translate(scope_translation_key(key), **options)
11
- end
12
- alias t translate
10
+ def translate(key, **options)
11
+ I18n.translate(scope_translation_key(key), **options)
12
+ end
13
+ alias t translate
13
14
 
14
- def scope_translation_key(key)
15
- if key.to_s.start_with?(".")
16
- "#{i18n_scope}.#{self.class.name.underscore}#{key}"
17
- else
18
- key
15
+ def scope_translation_key(key)
16
+ if key.to_s.start_with?(".")
17
+ "#{i18n_scope}.#{self.class.name.underscore}#{key}"
18
+ else
19
+ key
20
+ end
19
21
  end
20
22
  end
21
23
  end
@@ -1,3 +1,3 @@
1
1
  module Noticed
2
- VERSION = "1.2.7"
2
+ VERSION = "1.2.12"
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: 1.2.7
4
+ version: 1.2.12
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-08-07 00:00:00.000000000 Z
11
+ date: 2020-08-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -92,9 +92,11 @@ files:
92
92
  - README.md
93
93
  - Rakefile
94
94
  - app/channels/noticed/notification_channel.rb
95
+ - lib/generators/noticed/delivery_method_generator.rb
95
96
  - lib/generators/noticed/model_generator.rb
96
97
  - lib/generators/noticed/notification_generator.rb
97
98
  - lib/generators/noticed/templates/README
99
+ - lib/generators/noticed/templates/delivery_method.rb.tt
98
100
  - lib/generators/noticed/templates/notification.rb.tt
99
101
  - lib/noticed.rb
100
102
  - lib/noticed/base.rb
@@ -109,6 +111,7 @@ files:
109
111
  - lib/noticed/delivery_methods/vonage.rb
110
112
  - lib/noticed/engine.rb
111
113
  - lib/noticed/model.rb
114
+ - lib/noticed/text_coder.rb
112
115
  - lib/noticed/translation.rb
113
116
  - lib/noticed/version.rb
114
117
  - lib/tasks/noticed_tasks.rake