33require 'ar_query_matchers/queries/create_counter'
44require 'ar_query_matchers/queries/load_counter'
55require 'ar_query_matchers/queries/update_counter'
6+ require 'ar_query_matchers/queries/field_counter'
67require 'bigdecimal'
78
89module ArQueryMatchers
@@ -225,6 +226,86 @@ def failure_text
225226 end
226227 end
227228
229+ class FieldModels
230+ # The following will succeed:
231+ #
232+ # expect {
233+ # WcRiskClass.last.update_attributes(id: 9999)
234+ # WcRiskClass.last.update_attributes(id: 1234)
235+ # }.to query_by_field(
236+ # 'id' => [9999, 1234],
237+ # )
238+ #
239+ RSpec ::Matchers . define ( :query_by_field ) do |expected = { } |
240+ include MatcherConfiguration
241+ include MatcherErrors
242+
243+ # Convert the map of expected values to a Hash of all arrays
244+ expected = expected . transform_values { |v | v . is_a ( Array ) ? v : [ v ] }
245+
246+ match do |block |
247+ @query_stats = Queries ::FieldCounter . instrument ( &block )
248+ expected == @query_stats . query_values
249+ end
250+
251+ def failure_text
252+ expectation_failed_message ( 'query_by' , show_values : true )
253+ end
254+ end
255+
256+ # The following will succeed:
257+ #
258+ # expect {
259+ # WcRiskClass.last.update_attributes(id: 9999)
260+ # WcRiskClass.last.update_attributes(id: 1234)
261+ # }.to query_by_field_at_least(
262+ # 'id' => 9999,
263+ # )
264+ #
265+ RSpec ::Matchers . define ( :query_by_field_at_least ) do |expected = { } |
266+ include MatcherConfiguration
267+ include MatcherErrors
268+
269+ # Convert the map of expected values to a Hash of all arrays
270+ expected = expected . transform_values { |v | v . is_a? ( Array ) ? v : [ v ] }
271+
272+ match do |block |
273+ @query_stats = Queries ::FieldCounter . instrument ( &block )
274+ expected == @query_stats . query_values . select { |k , _ | expected . keys . include? ( k ) }
275+ end
276+
277+ def failure_text
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 )
305+ end
306+ end
307+ end
308+
228309 # Shared methods that are included in the matchers.
229310 # They configure it and ensure we get consistent and human readable error messages
230311 module MatcherConfiguration
@@ -246,22 +327,37 @@ def supports_block_expectations?
246327 end
247328
248329 module MatcherErrors
249- # Show the difference between expected and actual values with one value
250- # per line. This is done by hand because as of this writing the author
251- # doesn't understand how RSpec does its nice hash diff printing.
252- def difference ( keys )
253- max_key_length = keys . reduce ( 0 ) { | max , key | [ max , key . size ] . max }
330+ def create_display_string ( max_key_length , key , left , right , show_values )
331+ diff_array = right - left
332+ diff_array = left - right if diff_array . empty?
333+ " #{ key . rjust ( max_key_length , ' ' ) } – expected: #{ left } , got: #{ right } #{ "(difference: #{ diff_array } )" if show_values } "
334+ end
254335
336+ def loop_through_keys ( keys , transformed_expected , show_values )
337+ max_key_length = keys . reduce ( 0 ) { |max , key | [ max , key . size ] . max }
255338 keys . map do |key |
256- left = expected . fetch ( key , 0 )
257- right = @query_stats . queries . fetch ( key , { } ) . fetch ( :count , 0 )
339+ left = transformed_expected . fetch ( key , show_values ? [ ] : 0 )
340+ left = [ left ] unless left . is_a? ( Array ) || show_values
258341
259- diff = "#{ '+' if right > left } #{ right - left } "
342+ right = @query_stats . queries . fetch ( key , { } )
343+ right = show_values ? right . fetch ( :values , [ ] ) : right . fetch ( :count , 0 )
260344
261- " #{ key . rjust ( max_key_length , ' ' ) } – expected: #{ left } , got: #{ right } ( #{ diff } )"
345+ create_display_string ( max_key_length , key , left , right , show_values )
262346 end . compact
263347 end
264348
349+ # Show the difference between expected and actual values with one value
350+ # per line. This is done by hand because as of this writing the author
351+ # doesn't understand how RSpec does its nice hash diff printing.
352+ def difference ( keys , show_values : false )
353+ transformed_expected = expected
354+ if show_values
355+ transformed_expected = expected . transform_values { |v | v . is_a? ( Array ) ? v : [ v ] }
356+ end
357+
358+ loop_through_keys keys , transformed_expected , show_values
359+ end
360+
265361 def source_lines ( keys )
266362 line_frequency = @query_stats . query_lines_by_frequency
267363 keys_with_source_lines = keys . select { |key | line_frequency [ key ] . present? }
@@ -281,10 +377,33 @@ def no_queries_fail_message(crud_operation)
281377 "Expected ActiveRecord to not #{ crud_operation } any records, got #{ @query_stats . query_counts } \n \n Where unexpected queries came from:\n \n #{ source_lines ( @query_stats . query_counts . keys ) . join ( "\n " ) } "
282378 end
283379
284- def expectation_failed_message ( crud_operation )
380+ def reject_record ( subset , current_expected , key , ignore_missing )
381+ if subset && !current_expected [ key ] . nil?
382+ if ignore_missing
383+ @query_stats . queries [ key ] [ :values ] . empty? || ( current_expected [ key ] - @query_stats . queries [ key ] [ :values ] ) . empty?
384+ else
385+ ( current_expected [ key ] - @query_stats . queries [ key ] [ :values ] ) . empty?
386+ end
387+ else
388+ ignore_missing || @query_stats . queries [ key ] [ :values ] == current_expected [ key ]
389+ end
390+ end
391+
392+ def filter_model_names ( subset , show_values , ignore_missing )
285393 all_model_names = expected . keys + @query_stats . queries . keys
286- model_names_with_wrong_count = all_model_names . reject { |key | expected [ key ] == @query_stats . queries [ key ] [ :count ] } . uniq
287- "Expected ActiveRecord to #{ crud_operation } #{ expected } , got #{ @query_stats . query_counts } \n Expectations that differed:\n #{ difference ( model_names_with_wrong_count ) . join ( "\n " ) } \n \n Where unexpected queries came from:\n \n #{ source_lines ( model_names_with_wrong_count ) . join ( "\n " ) } "
394+ if show_values
395+ transformed_expected = expected . transform_values { |v | v . is_a? ( Array ) ? v : [ v ] }
396+ all_model_names . reject { |key | reject_record ( subset , transformed_expected , key , ignore_missing ) } . uniq
397+ else
398+ all_model_names . reject { |key | expected [ key ] == @query_stats . queries [ key ] [ :count ] } . uniq
399+ end
400+ end
401+
402+ def expectation_failed_message ( crud_operation , show_values : false , subset : false , ignore_missing : false )
403+ model_names_with_wrong_count = filter_model_names ( subset , show_values , ignore_missing )
404+ message = "Expected ActiveRecord to #{ crud_operation } #{ expected } , got #{ show_values ? @query_stats . query_values : @query_stats . query_counts } \n "
405+ message += "Expectations that differed:\n #{ difference ( model_names_with_wrong_count , show_values : show_values ) . join ( "\n " ) } " if show_values
406+ message + "\n \n Where unexpected queries came from:\n \n #{ source_lines ( model_names_with_wrong_count ) . join ( "\n " ) } "
288407 end
289408 end
290409 end
0 commit comments