rspec-activerecord-expectations 1.3.0 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92f14ca413e9e716ea9b3046abc13d3929893185cfef8cdeaf14b7bebc93755c
4
- data.tar.gz: 93bfa962b7954844560961d66d080fa2c45a3188b8c9b92420d1852e36ce718b
3
+ metadata.gz: 0f84a252fb5e5923d4bc6c03ed4d99ede76579017d8391dbf6f48a665236e54e
4
+ data.tar.gz: ce944950ee62940f67f79204966f5954fc28eac8f52dee79e18475933d9f3910
5
5
  SHA512:
6
- metadata.gz: bb81903bbb00357ab9613a59fb2684bfaad7671fbca955f9876d503f5b179f8ead6d36f248ea24909f30db3ddb6f4fc307c509a181d2fe85d33d1e04006856da
7
- data.tar.gz: f813c1e8d6e320f6810cc33620fe702c379f47124b6add4bc1bdabde81c4e12c60a6aac5711caf8c23672f838d6185106ffb3695770a7c4002d04404c87929cb
6
+ metadata.gz: 4828d15b2420ae821199db596b96cc5d4630e3994b6c12bb2d6b95c32067388bb30161b318b0c03c49771d5b95e3010757e564c70a1f6840ab6934c14479f58d
7
+ data.tar.gz: d8220ae71a3e1dc6be694f976185262e019d55c6eedc1ec255492fbb5cd7a2988ac45e3519c14f1936cf8d0fd09062710f71b2d788322771e4ccde864be436a9
@@ -11,19 +11,19 @@ jobs:
11
11
  strategy:
12
12
  matrix:
13
13
  os: [ubuntu-latest, macos-latest]
14
- ruby_version: ['3.0', '2.7']
14
+ ruby-version: ['3.1', '3.0', '2.7']
15
15
  rails: ['5.0', '5.1', '5.2', '6.0', '6.1', '7.0']
16
16
 
17
17
  runs-on: ${{ matrix.os }}
18
- name: ruby ${{ matrix.ruby_version }}, rails ${{ matrix.rails }}, ${{ matrix.os }}
18
+ name: ruby ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}, ${{ matrix.os }}
19
19
 
20
20
  steps:
21
21
  - uses: actions/checkout@v2
22
22
 
23
- - name: Set up Ruby ${{ matrix.ruby_version }}
23
+ - name: Set up Ruby ${{ matrix.ruby-version }}
24
24
  uses: ruby/setup-ruby@v1
25
25
  with:
26
- ruby_version: ${{ matrix.ruby_version }}
26
+ ruby-version: ${{ matrix.ruby-version }}
27
27
  bundler-cache: true
28
28
 
29
29
  - name: Install Gems w/ Rails ${{ matrix.rails }}
@@ -40,19 +40,19 @@ jobs:
40
40
  test_legacy:
41
41
  strategy:
42
42
  matrix:
43
- ruby_version: ['2.6', '2.5', '2.4', '2.3']
43
+ ruby-version: ['2.6', '2.5']
44
44
  rails: ['5.0', '5.1', '5.2']
45
45
 
46
46
  runs-on: ubuntu-latest
47
- name: legacy ruby ${{ matrix.ruby_version }}, rails ${{ matrix.rails }}
47
+ name: ruby ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}
48
48
 
49
49
  steps:
50
50
  - uses: actions/checkout@v2
51
51
 
52
- - name: Set up Ruby ${{ matrix.ruby_version }}
52
+ - name: Set up Ruby ${{ matrix.ruby-version }}
53
53
  uses: ruby/setup-ruby@v1
54
54
  with:
55
- ruby_version: ${{ matrix.ruby_version }}
55
+ ruby-version: ${{ matrix.ruby-version }}
56
56
  bundler-cache: true
57
57
 
58
58
  - name: Install Gems w/ Rails ${{ matrix.rails }}
@@ -65,3 +65,33 @@ jobs:
65
65
 
66
66
  - name: Run tests
67
67
  run: bundle exec rspec
