ar-query-matchers 0.1.0 → 0.5.1

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: ac905a2afca1579c9f829cb80ec5ea9db151311b7970e3a35f90528d0d9524b0
4
- data.tar.gz: 5f1413a65d8276a0b282070be3d86f8a910224e51ac8fff893d3882c62986f25
3
+ metadata.gz: 9c028b70b3fbf9ea3e703744480dfb98f6a1ef9b6ab2e90fc4c05879cb2eecff
4
+ data.tar.gz: c1191c7af4628af475e2e180ec3dd0233e80d69c0d5991d8278292fed6e1aec6
5
5
  SHA512:
6
- metadata.gz: 8c76ea6089d6e15a9ae2dcd9d07319756caa09d36d3645373a5362b04c677c67432305cd9598368231aaacd872604d73df20ca5c93c4d9e2bcd92ceea1860e90
7
- data.tar.gz: ba4fb57beaf019f17ac41fbc66cb974bcb76616db0d0b5ce1a3028e87b66d8cf2f6b954342ee59ca52162debb6e4cf7478ae41266f0fad53c353437fea3849d8
6
+ metadata.gz: f220e3dff11f0b978b6de20314646783814fadfc7cc15ad51d59236a76def60e98f77fe5495217f572046fffbd15a568b0f5827c56874a1b8dd71ec4c3822e04
7
+ data.tar.gz: 10ee5a3e0c6a39836d59649a0df91eb64f5967e8cc5df0d874755e33f1894d6f251f3f9e82bbc4395e97b67a1c820aaab929c3df05226e4d9c52e91fc133ddf7
@@ -0,0 +1,40 @@
1
+ # Changelog
2
+ All notable changes to this project will be documented in this file.
3
+
4
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.5.1] - 2020-11-19
10
+ ### Changed
11
+ - Removes zero count expectations from hash before comparing
12
+
13
+ ## [0.5.0] - 2020-07-23
14
+ ### Changed
15
+ - Add time information to query counter
16
+
17
+ ## [0.4.0] - 2020-07-20
18
+ ### Changed
19
+ - Upgrade the Rails dependency to allow for Rails 6.1
20
+
21
+ ## [0.3.0] - 2020-03-13
22
+ ### Changed
23
+ - Correct the Rails dependency to allow for Rails 6.0
24
+
25
+ ## [0.2.0] - 2019-09-15
26
+ ### Changed
27
+ - Package the CHANGELOG and README in the gem.
28
+ - Add additional gemspec metadata
29
+
30
+ ## [0.1.0] - 2019-09-14
31
+ ### Added
32
+ - First versions as a public ruby gem.
33
+
34
+ [Unreleased]: https://github.com/gusto/ar-query-matchers/compare/v0.5.1...HEAD
35
+ [0.5.1]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.5.1
36
+ [0.5.0]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.5.0
37
+ [0.4.0]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.4.0
38
+ [0.3.0]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.3.0
39
+ [0.2.0]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.2.0
40
+ [0.1.0]: https://github.com/gusto/ar-query-matchers/releases/tag/v0.1.0
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2019 Matan Zruya
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,14 +1,40 @@
1
1
  ## AR Query Matchers
