Skip to content

Commit 465a183

Browse files
committed
Fix to handle queries where you don't always expect a result
1 parent db6298a commit 465a183

File tree

3 files changed

+64
-25
lines changed

3 files changed

+64
-25
lines changed

lib/ar_query_matchers.rb

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ class FieldModels
249249
end
250250

251251
def failure_text
252-
expectation_failed_message('query_by', true)
252+
expectation_failed_message('query_by', show_values: true)
253253
end
254254
end
255255

@@ -267,15 +267,41 @@ def failure_text
267267
include MatcherErrors
268268

269269
# Convert the map of expected values to a Hash of all arrays
270-
temp_expected = expected.transform_values { |v| v.is_a?(Array) ? v : [v] }
270+
expected = expected.transform_values { |v| v.is_a?(Array) ? v : [v] }
271271

272272
match do |block|
273273
@query_stats = Queries::FieldCounter.instrument(&block)
274-
temp_expected == @query_stats.query_values.select { |k, _| temp_expected.keys.include?(k) }
274+
expected == @query_stats.query_values.select { |k, _| expected.keys.include?(k) }
275275
end
276276

277277
def failure_text
278-
expectation_failed_message('query_by', true, true)
278+
expectation_failed_message('query_by', show_values: true, subset: true)
279+
end
280+
end
281+
282+
# The following will succeed:
283+
#
284+
# expect {
285+
# WcRiskClass.last.update_attributes(id: 9999)
286+
# WcRiskClass.last.update_attributes(id: 1234)
287+
# }.to query_by_field_at_least_ignore_notfound(
288+
# 'id' => 6666,
289+
# )
290+
#
291+
RSpec::Matchers.define(:query_by_field_at_least_ignore_notfound) do |expected = {}|
292+
include MatcherConfiguration
293+
include MatcherErrors
294+
295+
# Convert the map of expected values to a Hash of all arrays
296+
expected = expected.transform_values { |v| v.is_a?(Array) ? v : [v] }
297+
298+
match do |block|
299+
@query_stats = Queries::FieldCounter.instrument(&block)
300+
expected.select { |k, _| @query_stats.query_values.keys.include?(k) } == @query_stats.query_values.select { |k, _| expected.keys.include?(k) }
301+
end
302+
303+
def failure_text
304+
expectation_failed_message('query_by', show_values: true, subset: true, ignore_missing: true)
279305
end
280306
end
281307
end
@@ -301,24 +327,33 @@ def supports_block_expectations?
301327
end
302328

