activity_notification 1.1.0 → 1.2.0

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.rubocop.yml +1 -1
  4. data/CHANGELOG.md +23 -11
  5. data/Gemfile.lock +52 -40
  6. data/README.md +177 -14
  7. data/activity_notification.gemspec +3 -1
  8. data/app/controllers/activity_notification/subscriptions_controller.rb +48 -2
  9. data/app/views/activity_notification/optional_targets/default/base/_default.text.erb +10 -0
  10. data/app/views/activity_notification/optional_targets/default/slack/_default.text.erb +6 -0
  11. data/app/views/activity_notification/subscriptions/default/_notification_keys.html.erb +19 -0
  12. data/app/views/activity_notification/subscriptions/default/_subscription.html.erb +32 -3
  13. data/app/views/activity_notification/subscriptions/default/index.html.erb +4 -0
  14. data/app/views/activity_notification/subscriptions/default/show.html.erb +5 -0
  15. data/app/views/activity_notification/subscriptions/default/subscribe_to_optional_target.js.erb +6 -0
  16. data/app/views/activity_notification/subscriptions/default/unsubscribe_to_optional_target.js.erb +6 -0
  17. data/gemfiles/Gemfile.rails-4.2.lock +15 -3
  18. data/gemfiles/Gemfile.rails-5.0.lock +47 -35
  19. data/lib/activity_notification.rb +1 -0
  20. data/lib/activity_notification/apis/notification_api.rb +83 -26
  21. data/lib/activity_notification/apis/subscription_api.rb +93 -8
  22. data/lib/activity_notification/common.rb +6 -2
  23. data/lib/activity_notification/helpers/view_helpers.rb +43 -0
  24. data/lib/activity_notification/models/concerns/notifiable.rb +73 -28
  25. data/lib/activity_notification/models/concerns/subscriber.rb +34 -7
  26. data/lib/activity_notification/models/concerns/target.rb +25 -13
  27. data/lib/activity_notification/models/subscription.rb +13 -2
  28. data/lib/activity_notification/optional_targets/amazon_sns.rb +42 -0
  29. data/lib/activity_notification/optional_targets/base.rb +79 -0
  30. data/lib/activity_notification/optional_targets/slack.rb +33 -0
  31. data/lib/activity_notification/rails/routes.rb +30 -20
  32. data/lib/activity_notification/roles/acts_as_notifiable.rb +70 -11
  33. data/lib/activity_notification/version.rb +1 -1
  34. data/lib/generators/activity_notification/views_generator.rb +2 -2
  35. data/lib/generators/templates/migrations/migration.rb +2 -2
  36. data/spec/concerns/apis/notification_api_spec.rb +97 -0
  37. data/spec/concerns/apis/subscription_api_spec.rb +206 -41
  38. data/spec/concerns/common_spec.rb +7 -2
  39. data/spec/concerns/models/notifiable_spec.rb +88 -2
  40. data/spec/concerns/models/subscriber_spec.rb +114 -13
  41. data/spec/concerns/models/target_spec.rb +17 -0
  42. data/spec/controllers/subscriptions_controller_shared_examples.rb +251 -28
  43. data/spec/helpers/view_helpers_spec.rb +56 -0
  44. data/spec/optional_targets/amazon_sns_spec.rb +46 -0
  45. data/spec/optional_targets/base_spec.rb +43 -0
  46. data/spec/optional_targets/slack_spec.rb +46 -0
  47. data/spec/rails_app/app/controllers/comments_controller.rb +1 -0
  48. data/spec/rails_app/app/models/admin.rb +1 -2
  49. data/spec/rails_app/app/models/article.rb +2 -3
  50. data/spec/rails_app/app/models/comment.rb +19 -7
  51. data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +8 -0
  52. data/spec/rails_app/db/migrate/20160715050420_create_activity_notification_tables.rb +1 -1
  53. data/spec/rails_app/db/migrate/20160715050433_create_test_tables.rb +2 -0
  54. data/spec/rails_app/db/schema.rb +3 -1
  55. data/spec/rails_app/db/seeds.rb +1 -1
  56. data/spec/rails_app/lib/custom_optional_targets/console_output.rb +13 -0
  57. data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +10 -0
  58. data/spec/roles/acts_as_notifiable_spec.rb +124 -2
  59. metadata +49 -4
  60. data/spec/rails_app/app/models/notification.rb +0 -6
@@ -50,12 +50,25 @@ module ActivityNotification
50
50
  subscription_params[:subscribing_to_email] = subscription_params[:subscribing]
51
51
  end
52
52
  subscription = subscriptions.new(subscription_params)
