bullet 2.2.1 → 2.3.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.
- data/.gitignore +1 -0
- data/.travis.yml +4 -1
- data/Gemfile +3 -2
- data/Gemfile.lock +85 -69
- data/Guardfile +8 -0
- data/lib/bullet.rb +13 -8
- data/lib/bullet/action_controller2.rb +4 -4
- data/lib/bullet/active_record2.rb +5 -6
- data/lib/bullet/active_record3.rb +2 -3
- data/lib/bullet/active_record31.rb +6 -8
- data/lib/bullet/detector/association.rb +27 -53
- data/lib/bullet/detector/counter.rb +34 -29
- data/lib/bullet/detector/n_plus_one_query.rb +47 -28
- data/lib/bullet/detector/unused_eager_association.rb +31 -30
- data/lib/bullet/notification/base.rb +14 -12
- data/lib/bullet/notification/n_plus_one_query.rb +6 -10
- data/lib/bullet/notification/unused_eager_loading.rb +1 -2
- data/lib/bullet/notification_collector.rb +1 -2
- data/lib/bullet/rack.rb +6 -3
- data/lib/bullet/registry/association.rb +4 -6
- data/lib/bullet/registry/base.rb +10 -7
- data/lib/bullet/registry/object.rb +6 -6
- data/lib/bullet/version.rb +1 -1
- data/perf/benchmark.rb +30 -12
- data/spec/bullet/detector/association_spec.rb +90 -0
- data/spec/bullet/detector/base_spec.rb +14 -0
- data/spec/bullet/detector/counter_spec.rb +65 -0
- data/spec/bullet/detector/n_plus_one_query_spec.rb +94 -0
- data/spec/bullet/detector/unused_eager_association_spec.rb +62 -0
- data/spec/bullet/notification/base_spec.rb +67 -0
- data/spec/bullet/notification/counter_cache_spec.rb +12 -0
- data/spec/bullet/notification/n_plus_one_query_spec.rb +13 -0
- data/spec/bullet/notification/unused_eager_loading_spec.rb +12 -0
- data/spec/bullet/notification_collector_spec.rb +32 -0
- data/spec/bullet/rack_spec.rb +80 -24
- data/spec/bullet/registry/association_spec.rb +26 -0
- data/spec/bullet/registry/base_spec.rb +44 -0
- data/spec/bullet/registry/object_spec.rb +25 -0
- data/spec/integration/association_for_chris_spec.rb +37 -0
- data/spec/integration/association_for_peschkaj_spec.rb +26 -0
- data/spec/{bullet → integration}/association_spec.rb +1 -359
- data/spec/integration/counter_spec.rb +37 -0
- data/spec/models/address.rb +3 -0
- data/spec/models/author.rb +3 -0
- data/spec/models/base_user.rb +5 -0
- data/spec/models/category.rb +7 -0
- data/spec/models/city.rb +3 -0
- data/spec/models/client.rb +4 -0
- data/spec/models/comment.rb +4 -0
- data/spec/models/company.rb +3 -0
- data/spec/models/contact.rb +3 -0
- data/spec/models/country.rb +3 -0
- data/spec/models/deal.rb +4 -0
- data/spec/models/document.rb +5 -0
- data/spec/models/email.rb +3 -0
- data/spec/models/entry.rb +3 -0
- data/spec/models/firm.rb +4 -0
- data/spec/models/folder.rb +2 -0
- data/spec/models/hotel.rb +4 -0
- data/spec/models/location.rb +3 -0
- data/spec/models/newspaper.rb +3 -0
- data/spec/models/page.rb +2 -0
- data/spec/models/person.rb +3 -0
- data/spec/models/pet.rb +3 -0
- data/spec/models/post.rb +11 -0
- data/spec/models/relationship.rb +4 -0
- data/spec/models/student.rb +3 -0
- data/spec/models/submission.rb +4 -0
- data/spec/models/teacher.rb +3 -0
- data/spec/models/user.rb +4 -0
- data/spec/models/writer.rb +2 -0
- data/spec/spec_helper.rb +16 -106
- data/spec/support/bullet_ext.rb +55 -0
- data/spec/support/rack_double.rb +55 -0
- data/spec/support/seed.rb +256 -0
- metadata +104 -14
- data/.watchr +0 -24
- data/spec/bullet/association_for_chris_spec.rb +0 -96
- data/spec/bullet/association_for_peschkaj_spec.rb +0 -86
- data/spec/bullet/counter_spec.rb +0 -136
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Bullet::Detector::Counter do
|
4
|
+
before(:each) do
|
5
|
+
Bullet.start_request
|
6
|
+
end
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
Bullet.end_request
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should need counter cache with all cities" do
|
13
|
+
Country.all.each do |country|
|
14
|
+
country.cities.size
|
15
|
+
end
|
16
|
+
Bullet.collected_counter_cache_notifications.should_not be_empty
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should not need counter cache if already define counter_cache" do
|
20
|
+
Person.all.each do |person|
|
21
|
+
person.pets.size
|
22
|
+
end
|
23
|
+
Bullet.collected_counter_cache_notifications.should be_empty
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should not need counter cache with only one object" do
|
27
|
+
Country.first.cities.size
|
28
|
+
Bullet.collected_counter_cache_notifications.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should not need counter cache with part of cities" do
|
32
|
+
Country.all.each do |country|
|
33
|
+
country.cities.where(:name => 'first').size
|
34
|
+
end
|
35
|
+
Bullet.collected_counter_cache_notifications.should be_empty
|
36
|
+
end
|
37
|
+
end
|
data/spec/models/city.rb
ADDED
data/spec/models/deal.rb
ADDED
data/spec/models/firm.rb
ADDED
data/spec/models/page.rb
ADDED
data/spec/models/pet.rb
ADDED
data/spec/models/post.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class Post < ActiveRecord::Base
|
2
|
+
belongs_to :category
|
3
|
+
has_many :comments
|
4
|
+
belongs_to :writer
|
5
|
+
|
6
|
+
|
7
|
+
scope :preload_posts, lambda { includes(:comments) }
|
8
|
+
scope :in_category_name, lambda { |name|
|
9
|
+
where(['categories.name = ?', name]).includes(:category)
|
10
|
+
}
|
11
|
+
end
|
data/spec/models/user.rb
ADDED
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
#require 'pry'
|
2
|
-
require 'rubygems'
|
3
1
|
require 'rspec'
|
4
2
|
require 'rspec/autorun'
|
5
3
|
require 'rails'
|
@@ -19,116 +17,28 @@ require 'bullet'
|
|
19
17
|
Bullet.enable = true
|
20
18
|
ActiveRecord::Migration.verbose = false
|
21
19
|
|
22
|
-
|
23
|
-
|
24
|
-
Bullet.notification_collector.collection.select do |notification|
|
25
|
-
notification.is_a? notification_class
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.collected_counter_cache_notifications
|
30
|
-
collected_notifications_of_class Bullet::Notification::CounterCache
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.collected_n_plus_one_query_notifications
|
34
|
-
collected_notifications_of_class Bullet::Notification::NPlusOneQuery
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.collected_unused_eager_association_notifications
|
38
|
-
collected_notifications_of_class Bullet::Notification::UnusedEagerLoading
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
module Bullet
|
43
|
-
module Detector
|
44
|
-
class Association
|
45
|
-
class <<self
|
46
|
-
# returns true if all associations are preloaded
|
47
|
-
def completely_preloading_associations?
|
48
|
-
Bullet.collected_n_plus_one_query_notifications.empty?
|
49
|
-
end
|
50
|
-
|
51
|
-
def has_unused_preload_associations?
|
52
|
-
Bullet.collected_unused_eager_association_notifications.present?
|
53
|
-
end
|
54
|
-
|
55
|
-
# returns true if a given object has a specific association
|
56
|
-
def creating_object_association_for?(object, association)
|
57
|
-
object_associations[object].present? && object_associations[object].include?(association)
|
58
|
-
end
|
59
|
-
|
60
|
-
# returns true if a given class includes the specific unpreloaded association
|
61
|
-
def detecting_unpreloaded_association_for?(klass, association)
|
62
|
-
for_class_and_assoc = Bullet.collected_n_plus_one_query_notifications.select do |notification|
|
63
|
-
notification.base_class == klass and
|
64
|
-
notification.associations.include?( association )
|
65
|
-
end
|
66
|
-
for_class_and_assoc.present?
|
67
|
-
end
|
68
|
-
|
69
|
-
# returns true if the given class includes the specific unused preloaded association
|
70
|
-
def unused_preload_associations_for?(klass, association)
|
71
|
-
for_class_and_assoc = Bullet.collected_unused_eager_association_notifications.select do |notification|
|
72
|
-
notification.base_class == klass and
|
73
|
-
notification.associations.include?( association )
|
74
|
-
end
|
75
|
-
for_class_and_assoc.present?
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
20
|
+
MODELS = File.join(File.dirname(__FILE__), "models")
|
21
|
+
$LOAD_PATH.unshift(MODELS)
|
81
22
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
def status= status
|
89
|
-
@status = status
|
90
|
-
end
|
91
|
-
|
92
|
-
def headers= headers
|
93
|
-
@headers = headers
|
94
|
-
end
|
95
|
-
|
96
|
-
def headers
|
97
|
-
@headers ||= {}
|
98
|
-
@headers
|
99
|
-
end
|
100
|
-
|
101
|
-
def response= response
|
102
|
-
@response = response
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
def status
|
107
|
-
@status || 200
|
108
|
-
end
|
109
|
-
|
110
|
-
def response
|
111
|
-
@response || ResponseDouble.new
|
112
|
-
end
|
23
|
+
# Autoload every model for the test suite that sits in spec/models.
|
24
|
+
Dir[ File.join(MODELS, "*.rb") ].sort.each do |file|
|
25
|
+
name = File.basename(file, ".rb")
|
26
|
+
autoload name.camelize.to_sym, name
|
113
27
|
end
|
114
28
|
|
115
|
-
|
116
|
-
|
117
|
-
@actual_body = actual_body
|
118
|
-
end
|
119
|
-
|
120
|
-
def body
|
121
|
-
@body ||= "Hello world!"
|
122
|
-
end
|
29
|
+
SUPPORT = File.join(File.dirname(__FILE__), "support")
|
30
|
+
Dir[ File.join(SUPPORT, "*.rb") ].sort.each { |file| require file }
|
123
31
|
|
124
|
-
|
125
|
-
|
32
|
+
RSpec.configure do |config|
|
33
|
+
config.before(:suite) do
|
34
|
+
Support::Seed.setup_db
|
35
|
+
Support::Seed.seed_db
|
126
36
|
end
|
127
37
|
|
128
|
-
|
129
|
-
|
38
|
+
config.after(:suite) do
|
39
|
+
Support::Seed.teardown_db
|
130
40
|
end
|
131
41
|
|
132
|
-
|
133
|
-
|
42
|
+
config.filter_run :focus => true
|
43
|
+
config.run_all_when_everything_filtered = true
|
134
44
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Bullet
|
2
|
+
def self.collected_notifications_of_class(notification_class)
|
3
|
+
Bullet.notification_collector.collection.select do |notification|
|
4
|
+
notification.is_a? notification_class
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.collected_counter_cache_notifications
|
9
|
+
collected_notifications_of_class Bullet::Notification::CounterCache
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.collected_n_plus_one_query_notifications
|
13
|
+
collected_notifications_of_class Bullet::Notification::NPlusOneQuery
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.collected_unused_eager_association_notifications
|
17
|
+
collected_notifications_of_class Bullet::Notification::UnusedEagerLoading
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Bullet
|
22
|
+
module Detector
|
23
|
+
class Association
|
24
|
+
class <<self
|
25
|
+
# returns true if all associations are preloaded
|
26
|
+
def completely_preloading_associations?
|
27
|
+
Bullet.collected_n_plus_one_query_notifications.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_unused_preload_associations?
|
31
|
+
Bullet.collected_unused_eager_association_notifications.present?
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns true if a given object has a specific association
|
35
|
+
def creating_object_association_for?(object, association)
|
36
|
+
object_associations[object.ar_key].present? && object_associations[object.ar_key].include?(association)
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns true if a given class includes the specific unpreloaded association
|
40
|
+
def detecting_unpreloaded_association_for?(klass, association)
|
41
|
+
Bullet.collected_n_plus_one_query_notifications.select { |notification|
|
42
|
+
notification.base_class == klass.to_s && notification.associations.include?(association)
|
43
|
+
}.present?
|
44
|
+
end
|
45
|
+
|
46
|
+
# returns true if the given class includes the specific unused preloaded association
|
47
|
+
def unused_preload_associations_for?(klass, association)
|
48
|
+
Bullet.collected_unused_eager_association_notifications.select { |notification|
|
49
|
+
notification.base_class == klass.to_s && notification.associations.include?(association)
|
50
|
+
}.present?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Support
|
2
|
+
class AppDouble
|
3
|
+
def call env
|
4
|
+
env = @env
|
5
|
+
[ status, headers, response ]
|
6
|
+
end
|
7
|
+
|
8
|
+
def status= status
|
9
|
+
@status = status
|
10
|
+
end
|
11
|
+
|
12
|
+
def headers= headers
|
13
|
+
@headers = headers
|
14
|
+
end
|
15
|
+
|
16
|
+
def headers
|
17
|
+
@headers ||= {"Content-Type" => "text/html"}
|
18
|
+
@headers
|
19
|
+
end
|
20
|
+
|
21
|
+
def response= response
|
22
|
+
@response = response
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def status
|
27
|
+
@status || 200
|
28
|
+
end
|
29
|
+
|
30
|
+
def response
|
31
|
+
@response || ResponseDouble.new
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ResponseDouble
|
36
|
+
def initialize actual_body = nil
|
37
|
+
@actual_body = actual_body
|
38
|
+
end
|
39
|
+
|
40
|
+
def body
|
41
|
+
@body ||= "<html><head></head><body></body></html>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def body= body
|
45
|
+
@body = body
|
46
|
+
end
|
47
|
+
|
48
|
+
def each
|
49
|
+
yield body
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|