bullet 5.5.0 → 6.1.5

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.
Files changed (123) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +66 -0
  3. data/CHANGELOG.md +83 -1
  4. data/Gemfile +6 -1
  5. data/Gemfile.mongoid-7.0 +15 -0
  6. data/Gemfile.rails-4.0 +1 -1
  7. data/Gemfile.rails-4.1 +1 -1
  8. data/Gemfile.rails-4.2 +1 -1
  9. data/Gemfile.rails-5.0 +2 -2
  10. data/Gemfile.rails-5.1 +15 -0
  11. data/Gemfile.rails-5.2 +15 -0
  12. data/Gemfile.rails-6.0 +15 -0
  13. data/Gemfile.rails-6.1 +15 -0
  14. data/Guardfile +2 -2
  15. data/Hacking.md +1 -1
  16. data/README.md +58 -37
  17. data/Rakefile +20 -21
  18. data/bullet.gemspec +21 -15
  19. data/lib/bullet/active_job.rb +13 -0
  20. data/lib/bullet/active_record4.rb +15 -34
  21. data/lib/bullet/active_record41.rb +14 -31
  22. data/lib/bullet/active_record42.rb +23 -36
  23. data/lib/bullet/active_record5.rb +178 -158
  24. data/lib/bullet/active_record52.rb +251 -0
  25. data/lib/bullet/active_record60.rb +278 -0
  26. data/lib/bullet/active_record61.rb +278 -0
  27. data/lib/bullet/bullet_xhr.js +64 -0
  28. data/lib/bullet/dependency.rb +60 -32
  29. data/lib/bullet/detector/association.rb +41 -32
  30. data/lib/bullet/detector/base.rb +2 -0
  31. data/lib/bullet/detector/counter_cache.rb +25 -17
  32. data/lib/bullet/detector/n_plus_one_query.rb +38 -24
  33. data/lib/bullet/detector/unused_eager_loading.rb +35 -26
  34. data/lib/bullet/detector.rb +2 -0
  35. data/lib/bullet/ext/object.rb +9 -5
  36. data/lib/bullet/ext/string.rb +3 -1
  37. data/lib/bullet/mongoid4x.rb +9 -8
  38. data/lib/bullet/mongoid5x.rb +9 -8
  39. data/lib/bullet/mongoid6x.rb +13 -12
  40. data/lib/bullet/mongoid7x.rb +57 -0
  41. data/lib/bullet/notification/base.rb +28 -25
  42. data/lib/bullet/notification/counter_cache.rb +3 -1
  43. data/lib/bullet/notification/n_plus_one_query.rb +8 -7
  44. data/lib/bullet/notification/unused_eager_loading.rb +8 -7
  45. data/lib/bullet/notification.rb +4 -1
  46. data/lib/bullet/notification_collector.rb +2 -1
  47. data/lib/bullet/rack.rb +66 -35
  48. data/lib/bullet/registry/association.rb +2 -0
  49. data/lib/bullet/registry/base.rb +2 -0
  50. data/lib/bullet/registry/object.rb +2 -0
  51. data/lib/bullet/registry.rb +2 -0
  52. data/lib/bullet/stack_trace_filter.rb +50 -20
  53. data/lib/bullet/version.rb +3 -2
  54. data/lib/bullet.rb +136 -55
  55. data/lib/generators/bullet/install_generator.rb +48 -0
  56. data/perf/benchmark.rb +19 -22
  57. data/rails/init.rb +2 -0
  58. data/spec/bullet/detector/association_spec.rb +6 -4
  59. data/spec/bullet/detector/base_spec.rb +2 -0
  60. data/spec/bullet/detector/counter_cache_spec.rb +17 -15
  61. data/spec/bullet/detector/n_plus_one_query_spec.rb +75 -46
  62. data/spec/bullet/detector/unused_eager_loading_spec.rb +45 -26
  63. data/spec/bullet/ext/object_spec.rb +18 -11
  64. data/spec/bullet/ext/string_spec.rb +7 -5
  65. data/spec/bullet/notification/base_spec.rb +38 -39
  66. data/spec/bullet/notification/counter_cache_spec.rb +5 -3
  67. data/spec/bullet/notification/n_plus_one_query_spec.rb +20 -5
  68. data/spec/bullet/notification/unused_eager_loading_spec.rb +9 -3
  69. data/spec/bullet/notification_collector_spec.rb +12 -10
  70. data/spec/bullet/rack_spec.rb +200 -48
  71. data/spec/bullet/registry/association_spec.rb +12 -10
  72. data/spec/bullet/registry/base_spec.rb +22 -20
  73. data/spec/bullet/registry/object_spec.rb +6 -4
  74. data/spec/bullet_spec.rb +64 -33
  75. data/spec/integration/{active_record4 → active_record}/association_spec.rb +238 -223
  76. data/spec/integration/counter_cache_spec.rb +26 -44
  77. data/spec/integration/mongoid/association_spec.rb +53 -65
  78. data/spec/models/address.rb +2 -0
  79. data/spec/models/attachment.rb +5 -0
  80. data/spec/models/author.rb +2 -0
  81. data/spec/models/base_user.rb +2 -0
  82. data/spec/models/category.rb +2 -0
  83. data/spec/models/city.rb +2 -0
  84. data/spec/models/client.rb +4 -0
  85. data/spec/models/comment.rb +3 -1
  86. data/spec/models/company.rb +2 -0
  87. data/spec/models/country.rb +2 -0
  88. data/spec/models/deal.rb +5 -0
  89. data/spec/models/document.rb +4 -2
  90. data/spec/models/entry.rb +2 -0
  91. data/spec/models/firm.rb +3 -0
  92. data/spec/models/folder.rb +2 -0
  93. data/spec/models/group.rb +4 -0
  94. data/spec/models/mongoid/address.rb +3 -1
  95. data/spec/models/mongoid/category.rb +4 -2
  96. data/spec/models/mongoid/comment.rb +3 -1
  97. data/spec/models/mongoid/company.rb +3 -1
  98. data/spec/models/mongoid/entry.rb +3 -1
  99. data/spec/models/mongoid/post.rb +6 -4
  100. data/spec/models/mongoid/user.rb +2 -0
  101. data/spec/models/newspaper.rb +3 -1
  102. data/spec/models/page.rb +2 -0
  103. data/spec/models/person.rb +2 -0
  104. data/spec/models/pet.rb +2 -0
  105. data/spec/models/post.rb +19 -0
  106. data/spec/models/relationship.rb +2 -0
  107. data/spec/models/reply.rb +2 -0
  108. data/spec/models/student.rb +2 -0
  109. data/spec/models/submission.rb +3 -0
  110. data/spec/models/teacher.rb +2 -0
  111. data/spec/models/user.rb +3 -0
  112. data/spec/models/writer.rb +2 -0
  113. data/spec/spec_helper.rb +19 -23
  114. data/spec/support/bullet_ext.rb +10 -9
  115. data/spec/support/mongo_seed.rb +38 -57
  116. data/spec/support/rack_double.rb +13 -19
  117. data/spec/support/sqlite_seed.rb +104 -76
  118. data/tasks/bullet_tasks.rake +4 -2
  119. data/test.sh +5 -0
  120. data/update.sh +3 -0
  121. metadata +34 -17
  122. data/.travis.yml +0 -16
  123. data/spec/integration/active_record5/association_spec.rb +0 -768
