expect_query 0.1.5 → 0.1.6

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: d3b8b4ed6d2b74521cc8ba8b8c3d916cccb41270c4c41823bc24ece67d51e2ba
4
- data.tar.gz: 9dad000453c5308bde228f74313820d502efcc8ddab0ea4d64012d1c2cfb8a9d
3
+ metadata.gz: 53cd97190ff208c5ddf0f7ea4b2defad56c2651766e25b2faa70e2d495c30774
4
+ data.tar.gz: da83d3a86398c231e636a0b7ba7938ed16f690a43a9085d7fc5fd6857ed6d34f
5
5
  SHA512:
6
- metadata.gz: 74308e0df458e741d4cb8af5b079b164858ab846bfca7d4253fb4c1b5018c8f5aa254b1085d23ec92f44a06fc019b75d20e75837f2df52af7e688d95f9955f5d
7
- data.tar.gz: bb3009cbc2d35194b6dc4e30aa1807ed70b313d56c8767303eec51e44757224e767f88d171824ad244a2ffb859ec2d3f86c6617ec0d2ce2ca6b739384ec4a94b
6
+ metadata.gz: 60cb5e8d7672d3124c2a21e4f22eebbe9b29aa3e0d214cb921b568af3f21b5101716b3af6773056734a44c97331b7a60163b579a8f0dfbc75d92e43167753b31
7
+ data.tar.gz: aeb1ba386cc83d8bb8ace523657ec557fef0a598c4f579631262fb3eb9a347a927cdeebc09d22c3677d49bd900d69c5845901697ac5a655009fe4e2f601e081c
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ExpectQuery
2
2
 
3
- `expect_query` provides Rails 8.0+ compatible assertions and matchers for counting SQL queries and Cache operations. It supports both RSpec and Minitest.
3
+ `expect_query` provides Rails 8.0+ compatible assertions and matchers for counting SQL queries and Cache operations. It supports both RSpec and Minitest, offering powerful tools to prevent N+1 queries and ensure efficient caching.
4
4
 
5
5
  ## Installation
6
6
 
@@ -22,55 +22,79 @@ require "expect_query/rspec"
22
22
 
23
23
  #### SQL Assertions
24
24
 
25
+ Verify the number of SQL queries executed.
26
+
25
27
  ```ruby
28
+ # Exact count
26
29
  expect {
27
30
  User.first
28
31
  }.to make_sql_queries(1)
29
32
 
33
+ # At most (useful for reducing N+1 without being brittle)
34
+ expect {
35
+ User.all.each(&:profile)
36
+ }.to make_sql_queries(at_most: 3)
37
+
38
+ # Filter by regex matching SQL
30
39
  expect {
31
40
  User.create(name: "Foo")
32
41
  }.to make_sql_queries(1, matching: /INSERT/)
33
-
34
- expect {
35
- # ...
36
- }.to make_sql_queries(at_most: 3)
37
42
  ```
38
43
 
39
44
  #### Cache Assertions
40
45
 
46
+ Verify cache operations (read, write, fetch, etc.).
47
+
41
48
  ```ruby
49
+ # Count specific operations
42
50
  expect {
43
51
  Rails.cache.read("foo")
44
52
  Rails.cache.write("bar", 1)
45
53
  }.to perform_cache_operations(read: 1, write: 1)
46
54
 
55
+ # Count total operations
47
56
  expect {
48
57
  Rails.cache.fetch("foo") { 1 }
49
- }.to perform_cache_operations(total: 2) # read + write (if miss)
58
+ }.to perform_cache_operations(total: 2) # 1 read (miss) + 1 write
59
+
60
+ # Filter by key matching regex
61
+ expect {
62
+ Rails.cache.read("user:1")
63
+ Rails.cache.read("post:1")
64
+ }.to perform_cache_operations(read: 2, matching: /user|post/)
65
+ ```
66
+
67
+ #### Chaining Matchers
68
+
69
+ You can verify both SQL and Cache operations in a single block using RSpec's compound matchers.
70
+
71
+ ```ruby
72
+ expect {
73
+ User.create(name: "Alice")
74
+ Rails.cache.write("user:alice", "data")
75
+ }.to make_sql_queries(1).and perform_cache_operations(write: 1)
50
76
  ```
51
77
 
52
78
  ### Multiple Cache Stores
53
79
 
54
- By default, assertions use `Rails.cache` (if available). You can specify a different store or multiple stores using `store` or `stores` options.
80
+ By default, assertions use `Rails.cache`. You can specify a different store or multiple stores using `store` or `stores` options.
55
81
 
56
82
  ```ruby
57
- # RSpec
83
+ # Check specific store
58
84
  expect {
59
85
  my_cache.write("foo", 1)
60
86
  }.to perform_cache_operations(write: 1, store: my_cache)
61
87
 
88
+ # Check multiple stores
62
89
  expect {
63
90
  cache1.write("a", 1)
64
91
  cache2.write("b", 1)
65
92
  }.to perform_cache_operations(total: 2, stores: [cache1, cache2])
66
93
  ```
67
94
 
68
- ```ruby
69
- # Minitest
70
- assert_cache_operations(write: 1, store: my_cache) do
71
- my_cache.write("foo", 1)
72
- end
73
- ```
95
+ ### Minitest
96
+
97
+ Add this to your `test_helper.rb`:
74
98
 
75
99
  ```ruby
76
100
  require "expect_query/minitest"
@@ -79,18 +103,60 @@ require "expect_query/minitest"
79
103
  #### SQL Assertions
80
104
 
81
105
  ```ruby
