activity_notification 1.7.1 → 2.0.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 (97) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +3 -0
  3. data/.travis.yml +16 -2
  4. data/CHANGELOG.md +22 -2
  5. data/Gemfile +7 -0
  6. data/Procfile +2 -0
  7. data/README.md +366 -32
  8. data/Rakefile +19 -10
  9. data/activity_notification.gemspec +5 -3
  10. data/app/channels/activity_notification/notification_channel.rb +37 -0
  11. data/app/channels/activity_notification/notification_with_devise_channel.rb +51 -0
  12. data/app/controllers/activity_notification/notifications_controller.rb +1 -1
  13. data/app/controllers/activity_notification/subscriptions_controller.rb +1 -1
  14. data/app/jobs/activity_notification/notify_all_job.rb +16 -0
  15. data/app/jobs/activity_notification/notify_job.rb +17 -0
  16. data/app/jobs/activity_notification/notify_to_job.rb +16 -0
  17. data/app/views/activity_notification/notifications/default/_default_without_grouping.html.erb +1 -1
  18. data/app/views/activity_notification/notifications/default/index.html.erb +55 -2
  19. data/bin/_dynamodblocal +4 -0
  20. data/{scripts → bin}/bundle_update.sh +1 -0
  21. data/bin/deploy_on_heroku.sh +14 -0
  22. data/bin/install_dynamodblocal.sh +5 -0
  23. data/bin/start_dynamodblocal.sh +47 -0
  24. data/bin/stop_dynamodblocal.sh +34 -0
  25. data/gemfiles/Gemfile.rails-4.2 +1 -0
  26. data/gemfiles/Gemfile.rails-5.0 +2 -0
  27. data/gemfiles/Gemfile.rails-5.1 +1 -0
  28. data/gemfiles/Gemfile.rails-5.2 +1 -0
  29. data/gemfiles/Gemfile.rails-6.0.rc +21 -0
  30. data/lib/activity_notification.rb +1 -0
  31. data/lib/activity_notification/apis/notification_api.rb +289 -136
  32. data/lib/activity_notification/apis/subscription_api.rb +80 -53
  33. data/lib/activity_notification/common.rb +3 -3
  34. data/lib/activity_notification/config.rb +89 -33
  35. data/lib/activity_notification/controllers/common_controller.rb +19 -7
  36. data/lib/activity_notification/helpers/errors.rb +4 -0
  37. data/lib/activity_notification/helpers/view_helpers.rb +1 -1
  38. data/lib/activity_notification/models/concerns/notifiable.rb +61 -53
  39. data/lib/activity_notification/models/concerns/subscriber.rb +7 -6
  40. data/lib/activity_notification/models/concerns/target.rb +73 -28
  41. data/lib/activity_notification/optional_targets/base.rb +2 -2
  42. data/lib/activity_notification/orm/active_record/notification.rb +4 -23
  43. data/lib/activity_notification/orm/dynamoid.rb +495 -0
  44. data/lib/activity_notification/orm/dynamoid/extension.rb +184 -0
  45. data/lib/activity_notification/orm/dynamoid/notification.rb +189 -0
  46. data/lib/activity_notification/orm/dynamoid/subscription.rb +82 -0
  47. data/lib/activity_notification/orm/mongoid.rb +4 -1
  48. data/lib/activity_notification/orm/mongoid/notification.rb +8 -25
  49. data/lib/activity_notification/orm/mongoid/subscription.rb +1 -1
  50. data/lib/activity_notification/roles/acts_as_notifiable.rb +33 -5
  51. data/lib/activity_notification/roles/acts_as_target.rb +62 -9
  52. data/lib/activity_notification/version.rb +1 -1
  53. data/lib/generators/templates/activity_notification.rb +30 -7
  54. data/lib/tasks/activity_notification_tasks.rake +14 -4
  55. data/spec/channels/notification_channel_shared_examples.rb +59 -0
  56. data/spec/channels/notification_channel_spec.rb +50 -0
  57. data/spec/channels/notification_with_devise_channel_spec.rb +99 -0
  58. data/spec/concerns/apis/notification_api_spec.rb +2 -2
  59. data/spec/concerns/apis/subscription_api_spec.rb +2 -2
  60. data/spec/concerns/models/notifiable_spec.rb +72 -7
  61. data/spec/concerns/models/subscriber_spec.rb +53 -49
  62. data/spec/concerns/models/target_spec.rb +135 -13
  63. data/spec/config_spec.rb +41 -1
  64. data/spec/controllers/notifications_controller_shared_examples.rb +7 -3
  65. data/spec/controllers/subscriptions_controller_shared_examples.rb +7 -3
  66. data/spec/helpers/view_helpers_spec.rb +12 -10
  67. data/spec/models/dummy/dummy_group_spec.rb +4 -0
  68. data/spec/models/dummy/dummy_notifiable_spec.rb +4 -0
  69. data/spec/models/dummy/dummy_notifier_spec.rb +4 -0
  70. data/spec/models/dummy/dummy_subscriber_spec.rb +3 -0
  71. data/spec/models/dummy/dummy_target_spec.rb +4 -0
  72. data/spec/models/notification_spec.rb +164 -45
  73. data/spec/models/subscription_spec.rb +69 -14
  74. data/spec/orm/dynamoid_spec.rb +115 -0
  75. data/spec/rails_app/app/assets/javascripts/application.js +2 -1
  76. data/spec/rails_app/app/assets/javascripts/cable.js +12 -0
  77. data/spec/rails_app/app/controllers/comments_controller.rb +3 -4
  78. data/spec/rails_app/app/models/admin.rb +6 -4
  79. data/spec/rails_app/app/models/article.rb +2 -2
  80. data/spec/rails_app/app/models/comment.rb +17 -5
  81. data/spec/rails_app/app/models/user.rb +5 -3
  82. data/spec/rails_app/app/views/activity_notification/notifications/users/overridden/custom/_test.html.erb +1 -0
  83. data/spec/rails_app/config/application.rb +6 -1
  84. data/spec/rails_app/config/cable.yml +8 -0
  85. data/spec/rails_app/config/dynamoid.rb +5 -0
  86. data/spec/rails_app/config/environment.rb +4 -1
  87. data/spec/rails_app/config/environments/production.rb +1 -1
  88. data/spec/rails_app/config/initializers/activity_notification.rb +30 -7
  89. data/spec/rails_app/config/locales/activity_notification.en.yml +2 -0
  90. data/spec/rails_app/db/seeds.rb +21 -5
  91. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +12 -4
  92. data/spec/roles/acts_as_notifiable_spec.rb +2 -2
  93. data/spec/roles/acts_as_target_spec.rb +1 -1
  94. data/spec/spec_helper.rb +15 -8
  95. metadata +67 -20
  96. data/spec/rails_app/app/models/.keep +0 -0
  97. data/spec/rails_app/app/views/activity_notification/notifications/users/overriden/custom/_test.html.erb +0 -1
