shoulda-matchers 2.8.0 → 3.0.0.rc1

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.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/.hound_config/ruby.yml +7 -0
  3. data/.travis.yml +11 -54
  4. data/Appraisals +45 -100
  5. data/CONTRIBUTING.md +51 -7
  6. data/Gemfile +7 -19
  7. data/Gemfile.lock +60 -134
  8. data/Guardfile +5 -0
  9. data/NEWS.md +203 -0
  10. data/README.md +95 -50
  11. data/Rakefile +1 -0
  12. data/doc_config/yard/templates/default/layout/html/setup.rb +1 -1
  13. data/gemfiles/4.0.0.gemfile +10 -7
  14. data/gemfiles/4.0.0.gemfile.lock +103 -79
  15. data/gemfiles/4.0.1.gemfile +10 -7
  16. data/gemfiles/4.0.1.gemfile.lock +109 -83
  17. data/gemfiles/4.1.gemfile +10 -7
  18. data/gemfiles/4.1.gemfile.lock +109 -85
  19. data/gemfiles/4.2.gemfile +10 -9
  20. data/gemfiles/4.2.gemfile.lock +86 -78
  21. data/lib/shoulda/matchers.rb +13 -18
  22. data/lib/shoulda/matchers/action_controller.rb +4 -1
  23. data/lib/shoulda/matchers/action_controller/flash_store.rb +95 -0
  24. data/lib/shoulda/matchers/action_controller/{strong_parameters_matcher.rb → permit_matcher.rb} +147 -30
  25. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  26. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +1 -1
  27. data/lib/shoulda/matchers/action_controller/render_with_layout_matcher.rb +1 -1
  28. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -1
  29. data/lib/shoulda/matchers/action_controller/route_matcher.rb +5 -1
  30. data/lib/shoulda/matchers/action_controller/route_params.rb +15 -6
  31. data/lib/shoulda/matchers/action_controller/session_store.rb +34 -0
  32. data/lib/shoulda/matchers/action_controller/set_flash_matcher.rb +30 -136
  33. data/lib/shoulda/matchers/action_controller/set_session_matcher.rb +28 -109
  34. data/lib/shoulda/matchers/action_controller/set_session_or_flash_matcher.rb +103 -0
  35. data/lib/shoulda/matchers/active_model/allow_mass_assignment_of_matcher.rb +1 -12
  36. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +79 -10
  37. data/lib/shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher.rb +10 -0
  38. data/lib/shoulda/matchers/active_model/validate_absence_of_matcher.rb +21 -0
  39. data/lib/shoulda/matchers/active_model/validate_acceptance_of_matcher.rb +24 -0
  40. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +22 -5
  41. data/lib/shoulda/matchers/active_model/validate_exclusion_of_matcher.rb +29 -10
  42. data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +27 -10
  43. data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -12
  44. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +56 -20
  45. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +3 -11
  46. data/lib/shoulda/matchers/active_model/validation_message_finder.rb +65 -0
  47. data/lib/shoulda/matchers/active_record/association_matcher.rb +40 -6
  48. data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +21 -7
  49. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +11 -40
  50. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -1
  51. data/lib/shoulda/matchers/active_record/define_enum_for_matcher.rb +2 -6
  52. data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +137 -22
  53. data/lib/shoulda/matchers/configuration.rb +20 -0
  54. data/lib/shoulda/matchers/doublespeak.rb +11 -1
  55. data/lib/shoulda/matchers/doublespeak/double.rb +29 -11
  56. data/lib/shoulda/matchers/doublespeak/double_collection.rb +4 -3
  57. data/lib/shoulda/matchers/doublespeak/method_call.rb +35 -0
  58. data/lib/shoulda/matchers/doublespeak/object_double.rb +7 -2
  59. data/lib/shoulda/matchers/doublespeak/proxy_implementation.rb +4 -3
  60. data/lib/shoulda/matchers/doublespeak/stub_implementation.rb +3 -3
  61. data/lib/shoulda/matchers/doublespeak/world.rb +21 -1
  62. data/lib/shoulda/matchers/integrations.rb +43 -0
  63. data/lib/shoulda/matchers/integrations/configuration.rb +68 -0
  64. data/lib/shoulda/matchers/integrations/configuration_error.rb +9 -0
  65. data/lib/shoulda/matchers/integrations/inclusion.rb +20 -0
  66. data/lib/shoulda/matchers/integrations/libraries.rb +15 -0
  67. data/lib/shoulda/matchers/integrations/libraries/action_controller.rb +31 -0
  68. data/lib/shoulda/matchers/integrations/libraries/active_model.rb +26 -0
  69. data/lib/shoulda/matchers/integrations/libraries/active_record.rb +26 -0
  70. data/lib/shoulda/matchers/integrations/libraries/missing_library.rb +19 -0
  71. data/lib/shoulda/matchers/integrations/libraries/rails.rb +30 -0
  72. data/lib/shoulda/matchers/integrations/rails.rb +12 -0
  73. data/lib/shoulda/matchers/integrations/registry.rb +28 -0
  74. data/lib/shoulda/matchers/integrations/test_frameworks.rb +16 -0
  75. data/lib/shoulda/matchers/integrations/test_frameworks/active_support_test_case.rb +37 -0
  76. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_4.rb +36 -0
  77. data/lib/shoulda/matchers/integrations/test_frameworks/minitest_5.rb +37 -0
  78. data/lib/shoulda/matchers/integrations/test_frameworks/missing_test_framework.rb +40 -0
  79. data/lib/shoulda/matchers/integrations/test_frameworks/rspec.rb +29 -0
  80. data/lib/shoulda/matchers/integrations/test_frameworks/test_unit.rb +36 -0
  81. data/lib/shoulda/matchers/rails_shim.rb +0 -40
  82. data/lib/shoulda/matchers/version.rb +1 -1
  83. data/script/SUPPORTED_VERSIONS +1 -1
  84. data/script/update_gems_in_all_appraisals +14 -0
  85. data/shoulda-matchers.gemspec +2 -2
  86. data/spec/acceptance/active_model_integration_spec.rb +4 -1
  87. data/spec/acceptance/independent_matchers_spec.rb +6 -6
  88. data/spec/acceptance/multiple_libraries_integration_spec.rb +52 -0
  89. data/spec/acceptance/rails_integration_spec.rb +15 -5
  90. data/spec/acceptance_spec_helper.rb +8 -0
  91. data/spec/doublespeak_spec_helper.rb +14 -0
  92. data/spec/support/acceptance/adds_shoulda_matchers_to_project.rb +110 -0
  93. data/spec/support/acceptance/helpers.rb +2 -0
  94. data/spec/support/acceptance/helpers/base_helpers.rb +6 -1
  95. data/spec/support/acceptance/helpers/command_helpers.rb +6 -2
  96. data/spec/support/acceptance/helpers/minitest_helpers.rb +0 -8
  97. data/spec/support/acceptance/helpers/n_unit_helpers.rb +25 -0
  98. data/spec/support/acceptance/helpers/rspec_helpers.rb +2 -0
  99. data/spec/support/acceptance/helpers/step_helpers.rb +13 -19
  100. data/spec/support/acceptance/matchers/have_output.rb +1 -1
  101. data/spec/support/tests/bundle.rb +1 -1
  102. data/spec/support/tests/command_runner.rb +25 -13
  103. data/spec/support/tests/current_bundle.rb +47 -0
  104. data/spec/support/tests/database.rb +28 -0
  105. data/spec/support/tests/database_adapters/postgresql.rb +25 -0
  106. data/spec/support/tests/database_adapters/sqlite3.rb +26 -0
  107. data/spec/support/tests/database_configuration.rb +33 -0
  108. data/spec/support/tests/database_configuration_registry.rb +28 -0
  109. data/spec/support/tests/filesystem.rb +25 -2
  110. data/spec/support/unit/helpers/active_record_versions.rb +12 -0
  111. data/spec/support/unit/helpers/class_builder.rb +6 -2
  112. data/spec/support/unit/helpers/column_type_helpers.rb +26 -0
  113. data/spec/support/unit/helpers/controller_builder.rb +0 -28
  114. data/spec/support/unit/helpers/database_helpers.rb +18 -0
  115. data/spec/support/unit/helpers/model_builder.rb +38 -6
  116. data/spec/support/unit/helpers/rails_versions.rb +2 -2
  117. data/spec/support/unit/matchers/fail_with_message_including_matcher.rb +9 -8
  118. data/spec/support/unit/matchers/fail_with_message_matcher.rb +1 -1
  119. data/spec/support/unit/rails_application.rb +29 -13
  120. data/spec/support/unit/record_validating_confirmation_builder.rb +1 -2
  121. data/spec/support/unit/shared_examples/set_session_or_flash.rb +355 -0
  122. data/spec/unit/shoulda/matchers/action_controller/permit_matcher_spec.rb +433 -0
  123. data/spec/unit/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +1 -5
  124. data/spec/unit/shoulda/matchers/action_controller/route_matcher_spec.rb +37 -0
  125. data/spec/unit/shoulda/matchers/action_controller/set_flash_matcher_spec.rb +23 -147
  126. data/spec/unit/shoulda/matchers/action_controller/set_session_matcher_spec.rb +8 -285
  127. data/spec/unit/shoulda/matchers/action_controller/set_session_or_flash_matcher_spec.rb +562 -0
  128. data/spec/unit/shoulda/matchers/active_model/allow_value_matcher_spec.rb +81 -14
  129. data/spec/unit/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +16 -8
  130. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +101 -9
  131. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +39 -1
  132. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +39 -1
  133. data/spec/unit/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +39 -0
  134. data/spec/unit/shoulda/matchers/active_model/validate_exclusion_of_matcher_spec.rb +0 -17
  135. data/spec/unit/shoulda/matchers/active_model/validate_inclusion_of_matcher_spec.rb +0 -17
  136. data/spec/unit/shoulda/matchers/active_model/validate_length_of_matcher_spec.rb +0 -17
  137. data/spec/unit/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +838 -271
  138. data/spec/unit/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +0 -19
  139. data/spec/unit/shoulda/matchers/active_record/association_matcher_spec.rb +93 -0
  140. data/spec/unit/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +3 -3
  141. data/spec/unit/shoulda/matchers/active_record/define_enum_for_matcher_spec.rb +25 -0
  142. data/spec/unit/shoulda/matchers/active_record/validate_uniqueness_of_matcher_spec.rb +905 -0
  143. data/spec/unit/shoulda/matchers/doublespeak/double_collection_spec.rb +17 -11
  144. data/spec/unit/shoulda/matchers/doublespeak/double_implementation_registry_spec.rb +1 -1
  145. data/spec/unit/shoulda/matchers/doublespeak/double_spec.rb +144 -43
  146. data/spec/unit/shoulda/matchers/doublespeak/object_double_spec.rb +1 -1
  147. data/spec/unit/shoulda/matchers/doublespeak/proxy_implementation_spec.rb +36 -11
  148. data/spec/unit/shoulda/matchers/doublespeak/stub_implementation_spec.rb +29 -16
  149. data/spec/unit/shoulda/matchers/doublespeak/world_spec.rb +8 -5
  150. data/spec/unit/shoulda/matchers/doublespeak_spec.rb +1 -1
  151. data/spec/unit_spec_helper.rb +15 -14
  152. data/spec/warnings_spy.rb +1 -1
  153. metadata +68 -29
  154. data/docs.watchr +0 -5
  155. data/gemfiles/3.0.gemfile +0 -26
  156. data/gemfiles/3.0.gemfile.lock +0 -173
  157. data/gemfiles/3.1.gemfile +0 -32
  158. data/gemfiles/3.1.gemfile.lock +0 -212
  159. data/gemfiles/3.1_1.9.2.gemfile +0 -32
  160. data/gemfiles/3.1_1.9.2.gemfile.lock +0 -212
  161. data/gemfiles/3.2.gemfile +0 -33
  162. data/gemfiles/3.2.gemfile.lock +0 -212
  163. data/gemfiles/3.2_1.9.2.gemfile +0 -31
  164. data/gemfiles/3.2_1.9.2.gemfile.lock +0 -207
  165. data/lib/shoulda/matchers/assertion_error.rb +0 -27
  166. data/lib/shoulda/matchers/doublespeak/structs.rb +0 -10
  167. data/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +0 -39
  168. data/lib/shoulda/matchers/integrations/rspec.rb +0 -19
  169. data/lib/shoulda/matchers/integrations/test_unit.rb +0 -34
  170. data/spec/unit/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +0 -331
  171. data/spec/unit/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +0 -564
