bullet 5.5.0 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -5
- data/CHANGELOG.md +31 -1
- data/Gemfile +6 -1
- data/Gemfile.rails-5.0 +1 -1
- data/Gemfile.rails-5.1 +15 -0
- data/Gemfile.rails-5.2 +15 -0
- data/Guardfile +2 -2
- data/Hacking.md +1 -1
- data/README.md +20 -23
- data/Rakefile +20 -21
- data/bullet.gemspec +20 -15
- data/lib/bullet/active_record4.rb +18 -14
- data/lib/bullet/active_record41.rb +16 -14
- data/lib/bullet/active_record42.rb +24 -22
- data/lib/bullet/active_record5.rb +102 -91
- data/lib/bullet/active_record52.rb +232 -0
- data/lib/bullet/dependency.rb +9 -1
- data/lib/bullet/detector/association.rb +17 -14
- data/lib/bullet/detector/base.rb +2 -0
- data/lib/bullet/detector/counter_cache.rb +14 -10
- data/lib/bullet/detector/n_plus_one_query.rb +21 -17
- data/lib/bullet/detector/unused_eager_loading.rb +30 -24
- data/lib/bullet/detector.rb +2 -0
- data/lib/bullet/ext/object.rb +6 -4
- data/lib/bullet/ext/string.rb +3 -1
- data/lib/bullet/mongoid4x.rb +7 -2
- data/lib/bullet/mongoid5x.rb +7 -2
- data/lib/bullet/mongoid6x.rb +11 -6
- data/lib/bullet/notification/base.rb +26 -19
- data/lib/bullet/notification/counter_cache.rb +3 -1
- data/lib/bullet/notification/n_plus_one_query.rb +7 -4
- data/lib/bullet/notification/unused_eager_loading.rb +7 -4
- data/lib/bullet/notification.rb +2 -0
- data/lib/bullet/notification_collector.rb +2 -1
- data/lib/bullet/rack.rb +22 -18
- data/lib/bullet/registry/association.rb +2 -0
- data/lib/bullet/registry/base.rb +2 -0
- data/lib/bullet/registry/object.rb +2 -0
- data/lib/bullet/registry.rb +2 -0
- data/lib/bullet/stack_trace_filter.rb +56 -18
- data/lib/bullet/version.rb +3 -2
- data/lib/bullet.rb +43 -33
- data/lib/generators/bullet/install_generator.rb +48 -0
- data/perf/benchmark.rb +12 -12
- data/rails/init.rb +2 -0
- data/spec/bullet/detector/association_spec.rb +6 -4
- data/spec/bullet/detector/base_spec.rb +2 -0
- data/spec/bullet/detector/counter_cache_spec.rb +13 -11
- data/spec/bullet/detector/n_plus_one_query_spec.rb +71 -46
- data/spec/bullet/detector/unused_eager_loading_spec.rb +21 -19
- data/spec/bullet/ext/object_spec.rb +10 -8
- data/spec/bullet/ext/string_spec.rb +7 -5
- data/spec/bullet/notification/base_spec.rb +39 -38
- data/spec/bullet/notification/counter_cache_spec.rb +5 -3
- data/spec/bullet/notification/n_plus_one_query_spec.rb +5 -3
- data/spec/bullet/notification/unused_eager_loading_spec.rb +4 -2
- data/spec/bullet/notification_collector_spec.rb +12 -10
- data/spec/bullet/rack_spec.rb +48 -47
- data/spec/bullet/registry/association_spec.rb +12 -10
- data/spec/bullet/registry/base_spec.rb +22 -20
- data/spec/bullet/registry/object_spec.rb +6 -4
- data/spec/bullet_spec.rb +24 -3
- data/spec/integration/{active_record4 → active_record}/association_spec.rb +176 -114
- data/spec/integration/counter_cache_spec.rb +15 -13
- data/spec/integration/mongoid/association_spec.rb +35 -33
- data/spec/models/address.rb +2 -0
- data/spec/models/author.rb +2 -0
- data/spec/models/base_user.rb +2 -0
- data/spec/models/category.rb +2 -0
- data/spec/models/city.rb +2 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +3 -1
- data/spec/models/company.rb +2 -0
- data/spec/models/country.rb +2 -0
- data/spec/models/document.rb +4 -2
- data/spec/models/entry.rb +2 -0
- data/spec/models/firm.rb +3 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/mongoid/address.rb +3 -1
- data/spec/models/mongoid/category.rb +4 -2
- data/spec/models/mongoid/comment.rb +3 -1
- data/spec/models/mongoid/company.rb +3 -1
- data/spec/models/mongoid/entry.rb +3 -1
- data/spec/models/mongoid/post.rb +6 -4
- data/spec/models/mongoid/user.rb +2 -0
- data/spec/models/newspaper.rb +3 -1
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +2 -0
- data/spec/models/pet.rb +2 -0
- data/spec/models/post.rb +17 -0
- data/spec/models/relationship.rb +2 -0
- data/spec/models/reply.rb +2 -0
- data/spec/models/student.rb +2 -0
- data/spec/models/submission.rb +2 -0
- data/spec/models/teacher.rb +2 -0
- data/spec/models/user.rb +2 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +15 -13
- data/spec/support/bullet_ext.rb +2 -0
- data/spec/support/mongo_seed.rb +36 -42
- data/spec/support/rack_double.rb +13 -19
- data/spec/support/sqlite_seed.rb +84 -76
- data/tasks/bullet_tasks.rake +4 -2
- data/test.sh +2 -0
- data/update.sh +2 -0
- metadata +17 -11
- data/spec/integration/active_record5/association_spec.rb +0 -768
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class CounterCache < Base
|
|
@@ -7,7 +9,7 @@ module Bullet
|
|
|
7
9
|
return unless Bullet.counter_cache_enable?
|
|
8
10
|
return unless object.primary_key_value
|
|
9
11
|
|
|
10
|
-
Bullet.debug(
|
|
12
|
+
Bullet.debug('Detector::CounterCache#add_counter_cache', "object: #{object.bullet_key}, associations: #{associations}")
|
|
11
13
|
if conditions_met?(object, associations)
|
|
12
14
|
create_notification object.class.to_s, associations
|
|
13
15
|
end
|
|
@@ -16,10 +18,11 @@ module Bullet
|
|
|
16
18
|
def add_possible_objects(object_or_objects)
|
|
17
19
|
return unless Bullet.start?
|
|
18
20
|
return unless Bullet.counter_cache_enable?
|
|
21
|
+
|
|
19
22
|
objects = Array(object_or_objects)
|
|
20
23
|
return if objects.map(&:primary_key_value).compact.empty?
|
|
21
24
|
|
|
22
|
-
Bullet.debug(
|
|
25
|
+
Bullet.debug('Detector::CounterCache#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
|
|
23
26
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
24
27
|
end
|
|
25
28
|
|
|
@@ -28,11 +31,11 @@ module Bullet
|
|
|
28
31
|
return unless Bullet.counter_cache_enable?
|
|
29
32
|
return unless object.primary_key_value
|
|
30
33
|
|
|
31
|
-
Bullet.debug(
|
|
34
|
+
Bullet.debug('Detector::CounterCache#add_impossible_object', "object: #{object.bullet_key}")
|
|
32
35
|
impossible_objects.add object.bullet_key
|
|
33
36
|
end
|
|
34
37
|
|
|
35
|
-
def conditions_met?(object,
|
|
38
|
+
def conditions_met?(object, _associations)
|
|
36
39
|
possible_objects.include?(object.bullet_key) && !impossible_objects.include?(object.bullet_key)
|
|
37
40
|
end
|
|
38
41
|
|
|
@@ -45,14 +48,15 @@ module Bullet
|
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
private
|
|
48
|
-
def create_notification(klazz, associations)
|
|
49
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
def create_notification(klazz, associations)
|
|
53
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:counter_cache, klazz)
|
|
54
|
+
|
|
55
|
+
if notify_associations.present?
|
|
56
|
+
notice = Bullet::Notification::CounterCache.new klazz, notify_associations
|
|
57
|
+
Bullet.notification_collector.add notice
|
|
55
58
|
end
|
|
59
|
+
end
|
|
56
60
|
end
|
|
57
61
|
end
|
|
58
62
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class NPlusOneQuery < Association
|
|
@@ -11,13 +13,15 @@ module Bullet
|
|
|
11
13
|
# if it is, keeps this unpreload associations and caller.
|
|
12
14
|
def call_association(object, associations)
|
|
13
15
|
return unless Bullet.start?
|
|
16
|
+
return unless Bullet.n_plus_one_query_enable?
|
|
14
17
|
return unless object.primary_key_value
|
|
15
18
|
return if inversed_objects.include?(object.bullet_key, associations)
|
|
19
|
+
|
|
16
20
|
add_call_object_associations(object, associations)
|
|
17
21
|
|
|
18
|
-
Bullet.debug(
|
|
22
|
+
Bullet.debug('Detector::NPlusOneQuery#call_association', "object: #{object.bullet_key}, associations: #{associations}")
|
|
19
23
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
|
20
|
-
Bullet.debug(
|
|
24
|
+
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
|
21
25
|
create_notification caller_in_project, object.class.to_s, associations
|
|
22
26
|
end
|
|
23
27
|
end
|
|
@@ -25,10 +29,11 @@ module Bullet
|
|
|
25
29
|
def add_possible_objects(object_or_objects)
|
|
26
30
|
return unless Bullet.start?
|
|
27
31
|
return unless Bullet.n_plus_one_query_enable?
|
|
32
|
+
|
|
28
33
|
objects = Array(object_or_objects)
|
|
29
34
|
return if objects.map(&:primary_key_value).compact.empty?
|
|
30
35
|
|
|
31
|
-
Bullet.debug(
|
|
36
|
+
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
|
|
32
37
|
objects.each { |object| possible_objects.add object.bullet_key }
|
|
33
38
|
end
|
|
34
39
|
|
|
@@ -37,7 +42,7 @@ module Bullet
|
|
|
37
42
|
return unless Bullet.n_plus_one_query_enable?
|
|
38
43
|
return unless object.primary_key_value
|
|
39
44
|
|
|
40
|
-
Bullet.debug(
|
|
45
|
+
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
|
|
41
46
|
impossible_objects.add object.bullet_key
|
|
42
47
|
end
|
|
43
48
|
|
|
@@ -46,7 +51,7 @@ module Bullet
|
|
|
46
51
|
return unless Bullet.n_plus_one_query_enable?
|
|
47
52
|
return unless object.primary_key_value
|
|
48
53
|
|
|
49
|
-
Bullet.debug(
|
|
54
|
+
Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
|
|
50
55
|
inversed_objects.add object.bullet_key, association
|
|
51
56
|
end
|
|
52
57
|
|
|
@@ -66,28 +71,27 @@ module Bullet
|
|
|
66
71
|
# check if object => associations already exists in object_associations.
|
|
67
72
|
def association?(object, associations)
|
|
68
73
|
value = object_associations[object.bullet_key]
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
# associations == v comparision order is important here because
|
|
74
|
+
value&.each do |v|
|
|
75
|
+
# associations == v comparison order is important here because
|
|
72
76
|
# v variable might be a squeel node where :== method is redefined,
|
|
73
77
|
# so it does not compare values at all and return unexpected results
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
end
|
|
78
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
|
79
|
+
return true if result
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
false
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
private
|
|
83
|
-
def create_notification(callers, klazz, associations)
|
|
84
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
def create_notification(callers, klazz, associations)
|
|
88
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:n_plus_one_query, klazz)
|
|
89
|
+
|
|
90
|
+
if notify_associations.present?
|
|
91
|
+
notice = Bullet::Notification::NPlusOneQuery.new(callers, klazz, notify_associations)
|
|
92
|
+
Bullet.notification_collector.add(notice)
|
|
90
93
|
end
|
|
94
|
+
end
|
|
91
95
|
end
|
|
92
96
|
end
|
|
93
97
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Detector
|
|
3
5
|
class UnusedEagerLoading < Association
|
|
@@ -17,7 +19,7 @@ module Bullet
|
|
|
17
19
|
object_association_diff = diff_object_associations bullet_key, associations
|
|
18
20
|
next if object_association_diff.empty?
|
|
19
21
|
|
|
20
|
-
Bullet.debug(
|
|
22
|
+
Bullet.debug('detect unused preload', "object: #{bullet_key}, associations: #{object_association_diff}")
|
|
21
23
|
create_notification(caller_in_project, bullet_key.bullet_class_name, object_association_diff)
|
|
22
24
|
end
|
|
23
25
|
end
|
|
@@ -27,20 +29,22 @@ module Bullet
|
|
|
27
29
|
return unless Bullet.unused_eager_loading_enable?
|
|
28
30
|
return if objects.map(&:primary_key_value).compact.empty?
|
|
29
31
|
|
|
30
|
-
Bullet.debug(
|
|
32
|
+
Bullet.debug('Detector::UnusedEagerLoading#add_eager_loadings', "objects: #{objects.map(&:bullet_key).join(', ')}, associations: #{associations}")
|
|
31
33
|
bullet_keys = objects.map(&:bullet_key)
|
|
32
34
|
|
|
33
|
-
to_add
|
|
34
|
-
|
|
35
|
+
to_add = []
|
|
36
|
+
to_merge = []
|
|
37
|
+
to_delete = []
|
|
38
|
+
eager_loadings.each do |k, _v|
|
|
35
39
|
key_objects_overlap = k & bullet_keys
|
|
36
40
|
|
|
37
41
|
next if key_objects_overlap.empty?
|
|
38
42
|
|
|
39
|
-
bullet_keys
|
|
43
|
+
bullet_keys -= k
|
|
40
44
|
if key_objects_overlap == k
|
|
41
45
|
to_add << [k, associations]
|
|
42
46
|
else
|
|
43
|
-
to_merge << [key_objects_overlap, (
|
|
47
|
+
to_merge << [key_objects_overlap, (eager_loadings[k].dup << associations)]
|
|
44
48
|
|
|
45
49
|
keys_without_objects = k - key_objects_overlap
|
|
46
50
|
to_merge << [keys_without_objects, eager_loadings[k]]
|
|
@@ -56,29 +60,31 @@ module Bullet
|
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
private
|
|
59
|
-
def create_notification(callers, klazz, associations)
|
|
60
|
-
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
Bullet.notification_collector.add(notice)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
64
|
+
def create_notification(callers, klazz, associations)
|
|
65
|
+
notify_associations = Array(associations) - Bullet.get_whitelist_associations(:unused_eager_loading, klazz)
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
coa = call_object_associations[related_bullet_key]
|
|
72
|
-
next if coa.nil?
|
|
73
|
-
all.merge coa
|
|
74
|
-
end
|
|
75
|
-
all.to_a
|
|
67
|
+
if notify_associations.present?
|
|
68
|
+
notice = Bullet::Notification::UnusedEagerLoading.new(callers, klazz, notify_associations)
|
|
69
|
+
Bullet.notification_collector.add(notice)
|
|
76
70
|
end
|
|
71
|
+
end
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
def call_associations(bullet_key, associations)
|
|
74
|
+
all = Set.new
|
|
75
|
+
eager_loadings.similarly_associated(bullet_key, associations).each do |related_bullet_key|
|
|
76
|
+
coa = call_object_associations[related_bullet_key]
|
|
77
|
+
next if coa.nil?
|
|
78
|
+
|
|
79
|
+
all.merge coa
|
|
81
80
|
end
|
|
81
|
+
all.to_a
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def diff_object_associations(bullet_key, associations)
|
|
85
|
+
potential_associations = associations - call_associations(bullet_key, associations)
|
|
86
|
+
potential_associations.reject { |a| a.is_a?(Hash) }
|
|
87
|
+
end
|
|
82
88
|
end
|
|
83
89
|
end
|
|
84
90
|
end
|
data/lib/bullet/detector.rb
CHANGED
data/lib/bullet/ext/object.rb
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
class Object
|
|
2
4
|
def bullet_key
|
|
3
|
-
"#{self.class}:#{
|
|
5
|
+
"#{self.class}:#{primary_key_value}"
|
|
4
6
|
end
|
|
5
7
|
|
|
6
8
|
def primary_key_value
|
|
7
9
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
|
8
|
-
self.class.primary_keys.map { |primary_key|
|
|
10
|
+
self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
|
|
9
11
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
|
10
|
-
|
|
12
|
+
send self.class.primary_key
|
|
11
13
|
else
|
|
12
|
-
|
|
14
|
+
id
|
|
13
15
|
end
|
|
14
16
|
end
|
|
15
17
|
end
|
data/lib/bullet/ext/string.rb
CHANGED
data/lib/bullet/mongoid4x.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Mongoid
|
|
3
5
|
def self.enable
|
|
@@ -21,13 +23,16 @@ module Bullet
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def each(&block)
|
|
24
|
-
|
|
26
|
+
return to_enum unless block_given?
|
|
27
|
+
|
|
28
|
+
records = []
|
|
29
|
+
origin_each { |record| records << record }
|
|
25
30
|
if records.length > 1
|
|
26
31
|
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
32
|
elsif records.size == 1
|
|
28
33
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
34
|
end
|
|
30
|
-
|
|
35
|
+
records.each(&block)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def eager_load(docs)
|
data/lib/bullet/mongoid5x.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Mongoid
|
|
3
5
|
def self.enable
|
|
@@ -21,13 +23,16 @@ module Bullet
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def each(&block)
|
|
24
|
-
|
|
26
|
+
return to_enum unless block_given?
|
|
27
|
+
|
|
28
|
+
records = []
|
|
29
|
+
origin_each { |record| records << record }
|
|
25
30
|
if records.length > 1
|
|
26
31
|
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
32
|
elsif records.size == 1
|
|
28
33
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
34
|
end
|
|
30
|
-
|
|
35
|
+
records.each(&block)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def eager_load(docs)
|
data/lib/bullet/mongoid6x.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Mongoid
|
|
3
5
|
def self.enable
|
|
@@ -8,26 +10,29 @@ module Bullet
|
|
|
8
10
|
alias_method :origin_each, :each
|
|
9
11
|
alias_method :origin_eager_load, :eager_load
|
|
10
12
|
|
|
11
|
-
def first
|
|
12
|
-
result = origin_first
|
|
13
|
+
def first(opt = {})
|
|
14
|
+
result = origin_first(opt)
|
|
13
15
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
14
16
|
result
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
def last
|
|
18
|
-
result = origin_last
|
|
19
|
+
def last(opt = {})
|
|
20
|
+
result = origin_last(opt)
|
|
19
21
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
|
20
22
|
result
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
def each(&block)
|
|
24
|
-
|
|
26
|
+
return to_enum unless block_given?
|
|
27
|
+
|
|
28
|
+
records = []
|
|
29
|
+
origin_each { |record| records << record }
|
|
25
30
|
if records.length > 1
|
|
26
31
|
Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
|
|
27
32
|
elsif records.size == 1
|
|
28
33
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
|
|
29
34
|
end
|
|
30
|
-
|
|
35
|
+
records.each(&block)
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
def eager_load(docs)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class Base
|
|
@@ -6,28 +8,32 @@ module Bullet
|
|
|
6
8
|
|
|
7
9
|
def initialize(base_class, association_or_associations, path = nil)
|
|
8
10
|
@base_class = base_class
|
|
9
|
-
@associations = association_or_associations.is_a?(Array) ?
|
|
11
|
+
@associations = association_or_associations.is_a?(Array) ? association_or_associations : [association_or_associations]
|
|
10
12
|
@path = path
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
def title
|
|
14
|
-
raise NoMethodError
|
|
16
|
+
raise NoMethodError, 'no method title defined'
|
|
15
17
|
end
|
|
16
18
|
|
|
17
19
|
def body
|
|
18
|
-
raise NoMethodError
|
|
20
|
+
raise NoMethodError, 'no method body defined'
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def call_stack_messages
|
|
22
|
-
|
|
24
|
+
''
|
|
23
25
|
end
|
|
24
26
|
|
|
25
27
|
def whoami
|
|
26
|
-
@user ||= ENV['USER'].presence || (
|
|
28
|
+
@user ||= ENV['USER'].presence || (begin
|
|
29
|
+
`whoami`.chomp
|
|
30
|
+
rescue StandardError
|
|
31
|
+
''
|
|
32
|
+
end)
|
|
27
33
|
if @user.present?
|
|
28
34
|
"user: #{@user}"
|
|
29
35
|
else
|
|
30
|
-
|
|
36
|
+
''
|
|
31
37
|
end
|
|
32
38
|
end
|
|
33
39
|
|
|
@@ -36,23 +42,23 @@ module Bullet
|
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def notify_inline
|
|
39
|
-
|
|
45
|
+
notifier.inline_notify(notification_data)
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
def notify_out_of_channel
|
|
43
|
-
|
|
49
|
+
notifier.out_of_channel_notify(notification_data)
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
def short_notice
|
|
47
|
-
[whoami.presence, url, title, body].compact.join(
|
|
53
|
+
[whoami.presence, url, title, body].compact.join(' ')
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
def notification_data
|
|
51
57
|
{
|
|
52
|
-
:
|
|
53
|
-
:
|
|
54
|
-
:
|
|
55
|
-
:
|
|
58
|
+
user: whoami,
|
|
59
|
+
url: url,
|
|
60
|
+
title: title,
|
|
61
|
+
body: body_with_caller
|
|
56
62
|
}
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -65,13 +71,14 @@ module Bullet
|
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
protected
|
|
68
|
-
def klazz_associations_str
|
|
69
|
-
" #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
|
|
70
|
-
end
|
|
71
74
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
def klazz_associations_str
|
|
76
|
+
" #{@base_class} => [#{@associations.map(&:inspect).join(', ')}]"
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def associations_str
|
|
80
|
+
":includes => #{@associations.map { |a| a.to_s.to_sym unless a.is_a? Hash }.inspect}"
|
|
81
|
+
end
|
|
75
82
|
end
|
|
76
83
|
end
|
|
77
84
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class NPlusOneQuery < Base
|
|
@@ -17,14 +19,15 @@ module Bullet
|
|
|
17
19
|
|
|
18
20
|
def notification_data
|
|
19
21
|
super.merge(
|
|
20
|
-
:
|
|
22
|
+
backtrace: []
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
protected
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
def call_stack_messages
|
|
29
|
+
(['Call stack'] + @callers).join("\n ")
|
|
30
|
+
end
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
module Notification
|
|
3
5
|
class UnusedEagerLoading < Base
|
|
@@ -17,14 +19,15 @@ module Bullet
|
|
|
17
19
|
|
|
18
20
|
def notification_data
|
|
19
21
|
super.merge(
|
|
20
|
-
|
|
22
|
+
backtrace: []
|
|
21
23
|
)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
26
|
protected
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
|
|
28
|
+
def call_stack_messages
|
|
29
|
+
(['Call stack'] + @callers).join("\n ")
|
|
30
|
+
end
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
end
|
data/lib/bullet/notification.rb
CHANGED
data/lib/bullet/rack.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bullet
|
|
2
4
|
class Rack
|
|
3
5
|
include Dependency
|
|
@@ -8,16 +10,17 @@ module Bullet
|
|
|
8
10
|
|
|
9
11
|
def call(env)
|
|
10
12
|
return @app.call(env) unless Bullet.enable?
|
|
13
|
+
|
|
11
14
|
Bullet.start_request
|
|
12
15
|
status, headers, response = @app.call(env)
|
|
13
16
|
|
|
14
17
|
response_body = nil
|
|
15
18
|
if Bullet.notification?
|
|
16
19
|
if !file?(headers) && !sse?(headers) && !empty?(response) &&
|
|
17
|
-
|
|
20
|
+
status == 200 && html_request?(headers, response)
|
|
18
21
|
response_body = response_body(response)
|
|
19
|
-
append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
20
|
-
append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
22
|
+
response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
|
|
23
|
+
response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
|
|
21
24
|
headers['Content-Length'] = response_body.bytesize.to_s
|
|
22
25
|
end
|
|
23
26
|
Bullet.perform_out_of_channel_notifications(env)
|
|
@@ -42,28 +45,29 @@ module Bullet
|
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def append_to_html_body(response_body, content)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
body = response_body.dup
|
|
49
|
+
if body.include?('</body>')
|
|
50
|
+
position = body.rindex('</body>')
|
|
51
|
+
body.insert(position, content)
|
|
48
52
|
else
|
|
49
|
-
|
|
53
|
+
body << content
|
|
50
54
|
end
|
|
51
55
|
end
|
|
52
56
|
|
|
53
57
|
def footer_note
|
|
54
|
-
"<div #{footer_div_attributes}>" + footer_close_button + Bullet.footer_info.uniq.join(
|
|
58
|
+
"<div #{footer_div_attributes}>" + footer_close_button + Bullet.footer_info.uniq.join('<br>') + '</div>'
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
def file?(headers)
|
|
58
|
-
headers[
|
|
62
|
+
headers['Content-Transfer-Encoding'] == 'binary' || headers['Content-Disposition']
|
|
59
63
|
end
|
|
60
64
|
|
|
61
65
|
def sse?(headers)
|
|
62
|
-
headers[
|
|
66
|
+
headers['Content-Type'] == 'text/event-stream'
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def html_request?(headers, response)
|
|
66
|
-
headers['Content-Type']
|
|
70
|
+
headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
|
|
67
71
|
end
|
|
68
72
|
|
|
69
73
|
def response_body(response)
|
|
@@ -77,13 +81,13 @@ module Bullet
|
|
|
77
81
|
private
|
|
78
82
|
|
|
79
83
|
def footer_div_attributes
|
|
80
|
-
|
|
81
|
-
data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
EOF
|
|
84
|
+
<<~EOF
|
|
85
|
+
data-is-bullet-footer ondblclick="this.parentNode.removeChild(this);" style="position: fixed; bottom: 0pt; left: 0pt; cursor: pointer; border-style: solid; border-color: rgb(153, 153, 153);
|
|
86
|
+
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
|
87
|
+
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
|
88
|
+
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
|
89
|
+
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
|
90
|
+
EOF
|
|
87
91
|
end
|
|
88
92
|
|
|
89
93
|
def footer_close_button
|