activity_notification 1.4.4 → 2.2.4

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 (271) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +22 -0
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  4. data/.github/pull_request_template.md +13 -0
  5. data/.github/workflows/build.yml +116 -0
  6. data/.gitignore +15 -3
  7. data/CHANGELOG.md +200 -1
  8. data/Gemfile +17 -2
  9. data/Procfile +2 -0
  10. data/README.md +168 -1033
  11. data/Rakefile +19 -10
  12. data/activity_notification.gemspec +14 -9
  13. data/app/channels/activity_notification/notification_api_channel.rb +12 -0
  14. data/app/channels/activity_notification/notification_api_with_devise_channel.rb +46 -0
  15. data/app/channels/activity_notification/notification_channel.rb +37 -0
  16. data/app/channels/activity_notification/notification_with_devise_channel.rb +51 -0
  17. data/app/controllers/activity_notification/apidocs_controller.rb +75 -0
  18. data/app/controllers/activity_notification/notifications_api_controller.rb +143 -0
  19. data/app/controllers/activity_notification/notifications_api_with_devise_controller.rb +7 -0
  20. data/app/controllers/activity_notification/notifications_controller.rb +60 -54
  21. data/app/controllers/activity_notification/subscriptions_api_controller.rb +197 -0
  22. data/app/controllers/activity_notification/subscriptions_api_with_devise_controller.rb +7 -0
  23. data/app/controllers/activity_notification/subscriptions_controller.rb +83 -73
  24. data/app/jobs/activity_notification/notify_all_job.rb +25 -0
  25. data/app/jobs/activity_notification/notify_job.rb +26 -0
  26. data/app/jobs/activity_notification/notify_to_job.rb +25 -0
  27. data/app/views/activity_notification/notifications/default/_default.html.erb +23 -23
  28. data/app/views/activity_notification/notifications/default/_default_without_grouping.html.erb +19 -19
  29. data/app/views/activity_notification/notifications/default/_index.html.erb +3 -3
  30. data/app/views/activity_notification/notifications/default/index.html.erb +60 -7
  31. data/app/views/activity_notification/notifications/default/open.js.erb +2 -2
  32. data/app/views/activity_notification/notifications/default/open_all.js.erb +2 -2
  33. data/app/views/activity_notification/notifications/default/show.html.erb +2 -2
  34. data/app/views/activity_notification/optional_targets/default/action_cable_channel/_default.html.erb +176 -0
  35. data/app/views/activity_notification/optional_targets/default/base/_default.text.erb +1 -1
  36. data/app/views/activity_notification/optional_targets/default/slack/_default.text.erb +1 -1
  37. data/app/views/activity_notification/subscriptions/default/_form.html.erb +2 -2
  38. data/app/views/activity_notification/subscriptions/default/_notification_keys.html.erb +5 -33
  39. data/app/views/activity_notification/subscriptions/default/_subscription.html.erb +8 -8
  40. data/app/views/activity_notification/subscriptions/default/index.html.erb +13 -9
  41. data/app/views/activity_notification/subscriptions/default/show.html.erb +3 -3
  42. data/app/views/activity_notification/subscriptions/default/subscribe.js.erb +1 -1
  43. data/app/views/activity_notification/subscriptions/default/subscribe_to_email.js.erb +1 -1
  44. data/app/views/activity_notification/subscriptions/default/subscribe_to_optional_target.js.erb +1 -1
  45. data/app/views/activity_notification/subscriptions/default/unsubscribe.js.erb +1 -1
  46. data/app/views/activity_notification/subscriptions/default/unsubscribe_to_email.js.erb +1 -1
  47. data/app/views/activity_notification/subscriptions/default/unsubscribe_to_optional_target.js.erb +1 -1
  48. data/bin/_dynamodblocal +4 -0
  49. data/bin/bundle_update.sh +7 -0
  50. data/bin/deploy_on_heroku.sh +16 -0
  51. data/bin/install_dynamodblocal.sh +5 -0
  52. data/bin/start_dynamodblocal.sh +47 -0
  53. data/bin/stop_dynamodblocal.sh +34 -0
  54. data/docs/CODE_OF_CONDUCT.md +76 -0
  55. data/docs/CONTRIBUTING.md +36 -0
  56. data/docs/Functions.md +1146 -0
  57. data/docs/Setup.md +817 -0
  58. data/docs/Testing.md +148 -0
  59. data/gemfiles/Gemfile.rails-5.0 +8 -1
  60. data/gemfiles/Gemfile.rails-5.1 +7 -1
  61. data/gemfiles/Gemfile.rails-5.2 +24 -0
  62. data/gemfiles/Gemfile.rails-6.0 +23 -0
  63. data/gemfiles/Gemfile.rails-6.1 +22 -0
  64. data/gemfiles/Gemfile.rails-7.0 +25 -0
  65. data/lib/activity_notification/apis/notification_api.rb +356 -159
  66. data/lib/activity_notification/apis/subscription_api.rb +98 -59
  67. data/lib/activity_notification/apis/swagger.rb +6 -0
  68. data/lib/activity_notification/common.rb +18 -7
  69. data/lib/activity_notification/config.rb +176 -30
  70. data/lib/activity_notification/controllers/common_api_controller.rb +30 -0
  71. data/lib/activity_notification/controllers/common_controller.rb +47 -27
  72. data/lib/activity_notification/controllers/concerns/swagger/error_responses.rb +55 -0
  73. data/lib/activity_notification/controllers/concerns/swagger/notifications_api.rb +273 -0
  74. data/lib/activity_notification/controllers/concerns/swagger/notifications_parameters.rb +92 -0
  75. data/lib/activity_notification/controllers/concerns/swagger/subscriptions_api.rb +405 -0
  76. data/lib/activity_notification/controllers/concerns/swagger/subscriptions_parameters.rb +50 -0
  77. data/lib/activity_notification/controllers/devise_authentication_controller.rb +22 -5
  78. data/lib/activity_notification/gem_version.rb +14 -0
  79. data/lib/activity_notification/helpers/errors.rb +6 -0
  80. data/lib/activity_notification/helpers/view_helpers.rb +118 -28
  81. data/lib/activity_notification/mailers/helpers.rb +19 -12
  82. data/lib/activity_notification/models/concerns/notifiable.rb +142 -55
  83. data/lib/activity_notification/models/concerns/subscriber.rb +28 -13
  84. data/lib/activity_notification/models/concerns/swagger/error_schema.rb +36 -0
  85. data/lib/activity_notification/models/concerns/swagger/notification_schema.rb +209 -0
  86. data/lib/activity_notification/models/concerns/swagger/subscription_schema.rb +162 -0
  87. data/lib/activity_notification/models/concerns/target.rb +131 -32
  88. data/lib/activity_notification/models/notification.rb +1 -0
  89. data/lib/activity_notification/models/subscription.rb +1 -0
  90. data/lib/activity_notification/models.rb +23 -1
  91. data/lib/activity_notification/optional_targets/action_cable_api_channel.rb +69 -0
  92. data/lib/activity_notification/optional_targets/action_cable_channel.rb +68 -0
  93. data/lib/activity_notification/optional_targets/base.rb +9 -15
  94. data/lib/activity_notification/orm/active_record/notification.rb +23 -34
  95. data/lib/activity_notification/orm/active_record/subscription.rb +1 -1
  96. data/lib/activity_notification/orm/active_record.rb +1 -1
  97. data/lib/activity_notification/orm/dynamoid/extension.rb +262 -0
  98. data/lib/activity_notification/orm/dynamoid/notification.rb +224 -0
  99. data/lib/activity_notification/orm/dynamoid/subscription.rb +82 -0
  100. data/lib/activity_notification/orm/dynamoid.rb +530 -0
  101. data/lib/activity_notification/orm/mongoid/notification.rb +29 -28
  102. data/lib/activity_notification/orm/mongoid/subscription.rb +3 -3
  103. data/lib/activity_notification/orm/mongoid.rb +33 -1
  104. data/lib/activity_notification/rails/routes.rb +273 -60
  105. data/lib/activity_notification/renderable.rb +22 -7
  106. data/lib/activity_notification/roles/acts_as_notifiable.rb +64 -1
  107. data/lib/activity_notification/roles/acts_as_target.rb +99 -9
  108. data/lib/activity_notification/version.rb +1 -1
  109. data/lib/activity_notification.rb +14 -0
  110. data/lib/generators/activity_notification/controllers_generator.rb +2 -1
  111. data/lib/generators/templates/activity_notification.rb +61 -7
  112. data/lib/generators/templates/controllers/README +2 -2
  113. data/lib/generators/templates/controllers/notifications_api_controller.rb +31 -0
  114. data/lib/generators/templates/controllers/notifications_api_with_devise_controller.rb +31 -0
  115. data/lib/generators/templates/controllers/notifications_controller.rb +1 -37
  116. data/lib/generators/templates/controllers/notifications_with_devise_controller.rb +1 -45
  117. data/lib/generators/templates/controllers/subscriptions_api_controller.rb +61 -0
  118. data/lib/generators/templates/controllers/subscriptions_api_with_devise_controller.rb +61 -0
  119. data/lib/generators/templates/controllers/subscriptions_controller.rb +14 -37
  120. data/lib/generators/templates/controllers/subscriptions_with_devise_controller.rb +14 -45
  121. data/lib/generators/templates/migrations/migration.rb +5 -5
  122. data/lib/generators/templates/models/README +8 -4
  123. data/lib/generators/templates/models/notification.rb +1 -1
  124. data/lib/generators/templates/models/subscription.rb +1 -1
  125. data/lib/tasks/activity_notification_tasks.rake +14 -4
  126. data/package.json +8 -0
  127. data/spec/channels/notification_api_channel_shared_examples.rb +59 -0
  128. data/spec/channels/notification_api_channel_spec.rb +49 -0
  129. data/spec/channels/notification_api_with_devise_channel_spec.rb +76 -0
  130. data/spec/channels/notification_channel_shared_examples.rb +59 -0
  131. data/spec/channels/notification_channel_spec.rb +48 -0
  132. data/spec/channels/notification_with_devise_channel_spec.rb +97 -0
  133. data/spec/concerns/apis/notification_api_spec.rb +177 -12
  134. data/spec/concerns/apis/subscription_api_spec.rb +146 -4
  135. data/spec/concerns/common_spec.rb +25 -3
  136. data/spec/concerns/models/notifiable_spec.rb +161 -11
  137. data/spec/concerns/models/subscriber_spec.rb +253 -79
  138. data/spec/concerns/models/target_spec.rb +180 -47
  139. data/spec/concerns/renderable_spec.rb +35 -16
  140. data/spec/config_spec.rb +52 -1
  141. data/spec/controllers/controller_spec_utility.rb +100 -0
  142. data/spec/controllers/notifications_api_controller_shared_examples.rb +506 -0
  143. data/spec/controllers/notifications_api_controller_spec.rb +19 -0
  144. data/spec/controllers/notifications_api_with_devise_controller_spec.rb +60 -0
  145. data/spec/controllers/notifications_controller_shared_examples.rb +55 -76
  146. data/spec/controllers/notifications_controller_spec.rb +1 -2
  147. data/spec/controllers/notifications_with_devise_controller_spec.rb +14 -8
  148. data/spec/controllers/subscriptions_api_controller_shared_examples.rb +750 -0
  149. data/spec/controllers/subscriptions_api_controller_spec.rb +19 -0
  150. data/spec/controllers/subscriptions_api_with_devise_controller_spec.rb +60 -0
  151. data/spec/controllers/subscriptions_controller_shared_examples.rb +99 -121
  152. data/spec/controllers/subscriptions_controller_spec.rb +1 -2
  153. data/spec/controllers/subscriptions_with_devise_controller_spec.rb +14 -8
  154. data/spec/factories/notifications.rb +1 -1
  155. data/spec/factories/subscriptions.rb +3 -3
  156. data/spec/factories/users.rb +3 -3
  157. data/spec/generators/migration/migration_generator_spec.rb +29 -4
  158. data/spec/helpers/view_helpers_spec.rb +31 -21
  159. data/spec/jobs/notify_all_job_spec.rb +23 -0
  160. data/spec/jobs/notify_job_spec.rb +23 -0
  161. data/spec/jobs/notify_to_job_spec.rb +23 -0
  162. data/spec/mailers/mailer_spec.rb +42 -1
  163. data/spec/models/dummy/dummy_group_spec.rb +4 -0
  164. data/spec/models/dummy/dummy_notifiable_spec.rb +4 -0
  165. data/spec/models/dummy/dummy_notifier_spec.rb +4 -0
  166. data/spec/models/dummy/dummy_subscriber_spec.rb +3 -0
  167. data/spec/models/dummy/dummy_target_spec.rb +4 -0
  168. data/spec/models/notification_spec.rb +181 -45
  169. data/spec/models/subscription_spec.rb +77 -27
  170. data/spec/optional_targets/action_cable_api_channel_spec.rb +34 -0
  171. data/spec/optional_targets/action_cable_channel_spec.rb +41 -0
  172. data/spec/optional_targets/amazon_sns_spec.rb +0 -2
  173. data/spec/optional_targets/slack_spec.rb +0 -2
  174. data/spec/orm/dynamoid_spec.rb +115 -0
  175. data/spec/rails_app/Rakefile +9 -0
  176. data/spec/rails_app/app/assets/config/manifest.js +3 -0
  177. data/spec/rails_app/app/assets/javascripts/application.js +2 -1
  178. data/spec/rails_app/app/assets/javascripts/cable.js +12 -0
  179. data/spec/rails_app/app/controllers/admins_controller.rb +21 -0
  180. data/spec/rails_app/app/controllers/application_controller.rb +1 -1
  181. data/spec/rails_app/app/controllers/articles_controller.rb +6 -1
  182. data/spec/rails_app/app/controllers/comments_controller.rb +3 -1
  183. data/spec/rails_app/app/controllers/spa_controller.rb +7 -0
  184. data/spec/rails_app/app/controllers/users/notifications_controller.rb +0 -65
  185. data/spec/rails_app/app/controllers/users/notifications_with_devise_controller.rb +0 -73
  186. data/spec/rails_app/app/controllers/users/subscriptions_controller.rb +0 -77
  187. data/spec/rails_app/app/controllers/users/subscriptions_with_devise_controller.rb +0 -85
  188. data/spec/rails_app/app/controllers/users_controller.rb +26 -0
  189. data/spec/rails_app/app/javascript/App.vue +40 -0
  190. data/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue +82 -0
  191. data/spec/rails_app/app/javascript/components/Top.vue +98 -0
  192. data/spec/rails_app/app/javascript/components/notifications/Index.vue +200 -0
  193. data/spec/rails_app/app/javascript/components/notifications/Notification.vue +133 -0
  194. data/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue +122 -0
  195. data/spec/rails_app/app/javascript/components/subscriptions/Index.vue +279 -0
  196. data/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue +112 -0
  197. data/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue +141 -0
  198. data/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue +226 -0
  199. data/spec/rails_app/app/javascript/config/development.js +5 -0
  200. data/spec/rails_app/app/javascript/config/environment.js +7 -0
  201. data/spec/rails_app/app/javascript/config/production.js +5 -0
  202. data/spec/rails_app/app/javascript/config/test.js +5 -0
  203. data/spec/rails_app/app/javascript/packs/application.js +18 -0
  204. data/spec/rails_app/app/javascript/packs/spa.js +14 -0
  205. data/spec/rails_app/app/javascript/router/index.js +73 -0
  206. data/spec/rails_app/app/javascript/store/index.js +37 -0
  207. data/spec/rails_app/app/models/admin.rb +15 -10
  208. data/spec/rails_app/app/models/article.rb +25 -20
  209. data/spec/rails_app/app/models/comment.rb +27 -62
  210. data/spec/rails_app/app/models/dummy/dummy_base.rb +1 -0
  211. data/spec/rails_app/app/models/dummy/dummy_group.rb +9 -0
  212. data/spec/rails_app/app/models/dummy/dummy_notifiable.rb +1 -0
  213. data/spec/rails_app/app/models/dummy/dummy_notifiable_target.rb +27 -0
  214. data/spec/rails_app/app/models/dummy/dummy_notifier.rb +1 -0
  215. data/spec/rails_app/app/models/dummy/dummy_subscriber.rb +1 -0
  216. data/spec/rails_app/app/models/dummy/dummy_target.rb +1 -0
  217. data/spec/rails_app/app/models/user.rb +44 -18
  218. data/spec/rails_app/app/views/activity_notification/notifications/default/article/_update.html.erb +146 -0
  219. data/spec/rails_app/app/views/activity_notification/notifications/users/overridden/custom/_test.html.erb +1 -0
  220. data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +1 -1
  221. data/spec/rails_app/app/views/articles/index.html.erb +68 -20
  222. data/spec/rails_app/app/views/articles/show.html.erb +1 -1
  223. data/spec/rails_app/app/views/layouts/_header.html.erb +9 -3
  224. data/spec/rails_app/app/views/spa/index.html.erb +2 -0
  225. data/spec/rails_app/babel.config.js +72 -0
  226. data/spec/rails_app/bin/webpack +18 -0
  227. data/spec/rails_app/bin/webpack-dev-server +18 -0
  228. data/spec/rails_app/config/application.rb +26 -6
  229. data/spec/rails_app/config/cable.yml +8 -0
  230. data/spec/rails_app/config/database.yml +1 -1
  231. data/spec/rails_app/config/dynamoid.rb +13 -0
  232. data/spec/rails_app/config/environment.rb +5 -1
  233. data/spec/rails_app/config/environments/development.rb +5 -0
  234. data/spec/rails_app/config/environments/production.rb +7 -1
  235. data/spec/rails_app/config/environments/test.rb +7 -11
  236. data/spec/rails_app/config/initializers/activity_notification.rb +63 -9
  237. data/spec/rails_app/config/initializers/copy_it.aws.rb.template +6 -0
  238. data/spec/rails_app/config/initializers/devise_token_auth.rb +55 -0
  239. data/spec/rails_app/config/initializers/mysql.rb +9 -0
  240. data/spec/rails_app/config/locales/activity_notification.en.yml +10 -4
  241. data/spec/rails_app/config/routes.rb +42 -1
  242. data/spec/rails_app/config/webpack/development.js +5 -0
  243. data/spec/rails_app/config/webpack/environment.js +7 -0
  244. data/spec/rails_app/config/webpack/loaders/vue.js +6 -0
  245. data/spec/rails_app/config/webpack/production.js +5 -0
  246. data/spec/rails_app/config/webpack/test.js +5 -0
  247. data/spec/rails_app/config/webpacker.yml +97 -0
  248. data/spec/rails_app/db/migrate/{20160715050433_create_test_tables.rb → 20160716000000_create_test_tables.rb} +1 -1
  249. data/spec/rails_app/db/migrate/{20160715050420_create_activity_notification_tables.rb → 20181209000000_create_activity_notification_tables.rb} +3 -3
  250. data/spec/rails_app/db/migrate/20191201000000_add_tokens_to_users.rb +10 -0
  251. data/spec/rails_app/db/schema.rb +46 -43
  252. data/spec/rails_app/db/seeds.rb +28 -4
  253. data/spec/rails_app/lib/custom_optional_targets/raise_error.rb +14 -0
  254. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +14 -4
  255. data/spec/rails_app/package.json +23 -0
  256. data/spec/rails_app/postcss.config.js +12 -0
  257. data/spec/roles/acts_as_group_spec.rb +0 -2
  258. data/spec/roles/acts_as_notifiable_spec.rb +80 -20
  259. data/spec/roles/acts_as_notifier_spec.rb +0 -2
  260. data/spec/roles/acts_as_target_spec.rb +1 -5
  261. data/spec/spec_helper.rb +13 -11
  262. data/spec/version_spec.rb +31 -0
  263. metadata +306 -53
  264. data/.travis.yml +0 -85
  265. data/Gemfile.lock +0 -234
  266. data/gemfiles/Gemfile.rails-4.2 +0 -17
  267. data/gemfiles/Gemfile.rails-4.2.lock +0 -225
  268. data/gemfiles/Gemfile.rails-5.0.lock +0 -234
  269. data/gemfiles/Gemfile.rails-5.1.lock +0 -234
  270. data/spec/rails_app/app/views/activity_notification/notifications/users/overriden/custom/_test.html.erb +0 -1
  271. /data/spec/rails_app/app/{models → assets/images}/.keep +0 -0