@@ -3,6 +3,7 @@ source 'https://rubygems.org'
3
3
  gemspec path: '../'
4
4
 
5
5
  gem 'rails', '~> 5.0.0'
6
+ gem 'sqlite3', '~> 1.3.13'
6
7
 
7
8
  group :development do
8
9
  gem 'bullet'
@@ -10,6 +11,7 @@ end
10
11
 
11
12
  group :test do
12
13
  gem 'rails-controller-testing'
14
+ gem 'action-cable-testing'
13
15
  gem 'ammeter'
14
16
  gem 'timecop'
15
17
  gem 'coveralls', require: false
@@ -10,6 +10,7 @@ end
10
10
 
11
11
  group :test do
12
12
  gem 'rails-controller-testing'
13
+ gem 'action-cable-testing'
13
14
  gem 'ammeter'
14
15
  gem 'timecop'
15
16
  gem 'coveralls', require: false
@@ -10,6 +10,7 @@ end
10
10
 
11
11
  group :test do
12
12
  gem 'rails-controller-testing'
13
+ gem 'action-cable-testing'
13
14
  gem 'ammeter'
14
15
  gem 'timecop'
15
16
  gem 'coveralls', require: false
@@ -0,0 +1,21 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec path: '../'
4
+
5
+ gem 'rails', '~> 6.0.0.rc2'
6
+
7
+ group :development do
8
+ gem 'bullet'
9
+ end
10
+
11
+ group :test do
12
+ #TODO https://github.com/rails/rails/issues/35417
13
+ gem 'rspec-rails', git: 'https://github.com/rspec/rspec-rails', branch: '4-0-dev'
14
+
15
+ gem 'rails-controller-testing'
16
+ gem 'ammeter'
17
+ gem 'timecop'
18
+ gem 'coveralls', require: false
19
+ end
20
+
21
+ gem 'dotenv-rails', groups: [:development, :test]
@@ -56,6 +56,7 @@ module ActivityNotification
56
56
  end
57
57
 
58
58
  # Load ActivityNotification helpers
59
+ require 'activity_notification/helpers/errors'
59
60
  require 'activity_notification/helpers/polymorphic_helpers'
60
61
  require 'activity_notification/helpers/view_helpers'
61
62
  require 'activity_notification/controllers/common_controller'
@@ -10,130 +10,187 @@ module ActivityNotification
10
10
  # Defines mailer class to send notification
