activity_notification 1.0.0 → 1.0.1

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +3 -3
  3. data/README.md +49 -9
  4. data/activity_notification.gemspec +1 -1
  5. data/app/controllers/activity_notification/notifications_controller.rb +75 -31
  6. data/app/mailers/activity_notification/mailer.rb +19 -4
  7. data/app/views/activity_notification/mailer/default/batch_default.html.erb +79 -0
  8. data/app/views/activity_notification/mailer/default/batch_default.text.erb +13 -0
  9. data/app/views/activity_notification/mailer/default/default.html.erb +75 -10
  10. data/app/views/activity_notification/mailer/default/default.text.erb +2 -2
  11. data/app/views/activity_notification/notifications/default/_default.html.erb +15 -14
  12. data/app/views/activity_notification/notifications/default/_default_without_grouping.html.erb +165 -0
  13. data/app/views/activity_notification/notifications/default/_index.html.erb +8 -4
  14. data/app/views/activity_notification/notifications/default/destroy.js.erb +2 -2
  15. data/app/views/activity_notification/notifications/default/index.html.erb +9 -5
  16. data/app/views/activity_notification/notifications/default/open.js.erb +6 -2
  17. data/app/views/activity_notification/notifications/default/open_all.js.erb +6 -2
  18. data/lib/activity_notification/apis/notification_api.rb +42 -9
  19. data/lib/activity_notification/helpers/view_helpers.rb +48 -19
  20. data/lib/activity_notification/mailers/helpers.rb +74 -37
  21. data/lib/activity_notification/models/concerns/target.rb +290 -26
  22. data/lib/activity_notification/models/notification.rb +85 -29
  23. data/lib/activity_notification/roles/acts_as_target.rb +4 -2
  24. data/lib/activity_notification/version.rb +1 -1
  25. data/spec/concerns/apis/notification_api_spec.rb +46 -0
  26. data/spec/concerns/models/target_spec.rb +281 -22
  27. data/spec/controllers/notifications_controller_shared_examples.rb +77 -0
  28. data/spec/helpers/view_helpers_spec.rb +39 -3
  29. data/spec/mailers/mailer_spec.rb +54 -1
  30. data/spec/models/notification_spec.rb +11 -0
  31. data/spec/rails_app/app/models/user.rb +1 -1
  32. data/spec/rails_app/app/views/layouts/_header.html.erb +2 -0
  33. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +6 -0
  34. data/spec/roles/acts_as_target_spec.rb +1 -1
  35. metadata +7 -4
@@ -32,27 +32,36 @@ module ActivityNotification
32
32
  #
33
33
  # @param [Object] target Target instance of the rendering notifications
34
34
  # @param [Hash] options Options for rendering notifications
35
- # @option options [String, Symbol] :target (nil) Target type name to find template or i18n text
36
- # @option options [String] :partial_root ("activity_notification/notifications/#{target.to_resources_name}", 'activity_notification/notifications/default') Root path of partial template
37
- # @option options [String] :notification_partial ("activity_notification/notifications/#{target.to_resources_name}", controller.target_view_path, 'activity_notification/notifications/default') Partial template name of the notification index content
38
- # @option options [String] :layout_root ('layouts') Root path of layout template
39
- # @option options [String] :notification_layout (nil) Layout template name of the notification index content
40
- # @option options [String] :fallback (nil) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
41
- # @option options [String] :partial ('index') Partial template name of the partial index
42
- # @option options [String] :layout (nil) Layout template name of the partial index
35
+ # @option options [String, Symbol] :target (nil) Target type name to find template or i18n text
36
+ # @option options [Symbol] :index_content (:with_attributes) Option method to load target notification index, [:simple, :unopened_simple, :opened_simple, :with_attributes, :unopened_with_attributes, :opened_with_attributes, :none] are available
37
+ # @option options [String] :partial_root ("activity_notification/notifications/#{target.to_resources_name}", 'activity_notification/notifications/default') Root path of partial template
38
+ # @option options [String] :notification_partial ("activity_notification/notifications/#{target.to_resources_name}", controller.target_view_path, 'activity_notification/notifications/default') Partial template name of the notification index content
39
+ # @option options [String] :layout_root ('layouts') Root path of layout template
40
+ # @option options [String] :notification_layout (nil) Layout template name of the notification index content
41
+ # @option options [String] :fallback (nil) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
42
+ # @option options [String] :partial ('index') Partial template name of the partial index
43
+ # @option options [String] :layout (nil) Layout template name of the partial index
44
+ # @option options [Integer] :limit (nil) Limit to query for notifications
45
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
46
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
47
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
48
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
49
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
50
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
51
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
52
+ # @option options [Array] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
43
53
  # @return [String] Rendered view or text as string