@@ -24,24 +24,25 @@ module Shoulda
24
24
  reflection.options[:through]
25
25
  end
26
26
 
27
- def join_table
28
- join_table =
27
+ def join_table_name
28
+ join_table_name =
29
29
  if has_and_belongs_to_many_name_table_name
30
30
  has_and_belongs_to_many_name_table_name
31
- elsif reflection.respond_to?(:join_table)
32
- reflection.join_table
33
31
  else
34
- reflection.options[:join_table]
32
+ reflection.join_table
35
33
  end
36
34
 
37
- join_table.to_s
35
+ join_table_name.to_s
38
36
  end
39
37
 
40
38
  def association_relation
41
- if reflection.respond_to?(:scope)
42
- convert_scope_to_relation(reflection.scope)
39
+ relation = associated_class.all
40
+
41
+ if reflection.scope
42
+ # Source: AR::Associations::AssociationScope#eval_scope
43
+ relation.instance_exec(subject, &reflection.scope)
43
44
  else
44
- convert_options_to_relation(reflection.options)
45
+ relation
45
46
  end
46
47
  end
47
48
 
@@ -68,37 +69,7 @@ module Shoulda
68
69
 
69
70
  attr_reader :reflection, :subject
70
71
 
71
- def convert_scope_to_relation(scope)
72
- relation = associated_class.all
73
-
74
- if scope
75
- # Source: AR::Associations::AssociationScope#eval_scope
76
- relation.instance_exec(subject, &scope)
77
- else
78
- relation
79
- end
80
- end
81
-
82
- def convert_options_to_relation(options)
83
- relation = associated_class.scoped
84
- relation = extend_relation_with(relation, :where, options[:conditions])
85
- relation = extend_relation_with(relation, :includes, options[:include])
86
- relation = extend_relation_with(relation, :order, options[:order])
87
- relation = extend_relation_with(relation, :group, options[:group])
88
- relation = extend_relation_with(relation, :having, options[:having])
89
- relation = extend_relation_with(relation, :limit, options[:limit])
90
- relation = extend_relation_with(relation, :offset, options[:offset])
91
- relation = extend_relation_with(relation, :select, options[:select])
92
- relation
93
- end
94
-
95
- def extend_relation_with(relation, method_name, value)
96
- if value
97
- relation.__send__(method_name, value)
98
- else
99
- relation
100
- end
101
- end
72
+ private
102
73
 