68
+
69
+ test_alt_rubies:
70
+ strategy:
71
+ matrix:
72
+ ruby-version: ['jruby', 'truffleruby']
73
+ rails: ['6.0', '6.1'] # jruby / jdbc-sqlite3 end up tightly bound to rails 6.x
74
+
75
+ runs-on: ubuntu-latest
76
+ name: ${{ matrix.ruby-version }}, rails ${{ matrix.rails }}
77
+
78
+ steps:
79
+ - uses: actions/checkout@v2
80
+
81
+ - name: Set up Ruby ${{ matrix.ruby-version }}
82
+ uses: ruby/setup-ruby@v1
83
+ with:
84
+ ruby-version: ${{ matrix.ruby-version }}
85
+ bundler-cache: true
86
+
87
+ - name: Install Gems w/ Rails ${{ matrix.rails }}
88
+ env:
89
+ MATRIX_RAILS_VERSION: ${{ matrix.rails }}
90
+ run: |
91
+ export BUNDLE_GEMFILE="${GITHUB_WORKSPACE}/gemfiles/rails_${MATRIX_RAILS_VERSION}.gemfile"
92
+ gem install bundler
93
+ bundle install --jobs 4 --retry 3
94
+
95
+ - name: Run tests
96
+ run: bundle exec rspec
97
+
data/CHANGELOG.md CHANGED
@@ -1,25 +1,48 @@
1
1
  # Changelog
2
2
 
3
- ## [1.3.0] - 2020-12-31
3
+ ## [2.3.0] - 2022-01-29
4
+ - Add quantifiers to transaction matchers
5
+ - Add much more complicated English output for readable matchers
6
+ - Update README for the same
7
+
8
+ ## [2.2.0] - 2022-01-14
9
+ - Adds transaction matcher to verify that code was executed within a
10
+ transaction at minimum
11
+ - Transaction matcher also allows for commits and rollbacks
12
+ - Update README for accompanying functionality
13
+
14
+ ## [2.1.1] - 2022-01-12
15
+ - Gemspec is really generated using the version of ruby that's locally in use.
16
+ Update that build artifact to use non-java dependencies.
17
+
18
+ ## [2.1.0] - 2022-01-12
19
+ - Change CI matrix for more version compatibility
20
+ - Update gemspec to move some deps into development only
21
+ - README changes
22
+
23
+ ## [2.0.0] - 2022-01-07
24
+ - Require much more recent ruby, to use syntax that's been around since 2017
25
+
26
+ ## [1.3.0] - 2021-12-31
4
27
  - Add `repeatedly_load` matcher
5
28
  - Add query type matchers for `load_queries`, `schema_queries`, `transaction_queries`, `destroy_queries`
6
29
  - Allow singular version of all query types (e.g. `transaction_queries` vs `transaction_query`)
7
30
  - Fix failure message for `execute.exactly` matcher
8
31
 
9
- ## [1.2.0] - 2020-12-31
32
+ ## [1.2.0] - 2021-12-31
10
33
  - Add `query` as a synonym for `queries`
11
34
  - Ignore schema and transaction queries in query count
12
35
  - Add beginning of recording specific query types
13
36
  - Add query count matcher for `exactly`
14
37
 
15
- ## [1.1.0] - 2020-12-30
38
+ ## [1.1.0] - 2021-12-30
16
39
  - Add query count matchers for e.g. `less_than_or_equal_to`, `greater_than`
17
40
 
18
- ## [1.0.1] - 2020-12-30
41
+ ## [1.0.1] - 2021-12-30
19
42
  - Pin all the dependencies to a proper working subset
20
43
  - Expand testing to many rails / ruby combinations
21
44
 
22
- ## [1.0.0] - 2020-12-30
45
+ ## [1.0.0] - 2021-12-30
23
46
  - Basic gem w/ all the fixins and README and such
24
47
  - Add `less_than` comparison, and `queries` type
25
48
  - Basic tests in place, and not bad tbh
data/README.md CHANGED
@@ -30,7 +30,8 @@ group 'test' do
30
30
  end
31
31
  ```
32
32
 
33
- Then, include the functionality within your spec helper (or in a support file).
33
+ Then, include the functionality within your `rails_helper.rb` (or in a support
34
+ file).
34
35
 
35
36
  ```ruby
36
37
  RSpec.configure do |config|