44
54
  def render_notification_of target, options = {}
45
55
  return unless target.is_a? ActivityNotification::Target
46
56
 
47
57
  # Prepare content for notifications index
48
- notification_options = options.merge target: target.to_resources_name,
49
- partial: options[:notification_partial], layout: options[:notification_layout]
50
- notification_index =
51
- case options[:index_content]
52
- when :simple then target.notification_index
53
- when :none then []
54
- else target.notification_index_with_attributes
55
- end
58
+ notification_options = options.merge( target: target.to_resources_name,
59
+ partial: options[:notification_partial],
60
+ layout: options[:notification_layout] )
61
+ index_options = options.slice( :limit, :reverse, :with_group_members,
62
+ :filtered_by_group, :filtered_by_group_type, :filtered_by_group_id,
63
+ :filtered_by_type, :filtered_by_key, :custom_filter )
64
+ notification_index = load_notification_index(target, options[:index_content], index_options)
56
65
  prepare_content_for(target, notification_index, notification_options)
57
66
 
58
67
  # Render partial index
@@ -173,6 +182,26 @@ module ActivityNotification
173
182
 
174
183
  private
175
184
 
185
+ # Load notification index from :index_content parameter
186
+ # @api private
187
+ #
188
+ # @param [Object] target Notification target instance
189
+ # @param [Symbol] index_content Method to load target notification index, [:simple, :unopened_simple, :opened_simple, :with_attributes, :unopened_with_attributes, :opened_with_attributes, :none] are available
190
+ # @param [Hash] params Option parameter to load notification index
191
+ # @return [Array<Notification>] Array of notification index
192
+ def load_notification_index(target, index_content, options = {})
193
+ case index_content
194
+ when :simple then target.notification_index(options)
195
+ when :unopened_simple then target.unopened_notification_index(options)
196
+ when :opened_simple then target.opened_notification_index(options)
197
+ when :with_attributes then target.notification_index_with_attributes(options)
198
+ when :unopened_with_attributes then target.unopened_notification_index_with_attributes(options)
199
+ when :opened_with_attributes then target.opened_notification_index_with_attributes(options)
200
+ when :none then []
201
+ else target.notification_index_with_attributes(options)
202
+ end
203
+ end
204
+
176
205
  # Prepare content for notification index
177
206
  # @api private
178
207
  #
@@ -194,14 +223,14 @@ module ActivityNotification
194
223
  # Render partial index of notifications
195
224
  # @api private
196
225
  #
197
- # @param [Object] target Notification target instance
198
- # @param [Hash] params Option parameter to send render
226
+ # @param [Object] target Notification target instance
227
+ # @param [Hash] params Option parameter to send render
199
228
  # @return [String] Rendered partial index view as string
200
229
  def render_partial_index(target, params)
201
230
  index_path = params.delete(:partial)
202
231
  partial = partial_index_path(target, index_path, params[:partial_root])
203
232
  layout = layout_path(params.delete(:layout), params[:layout_root])
204
- locals = (params[:locals] || {}).merge(target: target)
233
+ locals = (params[:locals] || {}).merge(target: target, parameters: params)
205
234
  begin
206
235
  render params.merge(partial: partial, layout: layout, locals: locals)
207
236
  rescue ActionView::MissingTemplate
@@ -10,43 +10,61 @@ module ActivityNotification
10
10
 
11
11
  # Send notification email with configured options.