106
+ # Exact count
82
107
  assert_sql_queries(1) do
83
108
  User.first
84
109
  end
110
+
111
+ # At most
112
+ assert_sql_queries(at_most: 3) do
113
+ User.all.map(&:name)
114
+ end
115
+
116
+ # Regex matching
117
+ assert_sql_queries(1, matching: /INSERT/) do
118
+ User.create(name: "Foo")
119
+ end
85
120
  ```
86
121
 
87
122
  #### Cache Assertions
88
123
 
89
124
  ```ruby
125
+ # Specific operations
90
126
  assert_cache_operations(read: 1, write: 1) do
91
127
  Rails.cache.read("foo")
92
128
  Rails.cache.write("bar", 1)
93
129
  end
130
+
131
+ # Total operations
132
+ assert_cache_operations(total: 2) do
133
+ Rails.cache.fetch("foo") { 1 }
134
+ end
135
+
136
+ # Specific store
137
+ assert_cache_operations(write: 1, store: my_cache) do
138
+ my_cache.write("foo", 1)
139
+ end
140
+ ```
141
+
142
+ #### Combined Assertions (IO Operations)
143
+
144
+ Use `assert_io_operations` to verify both SQL and Cache operations in a single block.
145
+
146
+ ```ruby
147
+ assert_io_operations(sql: { count: 1 }, cache: { write: 1 }) do
148
+ User.create(name: "Alice")
149
+ Rails.cache.write("user:alice", "data")
150
+ end
151
+
152
+ # With options
153
+ assert_io_operations(
154
+ sql: { count: 1, matching: /INSERT/ },
155
+ cache: { read: 1, matching: /user:/ }
156
+ ) do
157
+ User.create(name: "Bob")
158
+ Rails.cache.read("user:bob")
159
+ end
94
160
  ```
95
161
 
96
162
  ## Failure Messages
@@ -125,4 +191,3 @@ bundle exec rake
125
191
  ```
126
192
 
127
193
  This runs both RSpec and Minitest suites.
128
-
@@ -66,6 +66,36 @@ module ExpectQuery
66
66
  assert_equal expected, actual, "Expected #{expected} #{op} cache operations, but got #{actual}#{debug_info}"
67
67
  end
68
68
  end
69
+
70
+ def assert_io_operations(sql: nil, cache: nil, &block)
71
+ if sql
72
+ sql_args = sql.dup
73
+ count = sql_args.delete(:count)
74
+ matching = sql_args.delete(:matching)
75
+ at_most = sql_args.delete(:at_most)
76
+
77
+ # If unknown keys remain in sql_args, we could warn, but for now we verify strictness if possible?
78
+ # assert_sql_queries doesn't take **options, so we can't pass them.
79
+
80
+ inner_block = if cache
81
+ proc { assert_io_operations(cache: cache, &block) }
82
+ else
83
+ block
84
+ end
85
+
86
+ assert_sql_queries(count, matching: matching, at_most: at_most, &inner_block)
87
+ elsif cache
88
+ cache_args = cache.dup
89
+ count = cache_args.delete(:count)
90
+ matching = cache_args.delete(:matching)
91
+ store = cache_args.delete(:store)
92
+ stores = cache_args.delete(:stores)
93
+
94
+ assert_cache_operations(count, matching: matching, store: store, stores: stores, **cache_args, &block)
95
+ else
96
+ block.call
97
+ end
98
+ end
69
99
  end
70
100
  end
71
101
 
@@ -25,22 +25,22 @@ module ExpectQuery
25
25
  true
26
26
  end
27
27
 
28
- def matches?(block)
29
- @counter = ExpectQuery::SqlCounter.new(matching: @matching)
30
- ActiveSupport::Notifications.subscribed(@counter, "sql.active_record") do
31
- block.call
32
- end
33
-
34
- @actual = @counter.count
35
-
36
- if @at_most
37
- @actual <= @at_most
38
- elsif @expected
39
- @actual == @expected
40
- else
41
- @actual > 0 # generic "makes queries"
42
- end
28
+ def matches?(block)
29
+ @counter = ExpectQuery::SqlCounter.new(matching: @matching)
30
+ ActiveSupport::Notifications.subscribed(@counter, "sql.active_record") do
31
+ block.call
43
32
  end
33
+
34
+ @actual = @counter.count
35
+
36
+ if @at_most
37
+ @actual <= @at_most
38
+ elsif @expected
39
+ @actual == @expected
40
+ else
41
+ @actual > 0 # generic "makes queries"
42
+ end
43
+ end
44
44
 
45
45
  def failure_message
46
46
  msg = "expected to make "
@@ -86,12 +86,6 @@ module ExpectQuery
86
86
  store_ids << store.object_id
87
87
  end
88
88
 
89
- # If no stores are defined (e.g. no Rails.cache and no store passed), we can't strict filter by ID.
90
- # But we probably should pass empty array if we intended to track but found nothing?
91
- # Or if store_ids is empty, maybe we track ALL?
92
- # User said: "By default use Rails.cache". If Rails.cache is missing, maybe we should warn or track nothing?
93
- # Let's pass nil if we want to track ALL, but if we defaulted to Rails.cache and it's there, we pass it.
94
- # If user explicitly passed empty list?
95
89
  pass_ids = store_ids.empty? ? nil : store_ids
96
90
 
97
91
  @counter = ExpectQuery::CacheCounter.new(matching: @matching, store_ids: pass_ids)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ExpectQuery
4
- VERSION = "0.1.5"
4
+ VERSION = "0.1.6"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: expect_query
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jade Ornelas