data/spec/models/post.rb CHANGED
@@ -1,7 +1,10 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Post < ActiveRecord::Base
2
4
  belongs_to :category, inverse_of: :posts
3
5
  belongs_to :writer
4
6
  has_many :comments, inverse_of: :post
7
+ has_and_belongs_to_many :deals
5
8
 
6
9
  validates :category, presence: true
7
10
 
@@ -12,4 +15,20 @@ class Post < ActiveRecord::Base
12
15
  def link=(*)
13
16
  comments.new
14
17
  end
18
+
19
+ # see association_spec.rb 'should not detect newly assigned object in an after_save'
20
+ attr_accessor :trigger_after_save
21
+ after_save do
22
+ next unless trigger_after_save
23
+
24
+ temp_comment = Comment.new(post: self)
25
+
26
+ # this triggers self to be "possible", even though it's
27
+ # not saved yet
28
+ temp_comment.post
29
+
30
+ # category should NOT whine about not being pre-loaded, because
31
+ # it's obviously attached to a new object
32
+ category
33
+ end
15
34
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Relationship < ActiveRecord::Base
2
4
  belongs_to :firm
3
5
  belongs_to :client
data/spec/models/reply.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Reply < ActiveRecord::Base
2
4
  belongs_to :submission
3
5
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Student < ActiveRecord::Base
2
4
  has_and_belongs_to_many :teachers