12
12
  #
13
- # @param [Notification] notification Notification instance
14
- # @param [Hash] options Options for email notification
13
+ # @param [Notification] notification Notification instance to send email
14
+ # @param [Hash] options Options for notification email
15
+ # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised
15
16
  def notification_mail(notification, options = {})
16
17
  initialize_from_notification(notification)
17
18
  headers = headers_for(notification.key, options)
18
- begin
19
- mail headers
20
- rescue ActionView::MissingTemplate => e
21
- if options[:fallback].present?
22
- mail headers.merge(template_name: options[:fallback])
23
- else
24
- raise e
25
- end
26
- end
19
+ send_mail(headers, options[:fallback])
27
20
  end
28
-
21
+
22
+ # Send batch notification email with configured options.
23
+ #
24
+ # @param [Object] target Target of batch notification email
25
+ # @param [Array<Notification>] notifications Target notifications to send batch notification email
26
+ # @param [Hash] options Options for notification email
27
+ # @option options [String, Symbol] :fallback (:batch_default) Fallback template to use when MissingTemplate is raised
28
+ # @option options [String] :batch_key (nil) Key of the batch notification email, a key of the first notification will be used if not specified
29
+ def batch_notification_mail(target, notifications, options = {})
30
+ initialize_from_notifications(target, notifications)
31
+ batch_key = options.delete(:batch_key)
32
+ batch_key ||= @notification.key
33
+ headers = headers_for(batch_key, options)
34
+ @notification = nil
35
+ send_mail(headers, options[:fallback])
36
+ end
37
+
29
38
  # Initialize instance variables from notification.
30
39
  #
31
40
  # @param [Notification] notification Notification instance
32
41
  def initialize_from_notification(notification)
33
- @notification, @target, @notifiable = notification, notification.target, notification.notifiable
42
+ @notification, @target, @batch_email = notification, notification.target, false
34
43
  end
35
-
44
+
45
+ # Initialize instance variables from notifications.
46
+ #
47
+ # @param [Object] target Target of batch notification email
48
+ # @param [Array<Notification>] notifications Target notifications to send batch notification email
49
+ def initialize_from_notifications(target, notifications)
50
+ @target, @notifications, @notification, @batch_email = target, notifications, notifications.first, true
51
+ end
52
+
36
53
  # Prepare email header from notification key and options.
37
54
  #
38
55
  # @param [String] key Key of the notification
39
56
  # @param [Hash] options Options for email notification
40
57
  def headers_for(key, options)
41
- if @notifiable.respond_to?(:overriding_notification_email_key) and
42
- @notifiable.overriding_notification_email_key(@target, key).present?
43
- key = @notifiable.overriding_notification_email_key(@target, key)
58
+ if !@batch_email and
59
+ @notification.notifiable.respond_to?(:overriding_notification_email_key) and
60
+ @notification.notifiable.overriding_notification_email_key(@target, key).present?
61
+ key = @notification.notifiable.overriding_notification_email_key(@target, key)
44
62
  end
45
63
  headers = {
46
64
  subject: subject_for(key),
47
65
  to: mailer_to(@target),
48
- from: mailer_from(@notification),
49
- reply_to: mailer_reply_to(@notification),
66
+ from: mailer_from(key),
67
+ reply_to: mailer_reply_to(key),
50
68
  template_path: template_paths,
51
69
  template_name: template_name(key)
52
70
  }.merge(options)
@@ -54,7 +72,7 @@ module ActivityNotification
54
72
  @email = headers[:to]
55
73
  headers
56
74
  end
57
-
75
+
58
76
  # Returns target email address as 'to'.
59
77
  #
60
78
  # @param [Object] target Target instance to notify
@@ -62,38 +80,38 @@ module ActivityNotification
62
80
  def mailer_to(target)
63
81
  target.mailer_to
64
82
  end
65
-
83
+
66
84
  # Returns sender email address as 'reply_to'.
67
85
  #
68
- # @param [Notification] notification Notification instance
86
+ # @param [String] key Key of the notification or batch notification email
69
87
  # @return [String] Sender email address as 'reply_to'
