bullet 1.7.2 → 1.7.3
Sign up to get free protection for your applications and to get access to all the features.
- data/VERSION +1 -1
- data/bullet.gemspec +7 -5
- data/lib/bullet/active_record.rb +6 -12
- data/lib/bullet/association.rb +47 -20
- data/spec/bullet/association_for_peschkaj_spec.rb +84 -0
- data/spec/spec_helper.rb +1 -1
- metadata +7 -5
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.7.
|
1
|
+
1.7.3
|
data/bullet.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{bullet}
|
8
|
-
s.version = "1.7.
|
8
|
+
s.version = "1.7.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Richard Huang"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2010-01-01}
|
13
13
|
s.description = %q{The Bullet plugin is designed to help you increase your application's performance by reducing the number of queries it makes. It will watch your queries while you develop your application and notify you when you should add eager loading (N+1 queries) or when you're using eager loading that isn't necessary.}
|
14
14
|
s.email = %q{flyerhzm@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -31,6 +31,7 @@ Gem::Specification.new do |s|
|
|
31
31
|
"lib/bulletware.rb",
|
32
32
|
"rails/init.rb",
|
33
33
|
"spec/bullet/association_for_chris_spec.rb",
|
34
|
+
"spec/bullet/association_for_peschkaj_spec.rb",
|
34
35
|
"spec/bullet/association_spec.rb",
|
35
36
|
"spec/bullet/counter_spec.rb",
|
36
37
|
"spec/spec.opts",
|
@@ -43,10 +44,11 @@ Gem::Specification.new do |s|
|
|
43
44
|
s.rubygems_version = %q{1.3.5}
|
44
45
|
s.summary = %q{A plugin to kill N+1 queries and unused eager loading}
|
45
46
|
s.test_files = [
|
46
|
-
"spec/
|
47
|
-
"spec/bullet/association_spec.rb",
|
47
|
+
"spec/spec_helper.rb",
|
48
48
|
"spec/bullet/counter_spec.rb",
|
49
|
-
"spec/
|
49
|
+
"spec/bullet/association_spec.rb",
|
50
|
+
"spec/bullet/association_for_chris_spec.rb",
|
51
|
+
"spec/bullet/association_for_peschkaj_spec.rb"
|
50
52
|
]
|
51
53
|
|
52
54
|
if s.respond_to? :specification_version then
|
data/lib/bullet/active_record.rb
CHANGED
@@ -4,8 +4,8 @@ module Bullet
|
|
4
4
|
::ActiveRecord::Base.class_eval do
|
5
5
|
class << self
|
6
6
|
alias_method :origin_find_every, :find_every
|
7
|
-
# if select a collection of objects, then these objects have possible to cause N+1 query
|
8
|
-
# if select only one object, then the only one object has impossible to cause N+1 query
|
7
|
+
# if select a collection of objects, then these objects have possible to cause N+1 query.
|
8
|
+
# if select only one object, then the only one object has impossible to cause N+1 query.
|
9
9
|
def find_every(options)
|
10
10
|
records = origin_find_every(options)
|
11
11
|
|
@@ -26,7 +26,8 @@ module Bullet
|
|
26
26
|
|
27
27
|
::ActiveRecord::AssociationPreload::ClassMethods.class_eval do
|
28
28
|
alias_method :origin_preload_associations, :preload_associations
|
29
|
-
#
|
29
|
+
# include query for one to many associations.
|
30
|
+
# keep this eager loadings.
|
30
31
|
def preload_associations(records, associations, preload_options={})
|
31
32
|
records = [records].flatten.compact.uniq
|
32
33
|
return if records.empty?
|
@@ -38,14 +39,7 @@ module Bullet
|
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
41
|
-
::ActiveRecord::Associations::ClassMethods.class_eval do
|
42
|
-
# define one to many associations
|
43
|
-
alias_method :origin_collection_reader_method, :collection_reader_method
|
44
|
-
def collection_reader_method(reflection, association_proxy_class)
|
45
|
-
Bullet::Association.define_association(self, reflection.name)
|
46
|
-
origin_collection_reader_method(reflection, association_proxy_class)
|
47
|
-
end
|
48
|
-
|
42
|
+
::ActiveRecord::Associations::ClassMethods.class_eval do
|
49
43
|
# add include in named_scope
|
50
44
|
alias_method :origin_find_with_associations, :find_with_associations
|
51
45
|
def find_with_associations(options)
|
@@ -77,7 +71,7 @@ module Bullet
|
|
77
71
|
def load_target
|
78
72
|
Bullet::Association.call_association(@owner, @reflection.name)
|
79
73
|
origin_load_target
|
80
|
-
end
|
74
|
+
end
|
81
75
|
end
|
82
76
|
|
83
77
|
::ActiveRecord::Associations::AssociationProxy.class_eval do
|
data/lib/bullet/association.rb
CHANGED
@@ -4,6 +4,7 @@ module Bullet
|
|
4
4
|
include Bullet::Notification
|
5
5
|
|
6
6
|
def start_request
|
7
|
+
@@checked = false
|
7
8
|
end
|
8
9
|
|
9
10
|
def end_request
|
@@ -19,11 +20,10 @@ module Bullet
|
|
19
20
|
@@impossible_objects = nil
|
20
21
|
@@call_object_associations = nil
|
21
22
|
@@eager_loadings = nil
|
22
|
-
@@klazz_associations = nil
|
23
23
|
end
|
24
24
|
|
25
25
|
def notification?
|
26
|
-
check_unused_preload_associations
|
26
|
+
check_unused_preload_associations unless @@checked
|
27
27
|
has_unpreload_associations? or has_unused_preload_associations?
|
28
28
|
end
|
29
29
|
|
@@ -65,23 +65,17 @@ module Bullet
|
|
65
65
|
impossible_objects[klazz].uniq!
|
66
66
|
end
|
67
67
|
|
68
|
-
def add_klazz_associations(klazz, associations)
|
69
|
-
klazz_associations[klazz] ||= []
|
70
|
-
klazz_associations[klazz] << associations
|
71
|
-
unique(klazz_associations[klazz])
|
72
|
-
end
|
73
|
-
|
74
68
|
def add_eager_loadings(objects, associations)
|
75
69
|
objects = Array(objects)
|
76
70
|
eager_loadings[objects] ||= []
|
77
71
|
eager_loadings.each do |k, v|
|
78
72
|
unless (k & objects).empty?
|
79
73
|
if (k & objects) == k
|
80
|
-
eager_loadings[k]
|
74
|
+
eager_loadings[k] << associations
|
81
75
|
unique(eager_loadings[k])
|
82
76
|
break
|
83
77
|
else
|
84
|
-
eager_loadings.merge!({(k & objects) => (eager_loadings[k]
|
78
|
+
eager_loadings.merge!({(k & objects) => (eager_loadings[k].dup << associations)})
|
85
79
|
unique(eager_loadings[(k & objects)])
|
86
80
|
eager_loadings.merge!({(k - objects) => eager_loadings[k]}) unless (k - objects).empty?
|
87
81
|
unique(eager_loadings[(k - objects)])
|
@@ -91,15 +85,15 @@ module Bullet
|
|
91
85
|
end
|
92
86
|
end
|
93
87
|
unless objects.empty?
|
94
|
-
eager_loadings[objects] <<
|
88
|
+
eager_loadings[objects] << associations
|
95
89
|
unique(eager_loadings[objects])
|
96
90
|
end
|
97
91
|
end
|
98
92
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
93
|
+
# executed when object.assocations is called.
|
94
|
+
# first, it keeps this method call for object.association.
|
95
|
+
# then, it checks if this associations call is unpreload.
|
96
|
+
# if it is, keeps this unpreload associations and caller.
|
103
97
|
def call_association(object, associations)
|
104
98
|
add_call_object_associations(object, associations)
|
105
99
|
if unpreload_associations?(object, associations)
|
@@ -108,7 +102,13 @@ module Bullet
|
|
108
102
|
end
|
109
103
|
end
|
110
104
|
|
105
|
+
# check if there are unused preload associations.
|
106
|
+
# for each object => association
|
107
|
+
# get related_objects from eager_loadings associated with object and associations
|
108
|
+
# get call_object_association from associations of call_object_associations whose object is in related_objects
|
109
|
+
# if association not in call_object_association, then the object => association - call_object_association is ununsed preload assocations
|
111
110
|
def check_unused_preload_associations
|
111
|
+
@@checked = true
|
112
112
|
object_associations.each do |object, association|
|
113
113
|
related_objects = eager_loadings.select {|key, value| key.include?(object) and value == association}.collect(&:first).flatten
|
114
114
|
call_object_association = related_objects.collect { |related_object| call_object_associations[related_object] }.compact.flatten.uniq
|
@@ -126,6 +126,7 @@ module Bullet
|
|
126
126
|
end
|
127
127
|
|
128
128
|
private
|
129
|
+
# decide whether the object.associations is unpreloaded or not.
|
129
130
|
def unpreload_associations?(object, associations)
|
130
131
|
possible?(object) and !impossible?(object) and !association?(object, associations)
|
131
132
|
end
|
@@ -140,6 +141,7 @@ module Bullet
|
|
140
141
|
impossible_objects[klazz] and impossible_objects[klazz].include?(object)
|
141
142
|
end
|
142
143
|
|
144
|
+
# check if object => associations already exists in object_associations.
|
143
145
|
def association?(object, associations)
|
144
146
|
object_associations.each do |key, value|
|
145
147
|
if key == object
|
@@ -221,34 +223,59 @@ module Bullet
|
|
221
223
|
array.uniq!
|
222
224
|
end
|
223
225
|
|
226
|
+
# unpreload_associations keep the class relationships
|
227
|
+
# that the associations, belongs to the class, are used but not preloaded.
|
228
|
+
# e.g. { Post => [:comments] }
|
229
|
+
# so the unpreload_associations should be preloaded by find :include.
|
224
230
|
def unpreload_associations
|
225
231
|
@@unpreload_associations ||= {}
|
226
232
|
end
|
227
|
-
|
233
|
+
|
234
|
+
# unused_preload_associations keep the class relationships
|
235
|
+
# that the associations, belongs to the class, are preloaded but not used.
|
236
|
+
# e.g. { Post => [:comments] }
|
237
|
+
# so the unused_preload_associations should be removed from find :include.
|
228
238
|
def unused_preload_associations
|
229
239
|
@@unused_preload_associations ||= {}
|
230
240
|
end
|
231
241
|
|
242
|
+
# object_associations keep the object relationships
|
243
|
+
# that the object has many associations.
|
244
|
+
# e.g. { <Post id:1> => [:comments] }
|
245
|
+
# the object_associations keep all associations that may be or may no be
|
246
|
+
# unpreload associations or unused preload associations.
|
232
247
|
def object_associations
|
233
248
|
@@object_associations ||= {}
|
234
249
|
end
|
235
250
|
|
251
|
+
# call_object_assciations keep the object relationships
|
252
|
+
# that object.associations is called.
|
253
|
+
# e.g. { <Post id:1> => [:comments] }
|
254
|
+
# they are used to detect unused preload associations.
|
236
255
|
def call_object_associations
|
237
256
|
@@call_object_associations ||= {}
|
238
257
|
end
|
239
258
|
|
259
|
+
# possible_objects keep the class to object relationships
|
260
|
+
# that the objects may cause N+1 query.
|
261
|
+
# e.g. { Post => [<Post id:1>, <Post id:2>] }
|
240
262
|
def possible_objects
|
241
263
|
@@possible_objects ||= {}
|
242
264
|
end
|
243
265
|
|
266
|
+
# impossible_objects keep the class to objects relationships
|
267
|
+
# that the objects may not cause N+1 query.
|
268
|
+
# e.g. { Post => [<Post id:1>, <Post id:2>] }
|
269
|
+
# Notice: impossible_objects are not accurate,
|
270
|
+
# if find collection returns only one object, then the object is impossible object,
|
271
|
+
# impossible_objects are used to avoid treating 1+1 query to N+1 query.
|
244
272
|
def impossible_objects
|
245
273
|
@@impossible_objects ||= {}
|
246
274
|
end
|
247
275
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
276
|
+
# eager_loadings keep the object relationships
|
277
|
+
# that the associations are preloaded by find :include.
|
278
|
+
# e.g. { [<Post id:1>, <Post id:2>] => [:comments, :user] }
|
252
279
|
def eager_loadings
|
253
280
|
@@eager_loadings ||= {}
|
254
281
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
|
4
|
+
# This test is just used for http://github.com/flyerhzm/bullet/issues#issue/20
|
5
|
+
describe Bullet::Association, 'for peschkaj' do
|
6
|
+
|
7
|
+
def setup_db
|
8
|
+
ActiveRecord::Schema.define(:version => 1) do
|
9
|
+
create_table :categories do |t|
|
10
|
+
t.column :name, :string
|
11
|
+
end
|
12
|
+
|
13
|
+
create_table :submissions do |t|
|
14
|
+
t.column :name, :string
|
15
|
+
t.column :category_id, :integer
|
16
|
+
t.column :user_id, :integer
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :users do |t|
|
20
|
+
t.column :name, :string
|
21
|
+
t.column :category_id, :integer
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def teardown_db
|
27
|
+
ActiveRecord::Base.connection.tables.each do |table|
|
28
|
+
ActiveRecord::Base.connection.drop_table(table)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Category < ActiveRecord::Base
|
33
|
+
has_many :submissions
|
34
|
+
has_many :users
|
35
|
+
end
|
36
|
+
|
37
|
+
class Submission < ActiveRecord::Base
|
38
|
+
belongs_to :category
|
39
|
+
belongs_to :user
|
40
|
+
end
|
41
|
+
|
42
|
+
class User < ActiveRecord::Base
|
43
|
+
has_one :submission
|
44
|
+
belongs_to :category
|
45
|
+
end
|
46
|
+
|
47
|
+
before(:all) do
|
48
|
+
setup_db
|
49
|
+
|
50
|
+
category1 = Category.create(:name => "category1")
|
51
|
+
category2 = Category.create(:name => "category2")
|
52
|
+
|
53
|
+
user1 = User.create(:name => 'user1', :category => category1)
|
54
|
+
user2 = User.create(:name => 'user2', :category => category1)
|
55
|
+
|
56
|
+
submission1 = category1.submissions.create(:name => "submission1", :user => user1)
|
57
|
+
submission2 = category1.submissions.create(:name => "submission2", :user => user2)
|
58
|
+
submission3 = category2.submissions.create(:name => "submission3", :user => user1)
|
59
|
+
submission4 = category2.submissions.create(:name => "submission4", :user => user2)
|
60
|
+
end
|
61
|
+
|
62
|
+
after(:all) do
|
63
|
+
teardown_db
|
64
|
+
end
|
65
|
+
|
66
|
+
before(:each) do
|
67
|
+
Bullet::Association.start_request
|
68
|
+
end
|
69
|
+
|
70
|
+
after(:each) do
|
71
|
+
Bullet::Association.end_request
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should not detect unused preload associations" do
|
75
|
+
category = Category.find_by_name('category1', :include => {:submissions => :user}, :order => "id DESC")
|
76
|
+
category.submissions.map do |submission|
|
77
|
+
submission.name
|
78
|
+
submission.user.name
|
79
|
+
end
|
80
|
+
Bullet::Association.check_unused_preload_associations
|
81
|
+
Bullet::Association.should_not be_unused_preload_associations_for(Category, :submissions)
|
82
|
+
Bullet::Association.should_not be_unused_preload_associations_for(Submission, :user)
|
83
|
+
end
|
84
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -36,7 +36,7 @@ module Bullet
|
|
36
36
|
|
37
37
|
# returns true if the given class includes the specific unused preloaded association
|
38
38
|
def unused_preload_associations_for?(klazz, association)
|
39
|
-
|
39
|
+
unused_preload_associations[klazz].present? && unused_preload_associations[klazz].include?(association)
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bullet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.7.
|
4
|
+
version: 1.7.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-01 00:00:00 +08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -37,6 +37,7 @@ files:
|
|
37
37
|
- lib/bulletware.rb
|
38
38
|
- rails/init.rb
|
39
39
|
- spec/bullet/association_for_chris_spec.rb
|
40
|
+
- spec/bullet/association_for_peschkaj_spec.rb
|
40
41
|
- spec/bullet/association_spec.rb
|
41
42
|
- spec/bullet/counter_spec.rb
|
42
43
|
- spec/spec.opts
|
@@ -71,7 +72,8 @@ signing_key:
|
|
71
72
|
specification_version: 3
|
72
73
|
summary: A plugin to kill N+1 queries and unused eager loading
|
73
74
|
test_files:
|
74
|
-
- spec/bullet/association_for_chris_spec.rb
|
75
|
-
- spec/bullet/association_spec.rb
|
76
|
-
- spec/bullet/counter_spec.rb
|
77
75
|
- spec/spec_helper.rb
|
76
|
+
- spec/bullet/counter_spec.rb
|
77
|
+
- spec/bullet/association_spec.rb
|
78
|
+
- spec/bullet/association_for_chris_spec.rb
|
79
|
+
- spec/bullet/association_for_peschkaj_spec.rb
|