3
5
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Submission < ActiveRecord::Base
2
4
  belongs_to :user
3
5
  has_many :replies
6
+ has_one :attachment
4
7
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Teacher < ActiveRecord::Base
2
4
  has_and_belongs_to_many :students
3
5
  end
data/spec/models/user.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class User < ActiveRecord::Base
2
4
  has_one :submission
5
+ has_one :submission_attachment, through: :submission, source: :attachment, class_name: 'Attachment'
3
6
  belongs_to :category
4
7
  end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class Writer < BaseUser
2
4
  end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec'
2
4
  begin
3
5
  require 'active_record'
@@ -9,31 +11,31 @@ rescue LoadError
9
11
  end
10
12
 
11
13
  module Rails
12
- class <<self
14
+ class << self
13
15
  def root
14
16
  File.expand_path(__FILE__).split('/')[0..-3].join('/')
15
17
  end
16
18
 
17
19
  def env
18
- "test"
20
+ 'test'
19
21
  end
20
22
  end
21
23
  end
22
24
 
23
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib"))
25
+ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
24
26
  require 'bullet'
25
27
  extend Bullet::Dependency
26
28
  Bullet.enable = true
27
29
 
28
- MODELS = File.join(File.dirname(__FILE__), "models")
30
+ MODELS = File.join(File.dirname(__FILE__), 'models')
29
31
  $LOAD_PATH.unshift(MODELS)
30
- SUPPORT = File.join(File.dirname(__FILE__), "support")
31
- Dir[ File.join(SUPPORT, "*.rb") ].reject { |filename| filename =~ /_seed.rb$/ }.sort.each { |file| require file }
32
+ SUPPORT = File.join(File.dirname(__FILE__), 'support')
33
+ Dir[File.join(SUPPORT, '*.rb')].reject { |filename| filename =~ /_seed.rb$/ }.sort.each { |file| require file }
32
34
 
33
35
  RSpec.configure do |config|
34
36
  config.extend Bullet::Dependency
35
37
 
36
- config.filter_run :focus => true
38
+ config.filter_run focus: true
37
39
  config.run_all_when_everything_filtered = true
38
40
  end
39
41
 
@@ -41,11 +43,11 @@ if active_record?
41
43
  ActiveRecord::Migration.verbose = false
42
44
 
43
45
  # Autoload every active_record model for the test suite that sits in spec/models.
44
- Dir[ File.join(MODELS, "*.rb") ].sort.each do |filename|
45
- name = File.basename(filename, ".rb")
46
+ Dir[File.join(MODELS, '*.rb')].sort.each do |filename|
47
+ name = File.basename(filename, '.rb')
46
48
  autoload name.camelize.to_sym, name
47
49
  end
48
- require File.join(SUPPORT, "sqlite_seed.rb")
50
+ require File.join(SUPPORT, 'sqlite_seed.rb')
49
51
 
50
52
  RSpec.configure do |config|
51
53
  config.before(:suite) do
@@ -58,12 +60,10 @@ if active_record?
58
60
  Bullet.enable = true
59
61
  end
60
62
 
61
- config.after(:example) do
62
- Bullet.end_request
63
- end
63
+ config.after(:example) { Bullet.end_request }
64
64
  end
65
65
 
66
- if ENV["BULLET_LOG"]
66
+ if ENV['BULLET_LOG']
67
67
  require 'logger'
68
68
  ActiveRecord::Base.logger = Logger.new(STDOUT)
69
69
  end
@@ -71,8 +71,8 @@ end
71
71
 
72
72
  if mongoid?
73
73
  # Autoload every mongoid model for the test suite that sits in spec/models.
74
- Dir[ File.join(MODELS, "mongoid", "*.rb") ].sort.each { |file| require file }
75
- require File.join(SUPPORT, "mongo_seed.rb")
74
+ Dir[File.join(MODELS, 'mongoid', '*.rb')].sort.each { |file| require file }
75
+ require File.join(SUPPORT, 'mongo_seed.rb')
76
76
 
77
77
  RSpec.configure do |config|
78
78
  config.before(:suite) do
@@ -85,16 +85,12 @@ if mongoid?
85
85
  Support::MongoSeed.teardown_db
86
86
  end
87
87
 
88
- config.before(:each) do
89
- Bullet.start_request
90
- end
88
+ config.before(:each) { Bullet.start_request }
91
89
 