70
- def mailer_reply_to(notification)
71
- mailer_sender(notification, :reply_to)
88
+ def mailer_reply_to(key)
89
+ mailer_sender(key, :reply_to)
72
90
  end
73
-
91
+
74
92
  # Returns sender email address as 'from'.
75
93
  #
76
- # @param [Notification] notification Notification instance
94
+ # @param [String] key Key of the notification or batch notification email
77
95
  # @return [String] Sender email address as 'from'
78
- def mailer_from(notification)
79
- mailer_sender(notification, :from)
96
+ def mailer_from(key)
97
+ mailer_sender(key, :from)
80
98
  end
81
-
99
+
82
100
  # Returns sender email address configured in initializer or mailer class.
83
101
  #
84
- # @param [Notification] notification Notification instance
102
+ # @param [String] key Key of the notification or batch notification email
85
103
  # @return [String] Sender email address configured in initializer or mailer class
86
- def mailer_sender(notification, sender = :from)
104
+ def mailer_sender(key, sender = :from)
87
105
  default_sender = default_params[sender]
88
106
  if default_sender.present?
89
107
  default_sender.respond_to?(:to_proc) ? instance_eval(&default_sender) : default_sender
90
108
  elsif ActivityNotification.config.mailer_sender.is_a?(Proc)
91
- ActivityNotification.config.mailer_sender.call(notification)
109
+ ActivityNotification.config.mailer_sender.call(key)
92
110
  else
93
111
  ActivityNotification.config.mailer_sender
94
112
  end
95
113
  end
96
-
114
+
97
115
  # Returns template paths to find email view
98
116
  #
99
117
  # @return [Array<String>] Template paths to find email view
@@ -102,7 +120,7 @@ module ActivityNotification
102
120
  paths.unshift("activity_notification/mailer/#{@target.to_resources_name}") if @target.present?
103
121
  paths
104
122
  end
105
-
123
+
106
124
  # Returns template name from notification key
107
125
  #
108
126
  # @param [String] key Key of the notification
@@ -110,8 +128,8 @@ module ActivityNotification
110
128
  def template_name(key)
111
129
  key.gsub('.', '/')
112
130
  end
113
-
114
-
131
+
132
+
115
133
  # Set up a subject doing an I18n lookup.
116
134
  # At first, it attempts to set a subject based on the current mapping:
117
135
  # en:
@@ -131,7 +149,26 @@ module ActivityNotification
131
149
  k.insert(1, @target.to_resource_name)
132
150
  k = k.join('.')
133
151
  I18n.t(:mail_subject, scope: k,
134
- default: ["Notification of #{@notifiable.printable_type.downcase}"])
152
+ default: ["Notification of #{@notification.notifiable.printable_type.downcase}"])
153
+ end
154
+
155
+
156
+ private
157
+
158
+ # Send email with fallback option.
159
+ #
160
+ # @param [Hash] headers Prepared email header
161
+ # @param [String, Symbol] fallback Fallback option
162
+ def send_mail(headers, fallback = nil)
163
+ begin
164
+ mail headers
165
+ rescue ActionView::MissingTemplate => e
166
+ if fallback.present?
167
+ mail headers.merge(template_name: fallback)
168
+ else
169
+ raise e
170
+ end
171
+ end
135
172
  end
136
173
 
137
174
  end
@@ -15,6 +15,7 @@ module ActivityNotification
15
15
 
16
16
  class_attribute :_notification_email,
17
17
  :_notification_email_allowed,
18
+ :_batch_notification_email_allowed,
18
19
  :_notification_devise_resource,
19
20
  :_printable_notification_target_name
20
21
  set_target_class_defaults
@@ -32,10 +33,104 @@ module ActivityNotification
32
33
  def set_target_class_defaults
33
34
  self._notification_email = nil
34
35
  self._notification_email_allowed = ActivityNotification.config.email_enabled
36
+ self._batch_notification_email_allowed = ActivityNotification.config.email_enabled
35
37
  self._notification_devise_resource = ->(model) { model }