@@ -8,9 +8,7 @@ module ActivityNotification
8
8
  include Common
9
9
  include Renderable
10
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
11
+ self.table_name = ActivityNotification.config.notification_table_name
14
12
 
15
13
  # Belongs to target instance of this notification as polymorphic association.
16
14
  # @scope instance
@@ -25,14 +23,14 @@ module ActivityNotification
25
23
  # Belongs to group instance of this notification as polymorphic association.
26
24
  # @scope instance
27
25
  # @return [Object] Group instance of this notification
28
- belongs_to :group, { polymorphic: true }.merge(Rails::VERSION::MAJOR >= 5 ? { optional: true } : {})
26
+ belongs_to :group, polymorphic: true, optional: true
29
27
 
30
28
  # Belongs to group owner notification instance of this notification.
31
29
  # Only group member instance has :group_owner value.
32
30
  # Group owner instance has nil as :group_owner association.
33
31
  # @scope instance
34
32
  # @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 } : {})
33
+ belongs_to :group_owner, class_name: "ActivityNotification::Notification", optional: true
36
34
 
37
35
  # Has many group member notification instances of this notification.
38
36
  # Only group owner instance has :group_members value.
@@ -44,7 +42,7 @@ module ActivityNotification
44
42
  # Belongs to :notifier instance of this notification.