303329
module MatcherErrors
304-
def create_display_string(max_key_length, key, left, right, values_only)
305-
"#{key.rjust(max_key_length, ' ')} – expected: #{left}, got: #{right} #{"(#{'+' if right > left}#{right - left})" if values_only}"
330+
def create_display_string(max_key_length, key, left, right, show_values)
331+
diff_array = right - left
332+
if diff_array.empty?
333+
diff_array = left - right
334+
end
335+
"#{key.rjust(max_key_length, ' ')} – expected: #{left}, got: #{right} #{"(difference: #{diff_array})" if show_values}"
306336
end
307337

308338
# Show the difference between expected and actual values with one value
309339
# per line. This is done by hand because as of this writing the author
310340
# doesn't understand how RSpec does its nice hash diff printing.
311-
def difference(keys, values_only: false)
341+
def difference(keys, show_values: false)
312342
max_key_length = keys.reduce(0) { |max, key| [max, key.size].max }
313343

344+
_expected = expected
345+
if show_values
346+
_expected = expected.transform_values { |v| v.is_a?(Array) ? v : [v] }
347+
end
348+
314349
keys.map do |key|
315-
left = expected.fetch(key, values_only ? [] : 0)
316-
left = [left] unless left.is_a?(Array) || !values_only
350+
left = _expected.fetch(key, show_values ? [] : 0)
351+
left = [left] unless left.is_a?(Array) || show_values
317352

318353
right = @query_stats.queries.fetch(key, {})
319-
right = values_only ? right.fetch(:values, []) : right.fetch(:count, 0)
354+
right = show_values ? right.fetch(:values, []) : right.fetch(:count, 0)
320355

321-
create_display_string(max_key_length, key, left, right, values_only)
356+
create_display_string(max_key_length, key, left, right, show_values)
322357
end.compact
323358
end
324359

@@ -341,29 +376,32 @@ def no_queries_fail_message(crud_operation)
341376
"Expected ActiveRecord to not #{crud_operation} any records, got #{@query_stats.query_counts}\n\nWhere unexpected queries came from:\n\n#{source_lines(@query_stats.query_counts.keys).join("\n")}"
342377
end
343378

344-
def reject_record(subset, expected, key)
345-
if subset && !expected[key].nil?
346-
(expected[key] - @query_stats.queries[key][:values]).empty?
379+
def reject_record(subset, current_expected, key, ignore_missing)
380+
if subset && !current_expected[key].nil?
381+
if ignore_missing
382+
@query_stats.queries[key][:values].empty? || (current_expected[key] - @query_stats.queries[key][:values]).empty?
383+
else
384+
(current_expected[key] - @query_stats.queries[key][:values]).empty?
385+
end
347386
else
348-
@query_stats.queries[key][:values] == expected[key]
387+
ignore_missing || @query_stats.queries[key][:values] == current_expected[key]
349388
end
350389
end
351390

352-
def filter_model_names(subset, values_only)
391+
def filter_model_names(subset, show_values, ignore_missing)
353392
all_model_names = expected.keys + @query_stats.queries.keys
354-
if values_only
355-
all_model_names.reject { |key| reject_record(subset, expected, key) }.uniq
393+
if show_values
394+
_expected = expected.transform_values { |v| v.is_a?(Array) ? v : [v] }
395+
all_model_names.reject { |key| reject_record(subset, _expected, key, ignore_missing) }.uniq
356396
else
357397
all_model_names.reject { |key| expected[key] == @query_stats.queries[key][:count] }.uniq
358398
end
359399
end
360400

361-
def expectation_failed_message(crud_operation, values_only: false, subset: false)
362-
expected.transform_values { |v| v.is_a(Array) ? v : [v] } if values_only
363-
364-
model_names_with_wrong_count = filter_model_names(subset, values_only)
365-
"Expected ActiveRecord to #{crud_operation} #{expected}, got #{values_only ? @query_stats.query_values : @query_stats.query_counts}\n"\
366-
"Expectations that differed:\n#{difference(model_names_with_wrong_count, values_only: values_only).join("\n")}\n\nWhere unexpected queries came from:\n\n#{source_lines(model_names_with_wrong_count).join("\n")}"
401+
def expectation_failed_message(crud_operation, show_values: false, subset: false, ignore_missing: false)
402+
model_names_with_wrong_count = filter_model_names(subset, show_values, ignore_missing)
403+
"Expected ActiveRecord to #{crud_operation} #{expected}, got #{show_values ? @query_stats.query_values : @query_stats.query_counts}\n"\
404+
"Expectations that differed:\n#{difference(model_names_with_wrong_count, show_values: show_values).join("\n")}\n\nWhere unexpected queries came from:\n\n#{source_lines(model_names_with_wrong_count).join("\n")}"
367405
end
368406
end
369407
end

lib/ar_query_matchers/queries/field_counter.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ def filter_map(_name, sql)
4040
# - Anything with `, field,` in an INSERT (we need to check the values)
4141
select_field_query = sql.match(MODEL_FIELDS_PATTERN)
4242
# debugger if sql.match(/INSERT/)
43+
# TODO: MODEL_FIELDS_IN_PATTERN and MODEL_INSERT_PATTERN need to be handled
4344

4445
FieldName.new(select_field_query[:field_name], cleanup(select_field_query[:field_value])) if select_field_query
4546
end

lib/ar_query_matchers/queries/query_counter.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def add_to_query(queries, payload, model_obj, finish, start)
8787
comment = payload[:sql].match(MARGINALIA_SQL_COMMENT_PATTERN)
8888
queries[model_name][:lines] << comment[:line] if comment
8989
queries[model_name][:count] += 1
90-
queries[model_name][:values].append(model_obj&.model_value) if model_obj.respond_to?(:model_value)
90+
queries[model_name][:values].append(model_obj&.model_value) if model_obj.respond_to?(:model_value) and !queries[model_name][:values].include?(model_obj&.model_value)
9191
queries[model_name][:time] += (finish - start).round(6) # Round to microseconds
9292
end
9393

0 commit comments

Comments
 (0)