activity_notification 1.2.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +15 -5
  3. data/CHANGELOG.md +13 -2
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +78 -71
  6. data/README.md +64 -43
  7. data/activity_notification.gemspec +5 -4
  8. data/app/controllers/activity_notification/notifications_controller.rb +1 -1
  9. data/app/controllers/activity_notification/subscriptions_controller.rb +1 -1
  10. data/gemfiles/Gemfile.rails-4.2 +1 -1
  11. data/gemfiles/Gemfile.rails-4.2.lock +71 -62
  12. data/gemfiles/Gemfile.rails-5.0 +1 -1
  13. data/gemfiles/Gemfile.rails-5.0.lock +74 -67
  14. data/lib/activity_notification.rb +9 -2
  15. data/lib/activity_notification/apis/notification_api.rb +142 -76
  16. data/lib/activity_notification/apis/subscription_api.rb +72 -0
  17. data/lib/activity_notification/config.rb +12 -0
  18. data/lib/activity_notification/models/concerns/notifiable.rb +56 -6
  19. data/lib/activity_notification/models/concerns/notifier.rb +8 -1
  20. data/lib/activity_notification/models/concerns/subscriber.rb +13 -10
  21. data/lib/activity_notification/models/concerns/target.rb +7 -5
  22. data/lib/activity_notification/models/notification.rb +2 -270
  23. data/lib/activity_notification/models/subscription.rb +3 -101
  24. data/lib/activity_notification/optional_targets/base.rb +5 -1
  25. data/lib/activity_notification/orm/active_record.rb +16 -0
  26. data/lib/activity_notification/orm/active_record/notification.rb +252 -0
  27. data/lib/activity_notification/orm/active_record/subscription.rb +52 -0
  28. data/lib/activity_notification/orm/mongoid.rb +63 -0
  29. data/lib/activity_notification/orm/mongoid/notification.rb +255 -0
  30. data/lib/activity_notification/orm/mongoid/subscription.rb +73 -0
  31. data/lib/activity_notification/rails/routes.rb +7 -3
  32. data/lib/activity_notification/renderable.rb +5 -1
  33. data/lib/activity_notification/roles/acts_as_notifiable.rb +4 -18
  34. data/lib/activity_notification/version.rb +1 -1
  35. data/lib/generators/activity_notification/install_generator.rb +3 -5
  36. data/lib/generators/templates/activity_notification.rb +9 -4
  37. data/spec/concerns/apis/notification_api_spec.rb +27 -14
  38. data/spec/concerns/models/notifiable_spec.rb +10 -8
  39. data/spec/concerns/models/subscriber_spec.rb +12 -12
  40. data/spec/concerns/models/target_spec.rb +61 -51
  41. data/spec/controllers/subscriptions_controller_shared_examples.rb +0 -1
  42. data/spec/helpers/view_helpers_spec.rb +24 -7
  43. data/spec/models/notification_spec.rb +63 -52
  44. data/spec/models/subscription_spec.rb +6 -3
  45. data/spec/optional_targets/amazon_sns_spec.rb +8 -5
  46. data/spec/optional_targets/base_spec.rb +3 -1
  47. data/spec/optional_targets/slack_spec.rb +5 -5
  48. data/spec/rails_app/app/models/comment.rb +1 -1
  49. data/spec/rails_app/app/views/activity_notification/notifications/users/overriden/custom/_test.html.erb +1 -0
  50. data/spec/rails_app/config/application.rb +7 -0
  51. data/spec/rails_app/config/initializers/activity_notification.rb +5 -0
  52. data/spec/rails_app/config/mongoid.yml +13 -0
  53. data/spec/rails_app/db/seeds.rb +1 -1
  54. data/spec/rails_app/lib/custom_optional_targets/console_output.rb +7 -4
  55. data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +3 -0
  56. data/spec/roles/acts_as_notifiable_spec.rb +77 -16
  57. data/spec/spec_helper.rb +7 -0
  58. metadata +38 -14
