noticed 1.2.6 → 1.2.11

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: 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