activity_notification 2.4.1 → 2.5.1

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 (250) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/app/jobs/activity_notification/cascading_notification_job.rb +123 -0
  4. data/docs/Functions.md +197 -1
  5. data/lib/activity_notification/apis/cascading_notification_api.rb +208 -0
  6. data/lib/activity_notification/apis/notification_api.rb +78 -5
  7. data/lib/activity_notification/config.rb +10 -0
  8. data/lib/activity_notification/mailers/helpers.rb +27 -1
  9. data/lib/activity_notification/version.rb +1 -1
  10. data/lib/generators/templates/activity_notification.rb +8 -0
  11. metadata +8 -447
  12. data/.codeclimate.yml +0 -33
  13. data/.coveralls.yml +0 -1
  14. data/.github/ISSUE_TEMPLATE/bug_report.md +0 -22
  15. data/.github/ISSUE_TEMPLATE/feature_request.md +0 -17
  16. data/.github/pull_request_template.md +0 -13
  17. data/.github/workflows/build.yml +0 -100
  18. data/.gitignore +0 -74
  19. data/.rspec +0 -3
  20. data/.rubocop.yml +0 -1157
  21. data/.yardopts +0 -6
  22. data/CHANGELOG.md +0 -452
  23. data/Gemfile +0 -31
  24. data/Procfile +0 -2
  25. data/Rakefile +0 -28
  26. data/activity_notification.gemspec +0 -44
  27. data/ai-curated-specs/issues/172/design.md +0 -220
  28. data/ai-curated-specs/issues/172/tasks.md +0 -326
  29. data/ai-curated-specs/issues/188/design.md +0 -227
  30. data/ai-curated-specs/issues/188/requirements.md +0 -78
  31. data/ai-curated-specs/issues/188/tasks.md +0 -203
  32. data/ai-curated-specs/issues/188/upstream-contributions.md +0 -592
  33. data/ai-curated-specs/issues/50/design.md +0 -235
  34. data/ai-curated-specs/issues/50/requirements.md +0 -49
  35. data/ai-curated-specs/issues/50/tasks.md +0 -232
  36. data/bin/_dynamodblocal +0 -4
  37. data/bin/bundle_update.sh +0 -7
  38. data/bin/deploy_on_heroku.sh +0 -16
  39. data/bin/install_dynamodblocal.sh +0 -5
  40. data/bin/start_dynamodblocal.sh +0 -47
  41. data/bin/stop_dynamodblocal.sh +0 -34
  42. data/gemfiles/Gemfile.rails-5.0 +0 -25
  43. data/gemfiles/Gemfile.rails-5.1 +0 -25
  44. data/gemfiles/Gemfile.rails-5.2 +0 -24
  45. data/gemfiles/Gemfile.rails-6.0 +0 -23
  46. data/gemfiles/Gemfile.rails-6.1 +0 -22
  47. data/gemfiles/Gemfile.rails-7.0 +0 -25
  48. data/gemfiles/Gemfile.rails-7.1 +0 -23
  49. data/gemfiles/Gemfile.rails-7.2 +0 -23
  50. data/gemfiles/Gemfile.rails-8.0 +0 -24
  51. data/package.json +0 -8
  52. data/spec/channels/notification_api_channel_shared_examples.rb +0 -59
  53. data/spec/channels/notification_api_channel_spec.rb +0 -49
  54. data/spec/channels/notification_api_with_devise_channel_spec.rb +0 -76
  55. data/spec/channels/notification_channel_shared_examples.rb +0 -59
  56. data/spec/channels/notification_channel_spec.rb +0 -48
  57. data/spec/channels/notification_with_devise_channel_spec.rb +0 -97
  58. data/spec/concerns/apis/notification_api_spec.rb +0 -1627
  59. data/spec/concerns/apis/subscription_api_spec.rb +0 -474
  60. data/spec/concerns/common_spec.rb +0 -213
  61. data/spec/concerns/models/group_spec.rb +0 -61
  62. data/spec/concerns/models/notifiable_spec.rb +0 -782
  63. data/spec/concerns/models/notifier_spec.rb +0 -71
  64. data/spec/concerns/models/subscriber_spec.rb +0 -800
  65. data/spec/concerns/models/target_spec.rb +0 -1285
  66. data/spec/concerns/renderable_spec.rb +0 -129
  67. data/spec/config_spec.rb +0 -85
  68. data/spec/controllers/common_controller_spec.rb +0 -25
  69. data/spec/controllers/controller_spec_utility.rb +0 -100
  70. data/spec/controllers/dummy_common_controller.rb +0 -5
  71. data/spec/controllers/notifications_api_controller_shared_examples.rb +0 -619
  72. data/spec/controllers/notifications_api_controller_spec.rb +0 -19
  73. data/spec/controllers/notifications_api_with_devise_controller_spec.rb +0 -60
  74. data/spec/controllers/notifications_controller_shared_examples.rb +0 -743
  75. data/spec/controllers/notifications_controller_spec.rb +0 -11
  76. data/spec/controllers/notifications_with_devise_controller_spec.rb +0 -97
  77. data/spec/controllers/subscriptions_api_controller_shared_examples.rb +0 -750
  78. data/spec/controllers/subscriptions_api_controller_spec.rb +0 -19
  79. data/spec/controllers/subscriptions_api_with_devise_controller_spec.rb +0 -60
  80. data/spec/controllers/subscriptions_controller_shared_examples.rb +0 -946
  81. data/spec/controllers/subscriptions_controller_spec.rb +0 -11
  82. data/spec/controllers/subscriptions_with_devise_controller_spec.rb +0 -97
  83. data/spec/factories/admins.rb +0 -5
  84. data/spec/factories/articles.rb +0 -5
  85. data/spec/factories/comments.rb +0 -6
  86. data/spec/factories/dummy/dummy_group.rb +0 -4
  87. data/spec/factories/dummy/dummy_notifiable.rb +0 -4
  88. data/spec/factories/dummy/dummy_notifier.rb +0 -4
  89. data/spec/factories/dummy/dummy_subscriber.rb +0 -4
  90. data/spec/factories/dummy/dummy_target.rb +0 -4
  91. data/spec/factories/notifications.rb +0 -7
  92. data/spec/factories/subscriptions.rb +0 -8
  93. data/spec/factories/users.rb +0 -11
  94. data/spec/generators/controllers_generator_spec.rb +0 -85
  95. data/spec/generators/install_generator_spec.rb +0 -43
  96. data/spec/generators/migration/migration_generator_spec.rb +0 -80
  97. data/spec/generators/models_generator_spec.rb +0 -96
  98. data/spec/generators/views_generator_spec.rb +0 -195
  99. data/spec/helpers/polymorphic_helpers_spec.rb +0 -89
  100. data/spec/helpers/view_helpers_spec.rb +0 -547
  101. data/spec/jobs/notification_resilience_job_spec.rb +0 -167
  102. data/spec/jobs/notify_all_job_spec.rb +0 -23
  103. data/spec/jobs/notify_job_spec.rb +0 -23
  104. data/spec/jobs/notify_to_job_spec.rb +0 -23
  105. data/spec/mailers/mailer_spec.rb +0 -214
  106. data/spec/mailers/notification_resilience_spec.rb +0 -263
  107. data/spec/models/dummy/dummy_group_spec.rb +0 -10
  108. data/spec/models/dummy/dummy_notifiable_spec.rb +0 -10
  109. data/spec/models/dummy/dummy_notifier_spec.rb +0 -10
  110. data/spec/models/dummy/dummy_subscriber_spec.rb +0 -8
  111. data/spec/models/dummy/dummy_target_spec.rb +0 -10
  112. data/spec/models/notification_spec.rb +0 -472
  113. data/spec/models/subscription_spec.rb +0 -215
  114. data/spec/optional_targets/action_cable_api_channel_spec.rb +0 -34
  115. data/spec/optional_targets/action_cable_channel_spec.rb +0 -41
  116. data/spec/optional_targets/amazon_sns_spec.rb +0 -47
  117. data/spec/optional_targets/base_spec.rb +0 -45
  118. data/spec/optional_targets/slack_spec.rb +0 -44
  119. data/spec/orm/dynamoid_spec.rb +0 -115
  120. data/spec/rails_app/Rakefile +0 -15
  121. data/spec/rails_app/app/assets/config/manifest.js +0 -3
  122. data/spec/rails_app/app/assets/images/.keep +0 -0
  123. data/spec/rails_app/app/assets/javascripts/application.js +0 -3
  124. data/spec/rails_app/app/assets/javascripts/cable.js +0 -12
  125. data/spec/rails_app/app/assets/stylesheets/application.css +0 -15
  126. data/spec/rails_app/app/assets/stylesheets/reset.css +0 -85
  127. data/spec/rails_app/app/assets/stylesheets/style.css +0 -244
  128. data/spec/rails_app/app/controllers/admins_controller.rb +0 -21
  129. data/spec/rails_app/app/controllers/application_controller.rb +0 -5
  130. data/spec/rails_app/app/controllers/articles_controller.rb +0 -67
  131. data/spec/rails_app/app/controllers/comments_controller.rb +0 -36
  132. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  133. data/spec/rails_app/app/controllers/spa_controller.rb +0 -7
  134. data/spec/rails_app/app/controllers/users/notifications_controller.rb +0 -2
  135. data/spec/rails_app/app/controllers/users/notifications_with_devise_controller.rb +0 -2
  136. data/spec/rails_app/app/controllers/users/subscriptions_controller.rb +0 -2
  137. data/spec/rails_app/app/controllers/users/subscriptions_with_devise_controller.rb +0 -2
  138. data/spec/rails_app/app/controllers/users_controller.rb +0 -26
  139. data/spec/rails_app/app/helpers/application_helper.rb +0 -2
  140. data/spec/rails_app/app/helpers/devise_helper.rb +0 -2
  141. data/spec/rails_app/app/javascript/App.vue +0 -40
  142. data/spec/rails_app/app/javascript/components/DeviseTokenAuth.vue +0 -82
  143. data/spec/rails_app/app/javascript/components/Top.vue +0 -98
  144. data/spec/rails_app/app/javascript/components/notifications/Index.vue +0 -200
  145. data/spec/rails_app/app/javascript/components/notifications/Notification.vue +0 -133
  146. data/spec/rails_app/app/javascript/components/notifications/NotificationContent.vue +0 -122
  147. data/spec/rails_app/app/javascript/components/subscriptions/Index.vue +0 -279
  148. data/spec/rails_app/app/javascript/components/subscriptions/NewSubscription.vue +0 -112
  149. data/spec/rails_app/app/javascript/components/subscriptions/NotificationKey.vue +0 -141
  150. data/spec/rails_app/app/javascript/components/subscriptions/Subscription.vue +0 -226
  151. data/spec/rails_app/app/javascript/config/development.js +0 -5
  152. data/spec/rails_app/app/javascript/config/environment.js +0 -7
  153. data/spec/rails_app/app/javascript/config/production.js +0 -5
  154. data/spec/rails_app/app/javascript/config/test.js +0 -5
  155. data/spec/rails_app/app/javascript/packs/application.js +0 -18
  156. data/spec/rails_app/app/javascript/packs/spa.js +0 -14
  157. data/spec/rails_app/app/javascript/router/index.js +0 -73
  158. data/spec/rails_app/app/javascript/store/index.js +0 -37
  159. data/spec/rails_app/app/mailers/.keep +0 -0
  160. data/spec/rails_app/app/mailers/custom_notification_mailer.rb +0 -5
  161. data/spec/rails_app/app/models/admin.rb +0 -35
  162. data/spec/rails_app/app/models/article.rb +0 -54
  163. data/spec/rails_app/app/models/comment.rb +0 -81
  164. data/spec/rails_app/app/models/dummy/dummy_base.rb +0 -11
  165. data/spec/rails_app/app/models/dummy/dummy_group.rb +0 -23
  166. data/spec/rails_app/app/models/dummy/dummy_notifiable.rb +0 -15
  167. data/spec/rails_app/app/models/dummy/dummy_notifiable_target.rb +0 -27
  168. data/spec/rails_app/app/models/dummy/dummy_notifier.rb +0 -15
  169. data/spec/rails_app/app/models/dummy/dummy_subscriber.rb +0 -14
  170. data/spec/rails_app/app/models/dummy/dummy_target.rb +0 -16
  171. data/spec/rails_app/app/models/user.rb +0 -73
  172. data/spec/rails_app/app/views/activity_notification/mailer/dummy_subscribers/test_key.text.erb +0 -1
  173. data/spec/rails_app/app/views/activity_notification/notifications/default/article/_update.html.erb +0 -146
  174. data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_path_test.html.erb +0 -1
  175. data/spec/rails_app/app/views/activity_notification/notifications/default/custom/_test.html.erb +0 -1
  176. data/spec/rails_app/app/views/activity_notification/notifications/users/_custom_index.html.erb +0 -1
  177. data/spec/rails_app/app/views/activity_notification/notifications/users/custom/_test.html.erb +0 -1
  178. data/spec/rails_app/app/views/activity_notification/notifications/users/overridden/custom/_test.html.erb +0 -1
  179. data/spec/rails_app/app/views/activity_notification/optional_targets/admins/amazon_sns/comment/_default.text.erb +0 -10
  180. data/spec/rails_app/app/views/articles/_form.html.erb +0 -24
  181. data/spec/rails_app/app/views/articles/edit.html.erb +0 -8
  182. data/spec/rails_app/app/views/articles/index.html.erb +0 -113
  183. data/spec/rails_app/app/views/articles/new.html.erb +0 -7
  184. data/spec/rails_app/app/views/articles/show.html.erb +0 -49
  185. data/spec/rails_app/app/views/layouts/_header.html.erb +0 -46
  186. data/spec/rails_app/app/views/layouts/application.html.erb +0 -15
  187. data/spec/rails_app/app/views/spa/index.html.erb +0 -2
  188. data/spec/rails_app/babel.config.js +0 -72
  189. data/spec/rails_app/bin/bundle +0 -3
  190. data/spec/rails_app/bin/rails +0 -4
  191. data/spec/rails_app/bin/rake +0 -4
  192. data/spec/rails_app/bin/setup +0 -29
  193. data/spec/rails_app/bin/webpack +0 -18
  194. data/spec/rails_app/bin/webpack-dev-server +0 -18
  195. data/spec/rails_app/config/application.rb +0 -54
  196. data/spec/rails_app/config/boot.rb +0 -5
  197. data/spec/rails_app/config/cable.yml +0 -8
  198. data/spec/rails_app/config/database.yml +0 -36
  199. data/spec/rails_app/config/dynamoid.rb +0 -13
  200. data/spec/rails_app/config/environment.rb +0 -26
  201. data/spec/rails_app/config/environments/development.rb +0 -60
  202. data/spec/rails_app/config/environments/production.rb +0 -85
  203. data/spec/rails_app/config/environments/test.rb +0 -53
  204. data/spec/rails_app/config/initializers/activity_notification.rb +0 -104
  205. data/spec/rails_app/config/initializers/assets.rb +0 -11
  206. data/spec/rails_app/config/initializers/backtrace_silencers.rb +0 -7
  207. data/spec/rails_app/config/initializers/cookies_serializer.rb +0 -3
  208. data/spec/rails_app/config/initializers/copy_it.aws.rb.template +0 -6
  209. data/spec/rails_app/config/initializers/devise.rb +0 -278
  210. data/spec/rails_app/config/initializers/devise_token_auth.rb +0 -55
  211. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +0 -4
  212. data/spec/rails_app/config/initializers/inflections.rb +0 -16
  213. data/spec/rails_app/config/initializers/mime_types.rb +0 -4
  214. data/spec/rails_app/config/initializers/mysql.rb +0 -9
  215. data/spec/rails_app/config/initializers/session_store.rb +0 -3
  216. data/spec/rails_app/config/initializers/wrap_parameters.rb +0 -14
  217. data/spec/rails_app/config/initializers/zeitwerk.rb +0 -10
  218. data/spec/rails_app/config/locales/activity_notification.en.yml +0 -26
  219. data/spec/rails_app/config/locales/devise.en.yml +0 -62
  220. data/spec/rails_app/config/mongoid.yml +0 -13
  221. data/spec/rails_app/config/routes.rb +0 -50
  222. data/spec/rails_app/config/secrets.yml +0 -22
  223. data/spec/rails_app/config/webpack/development.js +0 -5
  224. data/spec/rails_app/config/webpack/environment.js +0 -7
  225. data/spec/rails_app/config/webpack/loaders/vue.js +0 -6
  226. data/spec/rails_app/config/webpack/production.js +0 -5
  227. data/spec/rails_app/config/webpack/test.js +0 -5
  228. data/spec/rails_app/config/webpacker.yml +0 -97
  229. data/spec/rails_app/config.ru +0 -4
  230. data/spec/rails_app/db/migrate/20160716000000_create_test_tables.rb +0 -42
  231. data/spec/rails_app/db/migrate/20181209000000_create_activity_notification_tables.rb +0 -33
  232. data/spec/rails_app/db/migrate/20191201000000_add_tokens_to_users.rb +0 -10
  233. data/spec/rails_app/db/schema.rb +0 -98
  234. data/spec/rails_app/db/seeds.rb +0 -95
  235. data/spec/rails_app/lib/custom_optional_targets/console_output.rb +0 -16
  236. data/spec/rails_app/lib/custom_optional_targets/raise_error.rb +0 -14
  237. data/spec/rails_app/lib/custom_optional_targets/wrong_target.rb +0 -13
  238. data/spec/rails_app/lib/mailer_previews/mailer_preview.rb +0 -29
  239. data/spec/rails_app/package.json +0 -23
  240. data/spec/rails_app/postcss.config.js +0 -12
  241. data/spec/rails_app/public/404.html +0 -67
  242. data/spec/rails_app/public/422.html +0 -67
  243. data/spec/rails_app/public/500.html +0 -66
  244. data/spec/rails_app/public/favicon.ico +0 -0
  245. data/spec/roles/acts_as_group_spec.rb +0 -30
  246. data/spec/roles/acts_as_notifiable_spec.rb +0 -432
  247. data/spec/roles/acts_as_notifier_spec.rb +0 -30
  248. data/spec/roles/acts_as_target_spec.rb +0 -36
  249. data/spec/spec_helper.rb +0 -56
  250. data/spec/version_spec.rb +0 -31