103
74
  def has_and_belongs_to_many_name
104
75
  reflection.options[:through]
@@ -4,7 +4,7 @@ module Shoulda
4
4
  module AssociationMatchers
5
5
  # @private
6
6
  class ModelReflector
7
- delegate :associated_class, :through?, :join_table,
7
+ delegate :associated_class, :through?, :join_table_name,
8
8
  :association_relation, :polymorphic?, :foreign_key,
9
9
  :association_foreign_key, to: :reflection
10
10
 
@@ -99,16 +99,12 @@ module Shoulda
99
99
  hashify(options[:expected_enum_values]).with_indifferent_access
100
100
  end
101
101
 
102
- def enum_method
103
- attribute_name.to_s.pluralize
104
- end
105
-
106
102
  def actual_enum_values
107
- model.class.send(enum_method)
103
+ model.class.send(attribute_name.to_s.pluralize)
108
104
  end
109
105
 
110
106
  def enum_defined?
111
- model.class.respond_to?(enum_method)
107
+ model.defined_enums.include?(attribute_name.to_s)
112
108
  end
113
109
 
114
110
  def enum_values_match?
@@ -88,6 +88,22 @@ module Shoulda
88
88
  #
89
89
  # #### Qualifiers
90
90
  #
91
+ # Use `on` if your validation applies only under a certain context.
92
+ #
93
+ # class Post < ActiveRecord::Base
94
+ # validates_uniqueness_of :title, on: :create
95
+ # end
96
+ #
97
+ # # RSpec
98
+ # describe Post do
99
+ # it { should validate_uniqueness_of(:title).on(:create) }
100
+ # end
101
+ #
102
+ # # Test::Unit
103
+ # class PostTest < ActiveSupport::TestCase
104
+ # should validate_uniqueness_of(:title).on(:create)
105
+ # end
106
+ #
91
107
  # ##### with_message