@@ -8,7 +8,10 @@ module ActivityNotification
8
8
  attr_accessor :view_context
9
9
 
10
10
  # Initialize method to create view context in this OptionalTarget instance
11
- def initialize
11
+ # @param [Hash] options Options for initializing target
12
+ # @option options [Boolean] :skip_initializing_target (false) Whether skip calling initialize_target method
13
+ # @option options [Hash] others Options for initializing target
14
+ def initialize(options = {})
12
15
  @view_context = ActionView::Base.new(ActionController::Base.view_paths, {})
13
16
  @view_context.class_eval do
14
17
  include Rails.application.routes.url_helpers
@@ -16,6 +19,7 @@ module ActivityNotification
16
19
  ActionMailer::Base.default_url_options
17
20
  end
18
21
  end
22
+ initialize_target(options) unless options.delete(:skip_initializing_target)
19
23
  end
20
24
 
21
25
  # Returns demodulized symbol class name as optional target name
@@ -0,0 +1,16 @@
1
+ module ActivityNotification
2
+ module Association
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ # Defines has_many association with ActivityNotification models.
7
+ # @return [ActiveRecord_AssociationRelation<Object>] Database query of associated model instances
8
+ def has_many_records(name, options = {})
9
+ has_many name, options
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ require_relative 'active_record/notification.rb'
16
+ require_relative 'active_record/subscription.rb'
@@ -0,0 +1,252 @@
1
+ require 'activity_notification/apis/notification_api'
2
+
3
+ module ActivityNotification
4
+ module ORM
5
+ module ActiveRecord
6
+ # Notification model implementation generated by ActivityNotification.
7
+ class Notification < ::ActiveRecord::Base
8
+ include Common
9
+ include Renderable
10
+ include NotificationApi
11
+ # @deprecated ActivityNotification.config.table_name as of 1.1.0
12
+ self.table_name = ActivityNotification.config.table_name || ActivityNotification.config.notification_table_name
13
+ # self.table_name = ActivityNotification.config.notification_table_name
14
+
15
+ # Belongs to target instance of this notification as polymorphic association.
16
+ # @scope instance
17
+ # @return [Object] Target instance of this notification
18
+ belongs_to :target, polymorphic: true
19
+
20
+ # Belongs to notifiable instance of this notification as polymorphic association.
21
+ # @scope instance
22
+ # @return [Object] Notifiable instance of this notification
23
+ belongs_to :notifiable, polymorphic: true
24
+
25
+ # Belongs to group instance of this notification as polymorphic association.
26
+ # @scope instance
27
+ # @return [Object] Group instance of this notification
28
+ belongs_to :group, polymorphic: true
29
+
30
+ # Belongs to group owner notification instance of this notification.
31
+ # Only group member instance has :group_owner value.
32
+ # Group owner instance has nil as :group_owner association.
33
+ # @scope instance
34
+ # @return [Notification] Group owner notification instance of this notification
35
+ belongs_to :group_owner, { class_name: "ActivityNotification::Notification" }.merge(Rails::VERSION::MAJOR >= 5 ? { optional: true } : {})
36
+
37
+ # Has many group member notification instances of this notification.
38
+ # Only group owner instance has :group_members value.
39
+ # Group member instance has nil as :group_members association.
40
+ # @scope instance
41
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of the group member notification instances of this notification
42
+ has_many :group_members, class_name: "ActivityNotification::Notification", foreign_key: :group_owner_id
43
+
44
+ # Belongs to :otifier instance of this notification.
45
+ # @scope instance
46
+ # @return [Object] Notifier instance of this notification
47
+ belongs_to :notifier, polymorphic: true
48
+
49
+ # Serialize parameters Hash
50
+ serialize :parameters, Hash
51
+
52
+ validates :target, presence: true
53
+ validates :notifiable, presence: true
54
+ validates :key, presence: true
55
+
56
+ # Selects group owner notifications only.
57
+ # @scope class
58
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
59
+ scope :group_owners_only, -> { where(group_owner_id: nil) }
60
+
61
+ # Selects group member notifications only.
62
+ # @scope class
63
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
64
+ scope :group_members_only, -> { where.not(group_owner_id: nil) }
65
+
66
+ # Selects unopened notifications only.
67
+ # @scope class
68
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
69
+ scope :unopened_only, -> { where(opened_at: nil) }
70
+
71
+ # Selects opened notifications only without limit.
72
+ # Be careful to get too many records with this method.
73
+ # @scope class
74
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
75
+ scope :opened_only!, -> { where.not(opened_at: nil) }
76
+
77
+ # Selects opened notifications only with limit.
78
+ # @scope class
79
+ # @param [Integer] limit Limit to query for opened notifications
80
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
81
+ scope :opened_only, ->(limit) { opened_only!.limit(limit) }
82
+
83
+ # Selects group member notifications in unopened_index.
84
+ # @scope class
85
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
86
+ scope :unopened_index_group_members_only, -> { where(group_owner_id: unopened_index.map(&:id)) }
87
+
88
+ # Selects group member notifications in opened_index.
89
+ # @scope class
90
+ # @param [Integer] limit Limit to query for opened notifications
91
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
92
+ scope :opened_index_group_members_only, ->(limit) { where(group_owner_id: opened_index(limit).map(&:id)) }
93
+
94
+ # Selects notifications within expiration.
95
+ # @scope class
96
+ # @param [ActiveSupport::Duration] expiry_delay Expiry period of notifications
97
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
98
+ scope :within_expiration_only, ->(expiry_delay) { where("created_at > ?", expiry_delay.ago) }
99
+
100
+ # Selects group member notifications with specified group owner ids.
101
+ # @scope class
102
+ # @param [Array<String>] owner_ids Array of group owner ids
103
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
104
+ scope :group_members_of_owner_ids_only, ->(owner_ids) { where(group_owner_id: owner_ids) }
105
+
106
+ # Selects filtered notifications by target instance.
107
+ # ActivityNotification::Notification.filtered_by_target(@user)
108
+ # is the same as
109
+ # @user.notifications
110
+ # @scope class
111
+ # @param [Object] target Target instance for filter
112
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
113
+ scope :filtered_by_target, ->(target) { where(target: target) }
114
+
115
+ # Selects filtered notifications by notifiable instance.
116
+ # @example Get filtered unopened notificatons of the @user for @comment as notifiable
117
+ # @notifications = @user.notifications.unopened_only.filtered_by_instance(@comment)
118
+ # @scope class
119
+ # @param [Object] notifiable Notifiable instance for filter
120
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
121
+ scope :filtered_by_instance, ->(notifiable) { where(notifiable: notifiable) }
122
+
123
+ # Selects filtered notifications by notifiable_type.
124
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class
125
+ # @notifications = @user.notifications.unopened_only.filtered_by_type('Comment')
126
+ # @scope class
127
+ # @param [String] notifiable_type Notifiable type for filter
128
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
129
+ scope :filtered_by_type, ->(notifiable_type) { where(notifiable_type: notifiable_type) }
130
+
131
+ # Selects filtered notifications by group instance.
132
+ # @example Get filtered unopened notificatons of the @user for @article as group
133
+ # @notifications = @user.notifications.unopened_only.filtered_by_group(@article)
134
+ # @scope class
135
+ # @param [Object] group Group instance for filter
136
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
137
+ scope :filtered_by_group, ->(group) { where(group: group) }
138
+
139
+ # Includes target instance with query for notifications.
140
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with target
141
+ scope :with_target, -> { includes(:target) }
142
+
143
+ # Includes notifiable instance with query for notifications.
144
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with notifiable
145
+ scope :with_notifiable, -> { includes(:notifiable) }
146
+
147
+ # Includes group instance with query for notifications.
148
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with group
149
+ scope :with_group, -> { includes(:group) }
150
+
151
+ # Includes group owner instances with query for notifications.
152
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with group owner
153
+ scope :with_group_owner, -> { includes(:group_owner) }
154
+
155
+ # Includes group member instances with query for notifications.
156
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with group members
157
+ scope :with_group_members, -> { includes(:group_members) }
158
+
159
+ # Includes notifier instance with query for notifications.
160
+ # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with notifier
161
+ scope :with_notifier, -> { includes(:notifier) }
162
+
163
+ # Returns latest notification instance.
164
+ # @return [Notification] Latest notification instance
165
+ def self.latest
166
+ latest_order.first
167
+ end
168
+
169
+ # Returns earliest notification instance.
170
+ # @return [Notification] Earliest notification instance
171
+ def self.earliest
172
+ earliest_order.first
173
+ end
174
+
175
+ # Selects unique keys from query for notifications.
176
+ # @return [Array<String>] Array of notification unique keys
177
+ def self.uniq_keys
178
+ select(:key).distinct.pluck(:key)
179
+ end
180
+
181
+ protected
182
+
183
+ # Returns count of group members of the unopened notification.
184
+ # This method is designed to cache group by query result to avoid N+1 call.
185
+ # @api protected
186
+ #
187
+ # @return [Integer] Count of group members of the unopened notification
188
+ def unopened_group_member_count
189
+ # Cache group by query result to avoid N+1 call
190
+ unopened_group_member_counts = target.notifications
191
+ .unopened_index_group_members_only
192
+ .group(:group_owner_id)
193
+ .count
194
+ unopened_group_member_counts[id] || 0
195
+ end
196
+
197
+ # Returns count of group members of the opened notification.
198
+ # This method is designed to cache group by query result to avoid N+1 call.
199
+ # @api protected
200
+ #
201
+ # @return [Integer] Count of group members of the opened notification
202
+ def opened_group_member_count(limit = ActivityNotification.config.opened_index_limit)
203
+ # Cache group by query result to avoid N+1 call
204
+ opened_group_member_counts = target.notifications
205
+ .opened_index_group_members_only(limit)
206
+ .group(:group_owner_id)
207
+ .count
208
+ count = opened_group_member_counts[id] || 0
209
+ count > limit ? limit : count
210
+ end
211
+
212
+ # Returns count of group member notifiers of the unopened notification not including group owner notifier.
213
+ # This method is designed to cache group by query result to avoid N+1 call.
214
+ # @api protected
215
+ #
216
+ # @return [Integer] Count of group member notifiers of the unopened notification
217
+ def unopened_group_member_notifier_count
218
+ # Cache group by query result to avoid N+1 call
219
+ unopened_group_member_notifier_counts = target.notifications
220
+ .unopened_index_group_members_only
221
+ .includes(:group_owner)
222
+ .where('group_owners_notifications.notifier_type = notifications.notifier_type')
223
+ .where.not('group_owners_notifications.notifier_id = notifications.notifier_id')
224
+ .references(:group_owner)
225
+ .group(:group_owner_id, :notifier_type)
226
+ .count('distinct notifications.notifier_id')
227
+ unopened_group_member_notifier_counts[[id, notifier_type]] || 0
228
+ end
229
+
230
+ # Returns count of group member notifiers of the opened notification not including group owner notifier.
231
+ # This method is designed to cache group by query result to avoid N+1 call.
232
+ # @api protected
233
+ #
234
+ # @return [Integer] Count of group member notifiers of the opened notification
235
+ def opened_group_member_notifier_count(limit = ActivityNotification.config.opened_index_limit)
236
+ # Cache group by query result to avoid N+1 call
237
+ opened_group_member_notifier_counts = target.notifications
238
+ .opened_index_group_members_only(limit)
239
+ .includes(:group_owner)
240
+ .where('group_owners_notifications.notifier_type = notifications.notifier_type')
241
+ .where.not('group_owners_notifications.notifier_id = notifications.notifier_id')
242
+ .references(:group_owner)
243
+ .group(:group_owner_id, :notifier_type)
244
+ .count('distinct notifications.notifier_id')
245
+ count = opened_group_member_notifier_counts[[id, notifier_type]] || 0
246
+ count > limit ? limit : count
247
+ end
248
+
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,52 @@
1
+ require 'activity_notification/apis/subscription_api'
2
+
3
+ module ActivityNotification
4
+ module ORM
5
+ module ActiveRecord
6
+ # Subscription model implementation generated by ActivityNotification.
7
+ class Subscription < ::ActiveRecord::Base
8
+ include SubscriptionApi
9
+ self.table_name = ActivityNotification.config.subscription_table_name
10
+
11
+ # Belongs to target instance of this subscription as polymorphic association.
12
+ # @scope instance
13
+ # @return [Object] Target instance of this subscription
14
+ belongs_to :target, polymorphic: true
15
+
16
+ # Serialize parameters Hash
17
+ serialize :optional_targets, Hash
18
+
19
+ validates :target, presence: true
20
+ validates :key, presence: true
21
+ validates_inclusion_of :subscribing, in: [true, false]
22
+ validates_inclusion_of :subscribing_to_email, in: [true, false]
23
+ validate :subscribing_to_email_cannot_be_true_when_subscribing_is_false
24
+ validates :subscribed_at, presence: true, if: :subscribing
25
+ validates :unsubscribed_at, presence: true, unless: :subscribing
26
+ validates :subscribed_to_email_at, presence: true, if: :subscribing_to_email
27
+ validates :unsubscribed_to_email_at, presence: true, unless: :subscribing_to_email
28
+ validate :subscribing_to_optional_target_cannot_be_true_when_subscribing_is_false
29
+
30
+ # Selects filtered subscriptions by target instance.
31
+ # ActivityNotification::Subscription.filtered_by_target(@user)
32
+ # is the same as
33
+ # @user.subscriptions
34
+ # @scope class
35
+ # @param [Object] target Target instance for filter
36
+ # @return [ActiveRecord_AssociationRelation<Subscription>] Database query of filtered subscriptions
37
+ scope :filtered_by_target, ->(target) { where(target: target) }
38
+
39
+ # Includes target instance with query for subscriptions.
40
+ # @return [ActiveRecord_AssociationRelation<Subscription>] Database query of subscriptions with target
41
+ scope :with_target, -> { includes(:target) }
42
+
43
+ # Selects unique keys from query for subscriptions.
44
+ # @return [Array<String>] Array of subscription unique keys
45
+ def self.uniq_keys
46
+ select(:key).distinct.pluck(:key)
47
+ end
48
+
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,63 @@
1
+ module ActivityNotification
2
+ module Association
3
+ extend ActiveSupport::Concern
4
+
5
+ class_methods do
6
+ # Defines has_many association with ActivityNotification models.
7
+ # @return [Mongoid::Criteria<Object>] Database query of associated model instances
8
+ def has_many_records(name, options = {})
9
+ has_many_polymorphic_xdb_records name, options
10
+ end
11
+
12
+ # Defines polymorphic belongs_to association with models in other database.
13
+ def belongs_to_polymorphic_xdb_record(name, _options = {})
14
+ association_name = name.to_s.singularize.underscore
15
+ id_field, type_field = "#{association_name}_id", "#{association_name}_type"
16
+ field id_field, type: Integer
17
+ field type_field, type: String
18
+
19
+ self.instance_eval do
20
+ define_method(name) do |reload = false|
21
+ reload and self.instance_variable_set("@#{name}", nil)
22
+ if self.instance_variable_get("@#{name}").blank?
23
+ if (class_name = self.send(type_field)).present?
24
+ object_class = class_name.classify.constantize
25
+ self.instance_variable_set("@#{name}", object_class.where(object_class.primary_key => self.send(id_field)).first)
26
+ end
27
+ end
28
+ self.instance_variable_get("@#{name}")
29
+ end
30
+
31
+ define_method("#{name}=") do |new_instance|
32
+ if new_instance.nil? then instance_id, instance_type = nil, nil else instance_id, instance_type = new_instance.id, new_instance.class.name end
33
+ self.send("#{id_field}=", instance_id)
34
+ self.send("#{type_field}=", instance_type)
35
+ self.instance_variable_set("@#{name}", nil)
36
+ end
37
+ end
38
+ end
39
+
40
+ # Defines polymorphic has_many association with models in other database.
41
+ # @todo Add dependent option
42
+ def has_many_polymorphic_xdb_records(name, options = {})
43
+ association_name = options[:as] || name.to_s.underscore
44
+ id_field, type_field = "#{association_name}_id", "#{association_name}_type"
45
+ object_name = options[:class_name] || name.to_s.singularize.camelize
46
+ object_class = object_name.classify.constantize
47
+
48
+ self.instance_eval do
49
+ define_method(name) do |reload = false|
50
+ reload and self.instance_variable_set("@#{name}", nil)
51
+ if self.instance_variable_get("@#{name}").blank?
52
+ self.instance_variable_set("@#{name}", object_class.where(id_field => self.id, type_field => self.class.name))
53
+ end
54
+ self.instance_variable_get("@#{name}")
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ require_relative 'mongoid/notification.rb'
63
+ require_relative 'mongoid/subscription.rb'
@@ -0,0 +1,255 @@
1
+ require 'mongoid'
2
+ require 'activity_notification/apis/notification_api'
3
+
4
+ module ActivityNotification
5
+ module ORM
6
+ module Mongoid
7
+ # Notification model implementation generated by ActivityNotification.
8
+ class Notification
9
+ include ::Mongoid::Document
10
+ include ::Mongoid::Timestamps
11
+ include ::Mongoid::Attributes::Dynamic
12
+ include GlobalID::Identification
13
+ include Common
14
+ include Renderable
15
+ include Association
16
+ include NotificationApi
17
+ store_in collection: ActivityNotification.config.notification_table_name
18
+
19
+ # Belongs to target instance of this notification as polymorphic association.
20
+ # @scope instance
21
+ # @return [Object] Target instance of this notification
22
+ belongs_to_polymorphic_xdb_record :target
23
+
24
+ # Belongs to notifiable instance of this notification as polymorphic association.
25
+ # @scope instance
26
+ # @return [Object] Notifiable instance of this notification
27
+ belongs_to_polymorphic_xdb_record :notifiable
28
+
29
+ # Belongs to group instance of this notification as polymorphic association.
30
+ # @scope instance
31
+ # @return [Object] Group instance of this notification
32
+ belongs_to_polymorphic_xdb_record :group
33
+
34
+ field :key, type: String
35
+ field :parameters, type: Hash, default: {}
36
+ field :opened_at, type: DateTime
37
+ field :group_owner_id, type: String
38
+
39
+ # Belongs to group owner notification instance of this notification.
40
+ # Only group member instance has :group_owner value.
41
+ # Group owner instance has nil as :group_owner association.
42
+ # @scope instance
43
+ # @return [Notification] Group owner notification instance of this notification
44
+ belongs_to :group_owner, { class_name: "ActivityNotification::Notification" }.merge(Rails::VERSION::MAJOR >= 5 ? { optional: true } : {})
45
+
46
+ # Has many group member notification instances of this notification.
47
+ # Only group owner instance has :group_members value.
48
+ # Group member instance has nil as :group_members association.
49
+ # @scope instance
50
+ # @return [Mongoid::Criteria<Notificaion>] Database query of the group member notification instances of this notification
51
+ has_many :group_members, class_name: "ActivityNotification::Notification", foreign_key: :group_owner_id
52
+
53
+ # Belongs to :otifier instance of this notification.
54
+ # @scope instance
55
+ # @return [Object] Notifier instance of this notification
56
+ belongs_to_polymorphic_xdb_record :notifier
57
+
58
+ validates :target, presence: true
59
+ validates :notifiable, presence: true
60
+ validates :key, presence: true
61
+
62
+ # Selects filtered notifications by type of the object.
63
+ # Filtering with ActivityNotification::Notification is defined as default scope.
64
+ # @return [Mongoid::Criteria<Notification>] Database query of filtered notifications
65
+ default_scope -> { where(_type: "ActivityNotification::Notification") }
66
+
67
+ # Selects group owner notifications only.
68
+ # @scope class
69
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
70
+ scope :group_owners_only, -> { where(:group_owner_id.exists => false) }
71
+
72
+ # Selects group member notifications only.
73
+ # @scope class
74
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
75
+ scope :group_members_only, -> { where(:group_owner_id.exists => true) }
76
+
77
+ # Selects unopened notifications only.
78
+ # @scope class
79
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
80
+ scope :unopened_only, -> { where(:opened_at.exists => false) }
81
+
82
+ # Selects opened notifications only without limit.
83
+ # Be careful to get too many records with this method.
84
+ # @scope class
85
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
86
+ scope :opened_only!, -> { where(:opened_at.exists => true) }
87
+
88
+ # Selects opened notifications only with limit.
89
+ # @scope class
90
+ # @param [Integer] limit Limit to query for opened notifications
91
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
92
+ scope :opened_only, ->(limit) { limit == 0 ? none : opened_only!.limit(limit) }
93
+
94
+ # Selects group member notifications in unopened_index.
95
+ # @scope class
96
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
97
+ scope :unopened_index_group_members_only, -> { where(:group_owner_id.in => unopened_index.map(&:id)) }
98
+
99
+ # Selects group member notifications in opened_index.
100
+ # @scope class
101
+ # @param [Integer] limit Limit to query for opened notifications
102
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
103
+ scope :opened_index_group_members_only, ->(limit) { where(:group_owner_id.in => opened_index(limit).map(&:id)) }
104
+
105
+ # Selects notifications within expiration.
106
+ # @scope class
107
+ # @param [ActiveSupport::Duration] expiry_delay Expiry period of notifications
108
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
109
+ scope :within_expiration_only, ->(expiry_delay) { where(:created_at.gt => expiry_delay.ago) }
110
+
111
+ # Selects group member notifications with specified group owner ids.
112
+ # @scope class
113
+ # @param [Array<String>] owner_ids Array of group owner ids
114
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
115
+ scope :group_members_of_owner_ids_only, ->(owner_ids) { where(:group_owner_id.in => owner_ids) }
116
+
117
+ # Selects filtered notifications by target instance.
118
+ # ActivityNotification::Notification.filtered_by_target(@user)
119
+ # is the same as
120
+ # @user.notifications
121
+ # @scope class
122
+ # @param [Object] target Target instance for filter
123
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
124
+ scope :filtered_by_target, ->(target) { target.present? ? where(target_id: target.id, target_type: target.class.name) : none }
125
+
126
+ # Selects filtered notifications by notifiable instance.
127
+ # @example Get filtered unopened notificatons of the @user for @comment as notifiable
128
+ # @notifications = @user.notifications.unopened_only.filtered_by_instance(@comment)
129
+ # @scope class
130
+ # @param [Object] notifiable Notifiable instance for filter
131
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
132
+ scope :filtered_by_instance, ->(notifiable) { notifiable.present? ? where(notifiable_id: notifiable.id, notifiable_type: notifiable.class.name) : none }
133
+
134
+ # Selects filtered notifications by group instance.
135
+ # @example Get filtered unopened notificatons of the @user for @article as group
136
+ # @notifications = @user.notifications.unopened_only.filtered_by_group(@article)
137
+ # @scope class
138
+ # @param [Object] group Group instance for filter
139
+ # @return [Mongoid::Criteria<Notificaion>] Database query of filtered notifications
140
+ scope :filtered_by_group, ->(group) {
141
+ group.present? ?
142
+ where(group_id: group.id, group_type: group.class.name) :
143
+ any_of({ :group_id.exists => false, :group_type.exists => false }, { group_id: nil, group_type: nil })
144
+ }
145
+
146
+ # Includes target instance with query for notifications.
147
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with target
148
+ scope :with_target, -> { }
149
+
150
+ # Includes notifiable instance with query for notifications.
151
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with notifiable
152
+ scope :with_notifiable, -> { }
153
+
154
+ # Includes group instance with query for notifications.
155
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with group
156
+ scope :with_group, -> { }
157
+
158
+ # Includes group owner instances with query for notifications.
159
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with group owner
160
+ scope :with_group_owner, -> { }
161
+
162
+ # Includes group member instances with query for notifications.
163
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with group members
164
+ scope :with_group_members, -> { }
165
+
166
+ # Includes notifier instance with query for notifications.
167
+ # @return [Mongoid::Criteria<Notificaion>] Database query of notifications with notifier
168
+ scope :with_notifier, -> { }
169
+
170
+ # Dummy reload method for test of notifications.
171
+ scope :reload, -> { }
172
+
173
+ # Returns latest notification instance.
174
+ # @return [Notification] Latest notification instance
175
+ def self.latest
176
+ latest_order.first
177
+ end
178
+
179
+ # Returns earliest notification instance.
180
+ # @return [Notification] Earliest notification instance
181
+ def self.earliest
182
+ earliest_order.first
183
+ end
184
+
185
+ # Selects unique keys from query for notifications.
186
+ # @return [Array<String>] Array of notification unique keys
187
+ def self.uniq_keys
188
+ # distinct method cannot keep original sort
189
+ # distinct(:key)
190
+ pluck(:key).uniq
191
+ end
192
+
193
+ # Returns if the notification is group owner.
194
+ # Calls NotificationApi#group_owner? as super method.
195
+ # @return [Boolean] If the notification is group owner
196
+ def group_owner?
197
+ super
198
+ end
199
+
200
+ protected
201
+
202
+ # Returns count of group members of the unopened notification.
203
+ # This method is designed to cache group by query result to avoid N+1 call.
204
+ # @api protected
205
+ # @todo Avoid N+1 call
206
+ #
207
+ # @return [Integer] Count of group members of the unopened notification
208
+ def unopened_group_member_count
209
+ group_members.unopened_only.count
210
+ end
211
+
212
+ # Returns count of group members of the opened notification.
213
+ # This method is designed to cache group by query result to avoid N+1 call.
214
+ # @api protected
215
+ # @todo Avoid N+1 call
216
+ #
217
+ # @return [Integer] Count of group members of the opened notification
218
+ def opened_group_member_count(limit = ActivityNotification.config.opened_index_limit)
219
+ limit == 0 and return 0
220
+ group_members.opened_only(limit).to_a.length #.count(true)
221
+ end
222
+
223
+ # Returns count of group member notifiers of the unopened notification not including group owner notifier.
224
+ # This method is designed to cache group by query result to avoid N+1 call.
225
+ # @api protected
226
+ # @todo Avoid N+1 call
227
+ #
228
+ # @return [Integer] Count of group member notifiers of the unopened notification
229
+ def unopened_group_member_notifier_count
230
+ group_members.unopened_only
231
+ .where(notifier_type: notifier_type)
232
+ .where(:notifier_id.ne => notifier_id)
233
+ .distinct(:notifier_id)
234
+ .count
235
+ end
236
+
237
+ # Returns count of group member notifiers of the opened notification not including group owner notifier.
238
+ # This method is designed to cache group by query result to avoid N+1 call.
239
+ # @api protected
240
+ # @todo Avoid N+1 call
241
+ #
242
+ # @return [Integer] Count of group member notifiers of the opened notification
243
+ def opened_group_member_notifier_count(limit = ActivityNotification.config.opened_index_limit)
244
+ limit == 0 and return 0
245
+ group_members.opened_only(limit)
246
+ .where(notifier_type: notifier_type)
247
+ .where(:notifier_id.ne => notifier_id)
248
+ .distinct(:notifier_id)
249
+ .to_a.length #.count(true)
250
+ end
251
+
252
+ end
253
+ end
254
+ end
255
+ end