@@ -1,7 +1,10 @@
1
+ require 'activity_notification/apis/cascading_notification_api'
2
+
1
3
  module ActivityNotification
2
4
  # Defines API for notification included in Notification model.
3
5
  module NotificationApi
4
6
  extend ActiveSupport::Concern
7
+ include CascadingNotificationApi
5
8
 
6
9
  included do
7
10
  # Defines store_notification as private clas method
@@ -228,7 +231,8 @@ module ActivityNotification
228
231
  notify_later(target_type, notifiable, options)
229
232
  else
230
233
  targets = notifiable.notification_targets(target_type, options[:pass_full_options] ? options : options[:key])
231
- unless targets.blank?
234
+ # Optimize blank check to avoid loading all records for ActiveRecord relations
235
+ unless targets_empty?(targets)
232
236
  notify_all(targets, notifiable, options)
233
237
  end
234
238
  end
@@ -269,10 +273,17 @@ module ActivityNotification
269
273
 
270
274
  # Generates notifications to specified targets.
271
275
  #
272
- # @example Notify to all users
276
+ # For large target collections, this method uses batch processing to reduce memory consumption:
277
+ # - ActiveRecord::Relation: Uses find_each (loads in batches of 1000 records)
278
+ # - Mongoid::Criteria: Uses each with cursor batching
279
+ # - Arrays: Standard iteration (already in memory)
280
+ #
281
+ # @example Notify to all users (with ActiveRecord relation for memory efficiency)
273
282
  # ActivityNotification::Notification.notify_all User.all, @comment
