activity_notification 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +56 -0
  4. data/.rspec +3 -0
  5. data/.travis.yml +28 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +174 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.md +437 -0
  10. data/Rakefile +19 -0
  11. data/activity_notification.gemspec +33 -0
  12. data/app/controllers/activity_notification/notifications_controller.rb +119 -0
  13. data/app/controllers/activity_notification/notifications_with_devise_controller.rb +29 -0
  14. data/app/mailers/activity_notification/mailer.rb +13 -0
  15. data/app/views/activity_notification/mailer/default/default.html.erb +7 -0
  16. data/app/views/activity_notification/notifications/default/_default.html.erb +36 -0
  17. data/app/views/activity_notification/notifications/default/_index.html.erb +9 -0
  18. data/app/views/activity_notification/notifications/default/destroy.js.erb +2 -0
  19. data/app/views/activity_notification/notifications/default/index.html.erb +17 -0
  20. data/app/views/activity_notification/notifications/default/open.js.erb +2 -0
  21. data/app/views/activity_notification/notifications/default/open_all.js.erb +2 -0
  22. data/app/views/activity_notification/notifications/default/show.html.erb +2 -0
  23. data/config/locales/en.yml +8 -0
  24. data/lib/activity_notification.rb +52 -0
  25. data/lib/activity_notification/apis/notification_api.rb +147 -0
  26. data/lib/activity_notification/common.rb +86 -0
  27. data/lib/activity_notification/config.rb +23 -0
  28. data/lib/activity_notification/controllers/store_controller.rb +30 -0
  29. data/lib/activity_notification/helpers/polymorphic_helpers.rb +32 -0
  30. data/lib/activity_notification/helpers/view_helpers.rb +108 -0
  31. data/lib/activity_notification/mailers/helpers.rb +97 -0
  32. data/lib/activity_notification/models/notifiable.rb +136 -0
  33. data/lib/activity_notification/models/notification.rb +50 -0
  34. data/lib/activity_notification/models/notifier.rb +11 -0
  35. data/lib/activity_notification/models/target.rb +104 -0
  36. data/lib/activity_notification/rails.rb +6 -0
  37. data/lib/activity_notification/rails/routes.rb +105 -0
  38. data/lib/activity_notification/renderable.rb +142 -0
  39. data/lib/activity_notification/roles/acts_as_notifiable.rb +37 -0
  40. data/lib/activity_notification/roles/acts_as_target.rb +30 -0
  41. data/lib/activity_notification/version.rb +3 -0
  42. data/lib/generators/activity_notification/controllers_generator.rb +44 -0
  43. data/lib/generators/activity_notification/install_generator.rb +45 -0
  44. data/lib/generators/activity_notification/migration/migration_generator.rb +17 -0
  45. data/lib/generators/activity_notification/notification/notification_generator.rb +17 -0
  46. data/lib/generators/activity_notification/views_generator.rb +44 -0
  47. data/lib/generators/templates/README +53 -0
  48. data/lib/generators/templates/active_record/migration.rb +18 -0
  49. data/lib/generators/templates/activity_notification.rb +18 -0
  50. data/lib/generators/templates/controllers/README +13 -0
  51. data/lib/generators/templates/controllers/notifications_controller.rb +66 -0
  52. data/lib/generators/templates/controllers/notifications_with_devise_controller.rb +74 -0
  53. data/lib/generators/templates/notification/notification.rb +3 -0
  54. data/lib/tasks/activity_notification_tasks.rake +4 -0
  55. data/spec/concerns/notification_api_spec.rb +531 -0
  56. data/spec/factories/articles.rb +5 -0
  57. data/spec/factories/comments.rb +6 -0
  58. data/spec/factories/notifications.rb +7 -0
  59. data/spec/factories/users.rb +5 -0
  60. data/spec/models/notification_spec.rb +259 -0
  61. data/spec/rails_app/Rakefile +6 -0
  62. data/spec/rails_app/app/controllers/application_controller.rb +5 -0
  63. data/spec/rails_app/app/controllers/concerns/.keep +0 -0
  64. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  65. data/spec/rails_app/app/mailers/.keep +0 -0
  66. data/spec/rails_app/app/models/.keep +0 -0
  67. data/spec/rails_app/app/models/article.rb +12 -0
  68. data/spec/rails_app/app/models/comment.rb +18 -0
  69. data/spec/rails_app/app/models/concerns/.keep +0 -0
  70. data/spec/rails_app/app/models/user.rb +8 -0
  71. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  72. data/spec/rails_app/bin/bundle +3 -0
  73. data/spec/rails_app/bin/rails +4 -0
  74. data/spec/rails_app/bin/rake +4 -0
  75. data/spec/rails_app/bin/setup +29 -0
  76. data/spec/rails_app/config.ru +4 -0
  77. data/spec/rails_app/config/application.rb +20 -0
  78. data/spec/rails_app/config/boot.rb +5 -0
  79. data/spec/rails_app/config/database.yml +25 -0
  80. data/spec/rails_app/config/environment.rb +12 -0
  81. data/spec/rails_app/config/environments/development.rb +44 -0
  82. data/spec/rails_app/config/environments/production.rb +79 -0
  83. data/spec/rails_app/config/environments/test.rb +45 -0
  84. data/spec/rails_app/config/initializers/activity_notification.rb +18 -0
  85. data/spec/rails_app/config/initializers/assets.rb +11 -0
  86. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  87. data/spec/rails_app/config/initializers/cookies_serializer.rb +3 -0
  88. data/spec/rails_app/config/initializers/devise.rb +274 -0
  89. data/spec/rails_app/config/initializers/filter_parameter_logging.rb +4 -0
  90. data/spec/rails_app/config/initializers/inflections.rb +16 -0
  91. data/spec/rails_app/config/initializers/mime_types.rb +4 -0
  92. data/spec/rails_app/config/initializers/session_store.rb +3 -0
  93. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  94. data/spec/rails_app/config/routes.rb +5 -0
  95. data/spec/rails_app/config/secrets.yml +22 -0
  96. data/spec/rails_app/db/migrate/20160715050420_create_notifications.rb +18 -0
  97. data/spec/rails_app/db/migrate/20160715050433_create_test_tables.rb +36 -0
  98. data/spec/rails_app/db/schema.rb +73 -0
  99. data/spec/rails_app/public/404.html +67 -0
  100. data/spec/rails_app/public/422.html +67 -0
  101. data/spec/rails_app/public/500.html +66 -0
  102. data/spec/rails_app/public/favicon.ico +0 -0
  103. data/spec/spec_helper.rb +34 -0
  104. metadata +309 -0
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'ActivityNotification'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+ Bundler::GemHelper.install_tasks
19
+
@@ -0,0 +1,33 @@
1
+ $:.push File.expand_path("../lib", __FILE__)
2
+
3
+ # Maintain your gem's version:
4
+ require "activity_notification/version"
5
+
6
+ # Describe your gem and declare its dependencies:
7
+ Gem::Specification.new do |s|
8
+ s.name = "activity_notification"
9
+ s.version = ActivityNotification::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ["Shota Yamazaki"]
12
+ s.email = ["shota.yamazaki.8@gmail.com"]
13
+ s.homepage = "https://github.com/simukappu/activity_notification"
14
+ s.summary = "Integrated user activity notifications for Rails"
15
+ s.description = "Integrated user activity notifications for Rails. Provides functions to configure multiple notification targets and make activity notifications with notifiable models, like adding comments, responding etc."
16
+ s.license = "MIT"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- spec/*`.split("\n")
20
+ s.require_paths = ["lib"]
21
+ s.required_ruby_version = '>= 2.1.0'
22
+
23
+ s.add_dependency 'rails', '~> 4.2'
24
+ #s.add_dependency 'railties', '>= 3.0.0'
25
+ s.add_dependency 'i18n', '>= 0.5.0'
26
+ s.add_dependency 'activerecord', '>= 3.0'
27
+
28
+ s.add_development_dependency 'sqlite3', '~> 1.3.11'
29
+ s.add_development_dependency "rspec-rails", '~> 3.5.1'
30
+ s.add_development_dependency "factory_girl_rails", '~> 4.7.0'
31
+ s.add_development_dependency 'simplecov', '~> 0.12.0'
32
+ s.add_development_dependency "devise", '~> 4.2.0'
33
+ end
@@ -0,0 +1,119 @@
1
+ module ActivityNotification
2
+ class NotificationsController < ActivityNotification.config.parent_controller.constantize
3
+ include ActivityNotification::StoreController
4
+ include ActivityNotification::PolymorphicHelpers
5
+ prepend_before_action :set_target
6
+ before_action :set_notification, except: [:index, :open_all]
7
+ before_action :set_view_prefixes, except: [:move]
8
+
9
+ DEFAULT_VIEW_DIRECTORY = "default"
10
+
11
+ # GET /:target_type/:target_id/notifcations
12
+ def index
13
+ @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
14
+ respond_to do |format|
15
+ format.html # index.html.erb
16
+ format.json { render json: @notifications.to_json(include: [:target, :notifiable, :group]) }
17
+ end
18
+ end
19
+
20
+ # POST /:target_type/:target_id/notifcations/open_all
21
+ def open_all
22
+ @target.open_all_notifications
23
+ @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
24
+ return_back_or_ajax(params[:filter], params[:limit])
25
+ end
26
+
27
+ # GET /:target_type/:target_id/notifcations/:id
28
+ def show
29
+ end
30
+
31
+ # DELETE /:target_type/:target_id/notifcations/:id
32
+ def destroy
33
+ @notification.destroy
34
+ @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
35
+ return_back_or_ajax(params[:filter], params[:limit])
36
+ end
37
+
38
+ # POST /:target_type/:target_id/notifcations/:id/open
39
+ def open
40
+ @notification.open!
41
+ @notifications = load_notification_index(params[:filter], params[:limit]) if params[:reload].to_s.to_boolean(true)
42
+ params[:move].to_s.to_boolean(false) ?
43
+ move :
44
+ return_back_or_ajax(params[:filter], params[:limit])
45
+ end
46
+
47
+ # GET /:target_type/:target_id/notifcations/:id/move
48
+ def move
49
+ @notification.open! if params[:open].to_s.to_boolean(false)
50
+ redirect_to @notification.notifiale_path
51
+ end
52
+
53
+ # No action routing
54
+ # This method is called from target_view_path method
55
+ # This method can be overriden
56
+ def controller_path
57
+ "activity_notification/notifications"
58
+ end
59
+
60
+ # No action routing
61
+ # This method needs to be public since it is called from view helper
62
+ def target_view_path
63
+ target_type = @target.to_resources_name
64
+ view_path = [controller_path, target_type].join('/')
65
+ lookup_context.exists?(action_name, view_path) ?
66
+ view_path :
67
+ [controller_path, DEFAULT_VIEW_DIRECTORY].join('/')
68
+ end
69
+
70
+ protected
71
+
72
+ def set_target
73
+ if (target_type = params[:target_type]).present?
74
+ target_class = target_type.to_model_class
75
+ @target = params[:target_id].present? ?
76
+ target_class.find_by_id!(params[:target_id]) :
77
+ target_class.find_by_id!(params["#{target_type.to_resource_name}_id"])
78
+ else
79
+ render text: "400 Bad Request: Missing parameter", status: 400
80
+ end
81
+ end
82
+
83
+ def set_notification
84
+ @notification = Notification.find_by_id!(params[:id])
85
+ if @target.present? and @notification.target != @target
86
+ render text: "403 Forbidden: Wrong target", status: 403
87
+ end
88
+ end
89
+
90
+ def load_notification_index(filter, limit)
91
+ limit = nil unless limit.to_i > 0
92
+ case filter
93
+ when 'opened'
94
+ @target.opened_notification_index_with_attributes(limit)
95
+ when 'unopened'
96
+ @target.unopened_notification_index_with_attributes(limit)
97
+ else
98
+ @target.notification_index_with_attributes(limit)
99
+ end
100
+ end
101
+
102
+ def set_view_prefixes
103
+ lookup_context.prefixes.prepend(target_view_path)
104
+ end
105
+
106
+ def return_back_or_ajax(filter, limit)
107
+ respond_to do |format|
108
+ if request.xhr?
109
+ format.js
110
+ elsif request.referer
111
+ redirect_to :back, filter: filter, limit: limit and return
112
+ else
113
+ redirect_to action: :index, filter: filter, limit: limit and return
114
+ end
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,29 @@
1
+ module ActivityNotification
2
+ class NotificationsWithDeviseController < NotificationsController
3
+ prepend_before_action :authenticate_devise_resource!
4
+ before_action :authenticate_target!
5
+
6
+ protected
7
+
8
+ def authenticate_devise_resource!
9
+ authenticate_method_name = "authenticate_#{params[:devise_type].to_resource_name}!"
10
+ if respond_to?(authenticate_method_name)
11
+ send(authenticate_method_name)
12
+ else
13
+ render text: "403 Forbidden: Unauthenticated", status: 403
14
+ end
15
+ end
16
+
17
+ def authenticate_target!
18
+ current_resource_method_name = "current_#{params[:devise_type].to_resource_name}"
19
+ if respond_to?(current_resource_method_name)
20
+ unless @target.authenticate_with_devise?(send(current_resource_method_name))
21
+ render text: "403 Forbidden: Unauthorized target", status: 403
22
+ end
23
+ else
24
+ render text: "403 Forbidden: Unauthenticated", status: 403
25
+ end
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,13 @@
1
+ if defined?(ActionMailer)
2
+ class ActivityNotification::Mailer < ActivityNotification.config.parent_mailer.constantize
3
+ include ActivityNotification::Mailers::Helpers
4
+
5
+ def send_notification_email(notification)
6
+ if notification.target.notification_email_allowed?(notification.notifiable, notification.key) and
7
+ notification.notifiable.notification_email_allowed?(notification.target, notification.key)
8
+ notification_mail(notification)
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ <p>Dear <%= @target.mailer_to %></p>
2
+
3
+ <p>This is a notification of <%= @notifiable.printable_name %> for you.</p>
4
+
5
+ <p><%= link_to "Move to #{@notifiable.printable_name}", move_notification_url_for(@notification, open: true) %></p>
6
+
7
+ <p>Thank you!</p>
@@ -0,0 +1,36 @@
1
+ <li>
2
+ <div>
3
+ <%= notification.notifier.present? ? notification.notifier.printable_name : 'Someone' %>
4
+ <% if notification.group_member_exists? %>
5
+ <%= " and other #{notification.group_member_count} people" %>
6
+ <% end %>
7
+ notified you of <%= notification.notifiable.printable_name %>
8
+ <%= "in #{notification.group.printable_name}" if notification.group.present? %>
9
+ </div>
10
+ <div>
11
+ <%= notification.opened? ? "Opened" : "Unopened" %>
12
+ </div>
13
+ <div>
14
+ <%= link_to "Move", move_notification_path_for(notification) %>
15
+ </div>
16
+ <% if notification.unopened? %>
17
+ <div>
18
+ <%= link_to "Open and move (GET)", move_notification_path_for(notification, open: true) %>
19
+ </div>
20
+ <div>
21
+ <%= link_to "Open and move (POST)", open_notification_path_for(notification, move: true), method: :post %>
22
+ </div>
23
+ <div>
24
+ <%= link_to "Open", open_notification_path_for(notification, filter: params[:filter], limit: params[:limit]), method: :post %>
25
+ </div>
26
+ <div>
27
+ <%= link_to "Open (Ajax)", open_notification_path_for(notification, filter: params[:filter], limit: params[:limit]), method: :post, remote: true %>
28
+ </div>
29
+ <% end %>
30
+ <div>
31
+ <%= link_to "Destroy", notification_path_for(notification, filter: params[:filter], limit: params[:limit]), method: :delete %>
32
+ </div>
33
+ <div>
34
+ <%= link_to "Destroy (Ajax)", notification_path_for(notification, filter: params[:filter], limit: params[:limit]), method: :delete, remote: true %>
35
+ </div>
36
+ </li>
@@ -0,0 +1,9 @@
1
+ <div>
2
+ <%= link_to open_all_notifications_path_for(@target), method: :post, remote: true do %>
3
+ <div id="notification_count"><%= @target.unopened_notification_count %></div>
4
+ <% end %>
5
+
6
+ <div id="notification_index">
7
+ <%= yield :notification_index %>
8
+ </div>
9
+ </div>
@@ -0,0 +1,2 @@
1
+ $("#notification_count").html("<%= @target.unopened_notification_count %>");
2
+ $("#notifications").html("<%= escape_javascript( render_notification(@notifications, fallback: "default") ) %>");
@@ -0,0 +1,17 @@
1
+ <h1>Notifications for <%= @target.printable_name %></h1>
2
+ <div id="notification_count"><%= @target.unopened_notification_count %></div>
3
+
4
+ <div>
5
+ <%= link_to "Open all", open_all_notifications_path_for(@target), method: :post %>
6
+ </div>
7
+ <div>
8
+ <%= link_to "Open all (ajax)", open_all_notifications_path_for(@target), method: :post, remote: true %>
9
+ </div>
10
+ <ul>
11
+ <div id="notifications">
12
+ <%= render_notification(@notifications, fallback: :default) %>
13
+ <%#= render_notification(@notifications, fallback: :text) %>
14
+ </div>
15
+ </ul>
16
+
17
+ <%#= render_notifications_of @target, fallback: :default, index_content: :with_attributes %>
@@ -0,0 +1,2 @@
1
+ $("#notification_count").html("<%= @target.unopened_notification_count %>");
2
+ $("#notifications").html("<%= escape_javascript( render_notification(@notifications, fallback: "default") ) %>");
@@ -0,0 +1,2 @@
1
+ $("#notification_count").html("<%= @target.unopened_notification_count %>");
2
+ $("#notifications").html("<%= escape_javascript( render_notification(@notifications, fallback: "default") ) %>");
@@ -0,0 +1,2 @@
1
+ <h1>Notification for <%= "#{@target.class.to_s.underscore} (#{@target.id})" %></h1>
2
+ <%= render_notification(@notification, fallback: "default") %>
@@ -0,0 +1,8 @@
1
+ # Additional translations of ActivityNotification
2
+
3
+ en:
4
+ notification:
5
+ default:
6
+ your_notifiable:
7
+ default:
8
+ mail_subject:
@@ -0,0 +1,52 @@
1
+ require 'rails'
2
+ require 'active_support'
3
+ require 'action_view'
4
+
5
+ module ActivityNotification
6
+ extend ActiveSupport::Concern
7
+ extend ActiveSupport::Autoload
8
+
9
+ autoload :Notification, 'activity_notification/models/notification'
10
+ autoload :Target, 'activity_notification/models/target'
11
+ autoload :Notifiable, 'activity_notification/models/notifiable'
12
+ autoload :ActsAsTarget, 'activity_notification/roles/acts_as_target'
13
+ autoload :ActsAsNotifiable, 'activity_notification/roles/acts_as_notifiable'
14
+ autoload :StoreController, 'activity_notification/controllers/store_controller'
15
+ autoload :NotificationApi, 'activity_notification/apis/notification_api'
16
+ autoload :Common
17
+ autoload :Config
18
+ autoload :Renderable
19
+ autoload :VERSION
20
+
21
+
22
+ module Mailers
23
+ autoload :Helpers, 'activity_notification/mailers/helpers'
24
+ end
25
+
26
+ # Returns ActivityNotification's configuration object.
27
+ def self.config
28
+ @config ||= ActivityNotification::Config.new
29
+ end
30
+
31
+ # Lets you set global configuration options.
32
+ #
33
+ # All available options and their defaults are in the example below:
34
+ # @example Initializer for Rails
35
+ # ActivityNotification.configure do |config|
36
+ # config.enabled = false
37
+ # config.table_name = "notifications"
38
+ # ...
39
+ # end
40
+ def self.configure(&block)
41
+ yield(config) if block_given?
42
+ end
43
+
44
+ end
45
+
46
+ # Load ActivityNotification helpers
47
+ require 'activity_notification/helpers/polymorphic_helpers'
48
+ require 'activity_notification/helpers/view_helpers'
49
+
50
+ # Define Rails::Engine
51
+ require 'activity_notification/rails'
52
+
@@ -0,0 +1,147 @@
1
+ module ActivityNotification
2
+ module NotificationApi
3
+ extend ActiveSupport::Concern
4
+
5
+ # Define store_notification as private clas method
6
+ included do
7
+ private_class_method :store_notification
8
+ end
9
+
10
+ # For notification API
11
+ class_methods do
12
+ def notify(target_type, notifiable, options = {})
13
+ targets = notifiable.notification_targets(target_type, options[:key])
14
+ unless targets.blank?
15
+ notify_all(targets, notifiable, options)
16
+ end
17
+ end
18
+
19
+ def notify_all(targets, notifiable, options = {})
20
+ targets.each do |target|
21
+ notify_to(target, notifiable, options)
22
+ end
23
+ end
24
+
25
+ def notify_to(target, notifiable, options = {})
26
+ send_email = options.has_key?(:send_email) ? options[:send_email] : true
27
+ send_later = options.has_key?(:send_later) ? options[:send_later] : true
28
+ # Store notification
29
+ notification = store_notification(target, notifiable, options)
30
+ # Send notification email
31
+ notification.send_notification_email(send_later) if send_email
32
+ notification
33
+ end
34
+
35
+ # Open all notifications of specified target
36
+ def open_all_of(target, opened_at = nil)
37
+ opened_at = DateTime.now if opened_at.blank?
38
+ Notification.where(target: target, opened_at: nil).update_all(opened_at: opened_at)
39
+ end
40
+
41
+ #TODO description
42
+ # Call from controllers or views to avoid N+1
43
+ def group_member_exists?(notifications)
44
+ Notification.where(group_owner_id: notifications.pluck(:id)).exists?
45
+ end
46
+
47
+ def available_options
48
+ [:key, :group, :parameters, :notifier, :send_email, :send_later].freeze
49
+ end
50
+
51
+ # Private class methods
52
+
53
+ def store_notification(target, notifiable, options = {})
54
+ target_type = target.to_class_name
55
+ key = options[:key] || notifiable.default_notification_key
56
+ group = options[:group] || notifiable.notification_group(target_type, key)
57
+ notifier = options[:notifier] || notifiable.notifier(target_type, key)
58
+ parameters = options[:parameters] || {}
59
+ parameters.merge!(options.except(*available_options))
60
+ parameters.merge!(notifiable.notification_parameters(target_type, key))
61
+
62
+ # Bundle notification group by target, notifiable_type, group and key
63
+ # Defferent notifiable.id can be made in a same group
64
+ group_owner = Notification.where(target: target, notifiable_type: notifiable.to_class_name, key: key, group: group)
65
+ .where(group_owner_id: nil, opened_at: nil).earliest
66
+ if group.present? and group_owner.present?
67
+ create(target: target, notifiable: notifiable, key: key, group: group, group_owner: group_owner, parameters: parameters, notifier: notifier)
68
+ else
69
+ create(target: target, notifiable: notifiable, key: key, group: group, parameters: parameters, notifier: notifier)
70
+ end
71
+ end
72
+ end
73
+
74
+
75
+ # Public instance methods
76
+
77
+ def send_notification_email(send_later = true)
78
+ if send_later
79
+ Mailer.send_notification_email(self).deliver_later
80
+ else
81
+ Mailer.send_notification_email(self).deliver_now
82
+ end
83
+ end
84
+
85
+ def open!(opened_at = nil)
86
+ opened_at = DateTime.now if opened_at.blank?
87
+ update(opened_at: opened_at)
88
+ group_members.update_all(opened_at: opened_at)
89
+ end
90
+
91
+ def unopened?
92
+ !opened?
93
+ end
94
+
95
+ def opened?
96
+ opened_at.present?
97
+ end
98
+
99
+ def group_owner?
100
+ group_owner_id.blank?
101
+ end
102
+
103
+ def group_member?
104
+ group_owner_id.present?
105
+ end
106
+
107
+ # Cache group-by query result to avoid N+1 call
108
+ def group_member_exists?(limit = ActivityNotification.config.opened_limit)
109
+ group_member_count(limit) > 0
110
+ end
111
+
112
+ # Cache group-by query result to avoid N+1 call
113
+ def group_member_count(limit = ActivityNotification.config.opened_limit)
114
+ notification = group_member? ? group_owner : self
115
+ notification.opened? ?
116
+ notification.opened_group_member_count(limit) :
117
+ notification.unopened_group_member_count
118
+ end
119
+
120
+ def notifiale_path
121
+ notifiable.notifiable_path(target_type)
122
+ end
123
+
124
+
125
+ # Protected instance methods
126
+ protected
127
+
128
+ def unopened_group_member_count
129
+ # Cache group-by query result to avoid N+1 call
130
+ unopened_group_member_counts = target.notifications
131
+ .unopened_index_group_members_only
132
+ .group(:group_owner_id)
133
+ .count
134
+ unopened_group_member_counts[id] || 0
135
+ end
136
+
137
+ def opened_group_member_count(limit = ActivityNotification.config.opened_limit)
138
+ # Cache group-by query result to avoid N+1 call
139
+ opened_group_member_counts = target.notifications
140
+ .opened_index_group_members_only(limit)
141
+ .group(:group_owner_id)
142
+ .count
143
+ opened_group_member_counts[id] || 0
144
+ end
145
+
146
+ end
147
+ end