36
38
  self._printable_notification_target_name = :printable_name
37
39
  nil
38
40
  end
41
+
42
+ # Gets all notifications for this target type.
43
+ #
44
+ # @option options [Integer] :limit (nil) Limit to query for notifications
45
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
46
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
47
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
48
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
49
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
50
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
51
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
52
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
53
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
54
+ # @return [Array<Notificaion>] All notifications for this target type
55
+ def all_notifications(options = {})
56
+ reverse = options[:reverse] || false
57
+ with_group_members = options[:with_group_members] || false
58
+ target_notifications = Notification.filtered_by_target_type(self.name)
59
+ .all_index!(reverse, with_group_members)
60
+ .filtered_by_options(options)
61
+ options[:limit].present? ? target_notifications.limit(options[:limit]) : target_notifications
62
+ end
63
+
64
+ # Gets all notifications for this target type grouped by targets.
65
+ #
66
+ # @example Get all notifications for for users grouped by user
67
+ # @notification_index_map = User.notification_index_map
68
+ # @notification_index_map.each do |user, notifications|
69
+ # # Do something for user and notifications
70
+ # end
71
+ #
72
+ # @option options [Integer] :limit (nil) Limit to query for notifications
73
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
74
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
75
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
76
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
77
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
78
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
79
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
80
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
81
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
82
+ # @return [Array<Notificaion>] All notifications for this target type grouped by targets
83
+ def notification_index_map(options = {})
84
+ all_notifications(options).group_by(&:target)
85
+ end
86
+
87
+ # Gets all unopened notifications for this target type grouped by targets.
88
+ #
89
+ # @example Get all unopened notifications for users grouped by user
90
+ # @unopened_notification_index_map = User.unopened_notification_index_map
91
+ # @unopened_notification_index_map.each do |user, notifications|
92
+ # # Do something for user and notifications
93
+ # end
94
+ #
95
+ # @option options [Integer] :limit (nil) Limit to query for notifications
96
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
97
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
98
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
99
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
100
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
101
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
102
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
103
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
104
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
105
+ # @return [Array<Notificaion>] All unopened notifications for this target type grouped by targets
106
+ def unopened_notification_index_map(options = {})
107
+ all_notifications(options).unopened_only.group_by(&:target)
108
+ end
109
+
110
+ # Send batch notification email to this type targets with unopened notifications.
111
+ #
112
+ # @example Send batch notification email to the users with unopened notifications of specified key
113
+ # User.send_batch_unopened_notification_email(filtered_by_key: 'this.key')
114
+ # @example Send batch notification email to the users with unopened notifications of specified key in 1 hour
115
+ # User.send_batch_unopened_notification_email(filtered_by_key: 'this.key', custom_filter: ["created_at >= ?", time.hour.ago])
116
+ #
117
+ # @option options [Integer] :limit (nil) Limit to query for notifications
118
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
119
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
120
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
121
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
122
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
123
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
124
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
125
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
126
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
127
+ # @return [Hash<Object, Mail::Message|ActionMailer::DeliveryJob>] Hash of target and sent email message or its delivery job
128
+ def send_batch_unopened_notification_email(options = {})
129
+ unopened_notification_index_map = unopened_notification_index_map(options)
130
+ unopened_notification_index_map.map { |target, notifications|
131
+ [target, Notification.send_batch_notification_email(target, notifications, options)]
132
+ }.to_h
133
+ end
39
134
  end
40
135
 
41
136
  # Returns target email address for email notification.
@@ -56,6 +151,16 @@ module ActivityNotification
56
151
  resolve_value(_notification_email_allowed, notifiable, key)
57
152
  end
58
153
 
154
+ # Returns if sending batch notification email is allowed for the target from configured field or overriden method.
155
+ # This method is able to be overriden.
156
+ #
157
+ # @param [Object] notifiable_type Notifiable type of the notifications
158
+ # @param [String] key Key of the notifications
159
+ # @return [Boolean] If sending batch notification email is allowed for the target
160
+ def batch_notification_email_allowed?(notifiable_type, key)
161
+ resolve_value(_batch_notification_email_allowed, notifiable_type, key)
162
+ end
163
+
59
164
  # Returns if current resource signed in with Devise is authenticated for the notification.