45
43
  # @scope instance
46
44
  # @return [Object] Notifier instance of this notification
47
- belongs_to :notifier, { polymorphic: true }.merge(Rails::VERSION::MAJOR >= 5 ? { optional: true } : {})
45
+ belongs_to :notifier, polymorphic: true, optional: true
48
46
 
49
47
  # Serialize parameters Hash
50
48
  serialize :parameters, Hash
@@ -120,14 +118,6 @@ module ActivityNotification
120
118
  # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
121
119
  scope :filtered_by_instance, ->(notifiable) { where(notifiable: notifiable) }
122
120
 
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
121
  # Selects filtered notifications by group instance.
132
122
  # @example Get filtered unopened notificatons of the @user for @article as group
133
123
  # @notifications = @user.notifications.unopened_only.filtered_by_group(@article)
@@ -136,6 +126,22 @@ module ActivityNotification
136
126
  # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of filtered notifications
137
127
  scope :filtered_by_group, ->(group) { where(group: group) }
138
128
 
129
+ # Selects filtered notifications later than specified time.
130
+ # @example Get filtered unopened notificatons of the @user later than @notification
131
+ # @notifications = @user.notifications.unopened_only.later_than(@notification.created_at)
132
+ # @scope class
133
+ # @param [Time] Created time of the notifications for filter
134
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
135
+ scope :later_than, ->(created_time) { where('created_at > ?', created_time) }
136
+
137
+ # Selects filtered notifications earlier than specified time.
138
+ # @example Get filtered unopened notificatons of the @user earlier than @notification
139
+ # @notifications = @user.notifications.unopened_only.earlier_than(@notification.created_at)
140
+ # @scope class
141
+ # @param [Time] Created time of the notifications for filter
142
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
143
+ scope :earlier_than, ->(created_time) { where('created_at < ?', created_time) }
144
+
139
145
  # Includes target instance with query for notifications.