11
11
  set_notification_mailer
12
12
 
13
- # Selects all notification index.
14
- # ActivityNotification::Notification.all_index!
15
- # is defined same as
16
- # ActivityNotification::Notification.group_owners_only.latest_order
17
- # @scope class
18
- # @example Get all notification index of the @user
19
- # @notifications = @user.notifications.all_index!
20
- # @notifications = @user.notifications.group_owners_only.latest_order
21
- # @param [Boolean] reverse If notification index will be ordered as earliest first
22
- # @param [Boolean] with_group_members If notification index will include group members
23
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
24
- scope :all_index!, ->(reverse = false, with_group_members = false) {
25
- target_index = with_group_members ? self : group_owners_only
26
- reverse ? target_index.earliest_order : target_index.latest_order
27
- }
28
-
29
- # Selects unopened notification index.
30
- # ActivityNotification::Notification.unopened_index
31
- # is defined same as
32
- # ActivityNotification::Notification.unopened_only.group_owners_only.latest_order
33
- # @scope class
34
- # @example Get unopened notificaton index of the @user
35
- # @notifications = @user.notifications.unopened_index
36
- # @notifications = @user.notifications.unopened_only.group_owners_only.latest_order
37
- # @param [Boolean] reverse If notification index will be ordered as earliest first
38
- # @param [Boolean] with_group_members If notification index will include group members
39
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
40
- scope :unopened_index, ->(reverse = false, with_group_members = false) {
41
- target_index = with_group_members ? unopened_only : unopened_only.group_owners_only
42
- reverse ? target_index.earliest_order : target_index.latest_order
43
- }
44
-
45
- # Selects unopened notification index.
46
- # ActivityNotification::Notification.opened_index(limit)
47
- # is defined same as
48
- # ActivityNotification::Notification.opened_only(limit).group_owners_only.latest_order
49
- # @scope class
50
- # @example Get unopened notificaton index of the @user with limit 10
51
- # @notifications = @user.notifications.opened_index(10)
52
- # @notifications = @user.notifications.opened_only(10).group_owners_only.latest_order
53
- # @param [Integer] limit Limit to query for opened notifications
54
- # @param [Boolean] reverse If notification index will be ordered as earliest first
55
- # @param [Boolean] with_group_members If notification index will include group members
56
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
57
- scope :opened_index, ->(limit, reverse = false, with_group_members = false) {
58
- target_index = with_group_members ? opened_only(limit) : opened_only(limit).group_owners_only
59
- reverse ? target_index.earliest_order : target_index.latest_order
60
- }
61
-
62
- # Selects filtered notifications by target_type.
63
- # @example Get filtered unopened notificatons of User as target type
64
- # @notifications = ActivityNotification.Notification.unopened_only.filtered_by_target_type('User')
65
- # @scope class
66
- # @param [String] target_type Target type for filter
67
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
68
- scope :filtered_by_target_type, ->(target_type) { where(target_type: target_type) }
69
-
70
- # Selects filtered notifications by notifiable_type.
71
- # @example Get filtered unopened notificatons of the @user for Comment notifiable class
72
- # @notifications = @user.notifications.unopened_only.filtered_by_type('Comment')
73
- # @scope class
74
- # @param [String] notifiable_type Notifiable type for filter
75
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
76
- scope :filtered_by_type, ->(notifiable_type) { where(notifiable_type: notifiable_type) }
77
-
78
- # Selects filtered notifications by key.
79
- # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
80
- # @notifications = @user.notifications.unopened_only.filtered_by_key('comment.reply')
81
- # @scope class
82
- # @param [String] key Key of the notification for filter
83
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
84
- scope :filtered_by_key, ->(key) { where(key: key) }
85
-
86
- # Selects filtered notifications by notifiable_type, group or key with filter options.
87
- # @example Get filtered unopened notificatons of the @user for Comment notifiable class
88
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment' })
89
- # @example Get filtered unopened notificatons of the @user for @article as group
90
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group: @article })
91
- # @example Get filtered unopened notificatons of the @user for Article instance id=1 as group
92
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group_type: 'Article', filtered_by_group_id: '1' })
93
- # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
94
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_key: 'comment.reply' })
95
- # @example Get filtered unopened notificatons of the @user for Comment notifiable class with key 'comment.reply'
96
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment', filtered_by_key: 'comment.reply' })
97
- # @example Get custom filtered notificatons of the @user
98
- # @notifications = @user.notifications.unopened_only.filtered_by_options({ custom_filter: ["created_at >= ?", time.hour.ago] })
99
- # @scope class
100
- # @param [Hash] options Options for filter
101
- # @option options [String] :filtered_by_type (nil) Notifiable type for filter
102
- # @option options [Object] :filtered_by_group (nil) Group instance for filter
103
- # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
104
- # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
105
- # @option options [String] :filtered_by_key (nil) Key of the notification for filter
106
- # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago])
107
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
108
- scope :filtered_by_options, ->(options = {}) {
109
- options = ActivityNotification.cast_to_indifferent_hash(options)
110
- filtered_notifications = all
111
- if options.has_key?(:filtered_by_type)
112
- filtered_notifications = filtered_notifications.filtered_by_type(options[:filtered_by_type])
113
- end
114
- if options.has_key?(:filtered_by_group)
115
- filtered_notifications = filtered_notifications.filtered_by_group(options[:filtered_by_group])
116
- end
117
- if options.has_key?(:filtered_by_group_type) && options.has_key?(:filtered_by_group_id)
118
- filtered_notifications = filtered_notifications
119
- .where(group_type: options[:filtered_by_group_type], group_id: options[:filtered_by_group_id])
13
+ # :only-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
14
+ # :only-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
15
+ # :except-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
16
+ # :except-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
17
+ unless ActivityNotification.config.orm == :dynamoid
18
+ # Selects all notification index.
19
+ # ActivityNotification::Notification.all_index!
20
+ # is defined same as
21
+ # ActivityNotification::Notification.group_owners_only.latest_order
22
+ # @scope class
23
+ # @example Get all notification index of the @user
24
+ # @notifications = @user.notifications.all_index!
25
+ # @notifications = @user.notifications.group_owners_only.latest_order
26
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
27
+ # @param [Boolean] with_group_members If notification index will include group members
28
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
29
+ scope :all_index!, ->(reverse = false, with_group_members = false) {
30
+ target_index = with_group_members ? self : group_owners_only
31
+ reverse ? target_index.earliest_order : target_index.latest_order
32
+ }
33
+
34
+ # Selects unopened notification index.
35
+ # ActivityNotification::Notification.unopened_index
36
+ # is defined same as
37
+ # ActivityNotification::Notification.unopened_only.group_owners_only.latest_order
38
+ # @scope class
39
+ # @example Get unopened notificaton index of the @user
40
+ # @notifications = @user.notifications.unopened_index
41
+ # @notifications = @user.notifications.unopened_only.group_owners_only.latest_order
42
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
43
+ # @param [Boolean] with_group_members If notification index will include group members
44
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
45
+ scope :unopened_index, ->(reverse = false, with_group_members = false) {
46
+ target_index = with_group_members ? unopened_only : unopened_only.group_owners_only
47
+ reverse ? target_index.earliest_order : target_index.latest_order
48
+ }
49
+
50
+ # Selects unopened notification index.
51
+ # ActivityNotification::Notification.opened_index(limit)
52
+ # is defined same as
53
+ # ActivityNotification::Notification.opened_only(limit).group_owners_only.latest_order
54
+ # @scope class
55
+ # @example Get unopened notificaton index of the @user with limit 10
56
+ # @notifications = @user.notifications.opened_index(10)
57
+ # @notifications = @user.notifications.opened_only(10).group_owners_only.latest_order
58
+ # @param [Integer] limit Limit to query for opened notifications
59
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
60
+ # @param [Boolean] with_group_members If notification index will include group members
61
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
62
+ scope :opened_index, ->(limit, reverse = false, with_group_members = false) {
63
+ target_index = with_group_members ? opened_only(limit) : opened_only(limit).group_owners_only
64
+ reverse ? target_index.earliest_order : target_index.latest_order
65
+ }
66
+
67
+ # Selects filtered notifications by target_type.
68
+ # @example Get filtered unopened notificatons of User as target type
69
+ # @notifications = ActivityNotification.Notification.unopened_only.filtered_by_target_type('User')
70
+ # @scope class
71
+ # @param [String] target_type Target type for filter
72
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
73
+ scope :filtered_by_target_type, ->(target_type) { where(target_type: target_type) }
74
+
75
+ # Selects filtered notifications by notifiable_type.
76
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class
77
+ # @notifications = @user.notifications.unopened_only.filtered_by_type('Comment')
78
+ # @scope class
79
+ # @param [String] notifiable_type Notifiable type for filter
80
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
81
+ scope :filtered_by_type, ->(notifiable_type) { where(notifiable_type: notifiable_type) }
82
+
83
+ # Selects filtered notifications by key.
84
+ # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
85
+ # @notifications = @user.notifications.unopened_only.filtered_by_key('comment.reply')
86
+ # @scope class
87
+ # @param [String] key Key of the notification for filter
88
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
89
+ scope :filtered_by_key, ->(key) { where(key: key) }
90
+
91
+ # Selects filtered notifications by notifiable_type, group or key with filter options.
92
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class
93
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment' })
94
+ # @example Get filtered unopened notificatons of the @user for @article as group
95
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group: @article })
96
+ # @example Get filtered unopened notificatons of the @user for Article instance id=1 as group
97
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group_type: 'Article', filtered_by_group_id: '1' })
98
+ # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
99
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_key: 'comment.reply' })
100
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class with key 'comment.reply'
101
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment', filtered_by_key: 'comment.reply' })
102
+ # @example Get custom filtered notificatons of the @user
103
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ custom_filter: ["created_at >= ?", time.hour.ago] })
104
+ # @scope class
105
+ # @param [Hash] options Options for filter
106
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
107
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
108
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
109
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
110
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
111
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ["created_at >= ?", time.hour.ago] with ActiveRecord or {:created_at.gt => time.hour.ago} with Mongoid)
112
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
113
+ scope :filtered_by_options, ->(options = {}) {
114
+ options = ActivityNotification.cast_to_indifferent_hash(options)
115
+ filtered_notifications = all
116
+ if options.has_key?(:filtered_by_type)
117
+ filtered_notifications = filtered_notifications.filtered_by_type(options[:filtered_by_type])
118
+ end
119
+ if options.has_key?(:filtered_by_group)
120
+ filtered_notifications = filtered_notifications.filtered_by_group(options[:filtered_by_group])
121
+ end
122
+ if options.has_key?(:filtered_by_group_type) && options.has_key?(:filtered_by_group_id)
123
+ filtered_notifications = filtered_notifications
124
+ .where(group_type: options[:filtered_by_group_type], group_id: options[:filtered_by_group_id])
125
+ end
126
+ if options.has_key?(:filtered_by_key)
127
+ filtered_notifications = filtered_notifications.filtered_by_key(options[:filtered_by_key])
128
+ end
129
+ if options.has_key?(:custom_filter)
130
+ filtered_notifications = filtered_notifications.where(options[:custom_filter])
131
+ end
132
+ filtered_notifications
133
+ }
134
+
135
+ # Orders by latest (newest) first as created_at: :desc.
136
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of notifications ordered by latest first
137
+ scope :latest_order, -> { order(created_at: :desc) }
138
+
139
+ # Orders by earliest (older) first as created_at: :asc.
140
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of notifications ordered by earliest first
141
+ scope :earliest_order, -> { order(created_at: :asc) }
142
+
143
+ # Orders by latest (newest) first as created_at: :desc.
144
+ # This method is to be overridden in implementation for each ORM.
145
+ # @param [Boolean] reverse If notifications will be ordered as earliest first
146
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of ordered notifications
147
+ scope :latest_order!, ->(reverse = false) { reverse ? earliest_order : latest_order }
148
+
149
+ # Orders by earliest (older) first as created_at: :asc.
150
+ # This method is to be overridden in implementation for each ORM.
151
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of notifications ordered by earliest first
152
+ scope :earliest_order!, -> { earliest_order }
153
+
154
+ # Returns latest notification instance.
155
+ # @return [Notification] Latest notification instance
156
+ def self.latest
157
+ latest_order.first
120
158
  end