60
165
  # This method is able to be overriden.
61
166
  #
@@ -81,21 +186,33 @@ module ActivityNotification
81
186
  # Returns count of unopened notifications of the target.
82
187
  #
83
188
  # @param [Hash] options Options for notification index
84
- # @option options [Integer] :limit (nil) Limit to query for notifications
189
+ # @option options [Integer] :limit (nil) Limit to query for notifications
190
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
191
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
192
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
193
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
194
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
195
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
196
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
85
197
  # @return [Integer] Count of unopened notifications of the target
86
- # @todo Add filter and reverse options
87
198
  def unopened_notification_count(options = {})
88
- unopened_notification_index(options).count
199
+ target_notifications = _unopened_notification_index(options)
200
+ target_notifications.present? ? target_notifications.count : 0
89
201
  end
90
202
 
91
203
  # Returns if the target has unopened notifications.
92
204
  #
93
205
  # @param [Hash] options Options for notification index
94
- # @option options [Integer] :limit (nil) Limit to query for notifications
206
+ # @option options [Integer] :limit (nil) Limit to query for notifications
207
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
208
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
209
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
210
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
211
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
212
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
95
213
  # @return [Boolean] If the target has unopened notifications
96
- # @todo Add filter and reverse options
97
214
  def has_unopened_notifications?(options = {})
98
- unopened_notification_index(options).present?
215
+ _unopened_notification_index(options).present?
99
216
  end
100
217
 
101
218
  # Gets automatically arranged notification index of the target.
@@ -108,9 +225,17 @@ module ActivityNotification
108
225
  # @notifications = @user.notification_index
109
226
  #
110
227
  # @param [Hash] options Options for notification index
111
- # @option options [Integer] :limit (nil) Limit to query for notifications
228
+ # @option options [Integer] :limit (nil) Limit to query for notifications
229
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
230
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
231
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
232
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
233
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
234
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
235
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
236
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
237
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
112
238
  # @return [Array<Notificaion>] Notification index of the target
113
- # @todo Add filter and reverse options
114
239
  def notification_index(options = {})
