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
@@ -0,0 +1,82 @@
1
+ require 'dynamoid'
2
+ require 'activity_notification/apis/subscription_api'
3
+
4
+ module ActivityNotification
5
+ module ORM
6
+ module Dynamoid
7
+ # Subscription model implementation generated by ActivityNotification.
8
+ class Subscription
9
+ include ::Dynamoid::Document
10
+ include ActiveModel::AttributeAssignment
11
+ include DynamoidExtension
12
+ include Association
13
+ include SubscriptionApi
14
+
15
+ table name: ActivityNotification.config.subscription_table_name, key: :id
16
+
17
+ # Belongs to target instance of this subscription as polymorphic association using composite key.
18
+ # @scope instance
19
+ # @return [Object] Target instance of this subscription
20
+ belongs_to_composite_xdb_record :target
21
+
22
+ field :key, :string
23
+ field :subscribing, :boolean, default: ActivityNotification.config.subscribe_as_default
24
+ field :subscribing_to_email, :boolean, default: ActivityNotification.config.subscribe_to_email_as_default
25
+ field :subscribed_at, :datetime
26
+ field :unsubscribed_at, :datetime
27
+ field :subscribed_to_email_at, :datetime
28
+ field :unsubscribed_to_email_at, :datetime
29
+ field :optional_targets, :raw, default: {}
30
+
31
+ global_secondary_index name: :index_target_key_created_at, hash_key: :target_key, range_key: :created_at, projected_attributes: :all
32
+
33
+ validates :target, presence: true
34
+ validates :key, presence: true, uniqueness: { scope: :target_key }
35
+ validates_inclusion_of :subscribing, in: [true, false]
36
+ validates_inclusion_of :subscribing_to_email, in: [true, false]
37
+ validate :subscribing_to_email_cannot_be_true_when_subscribing_is_false
38
+ validates :subscribed_at, presence: true, if: :subscribing
39
+ validates :unsubscribed_at, presence: true, unless: :subscribing
40
+ validates :subscribed_to_email_at, presence: true, if: :subscribing_to_email
41
+ validates :unsubscribed_to_email_at, presence: true, unless: :subscribing_to_email
42
+ validate :subscribing_to_optional_target_cannot_be_true_when_subscribing_is_false
43
+
44
+ %i[ filtered_by_association filtered_by_target
45
+ filtered_by_target_type filtered_by_key filtered_by_options
46
+ latest_order earliest_order latest_order! earliest_order!
47
+ latest_subscribed_order earliest_subscribed_order key_order
48
+ reload
49
+ uniq_keys
50
+ ].each do |method|
51
+ # Return a criteria chain in response to a method that will begin or end a chain.
52
+ # For more information, see Dynamoid::Criteria::Chain.
53
+ singleton_class.send(:define_method, method) do |*args, &block|
54
+ # Use scan_index_forward with true as default value to convert Dynamoid::Document into Dynamoid::Criteria::Chain
55
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/document.rb
56
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/components.rb
57
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria.rb
58
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
59
+ scan_index_forward(true).send(method, *args, &block)
60
+ end
61
+ end
62
+
63
+ %i[ with_target ].each do |method|
64
+ singleton_class.send(:define_method, method) do |*args, &block|
65
+ self
66
+ end
67
+ end
68
+
69
+ # Initialize without options to use Dynamoid.config.store_datetime_as_string
70
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/dumping.rb
71
+ @@date_time_dumper = ::Dynamoid::Dumping::DateTimeDumper.new({})
72
+
73
+ # Convert Time value to store in database as Hash value.
74
+ # @param [Time] time Time value to store in database as Hash value
75
+ # @return [Integer, String] Converted Time value
76
+ def self.convert_time_as_hash(time)
77
+ @@date_time_dumper.process(time)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,530 @@
1
+ require 'dynamoid/adapter_plugin/aws_sdk_v3'
2
+ require_relative 'dynamoid/extension.rb'
3
+
4
+ module ActivityNotification
5
+ module Association
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :_associated_composite_records
10
+ self._associated_composite_records = []
11
+ end
12
+
13
+ class_methods do
14
+ # Defines has_many association with ActivityNotification models.
15
+ # @return [Dynamoid::Criteria::Chain] Database query of associated model instances
16
+ def has_many_records(name, options = {})
17
+ has_many_composite_xdb_records name, options
18
+ end
19
+
20
+ # Defines polymorphic belongs_to association using composite key with models in other database.
21
+ def belongs_to_composite_xdb_record(name, _options = {})
22
+ association_name = name.to_s.singularize.underscore
23
+ composite_field = "#{association_name}_key".to_sym
24
+ field composite_field, :string
25
+ associated_record_field = "stored_#{association_name}".to_sym
26
+ field associated_record_field, :raw if ActivityNotification.config.store_with_associated_records && _options[:store_with_associated_records]
27
+
28
+ self.instance_eval do
29
+ define_method(name) do |reload = false|
30
+ reload and self.instance_variable_set("@#{name}", nil)
31
+ if self.instance_variable_get("@#{name}").blank?
32
+ composite_key = self.send(composite_field)
33
+ if composite_key.present? && (class_name = composite_key.split(ActivityNotification.config.composite_key_delimiter).first).present?
34
+ object_class = class_name.classify.constantize
35
+ self.instance_variable_set("@#{name}", object_class.where(id: composite_key.split(ActivityNotification.config.composite_key_delimiter).last).first)
36
+ end
37
+ end
38
+ self.instance_variable_get("@#{name}")
39
+ end
40
+
41
+ define_method("#{name}=") do |new_instance|
42
+ if new_instance.nil?
43
+ self.send("#{composite_field}=", nil)
44
+ else
45
+ self.send("#{composite_field}=", "#{new_instance.class.name}#{ActivityNotification.config.composite_key_delimiter}#{new_instance.id}")
46
+ associated_record_json = new_instance.as_json(_options[:as_json_options] || {})
47
+ # Cast Time and DateTime field to String to handle Dynamoid unsupported type error
48
+ if associated_record_json.present?
49
+ associated_record_json.each do |k, v|
50
+ associated_record_json[k] = v.to_s if v.is_a?(Time) || v.is_a?(DateTime)
51
+ end
52
+ end
53
+ self.send("#{associated_record_field}=", associated_record_json) if ActivityNotification.config.store_with_associated_records && _options[:store_with_associated_records]
54
+ end
55
+ self.instance_variable_set("@#{name}", nil)
56
+ end
57
+
58
+ define_method("#{association_name}_type") do
59
+ composite_key = self.send(composite_field)
60
+ composite_key.present? ? composite_key.split(ActivityNotification.config.composite_key_delimiter).first : nil
61
+ end
62
+
63
+ define_method("#{association_name}_id") do
64
+ composite_key = self.send(composite_field)
65
+ composite_key.present? ? composite_key.split(ActivityNotification.config.composite_key_delimiter).last : nil
66
+ end
67
+ end
68
+
69
+ self._associated_composite_records.push(association_name.to_sym)
70
+ end
71
+
72
+ # Defines polymorphic has_many association using composite key with models in other database.
73
+ # @todo Add dependent option
74
+ def has_many_composite_xdb_records(name, options = {})
75
+ association_name = options[:as] || name.to_s.underscore
76
+ composite_field = "#{association_name}_key".to_sym
77
+ object_name = options[:class_name] || name.to_s.singularize.camelize
78
+ object_class = object_name.classify.constantize
79
+
80
+ self.instance_eval do
81
+ # Set default reload arg to true since Dynamoid::Criteria::Chain is stateful on the query
82
+ define_method(name) do |reload = true|
83
+ reload and self.instance_variable_set("@#{name}", nil)
84
+ if self.instance_variable_get("@#{name}").blank?
85
+ new_value = object_class.where(composite_field => "#{self.class.name}#{ActivityNotification.config.composite_key_delimiter}#{self.id}")
86
+ self.instance_variable_set("@#{name}", new_value)
87
+ end
88
+ self.instance_variable_get("@#{name}")
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Defines update method as update_attributes method
95
+ def update(attributes)
96
+ attributes_with_association = attributes.map { |attribute, value|
97
+ self.class._associated_composite_records.include?(attribute) ?
98
+ ["#{attribute}_key".to_sym, value.nil? ? nil : "#{value.class.name}#{ActivityNotification.config.composite_key_delimiter}#{value.id}"] :
99
+ [attribute, value]
100
+ }.to_h
101
+ update_attributes(attributes_with_association)
102
+ end
103
+ end
104
+ end
105
+
106
+ # Monkey patching for Rails 6.0+
107
+ class ActiveModel::NullMutationTracker
108
+ # Monkey patching for Rails 6.0+
109
+ def force_change(attr_name); end if Rails::VERSION::MAJOR >= 6
110
+ end
111
+
112
+ # Entend Dynamoid to support ActivityNotification scope in Dynamoid::Criteria::Chain
113
+ # @private
114
+ module Dynamoid # :nodoc: all
115
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria.rb
116
+ # @private
117
+ module Criteria
118
+ # https://github.com/Dynamoid/dynamoid/blob/master/lib/dynamoid/criteria/chain.rb
119
+ # @private
120
+ class Chain
121
+ # Selects all notification index.
122
+ # ActivityNotification::Notification.all_index!
123
+ # is defined same as
124
+ # ActivityNotification::Notification.group_owners_only.latest_order
125
+ # @scope class
126
+ # @example Get all notification index of the @user
127
+ # @notifications = @user.notifications.all_index!
128
+ # @notifications = @user.notifications.group_owners_only.latest_order
129
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
130
+ # @param [Boolean] with_group_members If notification index will include group members
131
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
132
+ def all_index!(reverse = false, with_group_members = false)
133
+ target_index = with_group_members ? self : group_owners_only
134
+ reverse ? target_index.earliest_order : target_index.latest_order
135
+ end
136
+
137
+ # Selects unopened notification index.
138
+ # ActivityNotification::Notification.unopened_index
139
+ # is defined same as
140
+ # ActivityNotification::Notification.unopened_only.group_owners_only.latest_order
141
+ # @scope class
142
+ # @example Get unopened notificaton index of the @user
143
+ # @notifications = @user.notifications.unopened_index
144
+ # @notifications = @user.notifications.unopened_only.group_owners_only.latest_order
145
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
146
+ # @param [Boolean] with_group_members If notification index will include group members
147
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
148
+ def unopened_index(reverse = false, with_group_members = false)
149
+ target_index = with_group_members ? unopened_only : unopened_only.group_owners_only
150
+ reverse ? target_index.earliest_order : target_index.latest_order
151
+ end
152
+
153
+ # Selects unopened notification index.
154
+ # ActivityNotification::Notification.opened_index(limit)
155
+ # is defined same as
156
+ # ActivityNotification::Notification.opened_only(limit).group_owners_only.latest_order
157
+ # @scope class
158
+ # @example Get unopened notificaton index of the @user with limit 10
159
+ # @notifications = @user.notifications.opened_index(10)
160
+ # @notifications = @user.notifications.opened_only(10).group_owners_only.latest_order
161
+ # @param [Integer] limit Limit to query for opened notifications
162
+ # @param [Boolean] reverse If notification index will be ordered as earliest first
163
+ # @param [Boolean] with_group_members If notification index will include group members
164
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
165
+ def opened_index(limit, reverse = false, with_group_members = false)
166
+ target_index = with_group_members ? opened_only(limit) : opened_only(limit).group_owners_only
167
+ reverse ? target_index.earliest_order : target_index.latest_order
168
+ end
169
+
170
+ # Selects filtered notifications or subscriptions by associated instance.
171
+ # @scope class
172
+ # @param [String] name Association name
173
+ # @param [Object] instance Associated instance
174
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
175
+ def filtered_by_association(name, instance)
176
+ instance.present? ? where("#{name}_key" => "#{instance.class.name}#{ActivityNotification.config.composite_key_delimiter}#{instance.id}") : where("#{name}_key.null" => true)
177
+ end
178
+
179
+ # Selects filtered notifications or subscriptions by association type.
180
+ # @scope class
181
+ # @param [String] name Association name
182
+ # @param [Object] type Association type
183
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
184
+ def filtered_by_association_type(name, type)
185
+ type.present? ? where("#{name}_key.begins_with" => "#{type}#{ActivityNotification.config.composite_key_delimiter}") : none
186
+ end
187
+
188
+ # Selects filtered notifications or subscriptions by association type and id.
189
+ # @scope class
190
+ # @param [String] name Association name
191
+ # @param [Object] type Association type
192
+ # @param [String] id Association id
193
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
194
+ def filtered_by_association_type_and_id(name, type, id)
195
+ type.present? && id.present? ? where("#{name}_key" => "#{type}#{ActivityNotification.config.composite_key_delimiter}#{id}") : none
196
+ end
197
+
198
+ # Selects filtered notifications or subscriptions by target instance.
199
+ # ActivityNotification::Notification.filtered_by_target(@user)
200
+ # is the same as
201
+ # @user.notifications
202
+ # @scope class
203
+ # @param [Object] target Target instance for filter
204
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
205
+ def filtered_by_target(target)
206
+ filtered_by_association("target", target)
207
+ end
208
+
209
+ # Selects filtered notifications by notifiable instance.
210
+ # @example Get filtered unopened notificatons of the @user for @comment as notifiable
211
+ # @notifications = @user.notifications.unopened_only.filtered_by_instance(@comment)
212
+ # @scope class
213
+ # @param [Object] notifiable Notifiable instance for filter
214
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
215
+ def filtered_by_instance(notifiable)
216
+ filtered_by_association("notifiable", notifiable)
217
+ end
218
+
219
+ # Selects filtered notifications by group instance.
220
+ # @example Get filtered unopened notificatons of the @user for @article as group
221
+ # @notifications = @user.notifications.unopened_only.filtered_by_group(@article)
222
+ # @scope class
223
+ # @param [Object] group Group instance for filter
224
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
225
+ def filtered_by_group(group)
226
+ filtered_by_association("group", group)
227
+ end
228
+
229
+ # Selects filtered notifications or subscriptions by target_type.
230
+ # @example Get filtered unopened notificatons of User as target type
231
+ # @notifications = ActivityNotification.Notification.unopened_only.filtered_by_target_type('User')
232
+ # @scope class
233
+ # @param [String] target_type Target type for filter
234
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
235
+ def filtered_by_target_type(target_type)
236
+ filtered_by_association_type("target", target_type)
237
+ end
238
+
239
+ # Selects filtered notifications by notifiable_type.
240
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class
241
+ # @notifications = @user.notifications.unopened_only.filtered_by_type('Comment')
242
+ # @scope class
243
+ # @param [String] notifiable_type Notifiable type for filter
244
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
245
+ def filtered_by_type(notifiable_type)
246
+ filtered_by_association_type("notifiable", notifiable_type)
247
+ end
248
+
249
+ # Selects filtered notifications or subscriptions by key.
250
+ # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
251
+ # @notifications = @user.notifications.unopened_only.filtered_by_key('comment.reply')
252
+ # @scope class
253
+ # @param [String] key Key of the notification for filter
254
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
255
+ def filtered_by_key(key)
256
+ where(key: key)
257
+ end
258
+
259
+ # Selects filtered notifications later than specified time.
260
+ # @example Get filtered unopened notificatons of the @user later than @notification
261
+ # @notifications = @user.notifications.unopened_only.later_than(@notification.created_at)
262
+ # @scope class
263
+ # @param [Time] Created time of the notifications for filter
264
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
265
+ def later_than(created_time)
266
+ where('created_at.gt': created_time)
267
+ end
268
+
269
+ # Selects filtered notifications earlier than specified time.
270
+ # @example Get filtered unopened notificatons of the @user earlier than @notification
271
+ # @notifications = @user.notifications.unopened_only.earlier_than(@notification.created_at)
272
+ # @scope class
273
+ # @param [Time] Created time of the notifications for filter
274
+ # @return [ActiveRecord_AssociationRelation<Notificaion>, Mongoid::Criteria<Notificaion>] Database query of filtered notifications
275
+ def earlier_than(created_time)
276
+ where('created_at.lt': created_time)
277
+ end
278
+
279
+ # Selects filtered notifications or subscriptions by notifiable_type, group or key with filter options.
280
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class
281
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment' })
282
+ # @example Get filtered unopened notificatons of the @user for @article as group
283
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group: @article })
284
+ # @example Get filtered unopened notificatons of the @user for Article instance id=1 as group
285
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_group_type: 'Article', filtered_by_group_id: '1' })
286
+ # @example Get filtered unopened notificatons of the @user with key 'comment.reply'
287
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_key: 'comment.reply' })
288
+ # @example Get filtered unopened notificatons of the @user for Comment notifiable class with key 'comment.reply'
289
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ filtered_by_type: 'Comment', filtered_by_key: 'comment.reply' })
290
+ # @example Get custom filtered notificatons of the @user
291
+ # @notifications = @user.notifications.unopened_only.filtered_by_options({ custom_filter: ["created_at >= ?", time.hour.ago] })
292
+ # @scope class
293
+ # @param [Hash] options Options for filter
294
+ # @option options [String] :filtered_by_type (nil) Notifiable type for filter
295
+ # @option options [Object] :filtered_by_group (nil) Group instance for filter
296
+ # @option options [String] :filtered_by_group_type (nil) Group type for filter, valid with :filtered_by_group_id
297
+ # @option options [String] :filtered_by_group_id (nil) Group instance id for filter, valid with :filtered_by_group_type
298
+ # @option options [String] :filtered_by_key (nil) Key of the notification for filter
299
+ # @option options [String] :later_than (nil) ISO 8601 format time to filter notification index later than specified time
300
+ # @option options [String] :earlier_than (nil) ISO 8601 format time to filter notification index earlier than specified time
301
+ # @option options [Array|Hash] :custom_filter (nil) Custom notification filter (e.g. ['created_at.gt': time.hour.ago])
302
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications or subscriptions
303
+ def filtered_by_options(options = {})
304
+ options = ActivityNotification.cast_to_indifferent_hash(options)
305
+ filtered_notifications = self
306
+ if options.has_key?(:filtered_by_type)
307
+ filtered_notifications = filtered_notifications.filtered_by_type(options[:filtered_by_type])
308
+ end
309
+ if options.has_key?(:filtered_by_group)
310
+ filtered_notifications = filtered_notifications.filtered_by_group(options[:filtered_by_group])
311
+ end
312
+ if options.has_key?(:filtered_by_group_type) && options.has_key?(:filtered_by_group_id)
313
+ filtered_notifications = filtered_notifications.filtered_by_association_type_and_id("group", options[:filtered_by_group_type], options[:filtered_by_group_id])
314
+ end
315
+ if options.has_key?(:filtered_by_key)
316
+ filtered_notifications = filtered_notifications.filtered_by_key(options[:filtered_by_key])
317
+ end
318
+ if options.has_key?(:later_than)
319
+ filtered_notifications = filtered_notifications.later_than(Time.iso8601(options[:later_than]))
320
+ end
321
+ if options.has_key?(:earlier_than)
322
+ filtered_notifications = filtered_notifications.earlier_than(Time.iso8601(options[:earlier_than]))
323
+ end
324
+ if options.has_key?(:custom_filter)
325
+ filtered_notifications = filtered_notifications.where(options[:custom_filter])
326
+ end
327
+ filtered_notifications
328
+ end
329
+
330
+ # Orders by latest (newest) first as created_at: :desc.
331
+ # It uses sort key of Global Secondary Index in DynamoDB tables.
332
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications or subscriptions ordered by latest first
333
+ def latest_order
334
+ # order(created_at: :desc)
335
+ scan_index_forward(false)
336
+ end
337
+
338
+ # Orders by earliest (older) first as created_at: :asc.
339
+ # It uses sort key of Global Secondary Index in DynamoDB tables.
340
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications or subscriptions ordered by earliest first
341
+ def earliest_order
342
+ # order(created_at: :asc)
343
+ scan_index_forward(true)
344
+ end
345
+
346
+ # Orders by latest (newest) first as created_at: :desc and returns as array.
347
+ # @param [Boolean] reverse If notifications or subscriptions will be ordered as earliest first
348
+ # @return [Array] Array of notifications or subscriptions ordered by latest first
349
+ def latest_order!(reverse = false)
350
+ # order(created_at: :desc)
351
+ reverse ? earliest_order! : earliest_order!.reverse
352
+ end
353
+
354
+ # Orders by earliest (older) first as created_at: :asc and returns as array.
355
+ # It does not use sort key in DynamoDB tables.
356
+ # @return [Array] Array of notifications or subscriptions ordered by earliest first
357
+ def earliest_order!
358
+ # order(created_at: :asc)
359
+ all.to_a.sort_by {|n| n.created_at }
360
+ end
361
+
362
+ # Orders by latest (newest) first as subscribed_at: :desc.
363
+ # @return [Array] Array of subscriptions ordered by latest subscribed_at first
364
+ def latest_subscribed_order
365
+ # order(subscribed_at: :desc)
366
+ earliest_subscribed_order.reverse
367
+ end
368
+
369
+ # Orders by earliest (older) first as subscribed_at: :asc.
370
+ # @return [Array] Array of subscriptions ordered by earliest subscribed_at first
371
+ def earliest_subscribed_order
372
+ # order(subscribed_at: :asc)
373
+ all.to_a.sort_by {|n| n.subscribed_at }
374
+ end
375
+
376
+ # Orders by key name as key: :asc.
377
+ # @return [Array] Array of subscriptions ordered by key name
378
+ def key_order
379
+ # order(key: :asc)
380
+ all.to_a.sort_by {|n| n.key }
381
+ end
382
+
383
+ # Selects group owner notifications only.
384
+ # @scope class
385
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
386
+ def group_owners_only
387
+ where('group_owner_id.null': true)
388
+ end
389
+
390
+ # Selects group member notifications only.
391
+ # @scope class
392
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
393
+ def group_members_only
394
+ where('group_owner_id.not_null': true)
395
+ end
396
+
397
+ # Selects unopened notifications only.
398
+ # @scope class
399
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
400
+ def unopened_only
401
+ where('opened_at.null': true)
402
+ end
403
+
404
+ # Selects opened notifications only without limit.
405
+ # Be careful to get too many records with this method.
406
+ # @scope class
407
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
408
+ def opened_only!
409
+ where('opened_at.not_null': true)
410
+ end
411
+
412
+ # Selects opened notifications only with limit.
413
+ # @scope class
414
+ # @param [Integer] limit Limit to query for opened notifications
415
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
416
+ def opened_only(limit)
417
+ limit == 0 ? none : opened_only!.limit(limit)
418
+ end
419
+
420
+ # Selects group member notifications in unopened_index.
421
+ # @scope class
422
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
423
+ def unopened_index_group_members_only
424
+ group_owner_ids = unopened_index.map(&:id)
425
+ group_owner_ids.empty? ? none : where('group_owner_id.in': group_owner_ids)
426
+ end
427
+
428
+ # Selects group member notifications in opened_index.
429
+ # @scope class
430
+ # @param [Integer] limit Limit to query for opened notifications
431
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
432
+ def opened_index_group_members_only(limit)
433
+ group_owner_ids = opened_index(limit).map(&:id)
434
+ group_owner_ids.empty? ? none : where('group_owner_id.in': group_owner_ids)
435
+ end
436
+
437
+ # Selects notifications within expiration.
438
+ # @scope class
439
+ # @param [ActiveSupport::Duration] expiry_delay Expiry period of notifications
440
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
441
+ def within_expiration_only(expiry_delay)
442
+ where('created_at.gt': expiry_delay.ago)
443
+ end
444
+
445
+ # Selects group member notifications with specified group owner ids.
446
+ # @scope class
447
+ # @param [Array<String>] owner_ids Array of group owner ids
448
+ # @return [Dynamoid::Criteria::Chain] Database query of filtered notifications
449
+ def group_members_of_owner_ids_only(owner_ids)
450
+ owner_ids.present? ? where('group_owner_id.in': owner_ids) : none
451
+ end
452
+
453
+ # Includes target instance with query for notifications or subscriptions.
454
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with target
455
+ def with_target
456
+ self
457
+ end
458
+
459
+ # Includes notifiable instance with query for notifications.
460
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with notifiable
461
+ def with_notifiable
462
+ self
463
+ end
464
+
465
+ # Includes group instance with query for notifications.
466
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with group
467
+ def with_group
468
+ self
469
+ end
470
+
471
+ # Includes group owner instances with query for notifications.
472
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with group owner
473
+ def with_group_owner
474
+ self
475
+ end
476
+
477
+ # Includes group member instances with query for notifications.
478
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with group members
479
+ def with_group_members
480
+ self
481
+ end
482
+
483
+ # Includes notifier instance with query for notifications.
484
+ # @return [Dynamoid::Criteria::Chain] Database query of notifications with notifier
485
+ def with_notifier
486
+ self
487
+ end
488
+
489
+ # Dummy reload method for test of notifications or subscriptions.
490
+ def reload
491
+ self
492
+ end
493
+
494
+ # Returns latest notification instance.
495
+ # @return [Notification] Latest notification instance
496
+ def latest
497
+ latest_order.first
498
+ end
499
+
500
+ # Returns earliest notification instance.
501
+ # @return [Notification] Earliest notification instance
502
+ def earliest
503
+ earliest_order.first
504
+ end
505
+
506
+ # Returns latest notification instance.
507
+ # It does not use sort key in DynamoDB tables.
508
+ # @return [Notification] Latest notification instance
509
+ def latest!
510
+ latest_order!.first
511
+ end
512
+
513
+ # Returns earliest notification instance.
514
+ # It does not use sort key in DynamoDB tables.
515
+ # @return [Notification] Earliest notification instance
516
+ def earliest!
517
+ earliest_order!.first
518
+ end
519
+
520
+ # Selects unique keys from query for notifications or subscriptions.
521
+ # @return [Array<String>] Array of notification unique keys
522
+ def uniq_keys
523
+ all.to_a.collect {|n| n.key }.uniq
524
+ end
525
+ end
526
+ end
527
+ end
528
+
529
+ require_relative 'dynamoid/notification.rb'
530
+ require_relative 'dynamoid/subscription.rb'