121
- if options.has_key?(:filtered_by_key)
122
- filtered_notifications = filtered_notifications.filtered_by_key(options[:filtered_by_key])
159
+
160
+ # Returns earliest notification instance.
161
+ # @return [Notification] Earliest notification instance
162
+ def self.earliest
163
+ earliest_order.first
123
164
  end
124
- if options.has_key?(:custom_filter)
125
- filtered_notifications = filtered_notifications.where(options[:custom_filter])
165
+
166
+ # Returns latest notification instance.
167
+ # This method is to be overridden in implementation for each ORM.
168
+ # @return [Notification] Latest notification instance
169
+ def self.latest!
170
+ latest
126
171
  end
127
- filtered_notifications
128
- }
129
172
 
130
- # Orders by latest (newest) first as created_at: :desc.
131
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of notifications ordered by latest first
132
- scope :latest_order, -> { order(created_at: :desc) }
173
+ # Returns earliest notification instance.
174
+ # This method is to be overridden in implementation for each ORM.
175
+ # @return [Notification] Earliest notification instance
176
+ def self.earliest!
177
+ earliest
178
+ end
133
179
 
134
- # Orders by earliest (older) first as created_at: :asc.
135
- # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of notifications ordered by earliest first
136
- scope :earliest_order, -> { order(created_at: :asc) }
180
+ # Selects unique keys from query for notifications.
181
+ # @return [Array<String>] Array of notification unique keys
182
+ def self.uniq_keys
183
+ ## select method cannot be chained with order by other columns like created_at
184
+ # select(:key).distinct.pluck(:key)
185
+ ## distinct method cannot keep original sort
186
+ # distinct(:key)
187
+ pluck(:key).uniq
188
+ end
189
+ end
190
+ # :only-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
191
+ # :only-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
192
+ # :except-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
193
+ # :except-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
137
194
  end
