bullet 6.1.0 → 6.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd668fdfd675ea8437eee0242974d7cb1cfacc0479be4cc5f3f58ee7d05a6d63
4
- data.tar.gz: 3d841fb8471fe18b66135fa087ee3d1874f412c114d09ba05ba150e2daaf9775
3
+ metadata.gz: 74ea4e863bfe254dde17af73253ffe7041cd2b2dabee2dad05d1eccb6904f229
4
+ data.tar.gz: 284dcd1a516922384bdaf1b3dd9e6a5e6776c213c3ecbc9ca41b223c5b92d36b
5
5
  SHA512:
6
- metadata.gz: 714c614f2cf44f332f4f9996638c10dca565c92a2279316509256fefba2ca824fc9c71d2a3c3e4ca28edfc45849f76b61e521158e0051c365f5b8a81f41e9d8d
7
- data.tar.gz: a3b1a7e58b6e5554a641aa4b8edf16dbb4983b2f7a072c5b7275ee04a7251e4d095fed37f82d2945ef759603c8aeaf8a394d9e7fcf9f70a723e6235ab5f3e646
6
+ metadata.gz: 26c43bfdac9582f059d067d4f56d4d85ed28416654ff5fd4686bf9977cbfbc8d92f887079954662e904fbe3d1b861d02482ac2fefac30a859fbdad8c3833e225
7
+ data.tar.gz: 48decfa9d28b9936f5c1f39c669b6e8061fae649f9036c29d4b28138083a43db8739f3b73bb9c390e9f6baea62aa1f66f79e8ced69cf13e789b8f116c101f25f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  ## Next Release
2
2
 
3
+ ## 6.1.4 (02/26/2021)
4
+
5
+ * Added an option to stop adding HTTP headers to API requests
6
+
7
+ ## 6.1.3 (01/21/2021)
8
+
9
+ * Consider ThroughAssociation at SingularAssociation like CollectionAssociation
10
+ * Add xhr_script only when add_footer is enabled
11
+
12
+ ## 6.1.2 (12/12/2020)
13
+
14
+ * Revert "Make whitelist thread safe"
15
+
16
+ ## 6.1.1 (12/12/2020)
17
+
18
+ * Add support Rails 6.1
19
+ * Make whitelist thread safe
20
+
3
21
  ## 6.1.0 (12/28/2019)
4
22
 
5
23
  * Add skip_html_injection flag
data/Gemfile.rails-6.0 CHANGED
@@ -2,7 +2,7 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rails', '6.0.0'
5
+ gem 'rails', '~> 6.0.0'
6
6
  gem 'sqlite3'
7
7
  gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
8
  gem 'activerecord-import'
data/Gemfile.rails-6.1 ADDED
@@ -0,0 +1,15 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem 'rails', '~> 6.1.0'
6
+ gem 'sqlite3'
7
+ gem 'activerecord-jdbcsqlite3-adapter', platforms: [:jruby]
8
+ gem 'activerecord-import'
9
+
10
+ gem "rspec"
11
+
12
+ platforms :rbx do
13
+ gem 'rubysl', '~> 2.0'
14
+ gem 'rubinius-developer_tools'
15
+ end
data/README.md CHANGED
@@ -37,6 +37,13 @@ or add it into a Gemfile (Bundler):
37
37
  gem 'bullet', group: 'development'
38
38
  ```
39
39
 
40
+ enable the Bullet gem with generate command
41
+
42
+ ```ruby
43
+ bundle exec rails g bullet:install
44
+ ```
45
+ The generate command will auto generate the default configuration and may ask to include in the test environment as well. See below for custom configuration.
46
+
40
47
  **Note**: make sure `bullet` gem is added after activerecord (rails) and
41
48
  mongoid.
42
49
 
@@ -86,7 +93,8 @@ The code above will enable all of the Bullet notification systems:
86
93
  * `Bullet.rollbar`: add notifications to rollbar
87
94
  * `Bullet.sentry`: add notifications to sentry
88
95
  * `Bullet.add_footer`: adds the details in the bottom left corner of the page. Double click the footer or use close button to hide footer.
89
- * `Bullet.skip_html_injection`: prevents Bullet from injecting XHR into the returned HTML. This must be false for receiving alerts or console logging.
96
+ * `Bullet.skip_html_injection`: prevents Bullet from injecting code into the returned HTML. This must be false for receiving alerts, showing the footer or console logging.
97
+ * `Bullet.skip_http_headers`: don't add headers to API requests, and remove the javascript that relies on them. Note that this prevents bullet from logging warnings to the browser console or updating the footer.
90
98
  * `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