@@ -109,18 +110,34 @@ This matcher will track ActiveRecord's built in load methods to prevent those
109
110
  N+1 situations. Using eager loading (e.g. `Track.all.includes(:album)`) will
110
111
  allow these expectations to pass as expected!
111
112
 
112
- **Note:** At the moment, this expectation will fail if you use a mechanism
113
- that loads records in batches, such as with `find_in_batches`. This will cause
114
- records to be "repeatedly loaded", but this is actually expected behavior in
115
- this case. If your tests load a relatively small number of records (which is
116
- probable), your tests won't fail. But it is possible.
113
+ ### Testing Batch Queries
114
+
115
+ If your code loads records in batches, it may be more difficult to create
116
+ expectations for repeated loading. After all, each batch will execute its own
117
+ queries, which may look like repeated loading.
118
+
119
+ If your test has a small enough number of records that only one batch is
120
+ loaded, your tests may work just fine. But otherwise, you may want to allow
121
+ your code to specify a batch size in order to guarantee only a single batch
122
+ is loaded.
123
+
124
+ ```ruby
125
+ tracks = Track.all
126
+
127
+ expect {
128
+ TrackSerializer.perform(tracks, batch_size: tracks.count)
129
+ }.not_to repeatedly_load('Track')
130
+ ```
117
131
 
118
132
  ## Counting Queries
119
133
 
120
- ### Types of Comparisons
134
+ Some services won't necessarily have N+1 issues with records loading, but might
135
+ still have problems with executing too many queries. In this case, the
136
+ `repeatedly_load` matcher might be insufficient.
121
137
 
122
- Several comparison types are available, along with some aliases to allow for
123
- easier to read tests.
138
+ In this case, consider creating an expectation on the total number of queries
139
+ executed by your code. Several comparison types are available, along with some
140
+ aliases to allow for easier to read tests.
124
141
 
125
142
  ```ruby
126
143
  expect {}.to execute.less_than(20).queries
@@ -143,15 +160,10 @@ expect {}.to execute.at_least(1).query # singular form also accepted
143
160
 
144
161
  ### Specific Query Types
145
162
 
146
- You can of course make assertions for the total number of queries executed, but
147
- sometimes it's more valuable to assert particular _types_ of queries, such as
148
- inserts or find statements. Matchers are supported for this as well!
149
-
150
- **Note:** Transaction (for example, `ROLLBACK`) queries are not counted in any of these
151
- categories, nor are queries that load the DB schema.
152
-
153
- **Note:** Destroy and delete queries are both condensed into the matcher for
154
- `destroy_queries`.
163
+ You can make assertions for the total number of queries executed, but sometimes
164
+ it's more valuable to assert that a particular _type_ of query was executed.
165
+ For example, a particular number of queries to destroy records. There are
166
+ matchers available for that purpose as well!
155
167
 
156
168
  ```ruby
157
169
  expect {}.to execute.exactly(20).queries
@@ -165,6 +177,74 @@ expect {}.to execute.exactly(20).schema_queries
165
177
  expect {}.to execute.exactly(20).transaction_queries