283
+ # @example Notify to all users with custom batch size
284
+ # ActivityNotification::Notification.notify_all User.all, @comment, batch_size: 500
274
285
  #
275
- # @param [Array<Object>] targets Targets to send notifications
286
+ # @param [ActiveRecord::Relation, Mongoid::Criteria, Array<Object>] targets Targets to send notifications
276
287
  # @param [Object] notifiable Notifiable instance
277
288
  # @param [Hash] options Options for notifications
278
289
  # @option options [String] :key (notifiable.default_notification_key) Key of the notification
@@ -284,23 +295,32 @@ module ActivityNotification
284
295
  # @option options [Boolean] :send_email (true) Whether it sends notification email
285
296
  # @option options [Boolean] :send_later (true) Whether it sends notification email asynchronously
286
297
  # @option options [Boolean] :publish_optional_targets (true) Whether it publishes notification to optional targets
298
+ # @option options [Integer] :batch_size (1000) Batch size for ActiveRecord find_each (optional)
287
299
  # @option options [Hash<String, Hash>] :optional_targets ({}) Options for optional targets, keys are optional target name (:amazon_sns or :slack etc) and values are options
288
300
  # @return [Array<Notificaion>] Array of generated notifications
289
301
  def notify_all(targets, notifiable, options = {})
