activity_notification 1.0.0 → 1.0.1

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