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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +23 -11
- data/Gemfile.lock +52 -40
- data/README.md +177 -14
- data/activity_notification.gemspec +3 -1
- data/app/controllers/activity_notification/subscriptions_controller.rb +48 -2
- data/app/views/activity_notification/optional_targets/default/base/_default.text.erb +10 -0
- data/app/views/activity_notification/optional_targets/default/slack/_default.text.erb +6 -0
- data/app/views/activity_notification/subscriptions/default/_notification_keys.html.erb +19 -0
- data/app/views/activity_notification/subscriptions/default/_subscription.html.erb +32 -3
- data/app/views/activity_notification/subscriptions/default/index.html.erb +4 -0
- data/app/views/activity_notification/subscriptions/default/show.html.erb +5 -0
- data/app/views/activity_notification/subscriptions/default/subscribe_to_optional_target.js.erb +6 -0
- data/app/views/activity_notification/subscriptions/default/unsubscribe_to_optional_target.js.erb +6 -0
- data/gemfiles/Gemfile.rails-4.2.lock +15 -3
- data/gemfiles/Gemfile.rails-5.0.lock +47 -35
- data/lib/activity_notification.rb +1 -0
- data/lib/activity_notification/apis/notification_api.rb +83 -26
- data/lib/activity_notification/apis/subscription_api.rb +93 -8
- data/lib/activity_notification/common.rb +6 -2
- data/lib/activity_notification/helpers/view_helpers.rb +43 -0
- data/lib/activity_notification/models/concerns/notifiable.rb +73 -28
- data/lib/activity_notification/models/concerns/subscriber.rb +34 -7
- data/lib/activity_notification/models/concerns/target.rb +25 -13
- data/lib/activity_notification/models/subscription.rb +13 -2
- data/lib/activity_notification/optional_targets/amazon_sns.rb +42 -0
- data/lib/activity_notification/optional_targets/base.rb +79 -0
- data/lib/activity_notification/optional_targets/slack.rb +33 -0
- data/lib/activity_notification/rails/routes.rb +30 -20
- data/lib/activity_notification/roles/acts_as_notifiable.rb +70 -11
- data/lib/activity_notification/version.rb +1 -1
- data/lib/generators/activity_notification/views_generator.rb +2 -2
- data/lib/generators/templates/migrations/migration.rb +2 -2
- data/spec/concerns/apis/notification_api_spec.rb +97 -0
- data/spec/concerns/apis/subscription_api_spec.rb +206 -41
- data/spec/concerns/common_spec.rb +7 -2
- data/spec/concerns/models/notifiable_spec.rb +88 -2
- data/spec/concerns/models/subscriber_spec.rb +114 -13
- data/spec/concerns/models/target_spec.rb +17 -0
- data/spec/controllers/subscriptions_controller_shared_examples.rb +251 -28
- data/spec/helpers/view_helpers_spec.rb +56 -0
- data/spec/optional_targets/amazon_sns_spec.rb +46 -0
- data/spec/optional_targets/base_spec.rb +43 -0
- data/spec/optional_targets/slack_spec.rb +46 -0
- data/spec/rails_app/app/controllers/comments_controller.rb +1 -0
- data/spec/rails_app/app/models/admin.rb +1 -2
- data/spec/rails_app/app/models/article.rb +2 -3
- data/spec/rails_app/app/models/comment.rb +19 -7
- data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +8 -0
- data/spec/rails_app/db/migrate/20160715050420_create_activity_notification_tables.rb +1 -1
- data/spec/rails_app/db/migrate/20160715050433_create_test_tables.rb +2 -0
- data/spec/rails_app/db/schema.rb +3 -1
- data/spec/rails_app/db/seeds.rb +1 -1
- data/spec/rails_app/lib/custom_optional_targets/console_output.rb +13 -0
- data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +10 -0
- data/spec/roles/acts_as_notifiable_spec.rb +124 -2
- metadata +49 -4
- 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
|
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
|
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
|
306
|
-
# @option options [Object] :group
|
307
|
-
# @option options [ActiveSupport::Duration] :group_expiry_delay
|
308
|
-
# @option options [Object] :notifier
|
309
|
-
# @option options [Hash] :parameters
|
310
|
-
# @option options [Boolean] :send_email
|
311
|
-
# @option options [Boolean] :send_later
|
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]
|
437
|
-
# @param [
|
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)
|
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]
|
447
|
-
# @param [
|
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)
|
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 :
|
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
|
107
|
+
# user_subscriptions GET /users/:user_id/subscriptions(.:format)
|
108
108
|
# { controller:"activity_notification/subscriptions", action:"index", target_type:"users" }
|
109
|
-
# user_subscription
|
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
|
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
|
113
|
+
# user_subscription DELETE /users/:user_id/subscriptions/:id(.:format)
|
114
114
|
# { controller:"activity_notification/subscriptions", action:"destroy", target_type:"users" }
|
115
|
-
#
|
115
|
+
# subscribe_user_subscription POST /users/:user_id/subscriptions/:id/subscribe(.:format)
|
116
116
|
# { controller:"activity_notification/subscriptions", action:"subscribe", target_type:"users" }
|
117
|
-
#
|
117
|
+
# unsubscribe_user_subscription POST /users/:user_id/subscriptions/:id/unsubscribe(.:format)
|
118
118
|
# { controller:"activity_notification/subscriptions", action:"unsubscribe", target_type:"users" }
|
119
|
-
#
|
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
|
-
#
|
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
|
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
|
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
|
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
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
-
#
|
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
|
172
|
-
post :unsubscribe
|
173
|
-
post :subscribe_to_email
|
174
|
-
post :unsubscribe_to_email
|
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 :
|
122
|
-
#
|
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, :
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
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
|
-
|
153
|
-
.merge set_acts_as_parameters_for_target(target_type, [:
|
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
|