138
195
 
139
196
  class_methods do
@@ -160,6 +217,8 @@ module ActivityNotification
160
217
  # @option options [Boolean] :notify_later (false) Whether it generates notifications asynchronously
161
218
  # @option options [Boolean] :send_email (true) Whether it sends notification email
162
219
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
220
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcasts notification to ActionCable channel
221
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
163
222
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
164
223
  # @option options [Boolean] :pass_full_options (false) Whether it passes full options to notifiable.notification_targets, not a key only
165
224
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
@@ -198,6 +257,8 @@ module ActivityNotification
198
257
  # @option options [Hash] :parameters ({}) Additional parameters of the notifications
199
258
  # @option options [Boolean] :send_email (true) Whether it sends notification email
200
259
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
260
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcasts notification to ActionCable channel
261
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
201
262
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
202
263
  # @option options [Boolean] :pass_full_options (false) Whether it passes full options to notifiable.notification_targets, not a key only
203
264
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
@@ -224,6 +285,8 @@ module ActivityNotification
224
285
  # @option options [Boolean] :notify_later (false) Whether it generates notifications asynchronously
225
286
  # @option options [Boolean] :send_email (true) Whether it sends notification email
226
287
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
288
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcasts notification to ActionCable channel
289
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
227
290
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
228
291
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
229
292
  # @return [Array<Notificaion>] Array of generated notifications