92
108
  #
93
109
  # Use `with_message` if you are using a custom validation message.
@@ -241,9 +257,12 @@ module Shoulda
241
257
  @original_subject = subject
242
258
  @subject = subject.class.new
243
259
  @expected_message ||= :taken
260
+ @all_records = @subject.class.all
244
261
 
245
- set_scoped_attributes &&
262
+ scopes_match? &&
263
+ set_scoped_attributes &&
246
264
  validate_everything_except_duplicate_nils_or_blanks? &&
265
+ validate_case_sensitivity? &&
247
266
  validate_after_scope_change? &&
248
267
  allows_nil? &&
249
268
  allows_blank?
@@ -253,6 +272,37 @@ module Shoulda
253
272
 
254
273
  private
255
274
 
275
+ def validation
276
+ @subject.class._validators[@attribute].detect do |validator|
277
+ validator.is_a?(::ActiveRecord::Validations::UniquenessValidator)
278
+ end
279
+ end
280
+
281
+ def scopes_match?
282
+ expected_scopes = Array.wrap(@options[:scopes])
283
+
284
+ if validation
285
+ actual_scopes = Array.wrap(validation.options[:scope])
286
+ else
287
+ actual_scopes = []
288
+ end
289
+
290
+ if expected_scopes == actual_scopes
291
+ true
292
+ else
293
+ @failure_message = "Expected validation to be scoped to " +
294
+ "#{expected_scopes}"
295
+
296
+ if actual_scopes.present?
297
+ @failure_message << ", but it was scoped to #{actual_scopes}."
298
+ else
299
+ @failure_message << ", but it was not scoped to anything."
300
+ end
301
+
302
+ false
303
+ end
304
+ end
305
+
256
306
  def allows_nil?