91
99
  * `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
92
100
  Each item can be a string (match substring), a regex, or an array where the first item is a path to match, and the second
data/lib/bullet.rb CHANGED
@@ -39,9 +39,9 @@ module Bullet
39
39
  :stacktrace_excludes,
40
40
  :skip_html_injection
41
41
  attr_reader :whitelist
42
- attr_accessor :add_footer, :orm_patches_applied
42
+ attr_accessor :add_footer, :orm_patches_applied, :skip_http_headers
43
43
 
44
- available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.map { |notifier| "#{notifier}=" }
44
+ available_notifiers = UniformNotifier::AVAILABLE_NOTIFIERS.select { |notifier| notifier != :raise }.map { |notifier| "#{notifier}=" }
45
45
  available_notifiers_options = { to: UniformNotifier }
46
46
  delegate(*available_notifiers, **available_notifiers_options)
47
47
 
@@ -73,7 +73,7 @@ module Bullet
73
73
  end
74
74
 
75
75
  def app_root
76
- (defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
76
+ @app_root ||= (defined?(::Rails.root) ? Rails.root.to_s : Dir.pwd).to_s
77
77
  end
78
78
 
79
79
  def n_plus_one_query_enable?
@@ -89,11 +89,11 @@ module Bullet
89
89
  end
90
90
 
91
91
  def stacktrace_includes
92
- @stacktrace_includes || []
92
+ @stacktrace_includes ||= []
93
93
  end
94
94
 
95
95
  def stacktrace_excludes
96
- @stacktrace_excludes || []
96
+ @stacktrace_excludes ||= []
97
97
  end
98
98
 
99
99
  def add_whitelist(options)
@@ -240,8 +240,10 @@ module Bullet
240
240
  UniformNotifier.active_notifiers.include?(UniformNotifier::JavascriptConsole)
241
241
  end
242
242
 
243
- def skip_html_injection?
244
- @skip_html_injection || false
243
+ def inject_into_page?
244
+ return false if defined?(@skip_html_injection) && @skip_html_injection
245
+
246
+ console_enabled? || add_footer
245
247
  end
246
248
 
247
249
  private
@@ -3,7 +3,11 @@
3
3
  module Bullet
4
4
  module ActiveJob
5
5
  def self.included(base)
6
- base.class_eval { around_perform { |_job, block| Bullet.profile { block.call } } }
6
+ base.class_eval do
7
+ around_perform do |_job, block|
8
+ Bullet.profile { block.call }
9
+ end
10
+ end
7
11
  end
8
12
  end
9
13
  end
@@ -202,6 +202,17 @@ module Bullet
202
202
 
203
203
  if Bullet.start?
204
204
  if owner.class.name !~ /^HABTM_/ && !@inversed
205
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
206
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
207
+ association = owner.association reflection.through_reflection.name
208
+ Array(association.target).each do |through_record|
209
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
210
+ end
211
+
212
+ if reflection.through_reflection != through_reflection
213
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
214
+ end
215
+ end
205
216
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
206
217
 
207
218
  if Bullet::Detector::NPlusOneQuery.impossible?(owner)
@@ -229,6 +229,17 @@ module Bullet
229
229
 
230
230
  if Bullet.start?
231
231
  if owner.class.name !~ /^HABTM_/ && !@inversed
232
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
233
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
234
+ association = owner.association(reflection.through_reflection.name)
235
+ Array(association.target).each do |through_record|
236
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
237
+ end
238
+
239
+ if reflection.through_reflection != through_reflection
240
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
241
+ end
242
+ end
232
243
  Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
233
244
 
234
245
  if Bullet::Detector::NPlusOneQuery.impossible?(owner)
@@ -0,0 +1,278 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bullet
4
+ module SaveWithBulletSupport
5
+ def _create_record(*)
6
+ super do
7
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
8
+ yield(self) if block_given?
9
+ end
10
+ end
11
+ end
12
+
13
+ module ActiveRecord
14
+ def self.enable
15
+ require 'active_record'
16
+ ::ActiveRecord::Base.extend(
17
+ Module.new do
18
+ def find_by_sql(sql, binds = [], preparable: nil, &block)
19
+ result = super
20
+ if Bullet.start?
21
+ if result.is_a? Array
22
+ if result.size > 1
23
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
24
+ Bullet::Detector::CounterCache.add_possible_objects(result)
25
+ elsif result.size == 1
26
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
27
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
28
+ end
29
+ elsif result.is_a? ::ActiveRecord::Base
30
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result)
31
+ Bullet::Detector::CounterCache.add_impossible_object(result)
32
+ end
33
+ end
34
+ result
35
+ end
36
+ end
37
+ )
38
+
39
+ ::ActiveRecord::Base.prepend(SaveWithBulletSupport)
40
+
41
+ ::ActiveRecord::Relation.prepend(
42
+ Module.new do
43
+ # if select a collection of objects, then these objects have possible to cause N+1 query.
44
+ # if select only one object, then the only one object has impossible to cause N+1 query.
45
+ def records
46
+ result = super
47
+ if Bullet.start?
48
+ if result.first.class.name !~ /^HABTM_/
49
+ if result.size > 1
50
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result)
51
+ Bullet::Detector::CounterCache.add_possible_objects(result)
52
+ elsif result.size == 1
53
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result.first)
54
+ Bullet::Detector::CounterCache.add_impossible_object(result.first)
55
+ end
56
+ end
57
+ end
58
+ result
59
+ end
60
+ end
61
+ )
62
+
63
+ ::ActiveRecord::Associations::Preloader.prepend(
64
+ Module.new do
65
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
66
+ if Bullet.start?
67
+ records.compact!
68
+ if records.first.class.name !~ /^HABTM_/
69
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, association) }
70
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, association)
71
+ end
72
+ end
73
+ super
74
+ end
75
+
76
+ def preloaders_for_reflection(reflection, records, scope)
77
+ if Bullet.start?
78
+ records.compact!
79
+ if records.first.class.name !~ /^HABTM_/
80
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, reflection.name) }
81
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, reflection.name)
82
+ end
83
+ end
84
+ super
85
+ end
86
+ end
87
+ )
88
+
89
+ ::ActiveRecord::Associations::Preloader::ThroughAssociation.prepend(
90
+ Module.new do
91
+ def preloaded_records
92
+ if Bullet.start? && !defined?(@preloaded_records)
93
+ source_preloaders.each do |source_preloader|
94
+ reflection_name = source_preloader.send(:reflection).name
95
+ source_preloader.send(:owners).each do |owner|
96
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection_name)
97
+ end
98
+ end
99
+ end
100
+ super
101
+ end
102
+ end
103
+ )
104
+
105
+ ::ActiveRecord::FinderMethods.prepend(
106
+ Module.new do
107
+ # add includes in scope
108
+ def find_with_associations
109
+ return super { |r| yield r } if block_given?
110
+
111
+ records = super
112
+ if Bullet.start?
113
+ associations = (eager_load_values + includes_values).uniq
114
+ records.each { |record| Bullet::Detector::Association.add_object_associations(record, associations) }
115
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(records, associations)
116
+ end
117
+ records
118
+ end
119
+ end
120
+ )
121
+
122
+ ::ActiveRecord::Associations::JoinDependency.prepend(
123
+ Module.new do
124
+ def instantiate(result_set, strict_loading_value, &block)
125
+ @bullet_eager_loadings = {}
126
+ records = super
127
+
128
+ if Bullet.start?
129
+ @bullet_eager_loadings.each do |_klazz, eager_loadings_hash|
130
+ objects = eager_loadings_hash.keys
131
+ Bullet::Detector::UnusedEagerLoading.add_eager_loadings(
132
+ objects,
133
+ eager_loadings_hash[objects.first].to_a
134
+ )
135
+ end
136
+ end
137
+ records
138
+ end
139
+
140
+ def construct(ar_parent, parent, row, seen, model_cache, strict_loading_value)
141
+ if Bullet.start?
142
+ unless ar_parent.nil?
143
+ parent.children.each do |node|
144
+ key = aliases.column_alias(node, node.primary_key)
145
+ id = row[key]
146
+ next unless id.nil?
147
+
148
+ associations = node.reflection.name
149
+ Bullet::Detector::Association.add_object_associations(ar_parent, associations)
150
+ Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
151
+ @bullet_eager_loadings[ar_parent.class] ||= {}
152
+ @bullet_eager_loadings[ar_parent.class][ar_parent] ||= Set.new
153
+ @bullet_eager_loadings[ar_parent.class][ar_parent] << associations
154
+ end
155
+ end
156
+ end
157
+
158
+ super
159
+ end
160
+
161
+ # call join associations
162
+ def construct_model(record, node, row, model_cache, id, strict_loading_value)
163
+ result = super
164
+
165
+ if Bullet.start?
166
+ associations = node.reflection.name
167
+ Bullet::Detector::Association.add_object_associations(record, associations)
168
+ Bullet::Detector::NPlusOneQuery.call_association(record, associations)
169
+ @bullet_eager_loadings[record.class] ||= {}
170
+ @bullet_eager_loadings[record.class][record] ||= Set.new
171
+ @bullet_eager_loadings[record.class][record] << associations
172
+ end
173
+
174
+ result
175
+ end
176
+ end
177
+ )
178
+
179
+ ::ActiveRecord::Associations::CollectionAssociation.prepend(
180
+ Module.new do
181
+ def load_target
182
+ records = super
183
+
184
+ if Bullet.start?
185
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
186
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
187
+ association = owner.association(reflection.through_reflection.name)
188
+ Array(association.target).each do |through_record|
189
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
190
+ end
191
+
192
+ if reflection.through_reflection != through_reflection
193
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
194
+ end
195
+ end
196
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
197
+ if records.first.class.name !~ /^HABTM_/
198
+ if records.size > 1
199
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(records)
200
+ Bullet::Detector::CounterCache.add_possible_objects(records)
201
+ elsif records.size == 1
202
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(records.first)
203
+ Bullet::Detector::CounterCache.add_impossible_object(records.first)
204
+ end
205
+ end
206
+ end
207
+ records
208
+ end
209
+
210
+ def empty?
211
+ if Bullet.start? && !reflection.has_cached_counter?
212
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
213
+ end
214
+ super
215
+ end
216
+
217
+ def include?(object)
218
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) if Bullet.start?
219
+ super
220
+ end
221
+ end
222
+ )
223
+
224
+ ::ActiveRecord::Associations::SingularAssociation.prepend(
225
+ Module.new do
226
+ # call has_one and belongs_to associations
227
+ def target
228
+ result = super()
229
+
230
+ if Bullet.start?
231
+ if owner.class.name !~ /^HABTM_/ && !@inversed
232
+ if is_a? ::ActiveRecord::Associations::ThroughAssociation
233
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
234
+ association = owner.association(reflection.through_reflection.name)
235
+ Array(association.target).each do |through_record|
236
+ Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
237
+ end
238
+
239
+ if reflection.through_reflection != through_reflection
240
+ Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
241
+ end
242
+ end
243
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
244
+
245
+ if Bullet::Detector::NPlusOneQuery.impossible?(owner)
246
+ Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
247
+ else
248
+ Bullet::Detector::NPlusOneQuery.add_possible_objects(result) if result
249
+ end
250
+ end
251
+ end
252
+ result
253
+ end
254
+ end
255
+ )
256
+
257
+ ::ActiveRecord::Associations::HasManyAssociation.prepend(
258
+ Module.new do
259
+ def empty?
260
+ result = super
261
+ if Bullet.start? && !reflection.has_cached_counter?
262
+ Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name)
263
+ end
264
+ result
265
+ end
266
+
267
+ def count_records
268
+ result = reflection.has_cached_counter?
269
+ if Bullet.start? && !result && !is_a?(::ActiveRecord::Associations::ThroughAssociation)
270
+ Bullet::Detector::CounterCache.add_counter_cache(owner, reflection.name)
271
+ end
272
+ super
273
+ end
274
+ end
275
+ )
276
+ end
277
+ end
278
+ end
@@ -1,4 +1,4 @@
1
- (function() {
1
+ (function () {
2
2
  var oldOpen = window.XMLHttpRequest.prototype.open;
3
3
  var oldSend = window.XMLHttpRequest.prototype.send;
4
4
 
@@ -10,41 +10,41 @@
10
10
  if (isBulletInitiated()) return;
11
11
 
12
12
  function isBulletInitiated() {
13
- return oldOpen.name == 'bulletXHROpen' && oldSend.name == 'bulletXHRSend';
13
+ return oldOpen.name == "bulletXHROpen" && oldSend.name == "bulletXHRSend";
14
14
  }
15
15
  function bulletXHROpen(_, url) {
16
16
  this._storedUrl = url;
17
- return oldOpen.apply(this, arguments);
17
+ return Reflect.apply(oldOpen, this, arguments);
18
18
  }
19
19
  function bulletXHRSend() {
20
20
  if (this.onload) {
21
21
  this._storedOnload = this.onload;
22
22
  }
23
- this.addEventListener('load', bulletXHROnload);
24
- return oldSend.apply(this, arguments);
23
+ this.addEventListener("load", bulletXHROnload);
24
+ return Reflect.apply(oldSend, this, arguments);
25
25
  }
26
26
  function bulletXHROnload() {
27
27
  if (
28
- this._storedUrl.startsWith(window.location.protocol + '//' + window.location.host) ||
29
- !this._storedUrl.startsWith('http') // For relative paths
28
+ this._storedUrl.startsWith(window.location.protocol + "//" + window.location.host) ||
29
+ !this._storedUrl.startsWith("http") // For relative paths
30
30
  ) {
31
- var bulletFooterText = this.getResponseHeader('X-bullet-footer-text');
31
+ var bulletFooterText = this.getResponseHeader("X-bullet-footer-text");
32
32
  if (bulletFooterText) {
33
- setTimeout(() => {
34
- var oldHtml = document.getElementById('bullet-footer').innerHTML.split('<br>');
33
+ setTimeout(function() {
34
+ var oldHtml = document.querySelector("#bullet-footer").innerHTML.split("<br>");
35
35
  var header = oldHtml[0];
36
36
  oldHtml = oldHtml.slice(1, oldHtml.length);
37
37
  var newHtml = oldHtml.concat(JSON.parse(bulletFooterText));
38
38
  newHtml = newHtml.slice(newHtml.length - 10, newHtml.length); // rotate through 10 most recent
39
- document.getElementById('bullet-footer').innerHTML = `${header}<br>${newHtml.join('<br>')}`;
39
+ document.querySelector("#bullet-footer").innerHTML = `${header}<br>${newHtml.join("<br>")}`;
40
40
  }, 0);
41
41
  }
42
- var bulletConsoleText = this.getResponseHeader('X-bullet-console-text');
43
- if (bulletConsoleText && typeof console !== 'undefined' && console.log) {
44
- setTimeout(() => {
45
- JSON.parse(bulletConsoleText).forEach(message => {
42
+ var bulletConsoleText = this.getResponseHeader("X-bullet-console-text");
43
+ if (bulletConsoleText && typeof console !== "undefined" && console.log) {
44
+ setTimeout(function() {
45
+ JSON.parse(bulletConsoleText).forEach((message) => {
46
46
  if (console.groupCollapsed && console.groupEnd) {
47
- console.groupCollapsed('Uniform Notifier');
47
+ console.groupCollapsed("Uniform Notifier");
48
48
  console.log(message);
49
49
  console.groupEnd();
50
50
  } else {
@@ -55,7 +55,7 @@
55
55
  }
56
56
  }
57
57
  if (this._storedOnload) {
58
- return this._storedOnload.apply(this, arguments);
58
+ return Reflect.apply(this._storedOnload, this, arguments);
59
59
  }
60
60
  }
61
61
  window.XMLHttpRequest.prototype.open = bulletXHROpen;
@@ -27,6 +27,8 @@ module Bullet
27
27
  'active_record52'
28
28
  elsif active_record60?
29
29
  'active_record60'
30
+ elsif active_record61?
31
+ 'active_record61'
30
32
  else
31
33
  raise "Bullet does not support active_record #{::ActiveRecord::VERSION::STRING} yet"
32
34
  end
@@ -90,6 +92,10 @@ module Bullet
90
92
  active_record6? && ::ActiveRecord::VERSION::MINOR == 0
91
93
  end
92
94
 
95
+ def active_record61?
96
+ active_record6? && ::ActiveRecord::VERSION::MINOR == 1
97
+ end
98
+
93
99
  def mongoid4x?
94
100
  mongoid? && ::Mongoid::VERSION =~ /\A4/
95
101
  end
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
@@ -23,7 +23,7 @@ module Bullet
23
23
  end
24
24
 
25
25
  def each(&block)
26
- return to_enum unless block_given?
26
+ return to_enum unless block
27
27
 
28
28
  records = []
29
29
  origin_each { |record| records << record }
data/lib/bullet/rack.rb CHANGED
@@ -17,14 +17,14 @@ module Bullet
17
17
  response_body = nil
18
18
 
19
19
  if Bullet.notification?
20
- if !Bullet.skip_html_injection? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
20
+ if Bullet.inject_into_page? && !file?(headers) && !sse?(headers) && !empty?(response) && status == 200
21
21
  if html_request?(headers, response)
22
22
  response_body = response_body(response)
23
23
  response_body = append_to_html_body(response_body, footer_note) if Bullet.add_footer
24
24
  response_body = append_to_html_body(response_body, Bullet.gather_inline_notifications)
25
- response_body = append_to_html_body(response_body, xhr_script)
25
+ response_body = append_to_html_body(response_body, xhr_script) if Bullet.add_footer && !Bullet.skip_http_headers
26
26
  headers['Content-Length'] = response_body.bytesize.to_s
27
- else
27
+ elsif !Bullet.skip_http_headers
28
28
  set_header(headers, 'X-bullet-footer-text', Bullet.footer_info.uniq) if Bullet.add_footer
29
29
  set_header(headers, 'X-bullet-console-text', Bullet.text_notifications) if Bullet.console_enabled?
30
30
  end
@@ -46,6 +46,7 @@ module Bullet
46
46
 
47
47
  def append_to_html_body(response_body, content)
48
48
  body = response_body.dup
49
+ content = content.html_safe if content.respond_to?(:html_safe)
49
50
  if body.include?('</body>')
50
51
  position = body.rindex('</body>')
51
52
  body.insert(position, content)
@@ -55,14 +56,14 @@ module Bullet
55
56
  end
56
57
 
57
58
  def footer_note
58
- "<div #{footer_div_attributes}>" + footer_header + '<br>' + Bullet.footer_info.uniq.join('<br>') + '</div>'
59
+ "<details #{details_attributes}><summary #{summary_attributes}>Bullet Warnings</summary><div #{footer_content_attributes}>#{Bullet.footer_info.uniq.join('<br>')}#{footer_console_message}</div></details>"
59
60
  end
60
61
 
61
62
  def set_header(headers, header_name, header_array)
62
63
  # Many proxy applications such as Nginx and AWS ELB limit
63
64
  # the size a header to 8KB, so truncate the list of reports to
64
65
  # be under that limit
65
- header_array.pop while header_array.to_json.length > 8 * 1_024
66
+ header_array.pop while header_array.to_json.length > 8 * 1024
66
67
  headers[header_name] = header_array.to_json
67
68
  end
68
69
 
@@ -88,23 +89,28 @@ module Bullet
88
89
 
89
90
  private
90
91
 
91
- def footer_div_attributes
92
+ def details_attributes
92
93
  <<~EOF
93
- id="bullet-footer" 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);
94
- -moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
95
- -moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
96
- padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
97
- color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
94
+ id="bullet-footer" data-is-bullet-footer
95
+ style="cursor: pointer; position: fixed; left: 0px; bottom: 0px; z-index: 9999; background: #fdf2f2; color: #9b1c1c; font-size: 12px; border-radius: 0px 8px 0px 0px; border: 1px solid #9b1c1c;"
98
96
  EOF
99
97
  end
100
98
 
101
- def footer_header
102
- cancel_button =
103
- "<span onclick='this.parentNode.remove()' style='position:absolute; right: 10px; top: 0px; font-weight: bold; color: #333;'>&times;</span>"
99
+ def summary_attributes
100
+ <<~EOF
101
+ style="font-weight: 600; padding: 2px 8px"
102
+ EOF
103
+ end
104
+
105
+ def footer_content_attributes
106
+ <<~EOF
107
+ style="padding: 8px; border-top: 1px solid #9b1c1c;"
108
+ EOF
109
+ end
110
+
111
+ def footer_console_message
104
112
  if Bullet.console_enabled?
105
- "<span>See 'Uniform Notifier' in JS Console for Stacktrace</span>#{cancel_button}"
106
- else
107
- cancel_button
113
+ "<br/><span style='font-style: italic;'>See 'Uniform Notifier' in JS Console for Stacktrace</span>"
108
114
  end
109
115
  end
110
116
 
@@ -3,6 +3,7 @@
3
3
  module Bullet
4
4
  module StackTraceFilter
5
5
  VENDOR_PATH = '/vendor'
6
+ IS_RUBY_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
6
7
 
7
8
  def caller_in_project
8
9
  vendor_root = Bullet.app_root + VENDOR_PATH
@@ -47,20 +48,15 @@ module Bullet
47
48
  end
48
49
 
49
50
  def location_as_path(location)
50
- ruby_19? ? location : location.absolute_path.to_s
51
+ IS_RUBY_19 ? location : location.absolute_path.to_s
51
52
  end
52
53
 
53
54
  def select_caller_locations
54
- if ruby_19?
55
+ if IS_RUBY_19
55
56
  caller.select { |caller_path| yield caller_path }
56
57
  else
57
58
  caller_locations.select { |location| yield location }
58
59
  end
59
60
  end
60
-
61
- def ruby_19?
62
- @ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0') if @ruby_19.nil?
63
- @ruby_19
64
- end
65
61
  end
66
62
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bullet
4
- VERSION = '6.1.0'
4
+ VERSION = '6.1.4'
5
5
  end
@@ -10,40 +10,38 @@ module Bullet
10
10
 
11
11
  def enable_in_development
12
12
  environment(nil, env: 'development') do
13
- <<-"FILE"
14
-
15
- config.after_initialize do
16
- Bullet.enable = true
17
- Bullet.alert = true
18
- Bullet.bullet_logger = true
19
- Bullet.console = true
20
- # Bullet.growl = true
21
- Bullet.rails_logger = true
22
- Bullet.add_footer = true
23
- end
13
+ <<~FILE
14
+ config.after_initialize do
15
+ Bullet.enable = true
16
+ Bullet.alert = true
17
+ Bullet.bullet_logger = true
18
+ Bullet.console = true
19
+ # Bullet.growl = true
20
+ Bullet.rails_logger = true
21
+ Bullet.add_footer = true
22
+ end
23
+
24
24
  FILE
25
- .strip
26
25
  end
27
26
 
28
27
  say 'Enabled bullet in config/environments/development.rb'
29
28
  end
30
29
 
31
30
  def enable_in_test
32
- if yes?('Would you like to enable bullet in test environment? (y/n)')
33
- environment(nil, env: 'test') do
34
- <<-"FILE"
35
-
36
- config.after_initialize do
37
- Bullet.enable = true
38
- Bullet.bullet_logger = true
39
- Bullet.raise = true # raise an error if n+1 query occurs
40
- end
41
- FILE
42
- .strip
43
- end
31
+ return unless yes?('Would you like to enable bullet in test environment? (y/n)')
44
32
 
45
- say 'Enabled bullet in config/environments/test.rb'
33
+ environment(nil, env: 'test') do
34
+ <<~FILE
35
+ config.after_initialize do
36
+ Bullet.enable = true
37
+ Bullet.bullet_logger = true
38
+ Bullet.raise = true # raise an error if n+1 query occurs
39
+ end
40
+
41
+ FILE
46
42
  end
43
+
44
+ say 'Enabled bullet in config/environments/test.rb'
47
45
  end
48
46
  end
49
47
  end
@@ -47,7 +47,7 @@ module Bullet
47
47
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
48
48
  end
49
49
 
50
- it 'should be true when object is possible, and impossible' do
50
+ it 'should be false when object is possible, and impossible' do
51
51
  CounterCache.add_possible_objects(@post1)
52
52
  CounterCache.add_impossible_object(@post1)
53
53
  expect(CounterCache.conditions_met?(@post1, :associations)).to eq false
@@ -13,35 +13,31 @@ module Bullet
13
13
 
14
14
  context '.call_associations' do
15
15
  it 'should get empty array if eager_loadings' do
16
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
16
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
17
17
  end
18
18
 
19
19
  it 'should get call associations if object and association are both in eager_loadings and call_object_associations' do
20
20
  UnusedEagerLoading.add_eager_loadings([@post], :association)
21
21
  UnusedEagerLoading.add_call_object_associations(@post, :association)
22
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
23
- %i[association]
24
- )
22
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
25
23
  end
26
24
 
27
25
  it 'should not get call associations if not exist in call_object_associations' do
28
26
  UnusedEagerLoading.add_eager_loadings([@post], :association)
29
- expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new(%i[association]))).to be_empty
27
+ expect(UnusedEagerLoading.send(:call_associations, @post.bullet_key, Set.new([:association]))).to be_empty
30
28
  end
31
29
  end
32
30
 
33
31
  context '.diff_object_associations' do
34
32
  it 'should return associations not exist in call_association' do
35
- expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))).to eq(
36
- %i[association]
37
- )
33
+ expect(UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))).to eq([:association])
38
34
  end
39
35
 
40
36
  it 'should return empty if associations exist in call_association' do
41
37
  UnusedEagerLoading.add_eager_loadings([@post], :association)
42
38
  UnusedEagerLoading.add_call_object_associations(@post, :association)
43
39
  expect(
44
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
40
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
45
41
  ).to be_empty
46
42
  end
47
43
  end
@@ -51,7 +47,7 @@ module Bullet
51
47
  it 'should create notification if object_association_diff is not empty' do
52
48
  UnusedEagerLoading.add_object_associations(@post, :association)
53
49
  allow(UnusedEagerLoading).to receive(:caller_in_project).and_return(paths)
54
- expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', %i[association])
50
+ expect(UnusedEagerLoading).to receive(:create_notification).with(paths, 'Post', [:association])
55
51
  UnusedEagerLoading.check_unused_preload_associations
56
52
  end
57
53
 
@@ -60,9 +56,9 @@ module Bullet
60
56
  UnusedEagerLoading.add_eager_loadings([@post], :association)
61
57
  UnusedEagerLoading.add_call_object_associations(@post, :association)
62
58
  expect(
63
- UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new(%i[association]))
59
+ UnusedEagerLoading.send(:diff_object_associations, @post.bullet_key, Set.new([:association]))
64
60
  ).to be_empty
65
- expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', %i[association])
61
+ expect(UnusedEagerLoading).not_to receive(:create_notification).with('Post', [:association])
66
62
  UnusedEagerLoading.check_unused_preload_associations
67
63
  end
68
64
  end
@@ -21,9 +21,7 @@ module Bullet
21
21
  )
22
22
  end
23
23
  it do
24
- expect(subject.body).to eq(
25
- " Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])"
26
- )
24
+ expect(subject.body).to eq(" Post => [:comments, :votes]\n Add to your query: .includes([:comments, :votes])")
27
25
  end
28
26
  it { expect(subject.title).to eq('USE eager loading in path') }
29
27
  end
@@ -67,9 +67,8 @@ module Bullet
67
67
 
68
68
  it 'should change response body if notification is active' do
69
69
  expect(Bullet).to receive(:notification?).and_return(true)
70
- allow(Bullet).to receive(:skip_html_injection?).and_return(false)
70
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
71
71
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
72
- expect(middleware).to receive(:xhr_script).and_return('')
73
72
  expect(Bullet).to receive(:perform_out_of_channel_notifications)
74
73
  _, headers, response = middleware.call('Content-Type' => 'text/html')
75
74
  expect(headers['Content-Length']).to eq('56')
@@ -81,9 +80,121 @@ module Bullet
81
80
  response.body = '<html><head></head><body>é</body></html>'
82
81
  app.response = response
83
82
  expect(Bullet).to receive(:notification?).and_return(true)
83
+ allow(Bullet).to receive(:console_enabled?).and_return(true)
84
84
  expect(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
85
85
  _, headers, response = middleware.call('Content-Type' => 'text/html')
86
- expect(headers['Content-Length']).to eq((58 + middleware.send(:xhr_script).length).to_s)
86
+ expect(headers['Content-Length']).to eq('58')
87
+ end
88
+
89
+ context 'with injection notifiers' do
90
+ before do
91
+ expect(Bullet).to receive(:notification?).and_return(true)
92
+ allow(Bullet).to receive(:gather_inline_notifications).and_return('<bullet></bullet>')
93
+ allow(middleware).to receive(:xhr_script).and_return('<script></script>')
94
+ allow(middleware).to receive(:footer_note).and_return('footer')
95
+ expect(Bullet).to receive(:perform_out_of_channel_notifications)
96
+ end
97
+
98
+ it 'should change response body if add_footer is true' do
99
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
100
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
101
+
102
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
103
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
104
+ end
105
+
106
+ it 'should change response body for html safe string if add_footer is true' do
107
+ expect(Bullet).to receive(:add_footer).exactly(3).times.and_return(true)
108
+ app.response = Support::ResponseDouble.new.tap do |response|
109
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
110
+ end
111
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
112
+
113
+ expect(headers['Content-Length']).to eq((73 + middleware.send(:footer_note).length).to_s)
114
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet><script></script></body></html>])
115
+ end
116
+
117
+ it 'should add the footer-text header for non-html requests when add_footer is true' do
118
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
119
+ allow(Bullet).to receive(:footer_info).and_return(['footer text'])
120
+ app.headers = {'Content-Type' => 'application/json'}
121
+ _, headers, _response = middleware.call({})
122
+ expect(headers).to include('X-bullet-footer-text' => '["footer text"]')
123
+ end
124
+
125
+ it 'should change response body if console_enabled is true' do
126
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
127
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
128
+ expect(headers['Content-Length']).to eq('56')
129
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
130
+ end
131
+
132
+ it 'should change response body for html safe string if console_enabled is true' do
133
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
134
+ app.response = Support::ResponseDouble.new.tap do |response|
135
+ response.body = ActiveSupport::SafeBuffer.new('<html><head></head><body></body></html>')
136
+ end
137
+ _, headers, response = middleware.call('Content-Type' => 'text/html')
138
+ expect(headers['Content-Length']).to eq('56')
139
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
140
+ end
141
+
142
+ it 'should add headers for non-html requests when console_enabled is true' do
143
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
144
+ allow(Bullet).to receive(:text_notifications).and_return(['text notifications'])
145
+ app.headers = {'Content-Type' => 'application/json'}
146
+ _, headers, _response = middleware.call({})
147
+ expect(headers).to include('X-bullet-console-text' => '["text notifications"]')
148
+ end
149
+
150
+ it "shouldn't change response body unnecessarily" do
151
+ expected_response = Support::ResponseDouble.new 'Actual body'
152
+ app.response = expected_response
153
+ _, _, response = middleware.call({})
154
+ expect(response).to eq(expected_response)
155
+ end
156
+
157
+ it "shouldn't add headers unnecessarily" do
158
+ app.headers = {'Content-Type' => 'application/json'}
159
+ _, headers, _response = middleware.call({})
160
+ expect(headers).not_to include('X-bullet-footer-text')
161
+ expect(headers).not_to include('X-bullet-console-text')
162
+ end
163
+
164
+ context "when skip_http_headers is enabled" do
165
+ before do
166
+ allow(Bullet).to receive(:skip_http_headers).and_return(true)
167
+ end
168
+
169
+ it 'should include the footer but not the xhr script tag if add_footer is true' do
170
+ expect(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
171
+ _, headers, response = middleware.call({})
172
+
173
+ expect(headers['Content-Length']).to eq((56 + middleware.send(:footer_note).length).to_s)
174
+ expect(response).to eq(%w[<html><head></head><body>footer<bullet></bullet></body></html>])
175
+ end
176
+
177
+ it 'should not include the xhr script tag if console_enabled is true' do
178
+ expect(Bullet).to receive(:console_enabled?).and_return(true)
179
+ _, headers, response = middleware.call({})
180
+ expect(headers['Content-Length']).to eq('56')
181
+ expect(response).to eq(%w[<html><head></head><body><bullet></bullet></body></html>])
182
+ end
183
+
184
+ it 'should not add the footer-text header for non-html requests when add_footer is true' do
185
+ allow(Bullet).to receive(:add_footer).at_least(:once).and_return(true)
186
+ app.headers = {'Content-Type' => 'application/json'}
187
+ _, headers, _response = middleware.call({})
188
+ expect(headers).not_to include('X-bullet-footer-text')
189
+ end
190
+
191
+ it 'should not add headers for non-html requests when console_enabled is true' do
192
+ allow(Bullet).to receive(:console_enabled?).at_least(:once).and_return(true)
193
+ app.headers = {'Content-Type' => 'application/json'}
194
+ _, headers, _response = middleware.call({})
195
+ expect(headers).not_to include('X-bullet-console-text')
196
+ end
197
+ end
87
198
  end
88
199
 
89
200
  context 'when skip_html_injection is enabled' do
@@ -377,8 +377,7 @@ if active_record?
377
377
  it 'should detect unused preload with comment => author' do
378
378
  Comment.includes([:author, { post: :writer }]).where(['base_users.id = ?', BaseUser.first]).references(
379
379
  :base_users
380
- )
381
- .each { |comment| comment.post.writer.name }
380
+ ).each { |comment| comment.post.writer.name }
382
381
  Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
383
382
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
384
383
 
@@ -562,6 +561,42 @@ if active_record?
562
561
  end
563
562
  end
564
563
 
564
+ describe Bullet::Detector::Association, 'has_one :through' do
565
+ context 'user => attachment' do
566
+ it 'should detect non preload associations' do
567
+ User.all.each { |user| user.submission_attachment.file_name }
568
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
569
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
570
+
571
+ expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(User, :submission_attachment)
572
+ end
573
+
574
+ it 'should detect preload associations' do
575
+ User.includes(:submission_attachment).each { |user| user.submission_attachment.file_name }
576
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
577
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
578
+
579
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
580
+ end
581
+
582
+ it 'should not detect preload associations' do
583
+ User.all.map(&:name)
584
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
585
+ expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
586
+
587
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
588
+ end
589
+
590
+ it 'should detect unused preload associations' do
591
+ User.includes(:submission_attachment).map(&:name)
592
+ Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
593
+ expect(Bullet::Detector::Association).to be_unused_preload_associations_for(User, :submission_attachment)
594
+
595
+ expect(Bullet::Detector::Association).to be_completely_preloading_associations
596
+ end
597
+ end
598
+ end
599
+
565
600
  describe Bullet::Detector::Association, 'call one association that in possible objects' do
566
601
  it 'should not detect preload association' do
567
602
  Post.all
@@ -73,9 +73,9 @@ if mongoid?
73
73
  expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
74
74
 
75
75
  expect(Bullet::Detector::Association).not_to be_detecting_unpreloaded_association_for(
76
- Mongoid::Category,
77
- :posts
78
- )
76
+ Mongoid::Category,
77
+ :posts
78
+ )
79
79
  expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Mongoid::Category, :entries)
80
80
  end
81
81
 
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Attachment < ActiveRecord::Base
4
+ belongs_to :submission
5
+ end
@@ -3,4 +3,5 @@
3
3
  class Submission < ActiveRecord::Base
4
4
  belongs_to :user
5
5
  has_many :replies
6
+ has_one :attachment
6
7
  end
data/spec/models/user.rb CHANGED
@@ -2,5 +2,6 @@
2
2
 
3
3
  class User < ActiveRecord::Base
4
4
  has_one :submission
5
+ has_one :submission_attachment, through: :submission, source: :attachment, class_name: 'Attachment'
5
6
  belongs_to :category
6
7
  end
@@ -95,6 +95,9 @@ module Support
95
95
  submission1.replies.create(name: 'reply2')
96
96
  submission2.replies.create(name: 'reply3')
97
97
  submission2.replies.create(name: 'reply4')
98
+
99
+ submission1.create_attachment(file_name: 'submission1 file')
100
+ submission2.create_attachment(file_name: 'submission2 file')
98
101
  end
99
102
 
100
103
  def setup_db
@@ -240,6 +243,11 @@ module Support
240
243
  t.column :name, :string
241
244
  t.column :category_id, :integer
242
245
  end
246
+
247
+ create_table :attachments do |t|
248
+ t.column :file_name, :string
249
+ t.column :submission_id, :integer
250
+ end
243
251
  end
244
252
  end
245
253
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.1.0
4
+ version: 6.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Huang
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-28 00:00:00.000000000 Z
11
+ date: 2021-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -62,6 +62,7 @@ files:
62
62
  - Gemfile.rails-5.1
63
63
  - Gemfile.rails-5.2
64
64
  - Gemfile.rails-6.0
65
+ - Gemfile.rails-6.1
65
66
  - Guardfile
66
67
  - Hacking.md
67
68
  - MIT-LICENSE
@@ -76,6 +77,7 @@ files:
76
77
  - lib/bullet/active_record5.rb
77
78
  - lib/bullet/active_record52.rb
78
79
  - lib/bullet/active_record60.rb
80
+ - lib/bullet/active_record61.rb
79
81
  - lib/bullet/bullet_xhr.js
80
82
  - lib/bullet/dependency.rb
81
83
  - lib/bullet/detector.rb
@@ -127,6 +129,7 @@ files:
127
129
  - spec/integration/counter_cache_spec.rb
128
130
  - spec/integration/mongoid/association_spec.rb
129
131
  - spec/models/address.rb
132
+ - spec/models/attachment.rb
130
133
  - spec/models/author.rb
131
134
  - spec/models/base_user.rb
132
135
  - spec/models/category.rb
@@ -173,7 +176,7 @@ licenses:
173
176
  metadata:
174
177
  changelog_uri: https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md
175
178
  source_code_uri: https://github.com/flyerhzm/bullet
176
- post_install_message:
179
+ post_install_message:
177
180
  rdoc_options: []
178
181
  require_paths:
179
182
  - lib
@@ -188,8 +191,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
188
191
  - !ruby/object:Gem::Version
189
192
  version: 1.3.6
190
193
  requirements: []
191
- rubygems_version: 3.0.3
192
- signing_key:
194
+ rubygems_version: 3.1.4
195
+ signing_key:
193
196
  specification_version: 4
194
197
  summary: help to kill N+1 queries and unused eager loading.
195
198
  test_files:
@@ -214,6 +217,7 @@ test_files:
214
217
  - spec/integration/counter_cache_spec.rb
215
218
  - spec/integration/mongoid/association_spec.rb
216
219
  - spec/models/address.rb
220
+ - spec/models/attachment.rb
217
221
  - spec/models/author.rb
218
222
  - spec/models/base_user.rb
219
223
  - spec/models/category.rb