53
- subscription.subscribing ?
53
+ subscription.subscribing? ?
54
54
  subscription.assign_attributes(subscribing: true, subscribed_at: created_at) :
55
55
  subscription.assign_attributes(subscribing: false, unsubscribed_at: created_at)
56
- subscription.subscribing_to_email ?
56
+ subscription.subscribing_to_email? ?
57
57
  subscription.assign_attributes(subscribing_to_email: true, subscribed_to_email_at: created_at) :
58
58
  subscription.assign_attributes(subscribing_to_email: false, unsubscribed_to_email_at: created_at)
59
+ optional_targets = subscription.optional_targets
60
+ subscription.optional_target_names.each do |optional_target_name|
61
+ optional_targets = subscription.subscribing_to_optional_target?(optional_target_name) ?
62
+ optional_targets.merge(
63
+ Subscription.to_optional_target_key(optional_target_name) => true,
64
+ Subscription.to_optional_target_subscribed_at_key(optional_target_name) => created_at
65
+ ) :
66
+ optional_targets.merge(
67
+ Subscription.to_optional_target_key(optional_target_name) => false,
68
+ Subscription.to_optional_target_unsubscribed_at_key(optional_target_name) => created_at
69
+ )
70
+ end
71
+ subscription.assign_attributes(optional_targets: optional_targets)
59
72
  subscription.save ? subscription : nil
60
73
  end
61
74
 
@@ -118,7 +131,7 @@ module ActivityNotification
118
131
  # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
119
132
  # @return [Boolean] If the target subscribes to the notification