257
307
  if @options[:allow_nil]
258
308
  ensure_nil_record_in_database
@@ -353,6 +403,22 @@ module Shoulda
353
403
  disallows_value_of(existing_value, @expected_message)
354
404
  end
355
405
 
406
+ def validate_case_sensitivity?
407
+ value = existing_value
408
+
409
+ if value.respond_to?(:swapcase)
410
+ swapcased_value = value.swapcase
411
+
412
+ if @options[:case_insensitive]
413
+ disallows_value_of(swapcased_value, @expected_message)
414
+ else
415
+ allows_value_of(swapcased_value, @expected_message)
416
+ end
417
+ else
418
+ true
419
+ end
420
+ end
421
+
356
422
  def create_record_with_value
357
423
  @existing_record = create_record_in_database
358
424
  end
@@ -364,17 +430,18 @@ module Shoulda
364
430
  end
365
431
 
366
432
  def validate_after_scope_change?
367
- if @options[:scopes].blank?
433
+ if @options[:scopes].blank? || all_scopes_are_booleans?
368
434
  true
369
435
  else
370
- all_records = @subject.class.all
371
436
  @options[:scopes].all? do |scope|
372
- previous_value = all_records.map(&scope).max
373
-
374
- # Assume the scope is a foreign key if the field is nil
375
- previous_value ||= correct_type_for_column(@subject.class.columns_hash[scope.to_s])
437
+ previous_value = @all_records.map(&scope).compact.max
376
438
 
