activity_notification 1.2.1 → 1.3.0

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