290
302
  if options[:notify_later]
291
303
  notify_all_later(targets, notifiable, options)
292
304
  else
293
- targets.map { |target| notify_to(target, notifiable, options) }
305
+ # Optimize for large ActiveRecord relations by using batch processing
306
+ process_targets_in_batches(targets, notifiable, options)
294
307
  end
295
308
  end
296
309
  alias_method :notify_all_now, :notify_all
297
310
 
298
311
  # Generates notifications to specified targets later by ActiveJob queue.
299
312
  #
313
+ # Note: When passing ActiveRecord relations or Mongoid criteria to async jobs,
314
+ # they may be serialized to arrays before job execution, which can consume memory
315
+ # for large target sets. For very large datasets (10,000+ records), consider using
316
+ # notify_later with target_type instead, which generates notifications asynchronously
317
+ # without loading all targets upfront:
318
+ # ActivityNotification::Notification.notify(:users, @comment, notify_later: true)
319
+ #
300
320
  # @example Notify to all users later
301
321
  # ActivityNotification::Notification.notify_all_later User.all, @comment
302
322
  #
303
- # @param [Array<Object>] targets Targets to send notifications
323
+ # @param [ActiveRecord::Relation, Mongoid::Criteria, Array<Object>] targets Targets to send notifications
304
324
  # @param [Object] notifiable Notifiable instance