115
240
  arrange_notification_index(method(:unopened_notification_index),
116
241
  method(:opened_notification_index),
@@ -123,13 +248,19 @@ module ActivityNotification
123
248
  # @notifications = @user.unopened_notification_index
124
249
  #
125
250
  # @param [Hash] options Options for notification index
126
- # @option options [Integer] :limit (nil) Limit to query for notifications
251
+ # @option options [Integer] :limit (nil) Limit to query for notifications
252
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
253
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
254
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
255
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
256
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
257
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
258
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
259
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
260
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
127
261
  # @return [Array<Notificaion>] Unopened notification index of the target
128
- # @todo Add filter and reverse options
129
262
  def unopened_notification_index(options = {})
130
- options[:limit].present? ?
131
- notifications.unopened_index.limit(options[:limit]) :
132
- notifications.unopened_index
263
+ arrange_single_notification_index(method(:_unopened_notification_index), options)
133
264
  end
134
265
 
135
266
  # Gets opened notification index of the target.
@@ -138,12 +269,19 @@ module ActivityNotification
138
269
  # @notifications = @user.opened_notification_index(10)
139
270
  #
140
271
  # @param [Hash] options Options for notification index
141
- # @option options [Integer] :limit (ActivityNotification.config.opened_index_limit) Limit to query for notifications
272
+ # @option options [Integer] :limit (nil) Limit to query for notifications
273
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
274
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
275
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
276
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
277
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
278
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
279
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
280
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
281
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
142
282
  # @return [Array<Notificaion>] Opened notification index of the target
143
- # @todo Add filter and reverse options
144
283
  def opened_notification_index(options = {})
145
- limit = options[:limit] || ActivityNotification.config.opened_index_limit
146
- notifications.opened_index(limit)
284
+ arrange_single_notification_index(method(:_opened_notification_index), options)
147
285
  end
148
286
 
149
287
  # Generates notifications to this target.
@@ -190,9 +328,20 @@ module ActivityNotification
190
328
  # @notifications = @user.notification_index_with_attributes
191
329
  #
192
330
  # @param [Hash] options Options for notification index
193
- # @option options [Integer] :limit (nil) Limit to query for notifications
331
+ # @option options [Boolean] :send_later (false) If it sends notification email asynchronously
332
+ # @option options [String, Symbol] :fallback (:batch_default) Fallback template to use when MissingTemplate is raised
333
+ # @option options [String] :batch_key (nil) Key of the batch notification email, a key of the first notification will be used if not specified
334
+ # @option options [Integer] :limit (nil) Limit to query for notifications
335
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
336
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
337
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
338
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
339
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
340
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
341
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
342
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
343
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
194
344
  # @return [Array<Notificaion>] Notification index of the target with attributes
195
- # @todo Add filter and reverse options
196
345
  def notification_index_with_attributes(options = {})
197
346
  arrange_notification_index(method(:unopened_notification_index_with_attributes),
198
347
  method(:opened_notification_index_with_attributes),
@@ -205,11 +354,19 @@ module ActivityNotification
205
354
  # @notifications = @user.unopened_notification_index_with_attributes
206
355
  #
207
356
  # @param [Hash] options Options for notification index
208
- # @option options [Integer] :limit (nil) Limit to query for notifications
357
+ # @option options [Integer] :limit (nil) Limit to query for notifications
358
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
359
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
360
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
361
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
362
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
363
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
364
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
365
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
366
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
209
367
  # @return [Array<Notificaion>] Unopened notification index of the target with attributes
210
- # @todo Add filter and reverse options
211
368
  def unopened_notification_index_with_attributes(options = {})
212
- include_attributes unopened_notification_index(options)
369
+ include_attributes _unopened_notification_index(options)
213
370
  end
214
371
 
215
372
  # Gets opened notification index of the target with including attributes like target, notifiable, group and notifier.
@@ -218,15 +375,92 @@ module ActivityNotification
218
375
  # @notifications = @user.opened_notification_index_with_attributes(10)
219
376
  #
220
377
  # @param [Hash] options Options for notification index
221
- # @option options [Integer] :limit (ActivityNotification.config.opened_index_limit) Limit to query for notifications
378
+ # @option options [Integer] :limit (nil) Limit to query for notifications
379
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
380
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
381
+ # @option options [Boolean] :as_latest_group_member (false) If grouped notification will be shown as the latest group member (default is shown as the earliest member)
382
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
383
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
384
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
385
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
386
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
387
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
222
388
  # @return [Array<Notificaion>] Opened notification index of the target with attributes
223
- # @todo Add filter and reverse options
224
389
  def opened_notification_index_with_attributes(options = {})
225
- include_attributes opened_notification_index(options)
390
+ include_attributes _opened_notification_index(options)
226
391
  end
227
392
 
393
+ # Sends notification email to the target.
394
+ #
395
+ # @param [Hash] options Options for notification email
396
+ # @option options [Boolean] :send_later If it sends notification email asynchronously
397
+ # @option options [String, Symbol] :fallback (:default) Fallback template to use when MissingTemplate is raised
398
+ # @return [Mail::Message|ActionMailer::DeliveryJob] Email message or its delivery job, return NilClass for wrong target
399
+ def send_notification_email(notification, options = {})
400
+ if notification.target == self
401
+ notification.send_notification_email(options)
402
+ end
403
+ end
404
+
405
+ # Sends batch notification email to the target.
406
+ #
407
+ # @param [Array<Notification>] notifications Target notifications to send batch notification email
408
+ # @param [Hash] options Options for notification email
409
+ # @option options [Boolean] :send_later (false) If it sends notification email asynchronously
410
+ # @option options [String, Symbol] :fallback (:batch_default) Fallback template to use when MissingTemplate is raised
411
+ # @option options [String] :batch_key (nil) Key of the batch notification email, a key of the first notification will be used if not specified
412
+ # @return [Mail::Message|ActionMailer::DeliveryJob|NilClass] Email message or its delivery job, return NilClass for wrong target
413
+ def send_batch_notification_email(notifications, options = {})
414
+ return if notifications.blank?
415
+ if notifications.map{ |n| n.target }.uniq == [self]
416
+ Notification.send_batch_notification_email(self, notifications, options)
417
+ end
418
+ end
419
+
420
+
228
421
  private
229
422
 
423
+ # Gets unopened notification index of the target as ActiveRecord.
424
+ # @api private
425
+ #
426
+ # @param [Hash] options Options for notification index
427
+ # @option options [Integer] :limit (nil) Limit to query for notifications
428
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
429
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
430
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
431
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
432
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
433
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
434
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
435
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
436
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Unopened notification index of the target
437
+ def _unopened_notification_index(options = {})
438
+ reverse = options[:reverse] || false
439
+ with_group_members = options[:with_group_members] || false
440
+ target_index = notifications.unopened_index(reverse, with_group_members).filtered_by_options(options)
441
+ options[:limit].present? ? target_index.limit(options[:limit]) : target_index
442
+ end
443
+
444
+ # Gets opened notification index of the target as ActiveRecord.
445
+ #
446
+ # @param [Hash] options Options for notification index
447
+ # @option options [Integer] :limit (nil) Limit to query for notifications
448
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
449
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
450
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
451
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
452
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
453
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
454
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
455
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
456
+ # @return [Array<Notificaion>] Opened notification index of the target
457
+ def _opened_notification_index(options = {})
458
+ limit = options[:limit] || ActivityNotification.config.opened_index_limit
459
+ reverse = options[:reverse] || false
460
+ with_group_members = options[:with_group_members] || false
461
+ notifications.opened_index(limit, reverse, with_group_members).filtered_by_options(options)
462
+ end
463
+
230
464
  # Includes attributes like target, notifiable, group or notifier from the notification index.
231
465
  # When group member exists in the notification index, group will be included in addition to target, notifiable and or notifier.
232
466
  # Otherwise, target, notifiable and or notifier will be include without group.
@@ -244,6 +478,28 @@ module ActivityNotification
244
478
  end
245
479
  end
246
480
 
481
+ # Gets arranged single notification index of the target.
482
+ # @api private
483
+ #
484
+ # @param [Method] loading_index_method Method to load index
485
+ # @param [Hash] options Options for notification index
486
+ # @option options [Integer] :limit (nil) Limit to query for notifications
487
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
488
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
489
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
490
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
491
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
492
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
493
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
494
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
495
+ # @return [Array<Notificaion>] Notification index of the target
496
+ def arrange_single_notification_index(loading_index_method, options = {})
497
+ as_latest_group_member = options[:as_latest_group_member] || false
498
+ as_latest_group_member ?
499
+ loading_index_method.call(options).map{ |n| n.latest_group_member } :
500
+ loading_index_method.call(options).to_a
501
+ end
502
+
247
503
  # Gets automatically arranged notification index of the target.
248
504
  # When the target have unopened notifications, it returns unopened notifications first.
249
505
  # Additionaly, it returns opened notifications unless unopened index size overs the limit.
@@ -253,7 +509,15 @@ module ActivityNotification
253
509
  # @param [Method] loading_unopened_index_method Method to load unopened index
254
510
  # @param [Method] loading_opened_index_method Method to load opened index
255
511
  # @param [Hash] options Options for notification index
256
- # @option options [Integer] :limit (nil) Limit to query for notifications
512
+ # @option options [Integer] :limit (nil) Limit to query for notifications
513
+ # @option options [Boolean] :reverse (false) If notification index will be ordered as earliest first
514
+ # @option options [Boolean] :with_group_members (false) If notification index will include group members
515
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
516
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
517
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
518
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
519
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
520
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
257
521
  # @return [Array<Notificaion>] Notification index of the target
258
522
  def arrange_notification_index(loading_unopened_index_method, loading_opened_index_method, options = {})
259
523
  # When the target have unopened notifications