166
178
  ```
167
179
 
180
+ **Note:** Transaction (for example, `ROLLBACK`) queries are not counted in any of these
181
+ categories, nor are queries that load the DB schema.
182
+
183
+ **Note:** Destroy and delete queries are both condensed into the matcher for
184
+ the errorthe error `destroy_queries`.
185
+
186
+ ## Transaction Management
187
+
188
+ Sometimes, it makes sense to monitor whether database transactions were
189
+ successful or not. This is very similar to using `expect{}.to change(SomeModel,
190
+ :count)` in a spec, but nonetheless it can be useful to assert transactions
191
+ themselves. Some assertions are available for this purpose.
192
+
193
+ ```ruby
194
+ expect {}.to execute_a_transaction
195
+ expect {}.to rollback_a_transaction
196
+ expect {}.to roll_back_a_transaction
197
+ expect {}.to commit_a_transaction
198
+ ```
199
+
200
+ A complication to this scheme is that Rails tries not to make unnecessary database
201
+ calls, which means that attempting to save a model that has failing validations
202
+ won't actually attempt to save to the database.
203
+
204
+ ```ruby
205
+ expect {
206
+ MyClass.create!(required_field: nil)
207
+ }.to rollback_a_transaction
208
+ ```
209
+
210
+ This assertion will fail, as `create!` will never make it as far as the
211
+ database. That said, if you manually create a transaction, _and you select
212
+ data within that transaction_, you may assert a rollback.
213
+
214
+ ```ruby
215
+ expect {
216
+ MyClass.first # triggers the transaction
217
+ MyClass.create!(required_field: nil)
218
+ }.to rollback_a_transaction
219
+ ```
220
+
221
+ It you need to make transaction-related assertions of this sort, your best bet
222
+ may be to assert that a commit statement was _not_ issued.
223
+
224
+ ```ruby
225
+ expect do
226
+ MyClass.create!(required_field: nil)
227
+ rescue
228
+ # NOOP
229
+ end.not_to rollback_a_transaction
230
+ ```
231
+
232
+ Note that ActiveRecord will not only roll back the transaction, but also
233
+ re-raise errors. As such, it's necessary in this example to rescue that error,
234
+ otherwise the test would fail simply because the code caused a `raise`.
235
+
236
+ ### Counting Transactions
237
+
238
+ Similar to counting queries, you can quantify the number of transactions you
239
+ expect to succeed / fail. This is probably of limited value in all but some
240
+ very specific cases.
241
+
242
+ ```ruby
243
+ expect {}.to commit_a_transaction.once
244
+ expect {}.to rollback_a_transaction.exactly(5).times
245
+ expect {}.to commit_a_transaction.at_least(5).times
246
+ ```
247
+
168
248
  ## Future Planned Functionality
169
249
 
170
250
  This gem still has lots of future functionality. See below.
@@ -176,10 +256,6 @@ expect {}.to execute.at_least(2).load_queries("Audited::Audit")
176
256
  expect {}.to execute.at_least(2).activerecord_queries
177
257
  expect {}.to execute.at_least(2).hand_rolled_queries
178
258
 
179
- expect {}.not_to rollback_transaction.exactly(5).times
180
- expect {}.not_to commit_transaction.once
181
- expect {}.to run_a_transaction
182
-
183
259
  expect {}.to create.exactly(5).of_type(User)
184
260
  expect {}.to insert.exactly(5).subscription_changes
185
261
  expect {}.to update.exactly(2).of_any_type
@@ -1,16 +1,25 @@
1
1
  module RSpec::ActiveRecord::Expectations
2
2
  module Matchers
3
3
  class QueryCountMatcher
4
- attr_reader :failure_message, :failure_message_when_negated
4
+ attr_reader :collector, :quantifier, :comparison, :query_type
5
5
 
6
6
  def initialize
7
7
  @collector = Collector.new
8
+ @message_builder = MessageBuilder.new(self)
8
9
 
9
10
  @match_method = nil
10
- @comparison = nil
11
+ @quantifier = nil
11
12
  @query_type = nil
12
13
  end
13
14
 
15
+ def failure_message
16
+ @message_builder.failure_message
17
+ end
18
+
19
+ def failure_message_when_negated
20
+ @message_builder.failure_message_when_negated
21
+ end
22
+
14
23
  def supports_block_expectations?
15
24
  true
16
25
  end
@@ -26,39 +35,44 @@ module RSpec::ActiveRecord::Expectations
26
35
  result
27
36
  end
28
37
 
29
- # COMPARISON TYPES
38
+ # QUANTIFIERS
30
39
 
31
40
  def less_than(n)
32
- @comparison = n
33
- @match_method = method(:compare_less_than)
41
+ @quantifier = n
42
+ @comparison = :less_than
43
+ @match_method = -> { actual_count < @quantifier }
34
44
  self
35
45
  end
36
46
  alias_method :fewer_than, :less_than
37
47
 
38
48
  def less_than_or_equal_to(n)
39
- @comparison = n
40
- @match_method = method(:compare_less_than_or_equal_to)
49
+ @quantifier = n
50
+ @comparison = :less_than_or_equal_to
51
+ @match_method = -> { actual_count <= @quantifier }
41
52
  self
42
53
  end
43
54
  alias_method :at_most, :less_than_or_equal_to
44
55
 
45
56
  def greater_than(n)
46
- @comparison = n
47
- @match_method = method(:compare_greater_than)
57
+ @quantifier = n
58
+ @comparison = :greater_than
59
+ @match_method = -> { actual_count > @quantifier }
48
60
  self
49
61
  end
50
62
  alias_method :more_than, :greater_than
51
63
 
52
64
  def greater_than_or_equal_to(n)
53
- @comparison = n
54
- @match_method = method(:compare_greater_than_or_equal_to)
65
+ @quantifier = n
66
+ @comparison = :greater_than_or_equal_to
67
+ @match_method = -> { actual_count >= @quantifier }
55
68
  self
56
69
  end
57
70
  alias_method :at_least, :greater_than_or_equal_to
58
71
 
59
72
  def exactly(n)
60
- @comparison = n
61
- @match_method = method(:compare_exactly)
73
+ @quantifier = n
74
+ @comparison = :exactly
75
+ @match_method = -> { actual_count == @quantifier }
62
76
  self
63
77
  end
64
78
 
@@ -79,62 +93,10 @@ module RSpec::ActiveRecord::Expectations
79
93
  end
80
94
  end
81
95
 
82
- # TODO singularize everything
83
- alias_method :query, :queries
84
-
85
- private
86
-
87
- def humanized_query_type
88
- @query_type.to_s.gsub("_", " ")
89
- end
90
-
91
- # MATCHERS
92
-
93
- def compare_less_than
94
- count = @collector.queries_of_type(@query_type)
95
-
96
- @failure_message = "expected block to execute fewer than #{@comparison} #{humanized_query_type}, but it executed #{count}"
97
- @failure_message_when_negated = "expected block not to execute fewer than #{@comparison} #{humanized_query_type}, but it executed #{count}"
98
-
99
- count < @comparison
100
- end
101
-
102
- def compare_less_than_or_equal_to
103
- count = @collector.queries_of_type(@query_type)
104
-
105
- # boy this negated operator is confusing. don't use that plz.
106
- @failure_message = "expected block to execute at most #{@comparison} #{humanized_query_type}, but it executed #{count}"
107
- @failure_message_when_negated = "expected block not to execute any less than #{@comparison} #{humanized_query_type}, but it executed #{count}"
108
-
109
- count <= @comparison
110
- end
111
-
112
- def compare_greater_than
113
- count = @collector.queries_of_type(@query_type)
114
-
115
- @failure_message = "expected block to execute greater than #{@comparison} #{humanized_query_type}, but it executed #{count}"
116
- @failure_message_when_negated = "expected block not to execute greater than #{@comparison} #{humanized_query_type}, but it executed #{count}"
117
-
118
- count > @comparison
119
- end
120
-
121
- def compare_greater_than_or_equal_to
122
- count = @collector.queries_of_type(@query_type)
123
-
124
- # see above, negating this matcher is so confusing.
125
- @failure_message = "expected block to execute at least #{@comparison} #{humanized_query_type}, but it executed #{count}"
126
- @failure_message_when_negated = "expected block not to execute any more than #{@comparison} #{humanized_query_type}, but it executed #{count}"
127
-
128
- count >= @comparison
129
- end
130
-
131
- def compare_exactly
132
- count = @collector.queries_of_type(@query_type)
133
-
134
- @failure_message = "expected block to execute exactly #{@comparison} #{humanized_query_type}, but it executed #{count}"
135
- @failure_message_when_negated = "expected block not to execute exactly #{@comparison} #{humanized_query_type}, but it executed #{count}"
96
+ # helper for message builder
136
97
 
137
- count == @comparison
98
+ def actual_count
99
+ @collector.queries_of_type(@query_type)
138
100
  end
139
101
  end
140
102
  end
@@ -0,0 +1,99 @@
1
+ module RSpec::ActiveRecord::Expectations
2
+ module Matchers
3
+ class TransactionMatcher
4
+ attr_reader :collector, :quantifier, :comparison, :query_type
5
+
6
+ def initialize(transaction_type)
7
+ @collector = Collector.new
8
+ @query_type = transaction_type
9
+ @message_builder = MessageBuilder.new(self)
10
+
11
+ self.at_least(1)
12
+ end
13
+
14
+ def failure_message
15
+ @message_builder.failure_message
16
+ end
17
+
18
+ def failure_message_when_negated
19
+ @message_builder.failure_message_when_negated
20
+ end
21
+
22
+ def supports_block_expectations?
23
+ true
24
+ end
25
+
26
+ def matches?(block)
27
+ raise NoComparisonError unless @match_method
28
+
29
+ block.call
30
+ result = @match_method.call
31
+ @collector.finalize
32
+
33
+ result
34
+ end
35
+
36
+ # QUANTIFIERS
37
+
38
+ def less_than(n)
39
+ @quantifier = n
40
+ @comparison = :less_than
41
+ @match_method = -> { actual_count < @quantifier }
42
+ self
43
+ end
44
+ alias_method :fewer_than, :less_than
45
+
46
+ def less_than_or_equal_to(n)
47
+ @quantifier = n
48
+ @comparison = :less_than_or_equal_to
49
+ @match_method = -> { actual_count <= @quantifier }
50
+ self
51
+ end
52
+ alias_method :at_most, :less_than_or_equal_to
53
+
54
+ def greater_than(n)
55
+ @quantifier = n
56
+ @comparison = :greater_than
57
+ @match_method = -> { actual_count > @quantifier }
58
+ self
59
+ end
60
+ alias_method :more_than, :greater_than
61
+
62
+ def greater_than_or_equal_to(n)
63
+ @quantifier = n
64
+ @comparison = :greater_than_or_equal_to
65
+ @match_method = -> { actual_count >= @quantifier }
66
+ self
67
+ end
68
+ alias_method :at_least, :greater_than_or_equal_to
69
+
70
+ def exactly(n)
71
+ @quantifier = n
72
+ @comparison = :exactly
73
+ @match_method = -> { actual_count == @quantifier }
74
+ self
75
+ end
76
+
77
+ def once
78
+ exactly(1).time
79
+ end
80
+
81
+ def twice
82
+ exactly(2).times
83
+ end
84
+
85
+ def thrice # hehe
86
+ exactly(3).times
87
+ end
88
+
89
+ def times
90
+ self # NOOP
91
+ end
92
+ alias_method :time, :times
93
+
94
+ def actual_count
95
+ @collector.queries_of_type(@query_type)
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,160 @@
1
+ module RSpec::ActiveRecord::Expectations
2
+ class MessageBuilder
3
+ attr_reader :matcher, :phrase_builder
4
+
5
+ def initialize(matcher)
6
+ @matcher = matcher
7
+ @phrase_builder = case matcher
8
+ when Matchers::QueryCountMatcher then QueryPhrases.new(matcher)
9
+ when Matchers::TransactionMatcher then TransactionPhrases.new(matcher)
10
+ else raise ArgumentError
11
+ end
12
+ end
13
+
14
+ def failure_message
15
+ "expected block to #{phrase_builder.prefix}, but it #{phrase_builder.suffix}"
16
+ end
17
+
18
+ def failure_message_when_negated
19
+ "expected block not to #{phrase_builder.prefix}, but it #{phrase_builder.negative_suffix}"
20
+ end
21
+
22
+ private
23
+
24
+ # TODO: expect {}.not_to execute.less_than_or_equal_to(3).insert_queries
25
+ # expected block not to execute at most 3 insert queries, but it executed 0
26
+
27
+
28
+ class QueryPhrases
29
+ attr_reader :matcher
30
+
31
+ def initialize(matcher)
32
+ @matcher = matcher
33
+ end
34
+
35
+ def prefix
36
+ "execute #{comparison_phrase} #{query_type_name}"
37
+ end
38
+
39
+ def suffix
40
+ if matcher.actual_count == 0
41
+ "didn't execute any"
42
+ elsif matcher.actual_count == 1
43
+ "executed one"
44
+ else
45
+ "executed #{matcher.actual_count}"
46
+ end
47
+ end
48
+
49
+ # using positive suffix from above can cause double negatives
50
+ def negative_suffix
51
+ if matcher.comparison == :exactly
52
+ "did so"
53
+ elsif matcher.actual_count == 1
54
+ "executed one"
55
+ else
56
+ "executed #{matcher.actual_count}"
57
+ end
58
+ end
59
+
60
+ private
61
+
62
+ def query_type_name
63
+ if matcher.quantifier == 1
64
+ matcher.query_type.to_s.gsub("_", " ").gsub("queries", "query")
65
+ else
66
+ matcher.query_type.to_s.gsub("_", " ")
67
+ end
68
+ end
69
+
70
+ def comparison_phrase
71
+ quant = if matcher.quantifier == 1 && matcher.comparison == :exactly
72
+ "a"
73
+ elsif matcher.quantifier == 1
74
+ "one"
75
+ else
76
+ matcher.quantifier
77
+ end
78
+
79
+ case matcher.comparison
80
+ when :exactly then quant
81
+ when :greater_than then "more than #{quant}"
82
+ when :greater_than_or_equal_to then "at least #{quant}"
83
+ when :less_than then "less than #{quant}"
84
+ when :less_than_or_equal_to then "at most #{quant}"
85
+ else raise ArgumentError, "unsupported comparison matcher #{matcher.comparison}"
86
+ end
87
+ end
88
+ end
89
+
90
+ class TransactionPhrases
91
+ attr_reader :matcher
92
+
93
+ def initialize(matcher)
94
+ @matcher = matcher
95
+ end
96
+
97
+ def prefix
98
+ nouns = matcher.quantifier == 1 ? "transaction" : "transactions"
99
+ verb = case matcher.query_type
100
+ when :transaction_queries then "execute"
101
+ when :rollback_queries then "roll back"
102
+ when :commit_queries then "commit"
103
+ end
104
+
105
+ "#{verb} #{comparison} #{nouns}"
106
+ end
107
+
108
+ def suffix
109
+ singular_verb = case matcher.query_type
110
+ when :transaction_queries then "execute"
111
+ when :rollback_queries then "roll back"
112
+ when :commit_queries then "commit"
113
+ end
114
+
115
+ plural_verb = case matcher.query_type
116
+ when :transaction_queries then "executed"
117
+ when :rollback_queries then "rolled back"
118
+ when :commit_queries then "committed"
119
+ end
120
+
121
+ if matcher.actual_count == 0
122
+ "didn't #{singular_verb} any"
123
+ elsif matcher.actual_count == 1
124
+ "#{plural_verb} one"
125
+ else
126
+ "#{plural_verb} #{matcher.actual_count}"
127
+ end
128
+ end
129
+
130
+ def negative_suffix
131
+ if matcher.comparison == :exactly
132
+ "did so"
133
+ else
134
+ suffix
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ def comparison
141
+ quant = if matcher.quantifier == 1 && matcher.comparison == :exactly
142
+ "a"
143
+ elsif matcher.quantifier == 1
144
+ "one"
145
+ else
146
+ matcher.quantifier
147
+ end
148
+
149
+ case matcher.comparison
150
+ when :exactly then quant
151
+ when :greater_than then "more than #{quant}"
152
+ when :greater_than_or_equal_to then "at least #{quant}"
153
+ when :less_than then "less than #{quant}"
154
+ when :less_than_or_equal_to then "at most #{quant}"
155
+ else raise ArgumentError, "unsupported comparison matcher #{matcher.comparison}"
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
@@ -1,13 +1,18 @@
1
1
  module RSpec::ActiveRecord::Expectations
2
2
  class QueryInspector
3
3
  def self.valid_query_types
4
- [:queries, :schema_queries, :transaction_queries, :insert_queries,
5
- :load_queries, :destroy_queries, :exists_queries]
4
+ [:queries, :schema_queries, :insert_queries, :load_queries,
5
+ :destroy_queries, :exists_queries,
6
+ :transaction_queries, :commit_queries, :rollback_queries]
6
7
  end
7
8
 
8
9
  def categorize(query)
9
10
  if query[:name] == "SCHEMA"
10
11
  [:schema_queries]
12
+ elsif query[:sql] =~ /^commit/i
13
+ [:commit_queries]
14
+ elsif query[:sql] =~ /^rollback/i
15
+ [:rollback_queries]
11
16
  elsif query[:name] == "TRANSACTION"
12
17
  [:transaction_queries]
13
18
  elsif query[:name] =~ /Create$/
@@ -18,7 +23,7 @@ module RSpec::ActiveRecord::Expectations
18
23
  [:queries, :destroy_queries]
19
24
  elsif query[:name] =~ /Delete All$/
20
25
  [:queries, :destroy_queries]
21
- elsif query[:name] =~ /Exists\?$/
26
+ elsif query[:name] =~ /Exists\??$/
22
27
  [:queries, :exists_queries]
23
28
  else
24
29
  [:queries]
@@ -8,6 +8,22 @@ module RSpec
8
8
  def repeatedly_load(klass)
9
9
  Matchers::LoadMatcher.new(klass)
10
10
  end
11
+
12
+ def execute_a_transaction
13
+ Matchers::TransactionMatcher.new(:transaction_queries)
14
+ end
15
+
16
+ def rollback_a_transaction
17
+ Matchers::TransactionMatcher.new(:rollback_queries)
18
+ end
19
+
20
+ def roll_back_a_transaction
21
+ Matchers::TransactionMatcher.new(:rollback_queries)
22
+ end
23
+
24
+ def commit_a_transaction
25
+ Matchers::TransactionMatcher.new(:commit_queries)
26
+ end
11
27
  end
12
28
  end
13
29
  end
@@ -15,5 +31,7 @@ end
15
31
  require_relative 'expectations/errors'
16
32
  require_relative 'expectations/query_inspector'
17
33
  require_relative 'expectations/collector'
34
+ require_relative 'expectations/message_builder'
18
35
  require_relative 'expectations/matchers/query_count_matcher'
19
36
  require_relative 'expectations/matchers/load_matcher'
37
+ require_relative 'expectations/matchers/transaction_matcher'
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = "rspec-activerecord-expectations"
3
- spec.version = '1.3.0'
3
+ spec.version = '2.3.0'
4
4
  spec.authors = ["Joseph Mastey"]
5
5
  spec.email = ["hello@joemastey.com"]
6
6
 
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
  spec.description = %q{Adds new matchers to rspec to help you test whether your code is executing an unreasonable number of queries.}
9
9
  spec.homepage = "https://github.com/jmmastey/rspec-activerecord-expectations"
10
10
  spec.license = "MIT"
11
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
11
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
12
12
 
13
13
  spec.metadata["homepage_uri"] = spec.homepage
14
14
  spec.metadata["source_code_uri"] = spec.homepage
@@ -23,7 +23,12 @@ Gem::Specification.new do |spec|
23
23
  spec.require_paths = ["lib"]
24
24
 
25
25
  spec.add_dependency "activerecord", ">= 5.0.0", "< 7.1.0"
26
- spec.add_dependency "sqlite3", "~> 1.0"
26
+
27
+ if RUBY_PLATFORM == 'java'
28
+ spec.add_development_dependency "activerecord-jdbcsqlite3-adapter", '>= 60'
29
+ else
30
+ spec.add_development_dependency "sqlite3", "~> 1.0"
31
+ end
27
32
 
28
33
  spec.add_development_dependency "pry", "~> 0.0"
29
34
  spec.add_development_dependency "appraisal", "~> 2"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-activerecord-expectations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joseph Mastey
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-04 00:00:00.000000000 Z
11
+ date: 2022-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -37,7 +37,7 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.0'
40
- type: :runtime
40
+ type: :development
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
@@ -104,6 +104,8 @@ files:
104
104
  - lib/rspec/activerecord/expectations/errors.rb
105
105
  - lib/rspec/activerecord/expectations/matchers/load_matcher.rb
106
106
  - lib/rspec/activerecord/expectations/matchers/query_count_matcher.rb
107
+ - lib/rspec/activerecord/expectations/matchers/transaction_matcher.rb
108
+ - lib/rspec/activerecord/expectations/message_builder.rb
107
109
  - lib/rspec/activerecord/expectations/query_inspector.rb
108
110
  - rspec-activerecord-expectations.gemspec
109
111
  homepage: https://github.com/jmmastey/rspec-activerecord-expectations
@@ -121,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
121
123
  requirements:
122
124
  - - ">="
123
125
  - !ruby/object:Gem::Version
124
- version: 2.3.0
126
+ version: 2.5.0
125
127
  required_rubygems_version: !ruby/object:Gem::Requirement
126
128
  requirements:
127
129
  - - ">="