140
146
  # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with target
141
147
  scope :with_target, -> { includes(:target) }
@@ -160,27 +166,10 @@ module ActivityNotification
160
166
  # @return [ActiveRecord_AssociationRelation<Notificaion>] Database query of notifications with notifier
161
167
  scope :with_notifier, -> { includes(:notifier) }
162
168
 
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 method cannot be chained with order by other columns like created_at
179
- # select(:key).distinct.pluck(:key)
180
- pluck(:key).uniq
181
- end
182
-
183
169
  # Raise DeleteRestrictionError for notifications.
170
+ # @param [String] error_text Error text for raised exception
171
+ # @raise [ActiveRecord::DeleteRestrictionError] DeleteRestrictionError from used ORM
172
+ # @return [void]
184
173
  def self.raise_delete_restriction_error(error_text)
185
174
  raise ::ActiveRecord::DeleteRestrictionError.new(error_text)
186
175
  end
@@ -17,7 +17,7 @@ module ActivityNotification
17
17
  serialize :optional_targets, Hash
18
18
 
19
19
  validates :target, presence: true
20
- validates :key, presence: true
20
+ validates :key, presence: true, uniqueness: { scope: :target }
21
21
  validates_inclusion_of :subscribing, in: [true, false]
22
22
  validates_inclusion_of :subscribing_to_email, in: [true, false]