2
+ ![badge](https://action-badges.now.sh/gusto/ar-query-matchers?action=Run%20Tests)
2
3
 
3
- These RSpec matchers allows guarding against N+1 queries by specifying
4
- exactly how many queries we expect each of our models to perform.
4
+ These RSpec matchers allow guarding against N+1 queries by specifying
5
+ exactly how many queries you expect each of your ActiveRecord models to perform.
5
6
 
6
- They also help us reason about the type of record interactions happening in a block of code.
7
+ They could also help reasoning about which database interactions are happening inside a block of code.
7
8
 
8
9
  This pattern is a based on how Rails itself tests queries:
9
10
  https://github.com/rails/rails/blob/ac2bc00482c1cf47a57477edb6ab1426a3ba593c/activerecord/test/cases/test_case.rb#L104-L141
10
11
 
11
- This module defines a few categories of matchers:
12
+ Currently, this gem only supports RSpec matchers, but the code is meant to be adapted to support other testing frameworks.
13
+ If you'd like to pick that up, please have a look at: https://github.com/Gusto/ar-query-matchers/issues/13
14
+
15
+ ### Usage
16
+ Include it in your Gemfile:
17
+ ```ruby
18
+ group :test do
19
+ gem 'ar-query-matchers', '~> 0.2.0', require: false
20
+ end
21
+ ```
22
+
23
+ Start using it:
24
+ ```ruby
25
+ require 'ar_query_matchers'
26
+
27
+ RSpec.describe Employee do
28
+ it 'creating an employee creates exactly one record' do
29
+ expect {
30
+ Employee.create!(first_name: 'John', last_name: 'Doe')
31
+ }.to only_create_models('Employee' => '1')
32
+ end
33
+ end
34
+ ```
35
+
36
+ ### Matchers
37
+ This gem defines a few categories of matchers:
12
38
  - **Create**: Which models are created during a block
13
39
  - **Load**: Which models are fetched during a block
14
40
  - **Update**: Which models are updated during a block
@@ -32,7 +58,7 @@ expect { some_code() }.to only_load_models(
32
58
  )
33
59
  ```
34
60
 
35
- The following spec will pass only if there are exactly no select queries.
61
+ The following spec will pass only if there are no select queries.
36
62
  ```ruby
37
63
  expect { some_code() }.to not_load_models
38
64
  ```
@@ -61,11 +87,9 @@ Expected to run queries to load models exactly {"Address"=>1, "Payroll"=>1, "Use
61
87
  ```
62
88
 
63
89
  ### High Level Design:
64
- The RSpec matcher delegates to the query counters, asserts expectations and formats error messages to provide meaningful failures.
65
-
66
-
90
+ The RSpec matcher delegates to "query counters", asserts expectations and formats error messages to provide meaningful failures.
67
91
  The matchers are pretty simple, and delegate instrumentation into specialized QueryCounter classes.
68
- The QueryCounters are different classes instruments a ruby block by listening on all sql, parsing the queries and returning structured data describing the interactions.
92
+ The QueryCounters are different classes which instrument a ruby block by listening on all sql, parsing the queries and returning structured data describing the interactions.
69
93
 
70
94
  ```
71
95
  ┌────────────────────────────────────────────────────────────────────────────────────────┐
@@ -88,4 +112,4 @@ For more information, see:
88
112
  ### Known problems
89
113
  - The Rails 4 `ActiveRecord::Base#pluck` method doesn't issue a
90
114
  `Load` or `Exists` named query and therefore we don't capture the counts with
91
- this tool. This may be fixed in Rails 5/6.
115
+ this tool. This may be fixed in Rails 5/6.
@@ -3,9 +3,16 @@
3
3
  require 'ar_query_matchers/queries/create_counter'
4
4
  require 'ar_query_matchers/queries/load_counter'
5
5
  require 'ar_query_matchers/queries/update_counter'
6
+ require 'bigdecimal'
6
7
 
7
8
  module ArQueryMatchers
8
9
  module ArQueryMatchers
10
+ class Utility
11
+ def self.remove_superfluous_expectations(expected)
12
+ expected.select { |_, v| v.positive? }
13
+ end
14
+ end
15
+
9
16
  module CreateModels
10
17
  # The following will succeed:
11
18
  # expect {
@@ -22,7 +29,7 @@ module ArQueryMatchers
22
29
 
23
30
  match do |block|
24
31
  @query_stats = Queries::CreateCounter.instrument(&block)
25
- expected == @query_stats.query_counts
32
+ Utility.remove_superfluous_expectations(expected) == @query_stats.query_counts
26
33
  end
27
34
 
28
35
  def failure_text
@@ -82,7 +89,7 @@ module ArQueryMatchers
82
89
 
83
90
  match do |block|
84
91
  @query_stats = Queries::LoadCounter.instrument(&block)
85
- expected == @query_stats.query_counts
92
+ Utility.remove_superfluous_expectations(expected) == @query_stats.query_counts
86
93
  end
87
94
 
88
95
  def failure_text
@@ -146,7 +153,7 @@ module ArQueryMatchers
146
153
 
147
154
  match do |block|
148
155
  @query_stats = Queries::UpdateCounter.instrument(&block)
149
- expected == @query_stats.query_counts
156
+ Utility.remove_superfluous_expectations(expected) == @query_stats.query_counts
150
157
  end
151
158
 
152
159
  def failure_text
@@ -62,7 +62,7 @@ module ArQueryMatchers
62
62
  # @param [block] block to instrument
63
63
  # @return [QueryStats] stats about all the SQL queries executed during the block
64
64
  def instrument(&block)
65
- queries = Hash.new { |h, k| h[k] = { count: 0, lines: [] } }
65
+ queries = Hash.new { |h, k| h[k] = { count: 0, lines: [], time: BigDecimal(0) } }
66
66
  ActiveSupport::Notifications.subscribed(to_proc(queries), 'sql.active_record', &block)
67
67
  QueryStats.new(queries)
68
68
  end
@@ -75,11 +75,11 @@ module ArQueryMatchers
75
75
  private_constant :MARGINALIA_SQL_COMMENT_PATTERN
76
76
 
77
77
  def to_proc(queries)
78
- lambda do |_name, _start, _finish, _message_id, payload|
78
+ lambda do |_name, start, finish, _message_id, payload|
79
79
  return if payload[:cached]
80
80
 
81
81
  # Given a `sql.active_record` event, figure out which model is being
82
- # accessed. Some of the simpler queries have a :ame key that makes this
82
+ # accessed. Some of the simpler queries have a :name key that makes this
83
83
  # really easy. Others require parsing the SQL by hand.
84
84
  model_name = @query_filter.filter_map(payload[:name] || '', payload[:sql] || '')&.model_name
85
85
 
@@ -87,6 +87,7 @@ module ArQueryMatchers
87
87
  comment = payload[:sql].match(MARGINALIA_SQL_COMMENT_PATTERN)
88
88
  queries[model_name][:lines] << comment[:line] if comment
89
89
  queries[model_name][:count] += 1
90
+ queries[model_name][:time] += (finish - start).round(6) # Round to microseconds
90
91
  end
91
92
  end
92
93
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar-query-matchers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matan Zruya
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-14 00:00:00.000000000 Z
11
+ date: 2020-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -17,9 +17,9 @@ dependencies:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
- - - "<="
20
+ - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '6.0'
22
+ version: '7.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,9 +27,9 @@ dependencies:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
29
  version: '4.0'
30
- - - "<="
30
+ - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '6.0'
32
+ version: '7.0'
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: activesupport
35
35
  requirement: !ruby/object:Gem::Requirement
@@ -37,9 +37,9 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '4.0'
40
- - - "<="
40
+ - - "<"
41
41
  - !ruby/object:Gem::Version
42
- version: '6.0'
42
+ version: '7.0'
43
43
  type: :runtime
44
44
  prerelease: false
45
45
  version_requirements: !ruby/object:Gem::Requirement
@@ -47,9 +47,9 @@ dependencies:
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
49
  version: '4.0'
50
- - - "<="
50
+ - - "<"
51
51
  - !ruby/object:Gem::Version
52
- version: '6.0'
52
+ version: '7.0'
53
53
  - !ruby/object:Gem::Dependency
54
54
  name: rspec
55
55
  requirement: !ruby/object:Gem::Requirement
@@ -64,6 +64,20 @@ dependencies:
64
64
  - - "~>"
65
65
  - !ruby/object:Gem::Version
66
66
  version: '3.0'
67
+ - !ruby/object:Gem::Dependency
68
+ name: appraisal
69
+ requirement: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
67
81
  - !ruby/object:Gem::Dependency
68
82
  name: bundler
69
83
  requirement: !ruby/object:Gem::Requirement
@@ -82,30 +96,30 @@ dependencies:
82
96
  name: rake
83
97
  requirement: !ruby/object:Gem::Requirement
84
98
  requirements:
85
- - - "~>"
99
+ - - ">="
86
100
  - !ruby/object:Gem::Version
87
- version: '10.0'
101
+ version: '0'
88
102
  type: :development
89
103
  prerelease: false
90
104
  version_requirements: !ruby/object:Gem::Requirement
91
105
  requirements:
92
- - - "~>"
106
+ - - ">="
93
107
  - !ruby/object:Gem::Version
94
- version: '10.0'
108
+ version: '0'
95
109
  - !ruby/object:Gem::Dependency
96
110
  name: rspec
97
111
  requirement: !ruby/object:Gem::Requirement
98
112
  requirements:
99
- - - "~>"
113
+ - - ">="
100
114
  - !ruby/object:Gem::Version
101
- version: '3.0'
115
+ version: '0'
102
116
  type: :development
103
117
  prerelease: false
104
118
  version_requirements: !ruby/object:Gem::Requirement
105
119
  requirements:
106
- - - "~>"
120
+ - - ">="
107
121
  - !ruby/object:Gem::Version
108
- version: '3.0'
122
+ version: '0'
109
123
  - !ruby/object:Gem::Dependency
110
124
  name: rubocop
111
125
  requirement: !ruby/object:Gem::Requirement
@@ -124,23 +138,26 @@ dependencies:
124
138
  name: sqlite3
125
139
  requirement: !ruby/object:Gem::Requirement
126
140
  requirements:
127
- - - "~>"
141
+ - - ">="
128
142
  - !ruby/object:Gem::Version
129
- version: '1.4'
143
+ version: '0'
130
144
  type: :development
131
145
  prerelease: false
132
146
  version_requirements: !ruby/object:Gem::Requirement
133
147
  requirements:
134
- - - "~>"
148
+ - - ">="
135
149
  - !ruby/object:Gem::Version
136
- version: '1.4'
137
- description: ''
150
+ version: '0'
151
+ description: These RSpec matchers allow guarding against N+1 queries by specifying
152
+ exactly how many queries you expect each of your ActiveRecord models to perform.
138
153
  email:
139
154
  - mzruya@gmail.com
140
155
  executables: []
141
156
  extensions: []
142
157
  extra_rdoc_files: []
143
158
  files:
159
+ - CHANGELOG.md
160
+ - LICENSE.txt
144
161
  - README.md
145
162
  - lib/ar_query_matchers.rb
146
163
  - lib/ar_query_matchers/queries/create_counter.rb
@@ -157,7 +174,7 @@ licenses:
157
174
  metadata:
158
175
  homepage_uri: https://github.com/Gusto/ar-query-matchers
159
176
  source_code_uri: https://github.com/Gusto/ar-query-matchers
160
- changelog_uri: https://github.com/Gusto/ar-query-matchers
177
+ changelog_uri: https://github.com/Gusto/ar-query-matchers/blob/master/CHANGELOG.md
161
178
  post_install_message:
162
179
  rdoc_options: []
163
180
  require_paths:
@@ -173,8 +190,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
173
190
  - !ruby/object:Gem::Version
174
191
  version: '0'
175
192
  requirements: []
176
- rubygems_version: 3.0.3
193
+ rubygems_version: 3.1.4
177
194
  signing_key:
178
195
  specification_version: 4
179
- summary: ''
196
+ summary: Ruby test matchers for instrumenting ActiveRecord query counts
180
197
  test_files: []