377
- next_value = next_value_for(scope, previous_value)
439
+ next_value =
440
+ if previous_value.blank?
441
+ dummy_value_for(scope)
442
+ else
443
+ next_value_for(scope, previous_value)
444
+ end
378
445
 
379
446
  @subject.__send__("#{scope}=", next_value)
380
447
 
@@ -392,49 +459,97 @@ module Shoulda
392
459
  end
393
460
  end
394
461
 
395
- def correct_type_for_column(column)
396
- if column.type == :string
397
- '0'
398
- elsif column.type == :datetime
462
+ def dummy_value_for(scope)
463
+ column = column_for(scope)
464
+
465
+ if column.respond_to?(:array) && column.array
466
+ [ dummy_scalar_value_for(column) ]
467
+ else
468
+ dummy_scalar_value_for(column)
469
+ end
470
+ end
471
+
472
+ def dummy_scalar_value_for(column)
473
+ case column.type
474
+ when :integer
475
+ 0
476
+ when :date
477
+ Date.today
478
+ when :datetime
399
479
  DateTime.now
400
- elsif column.type == :uuid
480
+ when :uuid
401
481
  SecureRandom.uuid
482
+ when :boolean
483
+ true
402
484
  else
403
- 0
485
+ 'dummy value'
404
486
  end
405
487
  end
406
488
 
407
489
  def next_value_for(scope, previous_value)
408
- if @subject.class.respond_to?(:defined_enums) && @subject.defined_enums[scope.to_s]
490
+ if previous_value.is_a?(Array)
491
+ [ next_scalar_value_for(scope, previous_value[0]) ]
492
+ else
493
+ next_scalar_value_for(scope, previous_value)
494
+ end
495
+ end
496
+
497
+ def next_scalar_value_for(scope, previous_value)
498
+ column = column_for(scope)
499
+
500
+ if column.type == :uuid
501
+ SecureRandom.uuid
502
+ elsif defined_as_enum?(scope)
409
503
  available_values = available_enum_values_for(scope, previous_value)
410
504
  available_values.keys.last
411
- elsif scope.to_s =~ /_type$/ && model_class?(previous_value)
505
+ elsif polymorphic_type_attribute?(scope, previous_value)
412
506
  Uniqueness::TestModels.create(previous_value).to_s
413
507
  elsif previous_value.respond_to?(:next)
414
508
  previous_value.next
415
509
  elsif previous_value.respond_to?(:to_datetime)
416
510
  previous_value.to_datetime.next
511
+ elsif boolean_value?(previous_value)
512
+ !previous_value
417
513
  else
418
514
  previous_value.to_s.next
419
515
  end
420
516
  end
421
517
 
518
+ def all_scopes_are_booleans?
519
+ @options[:scopes].all? do |scope|
520
+ @all_records.map(&scope).all? { |s| boolean_value?(s) }
521
+ end
522
+ end
523
+
524
+ def boolean_value?(value)
525
+ value.in?([true, false])
526
+ end
527
+
528
+ def defined_as_enum?(scope)
529
+ @subject.class.respond_to?(:defined_enums) &&
530
+ @subject.defined_enums[scope.to_s]
531
+ end
532
+
533
+ def polymorphic_type_attribute?(scope, previous_value)
534
+ scope.to_s =~ /_type$/ && model_class?(previous_value)
535
+ end
536
+
422
537
  def available_enum_values_for(scope, previous_value)
423
538
  @subject.defined_enums[scope.to_s].reject do |key, _|
424
539
  key == previous_value
425
540
  end
426
541
  end
427
542
 
543
+ def existing_value
544
+ existing_record.__send__(@attribute)
545
+ end
546
+
428
547
  def class_name
429
548
  @subject.class.name
430
549
  end
431
550
 
432
- def existing_value
433
- value = existing_record.__send__(@attribute)
434
- if @options[:case_insensitive] && value.respond_to?(:swapcase!)
435
- value.swapcase!
436
- end
437
- value
551
+ def column_for(scope)
552
+ @subject.class.columns_hash[scope.to_s]
438
553
  end
