bullet 5.7.5 → 5.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -16
- data/Gemfile.rails-5.2 +1 -1
- data/README.md +7 -4
- data/Rakefile +1 -1
- data/bullet.gemspec +7 -3
- data/lib/bullet.rb +3 -2
- data/lib/bullet/active_record4.rb +13 -13
- data/lib/bullet/active_record41.rb +13 -13
- data/lib/bullet/active_record42.rb +14 -13
- data/lib/bullet/active_record5.rb +24 -13
- data/lib/bullet/active_record52.rb +19 -13
- data/lib/bullet/dependency.rb +1 -1
- data/lib/bullet/detector/association.rb +2 -2
- data/lib/bullet/detector/counter_cache.rb +1 -0
- data/lib/bullet/detector/n_plus_one_query.rb +9 -9
- data/lib/bullet/detector/unused_eager_loading.rb +1 -0
- data/lib/bullet/ext/object.rb +1 -1
- data/lib/bullet/ext/string.rb +1 -1
- data/lib/bullet/mongoid4x.rb +1 -0
- data/lib/bullet/mongoid5x.rb +1 -0
- data/lib/bullet/mongoid6x.rb +5 -4
- data/lib/bullet/notification/base.rb +1 -1
- data/lib/bullet/rack.rb +9 -8
- data/lib/bullet/stack_trace_filter.rb +41 -22
- data/lib/bullet/version.rb +1 -1
- data/lib/generators/bullet/install_generator.rb +3 -3
- data/spec/bullet/detector/n_plus_one_query_spec.rb +23 -0
- data/spec/integration/active_record/association_spec.rb +61 -1
- data/spec/models/client.rb +2 -0
- data/spec/models/firm.rb +1 -0
- data/spec/models/group.rb +4 -0
- data/spec/models/post.rb +15 -0
- data/spec/support/sqlite_seed.rb +9 -2
- metadata +9 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b65855ae5365567a18d1fb6ffdee5046b8cec35
|
4
|
+
data.tar.gz: 8df6b7afd18400dd2fe9bf39ec4a537fa04228af
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 212cf02f1520dfaf752f794e42e9b2611fa0eca8b786bf5ef281c36c416ddf24016a524029614447c6241ebb734a2491ab554fa505544d7a7bc6f2e5b767527d
|
7
|
+
data.tar.gz: d2d71ceb54033f58cb6d675ea88268f130a0ea8bcfd7b4987486e374a2d2a3fc9169691ff20a9ca0fd9cf2787656dacc19649a8473879e9d1d68484597ccc18b
|
data/CHANGELOG.md
CHANGED
@@ -1,27 +1,19 @@
|
|
1
1
|
## Next Release
|
2
2
|
|
3
|
-
## 5.
|
4
|
-
|
5
|
-
* Fix duplicate logs in mongoid 4.x and 5.x version
|
6
|
-
* Add magic comment frozen_string_literal: true
|
7
|
-
|
8
|
-
## 5.7.4 (10/03/2018)
|
3
|
+
## 5.8.0 (10/29/2018)
|
9
4
|
|
5
|
+
* Fix through reflection for rails 5.x
|
6
|
+
* Fix false positive in after_save/after_create callbacks
|
7
|
+
* Don't triger a preload error on "manual" preloads
|
10
8
|
* Avoid Bullet from making extra queries in mongoid6
|
9
|
+
* Support option for #first and #last on mongoid6.x
|
10
|
+
* Fix duplicate logs in mongoid 4.x and 5.x version
|
11
11
|
* Use caller for ruby 1.9 while caller_locations for 2.0+
|
12
|
-
|
13
|
-
## 5.7.3 (17/02/2018)
|
14
|
-
|
12
|
+
* Extend stacktrace matching for sub-file precision
|
15
13
|
* Exclude configured bundler path in addition to '/vendor'
|
16
|
-
* Support rails 5.1.5
|
17
|
-
|
18
|
-
## 5.7.2 (18/01/2018)
|
19
|
-
|
20
14
|
* Fix `caller_path` in `excluded_stacktrace_path`
|
21
|
-
|
22
|
-
## 5.7.1 (07/01/2017)
|
23
|
-
|
24
15
|
* Update `uniform_notifier` dependency to add Sentry support
|
16
|
+
* Integrate awesomecode.io and refactor code
|
25
17
|
|
26
18
|
## 5.7.0 (12/03/2017)
|
27
19
|
|
data/Gemfile.rails-5.2
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
[![Gem Version](https://badge.fury.io/rb/bullet.png)](http://badge.fury.io/rb/bullet)
|
4
4
|
[![Build Status](https://secure.travis-ci.org/flyerhzm/bullet.png)](http://travis-ci.org/flyerhzm/bullet)
|
5
|
-
[![AwesomeCode Status](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/
|
5
|
+
[![AwesomeCode Status for flyerhzm/bullet](https://awesomecode.io/projects/6755235b-e2c1-459e-bf92-b8b13d0c0472/status)](https://awesomecode.io/repos/flyerhzm/bullet)
|
6
6
|
[![Coderwall Endorse](http://api.coderwall.com/flyerhzm/endorsecount.png)](http://coderwall.com/flyerhzm)
|
7
7
|
|
8
8
|
The Bullet gem 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), when you're using eager loading that isn't necessary and when you should use counter cache.
|
@@ -64,7 +64,7 @@ config.after_initialize do
|
|
64
64
|
Bullet.rollbar = true
|
65
65
|
Bullet.add_footer = true
|
66
66
|
Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ]
|
67
|
-
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ]
|
67
|
+
Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware', ['my_file.rb', 'my_method'], ['my_file.rb', 16..20] ]
|
68
68
|
Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' }
|
69
69
|
end
|
70
70
|
```
|
@@ -87,6 +87,8 @@ The code above will enable all of the Bullet notification systems:
|
|
87
87
|
* `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.
|
88
88
|
* `Bullet.stacktrace_includes`: include paths with any of these substrings in the stack trace, even if they are not in your main app
|
89
89
|
* `Bullet.stacktrace_excludes`: ignore paths with any of these substrings in the stack trace, even if they are not in your main app.
|
90
|
+
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
|
91
|
+
item is a line number, a Range of line numbers, or a (bare) method name, to exclude only particular lines in a file.
|
90
92
|
* `Bullet.slack`: add notifications to slack
|
91
93
|
* `Bullet.raise`: raise errors, useful for making your specs fail unless they have optimized queries
|
92
94
|
|
@@ -126,10 +128,11 @@ class ApplicationController < ActionController::Base
|
|
126
128
|
around_action :skip_bullet
|
127
129
|
|
128
130
|
def skip_bullet
|
131
|
+
previous_value = Bullet.enable?
|
129
132
|
Bullet.enable = false
|
130
133
|
yield
|
131
134
|
ensure
|
132
|
-
Bullet.enable =
|
135
|
+
Bullet.enable = previous_value
|
133
136
|
end
|
134
137
|
end
|
135
138
|
```
|
@@ -180,7 +183,7 @@ If you find Bullet does not work for you, *please disable your browser's cache*.
|
|
180
183
|
|
181
184
|
### Profile a job
|
182
185
|
|
183
|
-
The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use
|
186
|
+
The Bullet gem uses rack middleware to profile requests. If you want to use Bullet without an http server, like to profile a job, you can use the profile method and fetch warnings
|
184
187
|
|
185
188
|
```ruby
|
186
189
|
Bullet.profile do
|
data/Rakefile
CHANGED
data/bullet.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
4
|
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
5
5
|
|
6
6
|
require 'bullet/version'
|
@@ -14,13 +14,17 @@ Gem::Specification.new do |s|
|
|
14
14
|
s.homepage = 'https://github.com/flyerhzm/bullet'
|
15
15
|
s.summary = 'help to kill N+1 queries and unused eager loading.'
|
16
16
|
s.description = 'help to kill N+1 queries and unused eager loading.'
|
17
|
+
s.metadata = {
|
18
|
+
'changelog_uri' => 'https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md',
|
19
|
+
'source_code_uri' => 'https://github.com/flyerhzm/bullet'
|
20
|
+
}
|
17
21
|
|
18
|
-
s.license
|
22
|
+
s.license = 'MIT'
|
19
23
|
|
20
24
|
s.required_rubygems_version = '>= 1.3.6'
|
21
25
|
|
22
26
|
s.add_runtime_dependency 'activesupport', '>= 3.0.0'
|
23
|
-
s.add_runtime_dependency 'uniform_notifier', '~> 1.11
|
27
|
+
s.add_runtime_dependency 'uniform_notifier', '~> 1.11'
|
24
28
|
|
25
29
|
s.files = `git ls-files`.split("\n")
|
26
30
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
data/lib/bullet.rb
CHANGED
@@ -19,8 +19,8 @@ module Bullet
|
|
19
19
|
autoload :Registry, 'bullet/registry'
|
20
20
|
autoload :NotificationCollector, 'bullet/notification_collector'
|
21
21
|
|
22
|
-
BULLET_DEBUG = 'BULLET_DEBUG'
|
23
|
-
TRUE = 'true'
|
22
|
+
BULLET_DEBUG = 'BULLET_DEBUG'
|
23
|
+
TRUE = 'true'
|
24
24
|
|
25
25
|
if defined? Rails::Railtie
|
26
26
|
class BulletRailtie < Rails::Railtie
|
@@ -163,6 +163,7 @@ module Bullet
|
|
163
163
|
|
164
164
|
def notification?
|
165
165
|
return unless start?
|
166
|
+
|
166
167
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
167
168
|
notification_collector.notifications_present?
|
168
169
|
end
|
@@ -48,21 +48,12 @@ module Bullet
|
|
48
48
|
end
|
49
49
|
|
50
50
|
::ActiveRecord::Persistence.class_eval do
|
51
|
-
def
|
52
|
-
|
53
|
-
|
54
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
51
|
+
def _create_record_with_bullet(*args)
|
52
|
+
_create_record_without_bullet(*args).tap do
|
53
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
55
54
|
end
|
56
55
|
end
|
57
|
-
alias_method_chain :
|
58
|
-
|
59
|
-
def save_with_bullet!(*args, &proc)
|
60
|
-
was_new_record = new_record?
|
61
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
62
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
63
|
-
end
|
64
|
-
end
|
65
|
-
alias_method_chain :save!, :bullet
|
56
|
+
alias_method_chain :_create_record, :bullet
|
66
57
|
end
|
67
58
|
|
68
59
|
::ActiveRecord::Associations::Preloader.class_eval do
|
@@ -74,6 +65,7 @@ module Bullet
|
|
74
65
|
if Bullet.start?
|
75
66
|
records = [records].flatten.compact.uniq
|
76
67
|
return if records.empty?
|
68
|
+
|
77
69
|
records.each do |record|
|
78
70
|
Bullet::Detector::Association.add_object_associations(record, associations)
|
79
71
|
end
|
@@ -199,6 +191,14 @@ module Bullet
|
|
199
191
|
end
|
200
192
|
# rubocop:enable Style/MethodCallWithoutArgsParentheses
|
201
193
|
end
|
194
|
+
|
195
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
196
|
+
def writer_with_bullet(record)
|
197
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
198
|
+
writer_without_bullet(record)
|
199
|
+
end
|
200
|
+
alias_method_chain :writer, :bullet
|
201
|
+
end
|
202
202
|
end
|
203
203
|
end
|
204
204
|
end
|
@@ -50,21 +50,12 @@ module Bullet
|
|
50
50
|
end
|
51
51
|
|
52
52
|
::ActiveRecord::Persistence.class_eval do
|
53
|
-
def
|
54
|
-
|
55
|
-
|
56
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
53
|
+
def _create_record_with_bullet(*args)
|
54
|
+
_create_record_without_bullet(*args).tap do
|
55
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
57
56
|
end
|
58
57
|
end
|
59
|
-
alias_method_chain :
|
60
|
-
|
61
|
-
def save_with_bullet!(*args, &proc)
|
62
|
-
was_new_record = new_record?
|
63
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
64
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
65
|
-
end
|
66
|
-
end
|
67
|
-
alias_method_chain :save!, :bullet
|
58
|
+
alias_method_chain :_create_record, :bullet
|
68
59
|
end
|
69
60
|
|
70
61
|
::ActiveRecord::Associations::Preloader.class_eval do
|
@@ -89,6 +80,7 @@ module Bullet
|
|
89
80
|
alias_method :origin_find_with_associations, :find_with_associations
|
90
81
|
def find_with_associations
|
91
82
|
return origin_find_with_associations { |r| yield r } if block_given?
|
83
|
+
|
92
84
|
records = origin_find_with_associations
|
93
85
|
if Bullet.start?
|
94
86
|
associations = (eager_load_values + includes_values).uniq
|
@@ -187,6 +179,14 @@ module Bullet
|
|
187
179
|
origin_count_records
|
188
180
|
end
|
189
181
|
end
|
182
|
+
|
183
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
184
|
+
def writer_with_bullet(record)
|
185
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
186
|
+
writer_without_bullet(record)
|
187
|
+
end
|
188
|
+
alias_method_chain :writer, :bullet
|
189
|
+
end
|
190
190
|
end
|
191
191
|
end
|
192
192
|
end
|
@@ -44,21 +44,12 @@ module Bullet
|
|
44
44
|
end
|
45
45
|
|
46
46
|
::ActiveRecord::Persistence.class_eval do
|
47
|
-
def
|
48
|
-
|
49
|
-
|
50
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
47
|
+
def _create_record_with_bullet(*args)
|
48
|
+
_create_record_without_bullet(*args).tap do
|
49
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
51
50
|
end
|
52
51
|
end
|
53
|
-
alias_method_chain :
|
54
|
-
|
55
|
-
def save_with_bullet!(*args, &proc)
|
56
|
-
was_new_record = new_record?
|
57
|
-
save_without_bullet!(*args, &proc).tap do |result|
|
58
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
59
|
-
end
|
60
|
-
end
|
61
|
-
alias_method_chain :save!, :bullet
|
52
|
+
alias_method_chain :_create_record, :bullet
|
62
53
|
end
|
63
54
|
|
64
55
|
::ActiveRecord::Relation.class_eval do
|
@@ -104,6 +95,7 @@ module Bullet
|
|
104
95
|
alias_method :origin_find_with_associations, :find_with_associations
|
105
96
|
def find_with_associations
|
106
97
|
return origin_find_with_associations { |r| yield r } if block_given?
|
98
|
+
|
107
99
|
records = origin_find_with_associations
|
108
100
|
if Bullet.start?
|
109
101
|
associations = (eager_load_values + includes_values).uniq
|
@@ -141,6 +133,7 @@ module Bullet
|
|
141
133
|
key = aliases.column_alias(node, node.primary_key)
|
142
134
|
id = row[key]
|
143
135
|
next unless id.nil?
|
136
|
+
|
144
137
|
associations = node.reflection.name
|
145
138
|
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
146
139
|
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
@@ -247,6 +240,14 @@ module Bullet
|
|
247
240
|
origin_count_records
|
248
241
|
end
|
249
242
|
end
|
243
|
+
|
244
|
+
::ActiveRecord::Associations::BelongsToAssociation.class_eval do
|
245
|
+
def writer_with_bullet(record)
|
246
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
247
|
+
writer_without_bullet(record)
|
248
|
+
end
|
249
|
+
alias_method_chain :writer, :bullet
|
250
|
+
end
|
250
251
|
end
|
251
252
|
end
|
252
253
|
end
|
@@ -2,17 +2,10 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module SaveWithBulletSupport
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def save!(*args)
|
13
|
-
was_new_record = new_record?
|
14
|
-
super(*args).tap do |result|
|
15
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
5
|
+
def _create_record(*)
|
6
|
+
super do
|
7
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
8
|
+
yield(self) if block_given?
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -82,6 +75,7 @@ module Bullet
|
|
82
75
|
# add includes in scope
|
83
76
|
def find_with_associations
|
84
77
|
return super { |r| yield r } if block_given?
|
78
|
+
|
85
79
|
records = super
|
86
80
|
if Bullet.start?
|
87
81
|
associations = (eager_load_values + includes_values).uniq
|
@@ -132,6 +126,7 @@ module Bullet
|
|
132
126
|
key = aliases.column_alias(node, node.primary_key)
|
133
127
|
id = row[key]
|
134
128
|
next unless id.nil?
|
129
|
+
|
135
130
|
associations = node.reflection.name
|
136
131
|
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
137
132
|
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
@@ -168,11 +163,20 @@ module Bullet
|
|
168
163
|
|
169
164
|
if Bullet.start?
|
170
165
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
171
|
-
|
172
|
-
|
166
|
+
refl = reflection.through_reflection
|
167
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
168
|
+
association = owner.association refl.name
|
173
169
|
Array(association.target).each do |through_record|
|
174
170
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
175
171
|
end
|
172
|
+
|
173
|
+
if refl.through_reflection?
|
174
|
+
while refl.through_reflection?
|
175
|
+
refl = refl.through_reflection
|
176
|
+
end
|
177
|
+
|
178
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, refl.name)
|
179
|
+
end
|
176
180
|
end
|
177
181
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
178
182
|
if records.first.class.name !~ /^HABTM_/
|
@@ -238,6 +242,13 @@ module Bullet
|
|
238
242
|
super
|
239
243
|
end
|
240
244
|
end)
|
245
|
+
|
246
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
|
247
|
+
def writer(record)
|
248
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
249
|
+
super
|
250
|
+
end
|
251
|
+
end)
|
241
252
|
end
|
242
253
|
end
|
243
254
|
end
|
@@ -2,17 +2,10 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module SaveWithBulletSupport
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def save!(*args)
|
13
|
-
was_new_record = new_record?
|
14
|
-
super(*args).tap do |result|
|
15
|
-
Bullet::Detector::NPlusOneQuery.add_impossible_object(self) if result && was_new_record
|
5
|
+
def _create_record(*)
|
6
|
+
super do
|
7
|
+
Bullet::Detector::NPlusOneQuery.add_impossible_object(self)
|
8
|
+
yield(self) if block_given?
|
16
9
|
end
|
17
10
|
end
|
18
11
|
end
|
@@ -82,6 +75,7 @@ module Bullet
|
|
82
75
|
# add includes in scope
|
83
76
|
def find_with_associations
|
84
77
|
return super { |r| yield r } if block_given?
|
78
|
+
|
85
79
|
records = super
|
86
80
|
if Bullet.start?
|
87
81
|
associations = (eager_load_values + includes_values).uniq
|
@@ -115,6 +109,7 @@ module Bullet
|
|
115
109
|
key = aliases.column_alias(node, node.primary_key)
|
116
110
|
id = row[key]
|
117
111
|
next unless id.nil?
|
112
|
+
|
118
113
|
associations = node.reflection.name
|
119
114
|
Bullet::Detector::Association.add_object_associations(ar_parent, associations)
|
120
115
|
Bullet::Detector::NPlusOneQuery.call_association(ar_parent, associations)
|
@@ -151,11 +146,15 @@ module Bullet
|
|
151
146
|
|
152
147
|
if Bullet.start?
|
153
148
|
if is_a? ::ActiveRecord::Associations::ThroughAssociation
|
154
|
-
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
155
|
-
association = owner.association through_reflection.name
|
149
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.through_reflection.name)
|
150
|
+
association = owner.association reflection.through_reflection.name
|
156
151
|
Array(association.target).each do |through_record|
|
157
152
|
Bullet::Detector::NPlusOneQuery.call_association(through_record, source_reflection.name)
|
158
153
|
end
|
154
|
+
|
155
|
+
if reflection.through_reflection != through_reflection
|
156
|
+
Bullet::Detector::NPlusOneQuery.call_association(owner, through_reflection.name)
|
157
|
+
end
|
159
158
|
end
|
160
159
|
Bullet::Detector::NPlusOneQuery.call_association(owner, reflection.name) unless @inversed
|
161
160
|
if records.first.class.name !~ /^HABTM_/
|
@@ -221,6 +220,13 @@ module Bullet
|
|
221
220
|
super
|
222
221
|
end
|
223
222
|
end)
|
223
|
+
|
224
|
+
::ActiveRecord::Associations::BelongsToAssociation.prepend(Module.new do
|
225
|
+
def writer(record)
|
226
|
+
Bullet::Detector::Association.add_object_associations(owner, reflection.name) if Bullet.start?
|
227
|
+
super
|
228
|
+
end
|
229
|
+
end)
|
224
230
|
end
|
225
231
|
end
|
226
232
|
end
|
data/lib/bullet/dependency.rb
CHANGED
@@ -9,7 +9,7 @@ module Bullet
|
|
9
9
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
10
10
|
return unless object.primary_key_value
|
11
11
|
|
12
|
-
Bullet.debug('Detector::Association#add_object_associations'
|
12
|
+
Bullet.debug('Detector::Association#add_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
|
13
13
|
object_associations.add(object.bullet_key, associations)
|
14
14
|
end
|
15
15
|
|
@@ -18,7 +18,7 @@ module Bullet
|
|
18
18
|
return if !Bullet.n_plus_one_query_enable? && !Bullet.unused_eager_loading_enable?
|
19
19
|
return unless object.primary_key_value
|
20
20
|
|
21
|
-
Bullet.debug('Detector::Association#add_call_object_associations'
|
21
|
+
Bullet.debug('Detector::Association#add_call_object_associations', "object: #{object.bullet_key}, associations: #{associations}")
|
22
22
|
call_object_associations.add(object.bullet_key, associations)
|
23
23
|
end
|
24
24
|
|
@@ -16,9 +16,10 @@ module Bullet
|
|
16
16
|
return unless Bullet.n_plus_one_query_enable?
|
17
17
|
return unless object.primary_key_value
|
18
18
|
return if inversed_objects.include?(object.bullet_key, associations)
|
19
|
+
|
19
20
|
add_call_object_associations(object, associations)
|
20
21
|
|
21
|
-
Bullet.debug('Detector::NPlusOneQuery#call_association'
|
22
|
+
Bullet.debug('Detector::NPlusOneQuery#call_association', "object: #{object.bullet_key}, associations: #{associations}")
|
22
23
|
if !excluded_stacktrace_path? && conditions_met?(object, associations)
|
23
24
|
Bullet.debug('detect n + 1 query', "object: #{object.bullet_key}, associations: #{associations}")
|
24
25
|
create_notification caller_in_project, object.class.to_s, associations
|
@@ -28,10 +29,11 @@ module Bullet
|
|
28
29
|
def add_possible_objects(object_or_objects)
|
29
30
|
return unless Bullet.start?
|
30
31
|
return unless Bullet.n_plus_one_query_enable?
|
32
|
+
|
31
33
|
objects = Array(object_or_objects)
|
32
34
|
return if objects.map(&:primary_key_value).compact.empty?
|
33
35
|
|
34
|
-
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects'
|
36
|
+
Bullet.debug('Detector::NPlusOneQuery#add_possible_objects', "objects: #{objects.map(&:bullet_key).join(', ')}")
|
35
37
|
objects.each { |object| possible_objects.add object.bullet_key }
|
36
38
|
end
|
37
39
|
|
@@ -40,7 +42,7 @@ module Bullet
|
|
40
42
|
return unless Bullet.n_plus_one_query_enable?
|
41
43
|
return unless object.primary_key_value
|
42
44
|
|
43
|
-
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object'
|
45
|
+
Bullet.debug('Detector::NPlusOneQuery#add_impossible_object', "object: #{object.bullet_key}")
|
44
46
|
impossible_objects.add object.bullet_key
|
45
47
|
end
|
46
48
|
|
@@ -49,7 +51,7 @@ module Bullet
|
|
49
51
|
return unless Bullet.n_plus_one_query_enable?
|
50
52
|
return unless object.primary_key_value
|
51
53
|
|
52
|
-
Bullet.debug('Detector::NPlusOneQuery#add_inversed_object'
|
54
|
+
Bullet.debug('Detector::NPlusOneQuery#add_inversed_object', "object: #{object.bullet_key}, association: #{association}")
|
53
55
|
inversed_objects.add object.bullet_key, association
|
54
56
|
end
|
55
57
|
|
@@ -69,14 +71,12 @@ module Bullet
|
|
69
71
|
# check if object => associations already exists in object_associations.
|
70
72
|
def association?(object, associations)
|
71
73
|
value = object_associations[object.bullet_key]
|
72
|
-
|
73
|
-
value.each do |v|
|
74
|
+
value&.each do |v|
|
74
75
|
# associations == v comparison order is important here because
|
75
76
|
# v variable might be a squeel node where :== method is redefined,
|
76
77
|
# so it does not compare values at all and return unexpected results
|
77
|
-
|
78
|
-
|
79
|
-
end
|
78
|
+
result = v.is_a?(Hash) ? v.key?(associations) : associations == v
|
79
|
+
return true if result
|
80
80
|
end
|
81
81
|
|
82
82
|
false
|
data/lib/bullet/ext/object.rb
CHANGED
@@ -7,7 +7,7 @@ class Object
|
|
7
7
|
|
8
8
|
def primary_key_value
|
9
9
|
if self.class.respond_to?(:primary_keys) && self.class.primary_keys
|
10
|
-
self.class.primary_keys.map { |primary_key| send primary_key }.join(','
|
10
|
+
self.class.primary_keys.map { |primary_key| send primary_key }.join(',')
|
11
11
|
elsif self.class.respond_to?(:primary_key) && self.class.primary_key
|
12
12
|
send self.class.primary_key
|
13
13
|
else
|
data/lib/bullet/ext/string.rb
CHANGED
data/lib/bullet/mongoid4x.rb
CHANGED
data/lib/bullet/mongoid5x.rb
CHANGED
data/lib/bullet/mongoid6x.rb
CHANGED
@@ -10,20 +10,21 @@ module Bullet
|
|
10
10
|
alias_method :origin_each, :each
|
11
11
|
alias_method :origin_eager_load, :eager_load
|
12
12
|
|
13
|
-
def first
|
14
|
-
result = origin_first
|
13
|
+
def first(opt = {})
|
14
|
+
result = origin_first(opt)
|
15
15
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
16
16
|
result
|
17
17
|
end
|
18
18
|
|
19
|
-
def last
|
20
|
-
result = origin_last
|
19
|
+
def last(opt = {})
|
20
|
+
result = origin_last(opt)
|
21
21
|
Bullet::Detector::NPlusOneQuery.add_impossible_object(result) if result
|
22
22
|
result
|
23
23
|
end
|
24
24
|
|
25
25
|
def each(&block)
|
26
26
|
return to_enum unless block_given?
|
27
|
+
|
27
28
|
records = []
|
28
29
|
origin_each { |record| records << record }
|
29
30
|
if records.length > 1
|
data/lib/bullet/rack.rb
CHANGED
@@ -10,6 +10,7 @@ module Bullet
|
|
10
10
|
|
11
11
|
def call(env)
|
12
12
|
return @app.call(env) unless Bullet.enable?
|
13
|
+
|
13
14
|
Bullet.start_request
|
14
15
|
status, headers, response = @app.call(env)
|
15
16
|
|
@@ -66,7 +67,7 @@ module Bullet
|
|
66
67
|
end
|
67
68
|
|
68
69
|
def html_request?(headers, response)
|
69
|
-
headers['Content-Type']
|
70
|
+
headers['Content-Type']&.include?('text/html') && response_body(response).include?('<html')
|
70
71
|
end
|
71
72
|
|
72
73
|
def response_body(response)
|
@@ -80,13 +81,13 @@ module Bullet
|
|
80
81
|
private
|
81
82
|
|
82
83
|
def footer_div_attributes
|
83
|
-
|
84
|
-
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);
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
EOF
|
84
|
+
<<~EOF
|
85
|
+
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);
|
86
|
+
-moz-border-top-colors: none; -moz-border-right-colors: none; -moz-border-bottom-colors: none;
|
87
|
+
-moz-border-left-colors: none; -moz-border-image: none; border-width: 2pt 2pt 0px 0px;
|
88
|
+
padding: 3px 5px; border-radius: 0pt 10pt 0pt 0px; background: none repeat scroll 0% 0% rgba(200, 200, 200, 0.8);
|
89
|
+
color: rgb(119, 119, 119); font-size: 16px; font-family: 'Arial', sans-serif; z-index:9999;"
|
90
|
+
EOF
|
90
91
|
end
|
91
92
|
|
92
93
|
def footer_close_button
|
@@ -2,52 +2,71 @@
|
|
2
2
|
|
3
3
|
module Bullet
|
4
4
|
module StackTraceFilter
|
5
|
-
VENDOR_PATH = '/vendor'
|
5
|
+
VENDOR_PATH = '/vendor'
|
6
6
|
|
7
7
|
def caller_in_project
|
8
8
|
app_root = rails? ? Rails.root.to_s : Dir.pwd
|
9
9
|
vendor_root = app_root + VENDOR_PATH
|
10
10
|
bundler_path = Bundler.bundle_path.to_s
|
11
|
-
select_caller_locations do |
|
11
|
+
select_caller_locations do |location|
|
12
|
+
caller_path = location_as_path(location)
|
12
13
|
caller_path.include?(app_root) && !caller_path.include?(vendor_root) && !caller_path.include?(bundler_path) ||
|
13
|
-
Bullet.stacktrace_includes.any?
|
14
|
-
case include_pattern
|
15
|
-
when String
|
16
|
-
caller_path.include?(include_pattern)
|
17
|
-
when Regexp
|
18
|
-
caller_path =~ include_pattern
|
19
|
-
end
|
20
|
-
end
|
14
|
+
Bullet.stacktrace_includes.any? { |include_pattern| pattern_matches?(location, include_pattern) }
|
21
15
|
end
|
22
16
|
end
|
23
17
|
|
24
18
|
def excluded_stacktrace_path?
|
25
19
|
Bullet.stacktrace_excludes.any? do |exclude_pattern|
|
26
|
-
caller_in_project.any?
|
27
|
-
caller_path = location.absolute_path.to_s
|
28
|
-
case exclude_pattern
|
29
|
-
when String
|
30
|
-
caller_path.include?(exclude_pattern)
|
31
|
-
when Regexp
|
32
|
-
caller_path =~ exclude_pattern
|
33
|
-
end
|
34
|
-
end
|
20
|
+
caller_in_project.any? { |location| pattern_matches?(location, exclude_pattern) }
|
35
21
|
end
|
36
22
|
end
|
37
23
|
|
38
24
|
private
|
39
25
|
|
26
|
+
def pattern_matches?(location, pattern)
|
27
|
+
path = location_as_path(location)
|
28
|
+
case pattern
|
29
|
+
when Array
|
30
|
+
pattern_path = pattern.first
|
31
|
+
filter = pattern.last
|
32
|
+
return false unless pattern_matches?(location, pattern_path)
|
33
|
+
|
34
|
+
case filter
|
35
|
+
when Range
|
36
|
+
filter.include?(location.lineno)
|
37
|
+
when Integer
|
38
|
+
filter == location.lineno
|
39
|
+
when String
|
40
|
+
filter == location.base_label
|
41
|
+
end
|
42
|
+
when String
|
43
|
+
path.include?(pattern)
|
44
|
+
when Regexp
|
45
|
+
path =~ pattern
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def location_as_path(location)
|
50
|
+
ruby_19? ? location : location.absolute_path.to_s
|
51
|
+
end
|
52
|
+
|
40
53
|
def select_caller_locations
|
41
|
-
if
|
54
|
+
if ruby_19?
|
42
55
|
caller.select do |caller_path|
|
43
56
|
yield caller_path
|
44
57
|
end
|
45
58
|
else
|
46
59
|
caller_locations.select do |location|
|
47
|
-
|
48
|
-
yield caller_path
|
60
|
+
yield location
|
49
61
|
end
|
50
62
|
end
|
51
63
|
end
|
64
|
+
|
65
|
+
def ruby_19?
|
66
|
+
if @ruby_19.nil?
|
67
|
+
@ruby_19 = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.0.0')
|
68
|
+
end
|
69
|
+
@ruby_19
|
70
|
+
end
|
52
71
|
end
|
53
72
|
end
|
data/lib/bullet/version.rb
CHANGED
@@ -3,9 +3,9 @@
|
|
3
3
|
module Bullet
|
4
4
|
module Generators
|
5
5
|
class InstallGenerator < ::Rails::Generators::Base
|
6
|
-
desc
|
7
|
-
Description:
|
8
|
-
|
6
|
+
desc <<~DESC
|
7
|
+
Description:
|
8
|
+
Enable bullet in development/test for your application.
|
9
9
|
DESC
|
10
10
|
|
11
11
|
def enable_in_development
|
@@ -101,6 +101,29 @@ module Bullet
|
|
101
101
|
expect(NPlusOneQuery).to_not receive(:create_notification)
|
102
102
|
NPlusOneQuery.call_association(@post, :association)
|
103
103
|
end
|
104
|
+
|
105
|
+
# just a sanity spec to make sure the following spec works correctly
|
106
|
+
it "should create notification when stacktrace contains methods that aren't in the exclude list" do
|
107
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
108
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
109
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
110
|
+
|
111
|
+
expect(NPlusOneQuery).to receive(:caller_locations).at_least(1).and_return([in_project, excluded_path])
|
112
|
+
expect(NPlusOneQuery).to receive(:conditions_met?).and_return(true)
|
113
|
+
expect(NPlusOneQuery).to receive(:create_notification)
|
114
|
+
NPlusOneQuery.call_association(@post, :association)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not create notification when stacktrace contains methods that are in the exclude list' do
|
118
|
+
method = NPlusOneQuery.method(:excluded_stacktrace_path?).source_location
|
119
|
+
Bullet.stacktrace_excludes = [method]
|
120
|
+
in_project = OpenStruct.new(absolute_path: File.join(Dir.pwd, 'abc', 'abc.rb'))
|
121
|
+
excluded_path = OpenStruct.new(absolute_path: method.first, lineno: method.last)
|
122
|
+
|
123
|
+
expect(NPlusOneQuery).to receive(:caller_locations).and_return([in_project, excluded_path])
|
124
|
+
expect(NPlusOneQuery).to_not receive(:create_notification)
|
125
|
+
NPlusOneQuery.call_association(@post, :association)
|
126
|
+
end
|
104
127
|
end
|
105
128
|
end
|
106
129
|
|
@@ -226,7 +226,7 @@ if active_record?
|
|
226
226
|
context 'post => comment' do
|
227
227
|
it 'should detect unused preload with post => comments' do
|
228
228
|
Post.includes(:comments).each do |post|
|
229
|
-
post.comments.first
|
229
|
+
post.comments.first&.name
|
230
230
|
end
|
231
231
|
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
232
232
|
expect(Bullet::Detector::Association).not_to be_unused_preload_associations_for(Post, :comments)
|
@@ -356,6 +356,28 @@ if active_record?
|
|
356
356
|
|
357
357
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
358
358
|
end
|
359
|
+
|
360
|
+
it 'should not detect newly assigned object in an after_save' do
|
361
|
+
new_post = Post.new(category: Category.first)
|
362
|
+
|
363
|
+
new_post.trigger_after_save = true
|
364
|
+
new_post.save!
|
365
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
366
|
+
|
367
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
368
|
+
end
|
369
|
+
|
370
|
+
it 'should not detect "manual" preload' do
|
371
|
+
comment = Comment.all.to_a.first
|
372
|
+
post = Post.find(comment.post_id)
|
373
|
+
# "manually" preload with out-of-band data
|
374
|
+
comment.post = post
|
375
|
+
# loading it should not trigger anything
|
376
|
+
comment.post
|
377
|
+
|
378
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
379
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
380
|
+
end
|
359
381
|
end
|
360
382
|
|
361
383
|
context 'comment => post => category' do
|
@@ -528,6 +550,44 @@ if active_record?
|
|
528
550
|
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
529
551
|
end
|
530
552
|
end
|
553
|
+
|
554
|
+
context 'firm => clients => groups' do
|
555
|
+
it 'should detect non preload associations' do
|
556
|
+
Firm.all.each do |firm|
|
557
|
+
firm.groups.map(&:name)
|
558
|
+
end
|
559
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
560
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
561
|
+
|
562
|
+
expect(Bullet::Detector::Association).to be_detecting_unpreloaded_association_for(Firm, :groups)
|
563
|
+
end
|
564
|
+
|
565
|
+
it 'should detect preload associations' do
|
566
|
+
Firm.includes(:groups).each do |firm|
|
567
|
+
firm.groups.map(&:name)
|
568
|
+
end
|
569
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
570
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
571
|
+
|
572
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
573
|
+
end
|
574
|
+
|
575
|
+
it 'should not detect preload associations' do
|
576
|
+
Firm.all.map(&:name)
|
577
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
578
|
+
expect(Bullet::Detector::Association).not_to be_has_unused_preload_associations
|
579
|
+
|
580
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'should detect unused preload associations' do
|
584
|
+
Firm.includes(:groups).map(&:name)
|
585
|
+
Bullet::Detector::UnusedEagerLoading.check_unused_preload_associations
|
586
|
+
expect(Bullet::Detector::Association).to be_unused_preload_associations_for(Firm, :groups)
|
587
|
+
|
588
|
+
expect(Bullet::Detector::Association).to be_completely_preloading_associations
|
589
|
+
end
|
590
|
+
end
|
531
591
|
end
|
532
592
|
|
533
593
|
describe Bullet::Detector::Association, 'has_one' do
|
data/spec/models/client.rb
CHANGED
data/spec/models/firm.rb
CHANGED
data/spec/models/post.rb
CHANGED
@@ -14,4 +14,19 @@ class Post < ActiveRecord::Base
|
|
14
14
|
def link=(*)
|
15
15
|
comments.new
|
16
16
|
end
|
17
|
+
|
18
|
+
# see association_spec.rb 'should not detect newly assigned object in an after_save'
|
19
|
+
attr_accessor :trigger_after_save
|
20
|
+
after_save do
|
21
|
+
next unless trigger_after_save
|
22
|
+
|
23
|
+
temp_comment = Comment.new(post: self)
|
24
|
+
# this triggers self to be "possible", even though it's
|
25
|
+
# not saved yet
|
26
|
+
temp_comment.post
|
27
|
+
|
28
|
+
# category should NOT whine about not being pre-loaded, because
|
29
|
+
# it's obviously attached to a new object
|
30
|
+
category
|
31
|
+
end
|
17
32
|
end
|
data/spec/support/sqlite_seed.rb
CHANGED
@@ -45,8 +45,10 @@ module Support
|
|
45
45
|
|
46
46
|
firm1 = Firm.create(name: 'first')
|
47
47
|
firm2 = Firm.create(name: 'second')
|
48
|
-
|
49
|
-
|
48
|
+
group1 = Group.create(name: 'first')
|
49
|
+
group2 = Group.create(name: 'second')
|
50
|
+
client1 = Client.create(name: 'first', group: group1)
|
51
|
+
client2 = Client.create(name: 'second', group: group2)
|
50
52
|
firm1.clients = [client1, client2]
|
51
53
|
firm2.clients = [client1, client2]
|
52
54
|
client1.firms << firm1
|
@@ -125,6 +127,7 @@ module Support
|
|
125
127
|
|
126
128
|
create_table :clients do |t|
|
127
129
|
t.column :name, :string
|
130
|
+
t.column :group_id, :integer
|
128
131
|
end
|
129
132
|
|
130
133
|
create_table :comments do |t|
|
@@ -171,6 +174,10 @@ module Support
|
|
171
174
|
t.column :name, :string
|
172
175
|
end
|
173
176
|
|
177
|
+
create_table :groups do |t|
|
178
|
+
t.column :name, :string
|
179
|
+
end
|
180
|
+
|
174
181
|
create_table :hotels do |t|
|
175
182
|
t.column :name, :string
|
176
183
|
t.column :location_id, :integer
|
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: 5.
|
4
|
+
version: 5.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Richard Huang
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-10-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,14 +30,14 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: 1.11
|
33
|
+
version: '1.11'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: 1.11
|
40
|
+
version: '1.11'
|
41
41
|
description: help to kill N+1 queries and unused eager loading.
|
42
42
|
email:
|
43
43
|
- flyerhzm@gmail.com
|
@@ -133,6 +133,7 @@ files:
|
|
133
133
|
- spec/models/entry.rb
|
134
134
|
- spec/models/firm.rb
|
135
135
|
- spec/models/folder.rb
|
136
|
+
- spec/models/group.rb
|
136
137
|
- spec/models/mongoid/address.rb
|
137
138
|
- spec/models/mongoid/category.rb
|
138
139
|
- spec/models/mongoid/comment.rb
|
@@ -163,7 +164,9 @@ files:
|
|
163
164
|
homepage: https://github.com/flyerhzm/bullet
|
164
165
|
licenses:
|
165
166
|
- MIT
|
166
|
-
metadata:
|
167
|
+
metadata:
|
168
|
+
changelog_uri: https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md
|
169
|
+
source_code_uri: https://github.com/flyerhzm/bullet
|
167
170
|
post_install_message:
|
168
171
|
rdoc_options: []
|
169
172
|
require_paths:
|
@@ -218,6 +221,7 @@ test_files:
|
|
218
221
|
- spec/models/entry.rb
|
219
222
|
- spec/models/firm.rb
|
220
223
|
- spec/models/folder.rb
|
224
|
+
- spec/models/group.rb
|
221
225
|
- spec/models/mongoid/address.rb
|
222
226
|
- spec/models/mongoid/category.rb
|
223
227
|
- spec/models/mongoid/comment.rb
|