305
325
  # @param [Hash] options Options for notifications
306
326
  # @option options [String] :key (notifiable.default_notification_key) Key of the notification
@@ -546,6 +566,59 @@ module ActivityNotification
546
566
  notification.after_store
547
567
  notification
548
568
  end
569
+
570
+ # Checks if targets collection is empty without loading all records
571
+ # @api private
572
+ # @param [Object] targets Targets collection (can be an ActiveRecord::Relation, Mongoid::Criteria, Array, etc.)
573
+ # @return [Boolean] True if targets is empty
574
+ def targets_empty?(targets)
575
+ # For ActiveRecord relations and Mongoid criteria, use exists? to avoid loading all records
576
+ if targets.respond_to?(:exists?)
577
+ !targets.exists?
578
+ else
579
+ # For arrays and other enumerables, use blank?
580
+ targets.blank?
581
+ end
582
+ end
583
+
584
+ # Processes targets in batches for memory efficiency with large collections
585
+ # @api private
586
+ #
587
+ # For ActiveRecord::Relation, uses find_each which loads records in batches (default 1000).
588
+ # For Mongoid::Criteria, uses each which leverages MongoDB's cursor batching.
589
+ # For Arrays and other enumerables, uses standard iteration.
590
+ #
591
+ # Note: When called from async jobs (notify_all_later), ActiveRecord relations may be
592
+ # serialized to arrays before reaching this method, which limits batch processing benefits.
593
+ # Consider using notify_later with target_type instead of notify_all_later with relations
594
+ # for large datasets in async scenarios.
595
+ #
596
+ # @param [Object] targets Targets collection (can be an ActiveRecord::Relation, Mongoid::Criteria, Array, etc.)
597
+ # @param [Object] notifiable Notifiable instance
598
+ # @param [Hash] options Options for notifications
599
+ # @option options [Integer] :batch_size (1000) Batch size for ActiveRecord find_each (optional)
600
+ # @return [Array<Notification>] Array of generated notifications
601
+ def process_targets_in_batches(targets, notifiable, options = {})
602
+ notifications = []
603
+
604
+ # For ActiveRecord relations, use find_each to process in batches
605
+ # This loads records in batches (default 1000) to avoid loading all records into memory
606
+ if targets.respond_to?(:find_each)
607
+ batch_options = {}
608
+ batch_options[:batch_size] = options[:batch_size] if options[:batch_size]
609
+
610
+ targets.find_each(**batch_options) do |target|
611
+ notification = notify_to(target, notifiable, options)
612
+ notifications << notification
613
+ end
614
+ else
615
+ # For arrays and other enumerables, use standard map approach
616
+ # Already in memory, so no batching benefit
617
+ notifications = targets.map { |target| notify_to(target, notifiable, options) }
618
+ end
619
+
620
+ notifications
621
+ end
549
622
  end