92
- config.after(:each) do
93
- Bullet.end_request
94
- end
90
+ config.after(:each) { Bullet.end_request }
95
91
  end
96
92
 
97
- if ENV["BULLET_LOG"]
93
+ if ENV['BULLET_LOG']
98
94
  Mongoid.logger = Logger.new(STDOUT)
99
95
  Moped.logger = Logger.new(STDOUT)
100
96
  end
@@ -1,8 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Bullet
2
4
  def self.collected_notifications_of_class(notification_class)
3
- Bullet.notification_collector.collection.select do |notification|
4
- notification.is_a? notification_class
5
- end
5
+ Bullet.notification_collector.collection.select { |notification| notification.is_a? notification_class }
6
6
  end
7
7
 
8
8
  def self.collected_counter_cache_notifications
@@ -21,7 +21,7 @@ end
21
21
  module Bullet
22
22
  module Detector
23
23
  class Association
24
- class <<self
24
+ class << self
25
25
  # returns true if all associations are preloaded
26
26
  def completely_preloading_associations?
27
27
  Bullet.collected_n_plus_one_query_notifications.empty?
@@ -33,21 +33,22 @@ module Bullet
33
33
 
34
34
  # returns true if a given object has a specific association
35
35
  def creating_object_association_for?(object, association)
36
- object_associations[object.bullet_key].present? && object_associations[object.bullet_key].include?(association)
36
+ object_associations[object.bullet_key].present? &&
37
+ object_associations[object.bullet_key].include?(association)
37
38
  end
38
39
 
39
40
  # returns true if a given class includes the specific unpreloaded association
40
41
  def detecting_unpreloaded_association_for?(klass, association)
41
- Bullet.collected_n_plus_one_query_notifications.select { |notification|
42
+ Bullet.collected_n_plus_one_query_notifications.select do |notification|
42
43
  notification.base_class == klass.to_s && notification.associations.include?(association)
43
- }.present?
44
+ end.present?
44
45
  end
45
46
 
46
47
  # returns true if the given class includes the specific unused preloaded association
47
48
  def unused_preload_associations_for?(klass, association)
48
- Bullet.collected_unused_eager_association_notifications.select { |notification|
49
+ Bullet.collected_unused_eager_association_notifications.select do |notification|
49
50
  notification.base_class == klass.to_s && notification.associations.include?(association)
50
- }.present?
51
+ end.present?
51
52
  end
52
53
  end
53
54
  end
@@ -1,68 +1,51 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Support
2
4
  module MongoSeed
3
- def seed_db
4
- category1 = Mongoid::Category.create(:name => 'first')
5
- category2 = Mongoid::Category.create(:name => 'second')
6
-
7
- post1 = category1.posts.create(:name => 'first')
8
- post1a = category1.posts.create(:name => 'like first')
9
- post2 = category2.posts.create(:name => 'second')
10
-
11
- post1.users << Mongoid::User.create(:name => 'first')
12
- post1.users << Mongoid::User.create(:name => 'another')
13
- post2.users << Mongoid::User.create(:name => 'second')
14
-
15
- comment1 = post1.comments.create(:name => 'first')
16
- comment2 = post1.comments.create(:name => 'first2')
17
- comment3 = post1.comments.create(:name => 'first3')
18
- comment4 = post1.comments.create(:name => 'second')
19
- comment8 = post1a.comments.create(:name => "like first 1")
20
- comment9 = post1a.comments.create(:name => "like first 2")
21
- comment5 = post2.comments.create(:name => 'third')
22
- comment6 = post2.comments.create(:name => 'fourth')
23
- comment7 = post2.comments.create(:name => 'fourth')
24
-
25
- entry1 = category1.entries.create(:name => 'first')
26
- entry2 = category1.entries.create(:name => 'second')
27
-
28
- company1 = Mongoid::Company.create(:name => 'first')
29
- company2 = Mongoid::Company.create(:name => 'second')
5
+ module_function
30
6
 
