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 CHANGED
@@ -1 +1 @@
1
- 1.7.2
1
+ 1.7.3
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bullet}
8
- s.version = "1.7.2"
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{2009-11-26}
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/bullet/association_for_chris_spec.rb",
47
- "spec/bullet/association_spec.rb",
47
+ "spec/spec_helper.rb",
48
48
  "spec/bullet/counter_spec.rb",
49
- "spec/spec_helper.rb"
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
@@ -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
- # add include for one to many associations query
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
@@ -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] = (eager_loadings[k] + Array(associations))
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] + Array(associations))})
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] << Array(associations)
88
+ eager_loadings[objects] << associations
95
89
  unique(eager_loadings[objects])
96
90
  end
97
91
  end
98
92
 
99
- def define_association(klazz, associations)
100
- add_klazz_associations(klazz, associations)
101
- end
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
- def klazz_associations
249
- @@klazz_associations ||= {}
250
- end
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
@@ -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
- unused_preload_associations[klazz].present? && unused_preload_associations[klazz].include?(association)
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.2
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: 2009-11-26 00:00:00 +08:00
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