550
623
 
551
624
  # :nocov:
@@ -82,6 +82,15 @@ module ActivityNotification
82
82
  # @return [String] Email address as sender of notification email.
83
83
  attr_accessor :mailer_sender
84
84
 
85
+ # @overload mailer_cc
86
+ # Returns carbon copy (CC) email address(es) for notification email
87
+ # @return [String, Array<String>, Proc] CC email address(es) for notification email.
88
+ # @overload mailer_cc=(value)
89
+ # Sets carbon copy (CC) email address(es) for notification email
90
+ # @param [String, Array<String>, Proc] mailer_cc The new mailer_cc
91
+ # @return [String, Array<String>, Proc] CC email address(es) for notification email.
92
+ attr_accessor :mailer_cc
93
+
85
94
  # @overload mailer
86
95
  # Returns mailer class for email notification
87
96
  # @return [String] Mailer class for email notification.
@@ -236,6 +245,7 @@ module ActivityNotification
236
245
  @subscribe_to_email_as_default = nil
237
246
  @subscribe_to_optional_targets_as_default = nil
238
247
  @mailer_sender = nil
248
+ @mailer_cc = nil
239
249
  @mailer = 'ActivityNotification::Mailer'
240
250
  @parent_mailer = 'ActionMailer::Base'
241
251
  @parent_job = 'ActiveJob::Base'