@@ -251,6 +314,8 @@ module ActivityNotification
251
314
  # @option options [Hash] :parameters ({}) Additional parameters of the notifications
252
315
  # @option options [Boolean] :send_email (true) Whether it sends notification email
253
316
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
317
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcasts notification to ActionCable channel
318
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
254
319
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
255
320
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
256
321
  # @return [Array<Notificaion>] Array of generated notifications
@@ -275,6 +340,8 @@ module ActivityNotification
275
340
  # @option options [Boolean] :notify_later (false) Whether it generates notifications asynchronously
276
341
  # @option options [Boolean] :send_email (true) Whether it sends notification email
277
342
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
343
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcasts notification to ActionCable channel
344
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
278
345
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
279
346
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
280
347
  # @return [Notification] Generated notification instance
@@ -282,8 +349,9 @@ module ActivityNotification
282
349
  if options[:notify_later]
283
350
  notify_later_to(target, notifiable, options)
284
351
  else
285
- send_email = options.has_key?(:send_email) ? options[:send_email] : true
286
- send_later = options.has_key?(:send_later) ? options[:send_later] : true
352
+ send_email = options.has_key?(:send_email) ? options[:send_email] : true
353
+ send_later = options.has_key?(:send_later) ? options[:send_later] : true
354
+ broadcast_action_cable = options.has_key?(:broadcast_action_cable) ? options[:broadcast_action_cable] : true
287
355
  publish_optional_targets = options.has_key?(:publish_optional_targets) ? options[:publish_optional_targets] : true
288
356
  # Generate notification
289
357
  notification = generate_notification(target, notifiable, options)
@@ -291,6 +359,12 @@ module ActivityNotification
291
359
  if notification.present? && send_email
292
360
  notification.send_notification_email({ send_later: send_later })
293
361
  end
362
+ # Broadcast to ActionCable subscribers
363
+ if notification.present? && broadcast_action_cable
364
+ action_cable_rendering_options = options[:action_cable_rendering] || {}
365
+ action_cable_rendering_options[:fallback] = action_cable_rendering_options[:fallback] || :default
366
+ notification.broadcast_to_action_cable_channel(action_cable_rendering_options)
367
+ end
294
368
  # Publish to optional targets
295
369
  if notification.present? && publish_optional_targets
296
370
  notification.publish_to_optional_targets(options[:optional_targets] || {})
@@ -316,6 +390,8 @@ module ActivityNotification
316
390
  # @option options [Hash] :parameters ({}) Additional parameters of the notifications
317
391
  # @option options [Boolean] :send_email (true) Whether it sends notification email
318
392
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
393
+ # @option options [Boolean] :broadcast_action_cable (true) Whether it broadcast∂s notification to ActionCable channel
394
+ # @option options [Hash] :action_cable_rendering ({fallback: :default}) Options for rendering params used by ActionCable, e.g. {fallback: :text} or {fallback: :default} etc. See also Renderable#render.
319
395
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
320
396
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
321
397
  # @return [Notification] Generated notification instance
