noticed 1.2.6 → 1.2.11

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: ac839e0194ed6ffcb49cdded96384b5e47aeed9dbd407e92b7e18d67d700efcb
4
- data.tar.gz: 4f8bea6c1e9b7b7c1d841a4110bce06a6db56aaa36db358d6ed129294268e6a8
3
+ metadata.gz: f215fc7642db8db6a38ba3354c4d58ad1621c6d6af5c4ee479a519fdab1fae8d
4
+ data.tar.gz: 874dc986fa682f54eaee86f0e8e638049cb8d4f310a2e4d906e76ba11b5d7405
5
5
  SHA512:
6
- metadata.gz: 5d5b49fa5aabd5e94ad370938f157e0ee55e28727b1f1da221924c2d5de40e629f29f909e9f77caa2908dad316777e6f7a66be1c6c290f5f013d3e9407781b28
7
- data.tar.gz: 5db5ca0a303953ea1b82d6bf2d1ab6948e4bcbde8b6cb56c73e1a286732239cdeeb7d77b677d40ed77fa6d801de1069c3882b2006616f99f288f97c2e0d1a958
6
+ metadata.gz: '039b85fe5977ecf3d3353ba275f0913f03a325a3affc913a5edc05f07e5555b38deff9fd23b492d16fbdaa58c0c5e3cf281267288c3ff69697d07e0ff840ded8'
7
+ data.tar.gz: 7a49698a38596e35382fffc86d12857414f9f8ecdbc68f2c66fd24e124e6b893f62f442e736d004c5fad0403aa55eecc30a749843c6a61b5f0fc45d1b9da84c2
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
 
@@ -401,6 +401,60 @@ Check if read / unread:
401
401
  @notification.unread?
402
402
  ```
403
403
 
404
+ #### Associating Notifications
405
+
406
+ Adding notification associations to your models makes querying and deleting notifications easy and is a pretty critical feature of most applications.
407
+
408
+ For example, in most cases, you'll want to delete notifications for records that are destroyed.
409
+
410
+ ##### JSON Columns
411
+
412
+ 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.
413
+
414
+ For example, we can query the notifications and delete them on destroy like so:
415
+
416
+ ```ruby
417
+ class Post < ApplicationRecord
418
+ def notifications
419
+ # Exact match
420
+ @notifications ||= Notification.where(params: { post: self })
421
+
422
+ # Or Postgres syntax to query the post key in the JSON column
423
+ # @notifications ||= Notification.where("params->'post' = ?", Noticed::Coder.dump(self).to_json)
424
+ end
425
+
426
+ before_destroy :destroy_notifications
427
+
428
+ def destroy_notifications
429
+ notifications.destroy_all
430
+ end
431
+ end
432
+ ```
433
+
434
+ ##### Polymorphic Assocation
435
+
436
+ 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.
437
+
438
+ 1. Add a polymorphic association to the Notification model. `rails g migration AddNotifiableToNotifications notifiable:belongs_to{polymorphic}`
439
+
440
+ 2. Add `has_many :notifications, as: :notifiable, dependent: :destroy` to each model
441
+
442
+ 3. Customize database `format: ` option to write the `notifiable` attribute(s) when saving the notification
443
+
444
+ ```ruby
445
+ class ExampleNotification < Noticed::Base
446
+ deliver_by :database, format: :format_for_database
447
+
448
+ def format_for_database
449
+ {
450
+ notifiable: params.delete(:post),
451
+ type: self.class.name,
452
+ params: params
453
+ }
454
+ end
455
+ end
456
+ ```
457
+
404
458
  ## 🙏 Contributing
405
459
 
406
460
  This project uses [Standard](https://github.com/testdouble/standard) for formatting Ruby code. Please make sure to run `standardrb` before submitting pull requests.
data/Rakefile CHANGED
@@ -18,6 +18,10 @@ require "bundler/gem_tasks"
18
18
 
19
19
  require "rake/testtask"
20
20
 
21
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
22
+ load "rails/tasks/engine.rake"
23
+ load "rails/tasks/statistics.rake"
24
+
21
25
  Rake::TestTask.new(:test) do |t|
22
26
  t.libs << "test"
23
27
  t.pattern = "test/**/*_test.rb"
@@ -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
@@ -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
@@ -29,9 +29,10 @@ module Noticed
29
29
  new(params)
30
30
  end
31
31
 
32
- def param(name)
33
- param_names.push(name)
32
+ def params(*names)
33
+ param_names.concat Array.wrap(names)
34
34
  end
35
+ alias param params
35
36
  end
36
37
 
37
38
  def initialize(params = {})
@@ -68,6 +69,9 @@ module Noticed
68
69
  def run_delivery(recipient, enqueue: true)
69
70
  delivery_methods = self.class.delivery_methods.dup
70
71
 
72
+ # Set recipient to instance var so it is available to Notification class
73
+ @recipient = recipient
74
+
71
75
  # Run database delivery inline first if it exists so other methods have access to the record
72
76
  if (index = delivery_methods.find_index { |m| m[:name] == :database })
73
77
  delivery_method = delivery_methods.delete_at(index)
@@ -2,12 +2,12 @@ module Noticed
2
2
  class Coder
3
3
  def self.load(data)
4
4
  return if data.nil?
5
- ActiveJob::Arguments.send(:deserialize_argument, JSON.parse(data))
5
+ ActiveJob::Arguments.send(:deserialize_argument, data)
6
6
  end
7
7
 
8
8
  def self.dump(data)
9
9
  return if data.nil?
10
- ActiveJob::Arguments.send(:serialize_argument, data).to_json
10
+ ActiveJob::Arguments.send(:serialize_argument, data)
11
11
  end
12
12
  end
13
13
  end
@@ -16,11 +16,19 @@ module Noticed
16
16
  end
17
17
 
18
18
  def channel
19
- if (method = options[:channel])
20
- notification.send(method)
21
- else
22
- Noticed::NotificationChannel
23
- end
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
30
+ end
31
+ end
24
32
  end
25
33
  end
26
34
  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
- "notifications.#{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.6"
2
+ VERSION = "1.2.11"
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.6
4
+ version: 1.2.11
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-06 00:00:00.000000000 Z
11
+ date: 2020-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -109,6 +109,7 @@ files:
109
109
  - lib/noticed/delivery_methods/vonage.rb
110
110
  - lib/noticed/engine.rb
111
111
  - lib/noticed/model.rb
112
+ - lib/noticed/text_coder.rb
112
113
  - lib/noticed/translation.rb
113
114
  - lib/noticed/version.rb
114
115
  - lib/tasks/noticed_tasks.rake