120
133
  def _subscribes_to_notification?(key, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
121
- evaluate_subscription(subscriptions.find_by_key(key), :subscribing, subscribe_as_default)
134
+ evaluate_subscription(subscriptions.find_by_key(key), :subscribing?, subscribe_as_default)
122
135
  end
123
136
 
124
137
  # Returns if the target subscribes to the notification email.
@@ -129,20 +142,34 @@ module ActivityNotification
129
142
  # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
130
143
  # @return [Boolean] If the target subscribes to the notification
131
144
  def _subscribes_to_notification_email?(key, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
132
- evaluate_subscription(subscriptions.find_by_key(key), :subscribing_to_email, subscribe_as_default)
145
+ evaluate_subscription(subscriptions.find_by_key(key), :subscribing_to_email?, subscribe_as_default)
133
146
  end
134
147
  alias_method :_subscribes_to_email?, :_subscribes_to_notification_email?
135
148
 
149
+ # Returns if the target subscribes to the specified optional target.
150
+ # This method can be overriden.
151
+ # @api protected
152
+ #
153
+ # @param [String] key Key of the notification
154
+ # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack)
155
+ # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
156
+ # @return [Boolean] If the target subscribes to the specified optional target
157
+ def _subscribes_to_optional_target?(key, optional_target_name, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
158
+ _subscribes_to_notification?(key, subscribe_as_default) &&
159
+ evaluate_subscription(subscriptions.find_by_key(key), :subscribing_to_optional_target?, subscribe_as_default, optional_target_name, subscribe_as_default)
160
+ end
161
+
136
162
  private
137
163
 
138
164
  # Returns if the target subscribes.
139
165
  # @api private
140
166
  # @param [Boolean] record Subscription record
141
- # @param [Symbol] field Evaluating subscription field of the record
167
+ # @param [Symbol] field Evaluating subscription field or method of the record
142
168
  # @param [Boolean] default Default subscription value to use when the subscription record does not configured
169
+ # @param [Array] args Arguments of evaluating subscription method
143
170
  # @return [Boolean] If the target subscribes
144
- def evaluate_subscription(record, field, default)
145
- default ? record.blank? || record.send(field) : record.present? && record.send(field)
171
+ def evaluate_subscription(record, field, default, *args)
172
+ default ? record.blank? || record.send(field, *args) : record.present? && record.send(field)
146
173
  end
147
174
 
148
175
  end
@@ -302,13 +302,15 @@ module ActivityNotification
302
302
  #
303
303
  # @param [Object] notifiable Notifiable instance to notify
304
304
  # @param [Hash] options Options for notifications
305
- # @option options [String] :key (notifiable.default_notification_key) Key of the notification
306
- # @option options [Object] :group (nil) Group unit of the notifications
307
- # @option options [ActiveSupport::Duration] :group_expiry_delay (nil) Expiry period of a notification group
308
- # @option options [Object] :notifier (nil) Notifier of the notifications
309
- # @option options [Hash] :parameters ({}) Additional parameters of the notifications
310
- # @option options [Boolean] :send_email (true) Whether it sends notification email
311
- # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
305
+ # @option options [String] :key (notifiable.default_notification_key) Key of the notification
306
+ # @option options [Object] :group (nil) Group unit of the notifications
307
+ # @option options [ActiveSupport::Duration] :group_expiry_delay (nil) Expiry period of a notification group
308
+ # @option options [Object] :notifier (nil) Notifier of the notifications
309
+ # @option options [Hash] :parameters ({}) Additional parameters of the notifications
310
+ # @option options [Boolean] :send_email (true) Whether it sends notification email
311
+ # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
312
+ # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
313
+ # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
312
314
  # @return [Notification] Generated notification instance
313
315
  def notify_to(notifiable, options = {})
314
316
  Notification.notify_to(self, notifiable, options)
@@ -433,24 +435,34 @@ module ActivityNotification
433
435
  # Returns if the target subscribes to the notification.
434
436
  # It also returns true when the subscription management is not allowed for the target.
435
437
  #
436
- # @param [String] key Key of the notification
437
- # @param [String] subscribe_as_default Default subscription value to use when the subscription record does not configured
438
+ # @param [String] key Key of the notification
439
+ # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
438
440
  # @return [Boolean] If the target subscribes the notification or the subscription management is not allowed for the target
439
441
  def subscribes_to_notification?(key, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
440
- !subscription_allowed?(key) or _subscribes_to_notification?(key, subscribe_as_default)
442
+ !subscription_allowed?(key) || _subscribes_to_notification?(key, subscribe_as_default)
441
443
  end
442
444
 
443
445
  # Returns if the target subscribes to the notification email.
444
446
  # It also returns true when the subscription management is not allowed for the target.
445
447
  #
446
- # @param [String] key Key of the notification
447
- # @param [String] subscribe_as_default Default subscription value to use when the subscription record does not configured
448
+ # @param [String] key Key of the notification
449
+ # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
448
450
  # @return [Boolean] If the target subscribes the notification email or the subscription management is not allowed for the target
449
451
  def subscribes_to_notification_email?(key, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
450
- !subscription_allowed?(key) or _subscribes_to_notification_email?(key, subscribe_as_default)
452
+ !subscription_allowed?(key) || _subscribes_to_notification_email?(key, subscribe_as_default)
451
453
  end
452
454
  alias_method :subscribes_to_email?, :subscribes_to_notification_email?
453
455
 
456
+ # Returns if the target subscribes to the specified optional target.
457
+ # It also returns true when the subscription management is not allowed for the target.
458
+ #
459
+ # @param [String] key Key of the notification
460
+ # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack)
461
+ # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured
462
+ # @return [Boolean] If the target subscribes the notification email or the subscription management is not allowed for the target
463
+ def subscribes_to_optional_target?(key, optional_target_name, subscribe_as_default = ActivityNotification.config.subscribe_as_default)
464
+ !subscription_allowed?(key) || _subscribes_to_optional_target?(key, optional_target_name, subscribe_as_default)
465
+ end
454
466
 
455
467
  private
456
468
 
@@ -10,7 +10,7 @@ module ActivityNotification
10
10
  belongs_to :target, polymorphic: true
11
11
 
12
12
  # Serialize parameters Hash
13
- serialize :parameters, Hash
13
+ serialize :optional_targets, Hash
14
14
 
15
15
  validates :target, presence: true
16
16
  validates :key, presence: true
@@ -21,6 +21,7 @@ module ActivityNotification
21
21
  validates :unsubscribed_at, presence: true, unless: :subscribing
22
22
  validates :subscribed_to_email_at, presence: true, if: :subscribing_to_email
23
23
  validates :unsubscribed_to_email_at, presence: true, unless: :subscribing_to_email
24
+ validate :subscribing_to_optional_target_cannot_be_true_when_subscribing_is_false
24
25
 
25
26
  # Selects filtered subscriptions by target instance.
26
27
  # ActivityNotification::Subscription.filtered_by_target(@user)
@@ -82,12 +83,22 @@ module ActivityNotification
82
83
  scope :key_order, -> { order(key: :asc) }
83
84
 
84
85
  private
86
+
85
87
  # Validates subscribing_to_email cannot be true when subscribing isfalse.
86
88
  def subscribing_to_email_cannot_be_true_when_subscribing_is_false
87
- if !subscribing && subscribing_to_email
89
+ if !subscribing && subscribing_to_email?
88
90
  errors.add(:subscribing_to_email, "cannot be true when subscribing is false")
89
91
  end
90
92
  end
91
93
 
94
+ # Validates subscribing_to_optional_target cannot be true when subscribing isfalse.
95
+ def subscribing_to_optional_target_cannot_be_true_when_subscribing_is_false
96
+ optional_target_names.each do |optional_target_name|
97
+ if !subscribing && subscribing_to_optional_target?(optional_target_name)
98
+ errors.add(:optional_targets, "#Subscription.to_optional_target_key(optional_target_name) cannot be true when subscribing is false")
99
+ end
100
+ end
101
+ end
102
+
92
103
  end
93
104
  end
@@ -0,0 +1,42 @@
1
+ module ActivityNotification
2
+ module OptionalTarget
3
+ # Optional target implementation for mobile push notification or SMS using Amazon SNS.
4
+ class AmazonSNS < ActivityNotification::OptionalTarget::Base
5
+ require 'aws-sdk'
6
+
7
+ # Initialize method to prepare Aws::SNS::Client
8
+ # @param [Hash] options Options for initializing
9
+ # @option options [String, Proc, Symbol] :topic_arn (nil) :topic_arn option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
10
+ # @option options [String, Proc, Symbol] :target_arn (nil) :target_arn option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
11
+ # @option options [String, Proc, Symbol] :phone_number (nil) :phone_number option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
12
+ # @option options [Hash] others Other options to be set Aws::SNS::Client.new
13
+ def initialize_target(options = {})
14
+ @topic_arn = options.delete(:topic_arn)
15
+ @target_arn = options.delete(:target_arn)
16
+ @phone_number = options.delete(:phone_number)
17
+ @sns_client = Aws::SNS::Client.new(options)
18
+ end
19
+
20
+ # Publishes notification message to Amazon SNS
21
+ # @param [Notification] notification Notification instance
22
+ # @param [Hash] options Options for publishing
23
+ # @option options [String, Proc, Symbol] :topic_arn (nil) :topic_arn option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
24
+ # @option options [String, Proc, Symbol] :target_arn (nil) :target_arn option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
25
+ # @option options [String, Proc, Symbol] :phone_number (nil) :phone_number option for Aws::SNS::Client#publish, it resolved by target instance like email_allowed?
26
+ # @option options [String] :partial_root ("activity_notification/optional_targets/#{target}/#{optional_target_name}", "activity_notification/optional_targets/#{target}/base", "activity_notification/optional_targets/default/#{optional_target_name}", "activity_notification/optional_targets/default/base") Partial template name
27
+ # @option options [String] :partial (self.key.tr('.', '/')) Root path of partial template
28
+ # @option options [String] :layout (nil) Layout template name
29
+ # @option options [String] :layout_root ('layouts') Root path of layout template
30
+ # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
31
+ # @option options [Hash] others Parameters to be set as locals
32
+ def notify(notification, options = {})
33
+ @sns_client.publish(
34
+ topic_arn: notification.target.resolve_value(options.delete(:topic_arn) || @topic_arn),
35
+ target_arn: notification.target.resolve_value(options.delete(:target_arn) || @target_arn),
36
+ phone_number: notification.target.resolve_value(options.delete(:phone_number) || @phone_number),
37
+ message: render_notification_message(notification, options)
38
+ )
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,79 @@
1
+ module ActivityNotification
2
+ # Optional target module to develop optional notification target classes.
3
+ module OptionalTarget
4
+ # Abstract optional target class to develop optional notification target class.
5
+ class Base
6
+ # View context to render notification message
7
+ # @return View context to render notification message
8
+ attr_accessor :view_context
9
+
10
+ # Initialize method to create view context in this OptionalTarget instance
11
+ def initialize
12
+ @view_context = ActionView::Base.new(ActionController::Base.view_paths, {})
13
+ @view_context.class_eval do
14
+ include Rails.application.routes.url_helpers
15
+ def default_url_options
16
+ ActionMailer::Base.default_url_options
17
+ end
18
+ end
19
+ end
20
+
21
+ # Returns demodulized symbol class name as optional target name
22
+ # @return Demodulized symbol class name as optional target name
23
+ def to_optional_target_name
24
+ self.class.name.demodulize.underscore.to_sym
25
+ end
26
+
27
+ # Initialize method to be overriden in user implementation class
28
+ # @param [Hash] _options Options for initializing
29
+ def initialize_target(_options = {})
30
+ raise NotImplementedError, "You have to implement #{self.class}##{__method__}"
31
+ end
32
+
33
+ # Publishing notification method to be overriden in user implementation class
34
+ # @param [Notification] _notification Notification instance
35
+ # @param [Hash] _options Options for publishing
36
+ def notify(_notification, _options = {})
37
+ raise NotImplementedError, "You have to implement #{self.class}##{__method__}"
38
+ end
39
+
40
+ protected
41
+
42
+ # Renders notification message with view context
43
+ # @param [Notification] notification Notification instance
44
+ # @param [Hash] options Options for rendering
45
+ # @option options [Hash] :assignment (nil) Optional instance variables to assign for views
46
+ # @option options [String] :partial_root ("activity_notification/optional_targets/#{target}/#{optional_target_name}", "activity_notification/optional_targets/#{target}/base", "activity_notification/optional_targets/default/#{optional_target_name}", "activity_notification/optional_targets/default/base") Partial template name
47
+ # @option options [String] :partial (self.key.tr('.', '/')) Root path of partial template
48
+ # @option options [String] :layout (nil) Layout template name
49
+ # @option options [String] :layout_root ('layouts') Root path of layout template
50
+ # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
51
+ # @option options [Hash] others Parameters to be set as locals
52
+ # @return [String] Rendered view or text as string
53
+ def render_notification_message(notification, options = {})
54
+ partial_root_list =
55
+ options[:partial_root].present? ?
56
+ [ options[:partial_root] ] :
57
+ [ "activity_notification/optional_targets/#{notification.target.to_resources_name}/#{to_optional_target_name}",
58
+ "activity_notification/optional_targets/#{notification.target.to_resources_name}/base",
59
+ "activity_notification/optional_targets/default/#{to_optional_target_name}",
60
+ "activity_notification/optional_targets/default/base"
61
+ ]
62
+ options[:fallback] ||= :default
63
+ @view_context.assign((options[:assignment] || {}).merge(notification: notification, target: notification.target))
64
+
65
+ message, missing_template = nil, nil
66
+ partial_root_list.each do |partial_root|
67
+ begin
68
+ message = notification.render(@view_context, options.merge(partial_root: partial_root)).to_str
69
+ break
70
+ rescue ActionView::MissingTemplate => e
71
+ missing_template = e
72
+ # Continue to next partial root
73
+ end
74
+ end
75
+ message.blank? ? (raise missing_template) : message
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,33 @@
1
+ module ActivityNotification
2
+ module OptionalTarget
3
+ # Optional target implementation for Slack.
4
+ class Slack < ActivityNotification::OptionalTarget::Base
5
+ require 'slack-notifier'
6
+
7
+ # Initialize method to prepare Slack::Notifier
8
+ # @param [Hash] options Options for initializing
9
+ # @option options [String, Proc, Symbol] :slack_name (nil) Target user name of Slack, it resolved by target instance like email_allowed?
10
+ # @option options [required, String] :webhook_url (nil) Webhook URL of Slack Incoming WebHooks integration
11
+ # @option options [Hash] others Other options to be set Slack::Notifier.new, like :channel, :username, :icon_emoji etc
12
+ def initialize_target(options = {})
13
+ @slack_name = options.delete(:slack_name)
14
+ @notifier = ::Slack::Notifier.new(options.delete(:webhook_url), options)
15
+ end
16
+
17
+ # Publishes notification message to Slack
18
+ # @param [Notification] notification Notification instance
19
+ # @param [Hash] options Options for publishing
20
+ # @option options [String, Proc, Symbol] :slack_name (nil) Target user name of Slack, it resolved by target instance like email_allowed?
21
+ # @option options [String] :partial_root ("activity_notification/optional_targets/#{target}/#{optional_target_name}", "activity_notification/optional_targets/#{target}/base", "activity_notification/optional_targets/default/#{optional_target_name}", "activity_notification/optional_targets/default/base") Partial template name
22
+ # @option options [String] :partial (self.key.tr('.', '/')) Root path of partial template
23
+ # @option options [String] :layout (nil) Layout template name
24
+ # @option options [String] :layout_root ('layouts') Root path of layout template
25
+ # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
26
+ # @option options [Hash] others Parameters to be set as locals
27
+ def notify(notification, options = {})
28
+ slack_name = notification.target.resolve_value(options.delete(:slack_name) || @slack_name)
29
+ @notifier.ping(render_notification_message(notification, options.merge(assignment: { slack_name: slack_name })))
30
+ end
31
+ end
32
+ end
33
+ end
@@ -104,44 +104,52 @@ module ActionDispatch::Routing
104
104
  # subscribed_by :users
105
105
  # This method creates the needed routes:
106
106
  # # Subscription routes
107
- # user_subscriptions GET /users/:user_id/subscriptions(.:format)
107
+ # user_subscriptions GET /users/:user_id/subscriptions(.:format)
108
108
  # { controller:"activity_notification/subscriptions", action:"index", target_type:"users" }
109
- # user_subscription GET /users/:user_id/subscriptions/:id(.:format)
109
+ # user_subscription GET /users/:user_id/subscriptions/:id(.:format)
110
110
  # { controller:"activity_notification/subscriptions", action:"show", target_type:"users" }
111
- # open_all_user_subscriptions POST /users/:user_id/subscriptions(.:format)
111
+ # open_all_user_subscriptions POST /users/:user_id/subscriptions(.:format)
112
112
  # { controller:"activity_notification/subscriptions", action:"create", target_type:"users" }
113
- # user_subscription DELETE /users/:user_id/subscriptions/:id(.:format)
113
+ # user_subscription DELETE /users/:user_id/subscriptions/:id(.:format)
114
114
  # { controller:"activity_notification/subscriptions", action:"destroy", target_type:"users" }
115
- # open_user_subscription POST /users/:user_id/subscriptions/:id/subscribe(.:format)
115
+ # subscribe_user_subscription POST /users/:user_id/subscriptions/:id/subscribe(.:format)
116
116
  # { controller:"activity_notification/subscriptions", action:"subscribe", target_type:"users" }
117
- # open_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe(.:format)
117
+ # unsubscribe_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe(.:format)
118
118
  # { controller:"activity_notification/subscriptions", action:"unsubscribe", target_type:"users" }
119
- # open_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_email(.:format)
119
+ # subscribe_to_email_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_email(.:format)
120
120
  # { controller:"activity_notification/subscriptions", action:"subscribe_to_email", target_type:"users" }
121
- # open_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_email(.:format)
121
+ # unsubscribe_to_email_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_email(.:format)
122
122
  # { controller:"activity_notification/subscriptions", action:"unsubscribe_to_email", target_type:"users" }
123
+ # subscribe_to_optional_target_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_optional_target(.:format)
124
+ # { controller:"activity_notification/subscriptions", action:"subscribe_to_optional_target", target_type:"users" }
125
+ # unsubscribe_to_optional_target_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_optional_target(.:format)
126
+ # { controller:"activity_notification/subscriptions", action:"unsubscribe_to_optional_target", target_type:"users" }
123
127
  #
124
128
  # When you use devise authentication and you want make subscription targets assciated with devise,
125
129
  # you can create as follows in your routes:
126
130
  # notify_to :users, with_devise: :users
127
131
  # This with_devise option creates the needed routes assciated with devise authentication:
128
132
  # # Subscription with devise routes
129
- # user_subscriptions GET /users/:user_id/subscriptions(.:format)
133
+ # user_subscriptions GET /users/:user_id/subscriptions(.:format)
130
134
  # { controller:"activity_notification/subscriptions_with_devise", action:"index", target_type:"users", devise_type:"users" }
131
- # user_subscription GET /users/:user_id/subscriptions/:id(.:format)
135
+ # user_subscription GET /users/:user_id/subscriptions/:id(.:format)
132
136
  # { controller:"activity_notification/subscriptions_with_devise", action:"show", target_type:"users", devise_type:"users" }
133
- # open_all_user_subscriptions POST /users/:user_id/subscriptions(.:format)
137
+ # open_all_user_subscriptions POST /users/:user_id/subscriptions(.:format)
134
138
  # { controller:"activity_notification/subscriptions_with_devise", action:"create", target_type:"users", devise_type:"users" }
135
- # user_subscription DELETE /users/:user_id/subscriptions/:id(.:format)
139
+ # user_subscription DELETE /users/:user_id/subscriptions/:id(.:format)
136
140
  # { controller:"activity_notification/subscriptions_with_devise", action:"destroy", target_type:"users", devise_type:"users" }
137
- # open_user_subscription POST /users/:user_id/subscriptions/:id/subscribe(.:format)
141
+ # subscribe_user_subscription POST /users/:user_id/subscriptions/:id/subscribe(.:format)
138
142
  # { controller:"activity_notification/subscriptions_with_devise", action:"subscribe", target_type:"users", devise_type:"users" }
139
- # open_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe(.:format)
143
+ # unsubscribe_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe(.:format)
140
144
  # { controller:"activity_notification/subscriptions_with_devise", action:"unsubscribe", target_type:"users", devise_type:"users" }
141
- # open_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_email(.:format)
145
+ # subscribe_to_email_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_email(.:format)
142
146
  # { controller:"activity_notification/subscriptions_with_devise", action:"subscribe_to_email", target_type:"users", devise_type:"users" }
143
- # open_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_email(.:format)
147
+ # unsubscribe_to_email_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_email(.:format)
144
148
  # { controller:"activity_notification/subscriptions_with_devise", action:"unsubscribe_to_email", target_type:"users", devise_type:"users" }
149
+ # subscribe_to_optional_target_user_subscription POST /users/:user_id/subscriptions/:id/subscribe_to_optional_target(.:format)
150
+ # { controller:"activity_notification/subscriptions_with_devise", action:"subscribe_to_optional_target", target_type:"users", devise_type:"users" }
151
+ # unsubscribe_to_optional_target_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe_to_optional_target(.:format)
152
+ # { controller:"activity_notification/subscriptions_with_devise", action:"unsubscribe_to_optional_target", target_type:"users", devise_type:"users" }
145
153
  #
146
154
  # @example Define subscribed_by in config/routes.rb
147
155
  # subscribed_by :users
@@ -168,10 +176,12 @@ module ActionDispatch::Routing
168
176
  resources_options = options.select { |key, _| [:with_devise, :model, :devise_defaults].exclude? key }
169
177
  self.resources options[:model], resources_options do
170
178
  member do
171
- post :subscribe unless ignore_path?(:subscribe, options)
172
- post :unsubscribe unless ignore_path?(:unsubscribe, options)
173
- post :subscribe_to_email unless ignore_path?(:subscribe_to_email, options)
174
- post :unsubscribe_to_email unless ignore_path?(:unsubscribe_to_email, options)
179
+ post :subscribe unless ignore_path?(:subscribe, options)
180
+ post :unsubscribe unless ignore_path?(:unsubscribe, options)
181
+ post :subscribe_to_email unless ignore_path?(:subscribe_to_email, options)
182
+ post :unsubscribe_to_email unless ignore_path?(:unsubscribe_to_email, options)
183
+ post :subscribe_to_optional_target unless ignore_path?(:subscribe_to_optional_target, options)
184
+ post :unsubscribe_to_optional_target unless ignore_path?(:unsubscribe_to_optional_target, options)
175
185
  end
176
186
  end
177
187
  end
@@ -118,14 +118,39 @@ module ActivityNotification
118
118
  # * :dependent_notifications
119
119
  # * Dependency for notifications to delete generated notifications with this notifiable.
120
120
  # This option is used to configure generated_notifications_as_notifiable association.
121
- # You can use :delete_all, :destroy, or :nullify for this option.
122
- # This parameter is a optional since no dependent option is used as default.
121
+ # You can use :delete_all, :destroy, :restrict_with_error, :restrict_with_exception, :update_group_and_delete_all or :update_group_and_destroy for this option.
122
+ # When you use :update_group_and_delete_all or :update_group_and_destroy to this parameter, the oldest group member notification becomes a new group owner as `before_destroy` of this Notifiable.
123
+ # This parameter is effective for all target and is a optional since no dependent option is used as default.
123
124
  # @example Define :delete_all dependency to generated notifications
124
125
  # # app/models/comment.rb
125
126
  # class Comment < ActiveRecord::Base
126
127
  # acts_as_notifiable :users, targets: User.all, dependent_notifications: :delete_all
127
128
  # end
128
129
  #
130
+ # * :optional_targets
131
+ # * Optional targets to integrate external notification serveces like Amazon SNS or Slack.
132
+ # You can use hash of optional target implementation class as key and initializing parameters as value for this parameter.
133
+ # When the hash parameter is passed, acts_as_notifiable will create new instance of optional target class and call initialize_target method with initializing parameters, then configure them as optional_targets for this notifiable and target.
134
+ # You can also use symbol of method name or lambda function which returns array of initialized optional target intstances.
135
+ # All optional target class must extends ActivityNotification::OptionalTarget::Base.
136
+ # This parameter is completely optional.
137
+ # @example Define to integrate with Amazon SNS, Slack and your custom ConsoleOutput targets
138
+ # # app/models/comment.rb
139
+ # class Comment < ActiveRecord::Base
140
+ # require 'activity_notification/optional_targets/amazon_sns'
141
+ # require 'activity_notification/optional_targets/slack'
142
+ # require 'custom_optional_targets/console_output'
143
+ # acts_as_notifiable :admins, targets: Admin.all,
144
+ # optional_targets: {
145
+ # ActivityNotification::OptionalTarget::AmazonSNS => { topic_arn: '<Topin ARN of yours>' },
146
+ # ActivityNotification::OptionalTarget::Slack => {
147
+ # webhook_url: '<Slack Webhook URL>',
148
+ # slack_name: :slack_name, channel: 'activity_notification', username: 'ActivityNotification', icon_emoji: ":ghost:"
149
+ # },
150
+ # CustomOptionalTarget::ConsoleOutput => {}
151
+ # }
152
+ # end
153
+ #
129
154
  # @param [Symbol] target_type Type of notification target as symbol
130
155
  # @param [Hash] options Options for notifiable model configuration
131
156
  # @option options [Symbol, Proc, Array] :targets (nil) Targets to send notifications
@@ -136,21 +161,42 @@ module ActivityNotification
136
161
  # @option options [Symbol, Proc, Boolean] :email_allowed (ActivityNotification.config.email_enabled) Whether activity_notification sends notification email
137
162
  # @option options [Symbol, Proc, String] :notifiable_path (polymorphic_path(self)) Path to redirect from open or move action of notification controller
138
163
  # @option options [Symbol, Proc, String] :printable_name (ActivityNotification::Common.printable_name) Printable notifiable name
139
- # @option options [Symbol, Proc] :dependent_notifications (nil) Dependency for notifications to delete generated notifications with this notifiable
164
+ # @option options [Symbol, Proc] :dependent_notifications (nil) Dependency for notifications to delete generated notifications with this notifiable, [:delete_all, :destroy, :restrict_with_error, :restrict_with_exception, :update_group_and_delete_all, :update_group_and_destroy] are available
165
+ # @option options [Hash<Class, Hash>] :optional_targets (nil) Optional target configurations with hash of `OptionalTarget` implementation class as key and initializing option parameter as value
140
166
  # @return [Hash] Configured parameters as notifiable model
141
167
  def acts_as_notifiable(target_type, options = {})
142
168
  include Notifiable
169
+ configured_params = {}
143
170
 
144
- if [:delete_all, :destroy, :nullify].include? options[:dependent_notifications]
145
- has_many :generated_notifications_as_notifiable,
146
- class_name: "::ActivityNotification::Notification",
147
- as: :notifiable,
148
- dependent: options[:dependent_notifications]
171
+ if [:delete_all, :destroy, :restrict_with_error, :restrict_with_exception, :update_group_and_delete_all, :update_group_and_destroy].include? options[:dependent_notifications]
172
+ case options[:dependent_notifications]
173
+ when :delete_all, :destroy, :restrict_with_error, :restrict_with_exception
174
+ has_many_generated_notifications options[:dependent_notifications]
175
+ when :update_group_and_delete_all
176
+ before_destroy :remove_generated_notifications_from_group
177
+ has_many_generated_notifications :delete_all
178
+ when :update_group_and_destroy
179
+ before_destroy :remove_generated_notifications_from_group
180
+ has_many_generated_notifications :destroy
181
+ end
182
+ configured_params = { dependent_notifications: options[:dependent_notifications] }
183
+ end
184
+
185
+ if options[:optional_targets].is_a?(Hash)
186
+ options[:optional_targets] = options[:optional_targets].map { |target_class, target_options|
187
+ optional_target = target_class.new
188
+ unless optional_target.kind_of?(ActivityNotification::OptionalTarget::Base)
189
+ raise TypeError, "#{optional_target.class.name} for an optional target is not a kind of ActivityNotification::OptionalTarget::Base"
190
+ end
191
+ optional_target.initialize_target(target_options)
192
+ optional_target
193
+ }
149
194
  end
150
195
 
151
196
  options[:printable_notifiable_name] ||= options.delete(:printable_name)
152
- set_acts_as_parameters_for_target(target_type, [:targets, :group, :group_expiry_delay, :parameters, :email_allowed], options, "notification_")
153
- .merge set_acts_as_parameters_for_target(target_type, [:notifier, :notifiable_path, :printable_notifiable_name], options)
197
+ configured_params
198
+ .merge set_acts_as_parameters_for_target(target_type, [:targets, :group, :group_expiry_delay, :parameters, :email_allowed], options, "notification_")
199
+ .merge set_acts_as_parameters_for_target(target_type, [:notifier, :notifiable_path, :printable_notifiable_name, :optional_targets], options)
154
200
  end
155
201
 
156
202
  # Returns array of available notifiable options in acts_as_notifiable.
@@ -164,9 +210,22 @@ module ActivityNotification
164
210
  :email_allowed,
165
211
  :notifiable_path,
166
212
  :printable_notifiable_name, :printable_name,
167
- :dependent_notifications
213
+ :dependent_notifications,
214
+ :optional_targets
168
215
  ].freeze
169
216
  end
217
+
218
+ private
219
+
220
+ # Define to have many notification instances for this notifiable with dependent option.
221
+ # @api private
222
+ def has_many_generated_notifications(dependent_option)
223
+ has_many :generated_notifications_as_notifiable,
224
+ class_name: "::ActivityNotification::Notification",
225
+ as: :notifiable,
226
+ dependent: dependent_option
227
+ end
228
+
170
229
  end
171
230
  end
172
231
  end