activity_notification 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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