@@ -74,6 +74,7 @@ module ActivityNotification
74
74
  subject: :subject_for,
75
75
  from: :mailer_from,
76
76
  reply_to: :mailer_reply_to,
77
+ cc: :mailer_cc,
77
78
  message_id: nil
78
79
  }.each do |header_name, default_method|
79
80
  overridding_method_name = "overriding_notification_email_#{header_name.to_s}"
@@ -81,7 +82,12 @@ module ActivityNotification
81
82
  @notification.notifiable.send(overridding_method_name, @target, key).present?
82
83
  @notification.notifiable.send(overridding_method_name, @target, key)
83
84
  elsif default_method
84
- send(default_method, key)
85
+ # Special handling for methods that take target instead of key
86
+ if [:mailer_cc].include?(default_method)
87
+ send(default_method, @target)
88
+ else
89
+ send(default_method, key)
90
+ end
85
91
  else
86
92
  nil
87
93
  end
@@ -99,6 +105,26 @@ module ActivityNotification
99
105
  target.mailer_to
100
106
  end
101
107
 
108
+ # Returns carbon copy (CC) email address(es).
109
+ #
110
+ # @param [Object] target Target instance to notify
111
+ # @return [String, Array<String>, nil] CC email address(es) or nil
112
+ def mailer_cc(target)
113
+ if target.respond_to?(:mailer_cc)
114
+ target.mailer_cc
115
+ elsif ActivityNotification.config.mailer_cc.present?
116
+ if ActivityNotification.config.mailer_cc.is_a?(Proc)
117
+ # Get the notification key from current context
118
+ key = @notification ? @notification.key : nil
119
+ ActivityNotification.config.mailer_cc.call(key)
120
+ else
121
+ ActivityNotification.config.mailer_cc
122
+ end
123
+ else
124
+ nil
125
+ end
126
+ end
127
+
102
128
  # Returns sender email address as 'reply_to'.
103
129
  #
104
130
  # @param [String] key Key of the notification or batch notification email
@@ -1,3 +1,3 @@
1
1
  module ActivityNotification
2
- VERSION = "2.4.1"
2
+ VERSION = "2.5.1"
3
3
  end
@@ -45,6 +45,14 @@ ActivityNotification.configure do |config|
45
45
  # note that it will be overwritten if you use your own mailer class with default "from" parameter.
46
46
  config.mailer_sender = 'please-change-me-at-config-initializers-activity_notification@example.com'
47
47
 
48
+ # Configure the carbon copy (CC) email address(es) for notification emails.
49
+ # You can set a single email address, an array of email addresses, or a Proc that returns either.
50
+ # Note that this can be overridden per target by defining a mailer_cc method in the target model,
51
+ # or per notification by defining overriding_notification_email_cc in the notifiable model.
52
+ # config.mailer_cc = 'admin@example.com'
53
+ # config.mailer_cc = ['admin@example.com', 'support@example.com']
54
+ # config.mailer_cc = ->(key){ key.include?('urgent') ? 'urgent@example.com' : nil }
55
+
48
56
  # Configure the class responsible to send e-mails.
49
57
  # config.mailer = "ActivityNotification::Mailer"
50
58