@@ -336,7 +412,7 @@ module ActivityNotification
336
412
  key = options[:key] || notifiable.default_notification_key
337
413
  if target.subscribes_to_notification?(key)
338
414
  # Store notification
339
- store_notification(target, notifiable, key, options)
415
+ notification = store_notification(target, notifiable, key, options)
340
416
  end
341
417
  end
342
418
 
@@ -366,7 +442,7 @@ module ActivityNotification
366
442
  # @param [Array<Notificaion>, ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] notifications Array or database query of the notifications to test member exists
367
443
  # @return [Boolean] If group member of the notifications exists
368
444
  def group_member_exists?(notifications)
369
- notifications.present? && group_members_of_owner_ids_only(notifications.map(&:id)).exists?
445
+ notifications.present? and group_members_of_owner_ids_only(notifications.map(&:id)).exists?
370
446
  end
371
447
 
372
448
  # Sends batch notification email to the target.
@@ -394,7 +470,7 @@ module ActivityNotification
394
470
  #
395
471
  # @return [Array<Notificaion>] Available options for kinds of notify methods
396
472
  def available_options
397
- [:key, :group, :parameters, :notifier, :send_email, :send_later].freeze
473
+ [:key, :group, :group_expiry_delay, :notifier, :parameters, :send_email, :send_later].freeze
398
474
  end
399
475
 
400
476
  # Defines mailer class to send notification
@@ -402,6 +478,25 @@ module ActivityNotification
402
478
  @@notification_mailer = ActivityNotification.config.mailer.constantize
403
479
  end
404
480
 
481
+ # Returns valid group owner within the expiration period
482
+ #
483
+ # @param [Object] target Target to send notifications
484
+ # @param [Object] notifiable Notifiable instance
485
+ # @param [String] key Key of the notification
486
+ # @param [Object] group Group unit of the notifications
487
+ # @param [ActiveSupport::Duration] group_expiry_delay Expiry period of a notification group
488
+ # @return [Notificaion] Valid group owner within the expiration period
489
+ def valid_group_owner(target, notifiable, key, group, group_expiry_delay)
490
+ return nil if group.blank?
491
+ # Bundle notification group by target, notifiable_type, group and key
492
+ # Different notifiable.id can be made in a same group
493
+ group_owner_notifications = filtered_by_target(target).filtered_by_type(notifiable.to_class_name).filtered_by_key(key)
494
+ .filtered_by_group(group).group_owners_only.unopened_only
495
+ group_expiry_delay.present? ?
496
+ group_owner_notifications.within_expiration_only(group_expiry_delay).earliest :
497
+ group_owner_notifications.earliest
498
+ end
499
+
405
500
  # Stores notifications to datastore
406
501
  # @api private
407
502
  def store_notification(target, notifiable, key, options = {})
@@ -412,20 +507,20 @@ module ActivityNotification
412
507
  parameters = options[:parameters] || {}
413
508
  parameters.merge!(options.except(*available_options))
414
509
  parameters.merge!(notifiable.notification_parameters(target_type, key))
510
+ group_owner = valid_group_owner(target, notifiable, key, group, group_expiry_delay)
415
511
 
416
- # Bundle notification group by target, notifiable_type, group and key
417
- # Different notifiable.id can be made in a same group
418
- group_owner_notifications = filtered_by_target(target).filtered_by_type(notifiable.to_class_name).filtered_by_key(key)
419
- .filtered_by_group(group).group_owners_only.unopened_only
420
- group_owner = group_expiry_delay.present? ?
421
- group_owner_notifications.within_expiration_only(group_expiry_delay).earliest :
422
- group_owner_notifications.earliest
423
- notification_fields = { target: target, notifiable: notifiable, key: key, group: group, parameters: parameters, notifier: notifier }
424
- notification_fields = notification_fields.merge(group_owner: group_owner) if group.present? && group_owner.present?
425
- create(notification_fields)
512
+ notification = new({ target: target, notifiable: notifiable, key: key, group: group, parameters: parameters, notifier: notifier, group_owner: group_owner })
513
+ notification.prepare_to_store.save
514
+ notification
426
515
  end
427
516
  end
428
517
 
518
+ # Returns prepared notification object to store
519
+ # @return [Object] prepared notification object to store
520
+ def prepare_to_store
521
+ self
522
+ end
523
+
429
524
  # Sends notification email to the target.
430
525
  #
431
526
  # @param [Hash] options Options for notification email
@@ -443,6 +538,64 @@ module ActivityNotification
443
538
  end
444
539
  end
445
540
 