439
554
  end
440
555
  end
@@ -0,0 +1,20 @@
1
+ module Shoulda
2
+ module Matchers
3
+ # @private
4
+ def self.configure
5
+ yield configuration
6
+ end
7
+
8
+ # @private
9
+ def self.configuration
10
+ @_configuration ||= Configuration.new
11
+ end
12
+
13
+ # @private
14
+ class Configuration
15
+ def integrate(&block)
16
+ Integrations::Configuration.apply(self, &block)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -13,6 +13,16 @@ module Shoulda
13
13
  def world
14
14
  @_world ||= World.new
15
15
  end
16
+
17
+ def debugging_enabled?
18
+ ENV['DEBUG_DOUBLESPEAK'] == '1'
19
+ end
20
+
21
+ def debug(&block)
22
+ if debugging_enabled?
23
+ puts block.call
24
+ end
25
+ end
16
26
  end
17
27
  end
18
28
  end
@@ -21,8 +31,8 @@ end
21
31
  require 'shoulda/matchers/doublespeak/double'
22
32
  require 'shoulda/matchers/doublespeak/double_collection'
23
33
  require 'shoulda/matchers/doublespeak/double_implementation_registry'
34
+ require 'shoulda/matchers/doublespeak/method_call'
24
35
  require 'shoulda/matchers/doublespeak/object_double'
25
36
  require 'shoulda/matchers/doublespeak/proxy_implementation'
26
- require 'shoulda/matchers/doublespeak/structs'
27
37
  require 'shoulda/matchers/doublespeak/stub_implementation'
28
38
  require 'shoulda/matchers/doublespeak/world'
@@ -5,7 +5,8 @@ module Shoulda
5
5
  class Double
6
6
  attr_reader :calls
7
7
 
8
- def initialize(klass, method_name, implementation)
8
+ def initialize(world, klass, method_name, implementation)
9
+ @world = world
9
10
  @klass = klass
10
11
  @method_name = method_name
11
12
  @implementation = implementation
@@ -36,36 +37,53 @@ module Shoulda
36
37
  end
37
38
  end
38
39
 
39
- def record_call(args, block)
40
- calls << MethodCall.new(args, block)
40
+ def record_call(call)
41
+ calls << call
41
42
  end
42
43
 
43
- def call_original_method(object, args, block)
44
- if original_method
45
- original_method.bind(object).call(*args, &block)
44
+ def call_original_method(call)
45
+ unbound_method = world.original_method_for(klass, call.method_name)
46
+
47
+ if unbound_method
48
+ unbound_method.bind(call.object).call(*call.args, &call.block)
46
49
  end
47
50
  end
48
51
 
49
52
  protected
50
53
 
51
- attr_reader :klass, :method_name, :implementation, :original_method
54
+ attr_reader :world, :klass, :method_name, :implementation,
55
+ :original_method
52
56
 
53
57
  def store_original_method
54
- @original_method = klass.instance_method(method_name)
58
+ world.store_original_method_for(klass, method_name)
55
59
  end
56
60
 
57
61
  def replace_method_with_double
58
- implementation = @implementation
59
62
  double = self
63
+ implementation = @implementation
64
+ _method_name = method_name
65
+
66
+ if klass.instance_methods(false).include?(method_name)
67
+ klass.__send__(:remove_method, method_name)
68
+ end
60
69
 
61
70
  klass.__send__(:define_method, method_name) do |*args, &block|
62
- implementation.call(double, self, args, block)
71
+ call = MethodCall.new(
72
+ double: double,
73
+ object: self,
74
+ method_name: _method_name,
75
+ args: args,
76
+ block: block
77
+ )
78
+ implementation.call(call)
63
79
  end
64
80
  end
65
81
 
66
82
  def restore_original_method
67
- original_method = @original_method
83
+ original_method = world.original_method_for(klass, method_name)
84
+
68
85
  klass.__send__(:remove_method, method_name)
86
+
69
87
  klass.__send__(:define_method, method_name) do |*args, &block|
70
88
  original_method.bind(self).call(*args, &block)
71
89
  end