31
- Mongoid::Address.create(:name => 'first', :company => company1)
32
- Mongoid::Address.create(:name => 'second', :company => company2)
7
+ def seed_db
8
+ category1 = Mongoid::Category.create(name: 'first')
9
+ category2 = Mongoid::Category.create(name: 'second')
10
+
11
+ post1 = category1.posts.create(name: 'first')
12
+ post1a = category1.posts.create(name: 'like first')
13
+ post2 = category2.posts.create(name: 'second')
14
+
15
+ post1.users << Mongoid::User.create(name: 'first')
16
+ post1.users << Mongoid::User.create(name: 'another')
17
+ post2.users << Mongoid::User.create(name: 'second')
18
+
19
+ comment1 = post1.comments.create(name: 'first')
20
+ comment2 = post1.comments.create(name: 'first2')
21
+ comment3 = post1.comments.create(name: 'first3')
22
+ comment4 = post1.comments.create(name: 'second')
23
+ comment8 = post1a.comments.create(name: 'like first 1')
24
+ comment9 = post1a.comments.create(name: 'like first 2')
25
+ comment5 = post2.comments.create(name: 'third')
26
+ comment6 = post2.comments.create(name: 'fourth')
27
+ comment7 = post2.comments.create(name: 'fourth')
28
+
29
+ entry1 = category1.entries.create(name: 'first')
30
+ entry2 = category1.entries.create(name: 'second')
31
+
32
+ company1 = Mongoid::Company.create(name: 'first')
33
+ company2 = Mongoid::Company.create(name: 'second')
34
+
35
+ Mongoid::Address.create(name: 'first', company: company1)
36
+ Mongoid::Address.create(name: 'second', company: company2)
33
37
  end
34
38
 
35
39
  def setup_db
36
- if Mongoid::VERSION =~ /\A2\.[4-8]/
37
- Mongoid.configure do |config|
38
- config.master = Mongo::Connection.new.db("bullet")
39
- end
40
- elsif Mongoid::VERSION =~ /\A3/
40
+ if Mongoid::VERSION =~ /\A4/
41
41
  Mongoid.configure do |config|
42
- config.connect_to("bullet")
42
+ config.load_configuration(sessions: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
43
43
  end
44
- elsif Mongoid::VERSION =~ /\A4/
44
+ else
45
45
  Mongoid.configure do |config|
46
- config.load_configuration(
47
- sessions: {
48
- default: {
49
- database: "bullet",
50
- hosts: [ "localhost:27017" ]
51
- }
52
- }
53
- )
54
- end
55
- elsif Mongoid::VERSION =~ /\A5/
56
- Mongoid.configure do |config|
57
- config.load_configuration(
58
- clients: {
59
- default: {
60
- database: "bullet",
61
- hosts: [ "localhost:27017" ]
62
- }
63
- }
64
- )
46
+ config.load_configuration(clients: { default: { database: 'bullet', hosts: %w[localhost:27017] } })
65
47
  end
48
+
66
49
  # Increase the level from DEBUG in order to avoid excessive logging to the screen
67
50
  Mongo::Logger.logger.level = Logger::WARN
68
51
  end
@@ -72,7 +55,5 @@ module Support
72
55
  Mongoid.purge!
73
56
  Mongoid::IdentityMap.clear if Mongoid.const_defined?(:IdentityMap)
74
57
  end
75
-
76
- extend self
77
58
  end
78
59
  end
@@ -1,28 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Support
2
4
  class AppDouble
3
- def call env
5
+ def call(_env)
4
6
  env = @env
5
- [ status, headers, response ]
7
+ [status, headers, response]
6
8
  end
7
9
 
8
- def status= status
9
- @status = status
10
- end
10
+ attr_writer :status
11
11
 
12
- def headers= headers
13
- @headers = headers
14
- end
12
+ attr_writer :headers
15
13
 
16
14
  def headers
17
- @headers ||= {"Content-Type" => "text/html"}
15
+ @headers ||= { 'Content-Type' => 'text/html' }
18
16
  @headers
19
17
  end
20
18
 
21
- def response= response
22
- @response = response
23
- end
19
+ attr_writer :response
24
20
 
25
21
  private
22
+
26
23
  def status
27
24
  @status || 200
28
25
  end
@@ -33,23 +30,20 @@ module Support
33
30
  end
34
31
 
35
32
  class ResponseDouble
36
- def initialize actual_body = nil
33
+ def initialize(actual_body = nil)
37
34
  @actual_body = actual_body
38
35
  end
39
36
 
40
37
  def body
41
- @body ||= "<html><head></head><body></body></html>"
38
+ @body ||= '<html><head></head><body></body></html>'
42
39
  end
43
40
 
44
- def body= body
45
- @body = body
46
- end
41
+ attr_writer :body
47
42
 
48
43
  def each
49
44
  yield body
50
45
  end
51
46
 
52
- def close
53
- end
47
+ def close; end
54
48
  end
55
49
  end