23
23
  validate :subscribing_to_email_cannot_be_true_when_subscribing_is_false
@@ -6,7 +6,7 @@ module ActivityNotification
6
6
  # Defines has_many association with ActivityNotification models.
7
7
  # @return [ActiveRecord_AssociationRelation<Object>] Database query of associated model instances
8
8
  def has_many_records(name, options = {})
9
- has_many name, options
9
+ has_many name, **options
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,262 @@
1
+ require 'dynamoid/adapter_plugin/aws_sdk_v3'
2
+
3
+ # Entend Dynamoid v3.1.0 to support none, limit, exists?, update_all, serializable_hash in Dynamoid::Criteria::Chain.
4
+ # ActivityNotification project will try to contribute these fundamental functions to Dynamoid upstream.
5
+ # @private
6
+ module Dynamoid # :nodoc: all
7
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria.rb
8
+ # @private
9
+ module Criteria
10
+ # @private
11
+ class None < Chain
12
+ def ==(other)
13
+ other.is_a?(None)
14
+ end
15
+
16
+ def records
17
+ []
18
+ end
19
+
20
+ def count
21
+ 0
22
+ end
23
+
24
+ def delete_all
25
+ end
26
+
27
+ def empty?
28
+ true
29
+ end
30
+ end
31
+
32
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
33
+ # @private
34
+ class Chain
35
+ # Return new none object
36
+ def none
37
+ None.new(self.source)
38
+ end
39
+
40
+ # Set query result limit as record_limit of Dynamoid
41
+ # @scope class
42
+ # @param [Integer] limit Query result limit as record_limit
43
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
44
+ def limit(limit)
45
+ record_limit(limit)
46
+ end
47
+
48
+ # Return if records exist
49
+ # @scope class
50
+ # @return [Boolean] If records exist
51
+ def exists?
52
+ record_limit(1).count > 0
53
+ end
54
+
55
+ # Return size of records as count
56
+ # @scope class
57
+ # @return [Integer] Size of records
58
+ def size
59
+ count
60
+ end
61
+
62
+ #TODO Make this batch
63
+ def update_all(conditions = {})
64
+ each do |document|
65
+ document.update_attributes(conditions)
66
+ end
67
+ end
68
+
69
+ # Return serializable_hash as array
70
+ def serializable_hash(options = {})
71
+ all.to_a.map { |r| r.serializable_hash(options) }
72
+ end
73
+ end
74
+
75
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria.rb
76
+ # @private
77
+ module ClassMethods
78
+ define_method(:none) do |*args, &blk|
79
+ chain = Dynamoid::Criteria::Chain.new(self)
80
+ chain.send(:none, *args, &blk)
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ # Entend Dynamoid to support query and scan with 'null' and 'not_null' conditions
87
+ # @private
88
+ module Dynamoid # :nodoc: all
89
+ # @private
90
+ module Criteria
91
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
92
+ # @private
93
+ module NullOperatorExtension
94
+ # @private
95
+ def field_hash(key)
96
+ name, operation = key.to_s.split('.')
97
+ val = type_cast_condition_parameter(name, query[key])
98
+
99
+ hash = case operation
100
+ when 'null'
101
+ { null: val }
102
+ when 'not_null'
103
+ { not_null: val }
104
+ else
105
+ return super(key)
106
+ end
107
+
108
+ { name.to_sym => hash }
109
+ end
110
+ end
111
+
112
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
113
+ # @private
114
+ class Chain
115
+ prepend NullOperatorExtension
116
+ end
117
+ end
118
+
119
+ # @private
120
+ module AdapterPlugin
121
+ # @private
122
+ class AwsSdkV3
123
+
124
+ NULL_OPERATOR_FIELD_MAP = {
125
+ null: 'NULL',
126
+ not_null: 'NOT_NULL'
127
+ }.freeze
128
+
129
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb
130
+ # @private
131
+ class Query < ::Dynamoid::AdapterPlugin::Query
132
+ # @private
133
+ def query_filter
134
+ conditions.except(*AwsSdkV3::RANGE_MAP.keys).reduce({}) do |result, (attr, cond)|
135
+ if AwsSdkV3::NULL_OPERATOR_FIELD_MAP.has_key?(cond.keys[0])
136
+ condition = { comparison_operator: AwsSdkV3::NULL_OPERATOR_FIELD_MAP[cond.keys[0]] }
137
+ else
138
+ condition = {
139
+ comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
140
+ attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
141
+ }
142
+ end
143
+ result[attr] = condition
144
+ result
145
+ end
146
+ end
147
+ end
148
+
149
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb
150
+ # @private
151
+ class Scan < ::Dynamoid::AdapterPlugin::Scan
152
+ # @private
153
+ def scan_filter
154
+ conditions.reduce({}) do |result, (attr, cond)|
155
+ if AwsSdkV3::NULL_OPERATOR_FIELD_MAP.has_key?(cond.keys[0])
156
+ condition = { comparison_operator: AwsSdkV3::NULL_OPERATOR_FIELD_MAP[cond.keys[0]] }
157
+ else
158
+ condition = {
159
+ comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
160
+ attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
161
+ }
162
+ end
163
+ result[attr] = condition
164
+ result
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+
172
+ # Entend Dynamoid to support uniqueness validator
173
+ # @private
174
+ module Dynamoid # :nodoc: all
175
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/validations.rb
176
+ # @private
177
+ module Validations
178
+ # Validates whether or not a field is unique against the records in the database.
179
+ class UniquenessValidator < ActiveModel::EachValidator
180
+ # Validate the document for uniqueness violations.
181
+ # @param [Document] document The document to validate.
182
+ # @param [Symbol] attribute The name of the attribute.
183
+ # @param [Object] value The value of the object.
184
+ def validate_each(document, attribute, value)
185
+ return unless validation_required?(document, attribute)
186
+ document.errors.add(attribute, :taken, options.except(:scope).merge(value: value)) if not_unique?(document, attribute, value)
187
+ end
188
+
189
+ private
190
+
191
+ # Are we required to validate the document?
192
+ # @api private
193
+ def validation_required?(document, attribute)
194
+ document.new_record? ||
195
+ document.send("attribute_changed?", attribute.to_s) ||
196
+ scope_value_changed?(document)
197
+ end
198
+
199
+ # Scope reference has changed?
200
+ # @api private
201
+ def scope_value_changed?(document)
202
+ Array.wrap(options[:scope]).any? do |item|
203
+ document.send("attribute_changed?", item.to_s)
204
+ end
205
+ end
206
+
207
+ # Check whether a record is uniqueness.
208
+ # @api private
209
+ def not_unique?(document, attribute, value)
210
+ klass = document.class
211
+ while klass.superclass.respond_to?(:validators) && klass.superclass.validators.include?(self)
212
+ klass = klass.superclass
213
+ end
214
+ criteria = create_criteria(klass, document, attribute, value)
215
+ criteria.exists?
216
+ end
217
+
218
+ # Create the validation criteria.
219
+ # @api private
220
+ def create_criteria(base, document, attribute, value)
221
+ criteria = scope(base, document)
222
+ filter_criteria(criteria, document, attribute)
223
+ end
224
+
225
+ # Scope the criteria to the scope options provided.
226
+ # @api private
227
+ def scope(criteria, document)
228
+ Array.wrap(options[:scope]).each do |item|
229
+ criteria = filter_criteria(criteria, document, item)
230
+ end
231
+ criteria
232
+ end
233
+
234
+ # Filter the criteria.
235
+ # @api private
236
+ def filter_criteria(criteria, document, attribute)
237
+ value = document.read_attribute(attribute)
238
+ value.nil? ? criteria.where("#{attribute}.null" => true) : criteria.where(attribute => value)
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ module ActivityNotification
245
+ # Dynamoid extension module for ActivityNotification.
246
+ module DynamoidExtension
247
+ extend ActiveSupport::Concern
248
+
249
+ class_methods do
250
+ # Defines delete_all method as calling delete_table and create_table methods
251
+ def delete_all
252
+ delete_table
253
+ create_table(sync: true)
254
+ end
255
+ end
256
+
257
+ # Returns an instance of the specified +klass+ with the attributes of the current record.
258
+ def becomes(klass)
259
+ self
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,224 @@
1
+ require 'dynamoid'
2
+ require 'activity_notification/apis/notification_api'
3
+
4
+ module ActivityNotification
5
+ module ORM
6
+ module Dynamoid
7
+ # Notification model implementation generated by ActivityNotification.
8
+ class Notification
9
+ include ::Dynamoid::Document
10
+ include ActiveModel::AttributeAssignment
11
+ include GlobalID::Identification
12
+ include DynamoidExtension
13
+ include Common
14
+ include Renderable
15
+ include Association
16
+ include NotificationApi
17
+
18
+ table name: ActivityNotification.config.notification_table_name, key: :id
19
+
20
+ # Belongs to target instance of this notification as polymorphic association using composite key.
21
+ # @scope instance
22
+ # @return [Object] Target instance of this notification
23
+ belongs_to_composite_xdb_record :target, store_with_associated_records: true, as_json_options: { methods: [:printable_type, :printable_target_name] }
24
+
25
+ # Belongs to notifiable instance of this notification as polymorphic association using composite key.
26
+ # @scope instance
27
+ # @return [Object] Notifiable instance of this notification
28
+ belongs_to_composite_xdb_record :notifiable, store_with_associated_records: true, as_json_options: { methods: [:printable_type] }
29
+
30
+ # Belongs to group instance of this notification as polymorphic association using composite key.
31
+ # @scope instance
32
+ # @return [Object] Group instance of this notification
33
+ belongs_to_composite_xdb_record :group, store_with_associated_records: true, as_json_options: { methods: [:printable_type, :printable_group_name] }
34
+
35
+ field :key, :string
36
+ field :parameters, :raw, default: {}
37
+ field :opened_at, :datetime
38
+ field :group_owner_id, :string
39
+
40
+ # Belongs to group owner notification instance of this notification.
41
+ # Only group member instance has :group_owner value.
42
+ # Group owner instance has nil as :group_owner association.
43
+ # @scope instance
44
+ # @return [Notification] Group owner notification instance of this notification
45
+ belongs_to :group_owner, { class_name: "ActivityNotification::Notification", foreign_key: :group_owner_id, optional: true }
46
+
47
+ # Customized method that belongs to group owner notification instance of this notification.
48
+ # @raise [Errors::RecordNotFound] Record not found error
49
+ # @return [Notification] Group owner notification instance of this notification
50
+ def group_owner
51
+ group_owner_id.nil? ? nil : Notification.find(group_owner_id)
52
+ end
53
+
54
+ # Has many group member notification instances of this notification.
55
+ # Only group owner instance has :group_members value.
56
+ # Group member instance has nil as :group_members association.
57
+ # @scope instance
58
+ # @return [Dynamoid::Criteria::Chain] Database query of the group member notification instances of this notification
59
+ # has_many :group_members, class_name: "ActivityNotification::Notification", foreign_key: :group_owner_id
60
+ def group_members
61
+ Notification.where(group_owner_id: id)
62
+ end
63
+
64
+ # Belongs to :otifier instance of this notification.
65
+ # @scope instance
66
+ # @return [Object] Notifier instance of this notification
67
+ belongs_to_composite_xdb_record :notifier, store_with_associated_records: true, as_json_options: { methods: [:printable_type, :printable_notifier_name] }
68
+
69
+ # Additional fields to store from instance method when config.store_with_associated_records is enabled
70
+ if ActivityNotification.config.store_with_associated_records
71
+ field :stored_notifiable_path, :string
72
+ field :stored_printable_notifiable_name, :string
73
+ field :stored_group_member_notifier_count, :integer
74
+ field :stored_group_notification_count, :integer
75
+ field :stored_group_members, :array
76
+
77
+ # Returns prepared notification object to store
78
+ # @return [Object] prepared notification object to store
79
+ def prepare_to_store
80
+ self.stored_notifiable_path = notifiable_path
81
+ self.stored_printable_notifiable_name = printable_notifiable_name
82
+ if group_owner?
83
+ self.stored_group_notification_count = 0
84
+ self.stored_group_member_notifier_count = 0
85
+ self.stored_group_members = []
86
+ end
87
+ self
88
+ end
89
+
90
+ # Call after store action with stored notification
91
+ def after_store
92
+ if group_owner?
93
+ self.stored_group_notification_count = group_notification_count
94
+ self.stored_group_member_notifier_count = group_member_notifier_count
95
+ self.stored_group_members = group_members.as_json
96
+ self.stored_group_members.each do |group_member|
97
+ # Cast Time and DateTime field to String to handle Dynamoid unsupported type error
98
+ group_member.each do |k, v|
99
+ group_member[k] = v.to_s if v.is_a?(Time) || v.is_a?(DateTime)
100
+ end
101
+ end
102
+ save
103
+ else
104
+ group_owner.after_store
105
+ end
106
+ end
107
+ end
108
+
109
+ # Mandatory global secondary index to query effectively
110
+ global_secondary_index name: :index_target_key_created_at, hash_key: :target_key, range_key: :created_at, projected_attributes: :all
111
+ global_secondary_index name: :index_group_owner_id_created_at, hash_key: :group_owner_id, range_key: :created_at, projected_attributes: :all
112
+ # Optional global secondary index to sort by created_at
113
+ global_secondary_index name: :index_notifier_key_created_at, hash_key: :notifier_key, range_key: :created_at, projected_attributes: :all
114
+ global_secondary_index name: :index_notifiable_key_created_at, hash_key: :notifiable_key, range_key: :created_at, projected_attributes: :all
115
+
116
+ validates :target, presence: true
117
+ validates :notifiable, presence: true
118
+ validates :key, presence: true
119
+
120
+ %i[ all_index! unopened_index opened_index
121
+ filtered_by_association filtered_by_target filtered_by_instance filtered_by_group
122
+ filtered_by_target_type filtered_by_type filtered_by_key filtered_by_options
123
+ latest_order earliest_order latest_order! earliest_order!
124
+ group_owners_only group_members_only unopened_only opened_only! opened_only
125
+ unopened_index_group_members_only opened_index_group_members_only
126
+ within_expiration_only(expiry_delay
127
+ group_members_of_owner_ids_only
128
+ reload
129
+ latest earliest latest! earliest!
130
+ uniq_keys
131
+ ].each do |method|
132
+ # Return a criteria chain in response to a method that will begin or end a chain.
133
+ # For more information, see Dynamoid::Criteria::Chain.
134
+ singleton_class.send(:define_method, method) do |*args, &block|
135
+ # Use scan_index_forward with true as default value to convert Dynamoid::Document into Dynamoid::Criteria::Chain
136
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/document.rb
137
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/components.rb
138
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria.rb
139
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
140
+ scan_index_forward(true).send(method, *args, &block)
141
+ end
142
+ end
143
+
144
+ %i[ with_target with_notifiable with_group with_group_owner with_group_members with_notifier ].each do |method|
145
+ singleton_class.send(:define_method, method) do |*args, &block|
146
+ self
147
+ end
148
+ end
149
+
150
+ # Returns if the notification is group owner.
151
+ # Calls NotificationApi#group_owner? as super method.
152
+ # @return [Boolean] If the notification is group owner
153
+ def group_owner?
154
+ super
155
+ end
156
+
157
+ # Raise ActivityNotification::DeleteRestrictionError for notifications.
158
+ # @param [String] error_text Error text for raised exception
159
+ # @raise [ActivityNotification::DeleteRestrictionError] DeleteRestrictionError from used ORM
160
+ # @return [void]
161
+ def self.raise_delete_restriction_error(error_text)
162
+ raise ActivityNotification::DeleteRestrictionError, error_text
163
+ end
164
+
165
+ protected
166
+
167
+ # Returns count of group members of the unopened notification.
168
+ # This method is designed to cache group by query result to avoid N+1 call.
169
+ # @api protected
170
+ # @todo Avoid N+1 call
171
+ #
172
+ # @return [Integer] Count of group members of the unopened notification
173
+ def unopened_group_member_count
174
+ group_members.unopened_only.count
175
+ end
176
+
177
+ # Returns count of group members of the opened notification.
178
+ # This method is designed to cache group by query result to avoid N+1 call.
179
+ # @api protected
180
+ # @todo Avoid N+1 call
181
+ #
182
+ # @param [Integer] limit Limit to query for opened notifications
183
+ # @return [Integer] Count of group members of the opened notification
184
+ def opened_group_member_count(limit = ActivityNotification.config.opened_index_limit)
185
+ limit == 0 and return 0
186
+ group_members.opened_only(limit).to_a.length
187
+ end
188
+
189
+ # Returns count of group member notifiers of the unopened notification not including group owner notifier.
190
+ # This method is designed to cache group by query result to avoid N+1 call.
191
+ # @api protected
192
+ # @todo Avoid N+1 call
193
+ #
194
+ # @return [Integer] Count of group member notifiers of the unopened notification
195
+ def unopened_group_member_notifier_count
196
+ group_members.unopened_only
197
+ .filtered_by_association_type("notifier", notifier)
198
+ .where("notifier_key.ne": notifier_key)
199
+ .to_a
200
+ .collect {|n| n.notifier_key }.compact.uniq
201
+ .length
202
+ end
203
+
204
+ # Returns count of group member notifiers of the opened notification not including group owner notifier.
205
+ # This method is designed to cache group by query result to avoid N+1 call.
206
+ # @api protected
207
+ # @todo Avoid N+1 call
208
+ #
209
+ # @param [Integer] limit Limit to query for opened notifications
210
+ # @return [Integer] Count of group member notifiers of the opened notification
211
+ def opened_group_member_notifier_count(limit = ActivityNotification.config.opened_index_limit)
212
+ limit == 0 and return 0
213
+ group_members.opened_only(limit)
214
+ .filtered_by_association_type("notifier", notifier)
215
+ .where("notifier_key.ne": notifier_key)
216
+ .to_a
217
+ .collect {|n| n.notifier_key }.compact.uniq
218
+ .length
219
+ end
220
+
221
+ end
222
+ end
223
+ end
224
+ end