541
+ # :only-rails5-plus#only-rails-with-callback-issue:
542
+ # :only-rails5-plus#only-rails-without-callback-issue:
543
+ # :only-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
544
+ # :only-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
545
+ if Rails::VERSION::MAJOR >= 5
546
+ # Broadcast to ActionCable subscribers
547
+ # @param [Hash] params Parameters for rendering notifications
548
+ # @option params [String, Symbol] :target (nil) Target type name to find template or i18n text
549
+ # @option params [String] :partial_root ("activity_notification/notifications/#{target}", controller.target_view_path, 'activity_notification/notifications/default') Partial template name
550
+ # @option params [String] :partial (self.key.tr('.', '/')) Root path of partial template
551
+ # @option params [String] :layout (nil) Layout template name
552
+ # @option params [String] :layout_root ('layouts') Root path of layout template
553
+ # @option params [String, Symbol] :fallback (nil) Fallback template to use when MissingTemplate is raised. Set :text to use i18n text as fallback.
554
+ # @option params [String] :filter (nil) Filter option to load notification index (Nothing as auto, 'opened' or 'unopened')
555
+ # @option params [String] :limit (nil) Limit to query for notifications
556
+ # @option params [String] :without_grouping ('false') If notification index will include group members
557
+ # @option params [String] :with_group_members ('false') If notification index will include group members
558
+ # @option params [String] :filtered_by_type (nil) Notifiable type for filter
559
+ # @option params [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
560
+ # @option params [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
561
+ # @option params [String] :filtered_by_key (nil) Key of the notification for filter
562
+ # @option params [Hash] others Parameters to be set as locals
563
+ def broadcast_to_action_cable_channel(params = {})
564
+ if target.notification_action_cable_allowed?(notifiable, key) &&
565
+ notifiable.notification_action_cable_allowed?(target, key)
566
+ target_channel_name = "#{ActivityNotification.config.notification_channel_prefix}_#{target_type}#{ActivityNotification.config.composite_key_delimiter}#{target_id}"
567
+ index_options = params.slice(:filter, :limit, :without_grouping, :with_group_members, :filtered_by_type, :filtered_by_group_type, :filtered_by_group_id, :filtered_by_key)
568
+ ActionCable.server.broadcast(target_channel_name,
569
+ id: id,
570
+ view: render(ActivityNotification::NotificationsController.renderer, params),
571
+ text: text(params),
572
+ notifiable_path: notifiable_path,
573
+ group_owner_id: group_owner_id,
574
+ group_owner_view: group_owner? ? nil : group_owner.render(ActivityNotification::NotificationsController.renderer, params),
575
+ unopened_notification_count: target.unopened_notification_count(index_options)
576
+ )
577
+ end
578
+ end
579
+ # :only-rails5-plus#only-rails-with-callback-issue:
580
+ # :only-rails5-plus#only-rails-without-callback-issue:
581
+ # :only-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
582
+ # :only-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
583
+ # :except-rails5-plus#only-rails-with-callback-issue:
584
+ # :except-rails5-plus#only-rails-without-callback-issue:
585
+ # :except-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
586
+ # :except-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
587
+ else
588
+ # Broadcast to ActionCable subscribers
589
+ # Do nothing with Rails < 5.0
590
+ # @param [Hash] params Parameters for rendering notifications
591
+ def broadcast_to_action_cable_channel(params = {})
592
+ end
593
+ end
594
+ # :except-rails5-plus#only-rails-with-callback-issue:
595
+ # :except-rails5-plus#only-rails-without-callback-issue:
596
+ # :except-rails5-plus#only-rails-with-callback-issue#except-dynamoid:
597
+ # :except-rails5-plus#only-rails-without-callback-issue#except-dynamoid:
598
+
446
599
  # Publishes notification to the optional targets.
447
600
  #
448
601
  # @param [Hash] options Options for optional targets
@@ -611,13 +764,13 @@ module ActivityNotification
611
764
  target.subscribes_to_optional_target?(key, optional_target_name)
612
765
  end
613
766
 
614
- # Returns optional_targets of the notification from configured field or overriden method.
767
+ # Returns optional_targets of the notification from configured field or overridden method.
615
768
  # @return [Array<ActivityNotification::OptionalTarget::Base>] Array of optional target instances
616
769
  def optional_targets
617
770
  notifiable.optional_targets(target.to_resources_name, key)
618
771
  end
619
772
 
620
- # Returns optional_target names of the notification from configured field or overriden method.
773
+ # Returns optional_target names of the notification from configured field or overridden method.
621
774
  # @return [Array<Symbol>] Array of optional target names
622
775
  def optional_target_names
623
776
  notifiable.optional_target_names(target.to_resources_name, key)