shoulda-matchers 2.5.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +8 -7
  2. data/.travis.yml +4 -0
  3. data/Appraisals +8 -0
  4. data/CONTRIBUTING.md +1 -1
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +77 -66
  7. data/MIT-LICENSE +1 -1
  8. data/NEWS.md +63 -1
  9. data/README.md +189 -33
  10. data/Rakefile +6 -5
  11. data/features/rails_integration.feature +1 -1
  12. data/features/step_definitions/rails_steps.rb +7 -6
  13. data/gemfiles/3.0.gemfile +2 -2
  14. data/gemfiles/3.0.gemfile.lock +14 -5
  15. data/gemfiles/3.1.gemfile +2 -2
  16. data/gemfiles/3.1.gemfile.lock +14 -5
  17. data/gemfiles/3.2.gemfile +2 -2
  18. data/gemfiles/3.2.gemfile.lock +16 -7
  19. data/gemfiles/4.0.0.gemfile +2 -2
  20. data/gemfiles/4.0.0.gemfile.lock +15 -6
  21. data/gemfiles/4.0.1.gemfile +2 -2
  22. data/gemfiles/4.0.1.gemfile.lock +15 -6
  23. data/gemfiles/4.1.gemfile +19 -0
  24. data/gemfiles/4.1.gemfile.lock +176 -0
  25. data/lib/shoulda/matchers.rb +17 -1
  26. data/lib/shoulda/matchers/action_controller.rb +4 -2
  27. data/lib/shoulda/matchers/action_controller/callback_matcher.rb +100 -0
  28. data/lib/shoulda/matchers/action_controller/redirect_to_matcher.rb +1 -1
  29. data/lib/shoulda/matchers/action_controller/render_template_matcher.rb +4 -4
  30. data/lib/shoulda/matchers/action_controller/rescue_from_matcher.rb +1 -1
  31. data/lib/shoulda/matchers/action_controller/route_matcher.rb +12 -12
  32. data/lib/shoulda/matchers/action_controller/route_params.rb +1 -1
  33. data/lib/shoulda/matchers/action_controller/set_the_flash_matcher.rb +2 -1
  34. data/lib/shoulda/matchers/action_controller/strong_parameters_matcher.rb +167 -0
  35. data/lib/shoulda/matchers/active_model.rb +4 -2
  36. data/lib/shoulda/matchers/active_model/allow_value_matcher.rb +23 -5
  37. data/lib/shoulda/matchers/active_model/disallow_value_matcher.rb +0 -4
  38. data/lib/shoulda/matchers/active_model/ensure_inclusion_of_matcher.rb +66 -14
  39. data/lib/shoulda/matchers/active_model/ensure_length_of_matcher.rb +8 -8
  40. data/lib/shoulda/matchers/active_model/errors.rb +40 -0
  41. data/lib/shoulda/matchers/active_model/helpers.rb +6 -6
  42. data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +33 -14
  43. data/lib/shoulda/matchers/active_model/numericality_matchers/even_number_matcher.rb +26 -0
  44. data/lib/shoulda/matchers/active_model/numericality_matchers/{odd_even_number_matcher.rb → numeric_type_matcher.rb} +9 -20
  45. data/lib/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher.rb +26 -0
  46. data/lib/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher.rb +5 -21
  47. data/lib/shoulda/matchers/active_model/validate_confirmation_of_matcher.rb +1 -1
  48. data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +71 -22
  49. data/lib/shoulda/matchers/active_model/validate_presence_of_matcher.rb +6 -1
  50. data/lib/shoulda/matchers/active_model/validate_uniqueness_of_matcher.rb +25 -6
  51. data/lib/shoulda/matchers/active_record.rb +1 -0
  52. data/lib/shoulda/matchers/active_record/association_matcher.rb +67 -13
  53. data/lib/shoulda/matchers/active_record/association_matchers/inverse_of_matcher.rb +40 -0
  54. data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +24 -1
  55. data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +1 -1
  56. data/lib/shoulda/matchers/active_record/have_db_column_matcher.rb +1 -1
  57. data/lib/shoulda/matchers/active_record/have_db_index_matcher.rb +1 -1
  58. data/lib/shoulda/matchers/assertion_error.rb +7 -2
  59. data/lib/shoulda/matchers/error.rb +24 -0
  60. data/lib/shoulda/matchers/independent.rb +10 -0
  61. data/lib/shoulda/matchers/independent/delegate_matcher.rb +157 -0
  62. data/lib/shoulda/matchers/independent/delegate_matcher/stubbed_target.rb +34 -0
  63. data/lib/shoulda/matchers/integrations/nunit_test_case_detection.rb +36 -0
  64. data/lib/shoulda/matchers/integrations/rspec.rb +13 -14
  65. data/lib/shoulda/matchers/integrations/test_unit.rb +11 -9
  66. data/lib/shoulda/matchers/version.rb +1 -1
  67. data/lib/shoulda/matchers/warn.rb +7 -0
  68. data/shoulda-matchers.gemspec +2 -1
  69. data/spec/shoulda/matchers/action_controller/callback_matcher_spec.rb +79 -0
  70. data/spec/shoulda/matchers/action_controller/filter_param_matcher_spec.rb +3 -3
  71. data/spec/shoulda/matchers/action_controller/redirect_to_matcher_spec.rb +11 -11
  72. data/spec/shoulda/matchers/action_controller/render_template_matcher_spec.rb +21 -21
  73. data/spec/shoulda/matchers/action_controller/render_with_layout_matcher_spec.rb +10 -10
  74. data/spec/shoulda/matchers/action_controller/rescue_from_matcher_spec.rb +45 -18
  75. data/spec/shoulda/matchers/action_controller/respond_with_matcher_spec.rb +8 -8
  76. data/spec/shoulda/matchers/action_controller/route_matcher_spec.rb +19 -19
  77. data/spec/shoulda/matchers/action_controller/route_params_spec.rb +6 -6
  78. data/spec/shoulda/matchers/action_controller/set_session_matcher_spec.rb +11 -11
  79. data/spec/shoulda/matchers/action_controller/set_the_flash_matcher_spec.rb +44 -44
  80. data/spec/shoulda/matchers/action_controller/strong_parameters_matcher_spec.rb +205 -0
  81. data/spec/shoulda/matchers/active_model/allow_mass_assignment_of_matcher_spec.rb +24 -24
  82. data/spec/shoulda/matchers/active_model/allow_value_matcher_spec.rb +37 -37
  83. data/spec/shoulda/matchers/active_model/disallow_value_matcher_spec.rb +17 -21
  84. data/spec/shoulda/matchers/active_model/ensure_exclusion_of_matcher_spec.rb +24 -24
  85. data/spec/shoulda/matchers/active_model/ensure_inclusion_of_matcher_spec.rb +173 -67
  86. data/spec/shoulda/matchers/active_model/ensure_length_of_matcher_spec.rb +40 -40
  87. data/spec/shoulda/matchers/active_model/exception_message_finder_spec.rb +20 -20
  88. data/spec/shoulda/matchers/active_model/helpers_spec.rb +27 -25
  89. data/spec/shoulda/matchers/active_model/numericality_matchers/comparison_matcher_spec.rb +126 -13
  90. data/spec/shoulda/matchers/active_model/numericality_matchers/even_number_matcher_spec.rb +59 -0
  91. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_number_matcher_spec.rb +59 -0
  92. data/spec/shoulda/matchers/active_model/numericality_matchers/only_integer_matcher_spec.rb +27 -26
  93. data/spec/shoulda/matchers/active_model/validate_absence_of_matcher_spec.rb +15 -15
  94. data/spec/shoulda/matchers/active_model/validate_acceptance_of_matcher_spec.rb +8 -8
  95. data/spec/shoulda/matchers/active_model/validate_confirmation_of_matcher_spec.rb +9 -9
  96. data/spec/shoulda/matchers/active_model/validate_numericality_of_matcher_spec.rb +229 -44
  97. data/spec/shoulda/matchers/active_model/validate_presence_of_matcher_spec.rb +44 -25
  98. data/spec/shoulda/matchers/active_model/validate_uniqueness_of_matcher_spec.rb +110 -62
  99. data/spec/shoulda/matchers/active_model/validation_message_finder_spec.rb +19 -19
  100. data/spec/shoulda/matchers/active_record/accept_nested_attributes_for_matcher_spec.rb +30 -30
  101. data/spec/shoulda/matchers/active_record/association_matcher_spec.rb +378 -192
  102. data/spec/shoulda/matchers/active_record/association_matchers/model_reflection_spec.rb +4 -0
  103. data/spec/shoulda/matchers/active_record/have_db_column_matcher_spec.rb +33 -33
  104. data/spec/shoulda/matchers/active_record/have_db_index_matcher_spec.rb +21 -17
  105. data/spec/shoulda/matchers/active_record/have_readonly_attributes_matcher_spec.rb +8 -8
  106. data/spec/shoulda/matchers/active_record/serialize_matcher_spec.rb +14 -14
  107. data/spec/shoulda/matchers/independent/delegate_matcher/stubbed_target_spec.rb +43 -0
  108. data/spec/shoulda/matchers/independent/delegate_matcher_spec.rb +184 -0
  109. data/spec/spec_helper.rb +4 -0
  110. data/spec/support/activemodel_helpers.rb +2 -2
  111. data/spec/support/capture_helpers.rb +19 -0
  112. data/spec/support/controller_builder.rb +22 -3
  113. data/spec/support/fail_with_message_including_matcher.rb +33 -0
  114. data/spec/support/model_builder.rb +1 -1
  115. data/spec/support/shared_examples/numerical_submatcher.rb +19 -0
  116. data/spec/support/shared_examples/numerical_type_submatcher.rb +17 -0
  117. data/spec/support/test_application.rb +23 -0
  118. metadata +90 -22
  119. checksums.yaml +0 -7
  120. data/spec/shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher_spec.rb +0 -97
  121. data/spec/support/shared_examples/numerical_submatcher_spec.rb +0 -23
@@ -46,7 +46,7 @@ module Shoulda # :nodoc:
46
46
 
47
47
  def redirects_to_url?
48
48
  begin
49
- @context.send(:assert_redirected_to, url)
49
+ @context.__send__(:assert_redirected_to, url)
50
50
  @failure_message_when_negated = "Didn't expect to redirect to #{url}"
51
51
  true
52
52
  rescue Shoulda::Matchers::AssertionError => error
@@ -8,13 +8,13 @@ module Shoulda # :nodoc:
8
8
  # it { should render_template(:show) }
9
9
  #
10
10
  # assert that the "_customer" partial was rendered
11
- # it { should render_template(:partial => '_customer') }
11
+ # it { should render_template(partial: '_customer') }
12
12
  #
13
13
  # assert that the "_customer" partial was rendered twice
14
- # it { should render_template(:partial => '_customer', :count => 2) }
14
+ # it { should render_template(partial: '_customer', count: 2) }
15
15
  #
16
16
  # assert that no partials were rendered
17
- # it { should render_template(:partial => false) }
17
+ # it { should render_template(partial: false) }
18
18
  def render_template(options = {}, message = nil)
19
19
  RenderTemplateMatcher.new(options, message, self)
20
20
  end
@@ -50,7 +50,7 @@ module Shoulda # :nodoc:
50
50
 
51
51
  def renders_template?
52
52
  begin
53
- @context.send(:assert_template, @options, @message)
53
+ @context.__send__(:assert_template, @options, @message)
54
54
  @failure_message_when_negated = "Didn't expect to render #{@template}"
55
55
  true
56
56
  rescue Shoulda::Matchers::AssertionError => error
@@ -72,7 +72,7 @@ module Shoulda
72
72
 
73
73
  def handler_exists?
74
74
  if expected_method.present?
75
- controller.respond_to? expected_method
75
+ controller.respond_to? expected_method, true
76
76
  else
77
77
  true
78
78
  end
@@ -12,20 +12,20 @@ module Shoulda # :nodoc:
12
12
  # Examples:
13
13
  #
14
14
  # it { should route(:get, '/posts').
15
- # to(:controller => :posts, :action => :index) }
15
+ # to(controller: :posts, action: :index) }
16
16
  # it { should route(:get, '/posts').to('posts#index') }
17
- # it { should route(:get, '/posts/new').to(:action => :new) }
18
- # it { should route(:post, '/posts').to(:action => :create) }
19
- # it { should route(:get, '/posts/1').to(:action => :show, :id => 1) }
20
- # it { should route(:get, '/posts/1').to('posts#show', :id => 1) }
21
- # it { should route(:get, '/posts/1/edit').to(:action => :edit, :id => 1) }
22
- # it { should route(:put, '/posts/1').to(:action => :update, :id => 1) }
17
+ # it { should route(:get, '/posts/new').to(action: :new) }
18
+ # it { should route(:post, '/posts').to(action: :create) }
19
+ # it { should route(:get, '/posts/1').to(action: :show, id: 1) }
20
+ # it { should route(:get, '/posts/1').to('posts#show', id: 1) }
21
+ # it { should route(:get, '/posts/1/edit').to(action: :edit, id: 1) }
22
+ # it { should route(:put, '/posts/1').to(action: :update, id: 1) }
23
23
  # it { should route(:delete, '/posts/1').
24
- # to(:action => :destroy, :id => 1) }
24
+ # to(action: :destroy, id: 1) }
25
25
  # it { should route(:get, '/users/1/posts/1').
26
- # to(:action => :show, :id => 1, :user_id => 1) }
26
+ # to(action: :show, id: 1, user_id: 1) }
27
27
  # it { should route(:get, '/users/1/posts/1').
28
- # to('posts#show', :id => 1, :user_id => 1) }
28
+ # to('posts#show', id: 1, user_id: 1) }
29
29
  def route(method, path)
30
30
  RouteMatcher.new(method, path, self)
31
31
  end
@@ -70,8 +70,8 @@ module Shoulda # :nodoc:
70
70
 
71
71
  def route_recognized?
72
72
  begin
73
- @context.send(:assert_routing,
74
- { :method => @method, :path => @path },
73
+ @context.__send__(:assert_routing,
74
+ { method: @method, path: @path },
75
75
  @params)
76
76
 
77
77
  @failure_message_when_negated = "Didn't expect to #{description}"
@@ -25,7 +25,7 @@ module Shoulda # :nodoc:
25
25
  def extract_params_from_string
26
26
  params = args[1] || {}
27
27
  controller, action = args[0].split('#')
28
- params.merge!(:controller => controller, :action => action)
28
+ params.merge!(controller: controller, action: action)
29
29
  end
30
30
 
31
31
  def stringify_params
@@ -85,7 +85,8 @@ module Shoulda # :nodoc:
85
85
 
86
86
  def flash_values
87
87
  if @options.key?(:key)
88
- [flash.to_hash[@options[:key]]]
88
+ flash_hash = HashWithIndifferentAccess.new(flash.to_hash)
89
+ [flash_hash[@options[:key]]]
89
90
  else
90
91
  flash.to_hash.values
91
92
  end
@@ -0,0 +1,167 @@
1
+ begin
2
+ require 'strong_parameters'
3
+ rescue LoadError
4
+ end
5
+
6
+ module Shoulda
7
+ module Matchers
8
+ module ActionController
9
+ def permit(*attributes)
10
+ StrongParametersMatcher.new(self, attributes)
11
+ end
12
+
13
+ class StrongParametersMatcher
14
+ def self.stubbed_parameters_class
15
+ @stubbed_parameters_class ||= build_stubbed_parameters_class
16
+ end
17
+
18
+ def self.build_stubbed_parameters_class
19
+ Class.new(::ActionController::Parameters) do
20
+ include StubbedParameters
21
+ end
22
+ end
23
+
24
+ def initialize(context = nil, attributes)
25
+ @attributes = attributes
26
+ @context = context
27
+ end
28
+
29
+ def for(action, options = {})
30
+ @action = action
31
+ @verb = options[:verb] || verb_for_action
32
+ self
33
+ end
34
+
35
+ def in_context(context)
36
+ @context = context
37
+ self
38
+ end
39
+
40
+ def description
41
+ "permit #{verb.upcase} ##{action} to receive parameters #{attributes_as_sentence}"
42
+ end
43
+
44
+ def matches?(controller = nil)
45
+ simulate_controller_action && parameters_difference.empty?
46
+ end
47
+
48
+ def does_not_match?(controller = nil)
49
+ simulate_controller_action && parameters_intersection.empty?
50
+ end
51
+
52
+ def failure_message
53
+ "Expected controller to permit #{parameters_difference.to_sentence}, but it did not."
54
+ end
55
+ alias failure_message_for_should failure_message
56
+
57
+ def failure_message_when_negated
58
+ "Expected controller not to permit #{parameters_intersection.to_sentence}, but it did."
59
+ end
60
+ alias failure_message_for_should_not failure_message_when_negated
61
+
62
+ private
63
+
64
+ attr_reader :verb, :action, :attributes, :context
65
+
66
+ def simulate_controller_action
67
+ ensure_action_and_verb_present!
68
+ stub_model_attributes
69
+
70
+ begin
71
+ context.send(verb, action)
72
+ ensure
73
+ unstub_model_attributes
74
+ end
75
+
76
+ verify_permit_call
77
+ end
78
+
79
+ def verify_permit_call
80
+ @model_attrs.permit_was_called
81
+ end
82
+
83
+ def parameters_difference
84
+ attributes - @model_attrs.shoulda_permitted_params
85
+ end
86
+
87
+ def parameters_intersection
88
+ attributes & @model_attrs.shoulda_permitted_params
89
+ end
90
+
91
+ def stub_model_attributes
92
+ @model_attrs = self.class.stubbed_parameters_class.new(arbitrary_attributes)
93
+
94
+ local_model_attrs = @model_attrs
95
+ ::ActionController::Parameters.class_eval do
96
+ alias_method :'shoulda_original_[]', :[]
97
+
98
+ define_method :[] do |*args|
99
+ local_model_attrs
100
+ end
101
+ end
102
+ end
103
+
104
+ def unstub_model_attributes
105
+ ::ActionController::Parameters.class_eval do
106
+ alias_method :[], :'shoulda_original_[]'
107
+ undef_method :'shoulda_original_[]'
108
+ end
109
+ end
110
+
111
+ def ensure_action_and_verb_present!
112
+ if action.blank?
113
+ raise ActionNotDefinedError
114
+ end
115
+ if verb.blank?
116
+ raise VerbNotDefinedError
117
+ end
118
+ end
119
+
120
+ def arbitrary_attributes
121
+ {any_key: 'any_value'}
122
+ end
123
+
124
+ def verb_for_action
125
+ verb_lookup = { create: :post, update: :put }
126
+ verb_lookup[action]
127
+ end
128
+
129
+ def attributes_as_sentence
130
+ attributes.map(&:inspect).to_sentence
131
+ end
132
+ end
133
+
134
+ module StrongParametersMatcher::StubbedParameters
135
+ extend ActiveSupport::Concern
136
+
137
+ included do
138
+ attr_accessor :permit_was_called, :shoulda_permitted_params
139
+ end
140
+
141
+ def initialize(*)
142
+ @permit_was_called = false
143
+ super
144
+ end
145
+
146
+ def permit(*args)
147
+ self.shoulda_permitted_params = args
148
+ self.permit_was_called = true
149
+ nil
150
+ end
151
+ end
152
+
153
+ class StrongParametersMatcher::ActionNotDefinedError < StandardError
154
+ def message
155
+ 'You must specify the controller action using the #for method.'
156
+ end
157
+ end
158
+
159
+ class StrongParametersMatcher::VerbNotDefinedError < StandardError
160
+ def message
161
+ 'You must specify an HTTP verb when using a non-RESTful action.' +
162
+ ' e.g. for(:authorize, verb: :post)'
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end
@@ -13,8 +13,10 @@ require 'shoulda/matchers/active_model/validate_uniqueness_of_matcher'
13
13
  require 'shoulda/matchers/active_model/validate_acceptance_of_matcher'
14
14
  require 'shoulda/matchers/active_model/validate_confirmation_of_matcher'
15
15
  require 'shoulda/matchers/active_model/validate_numericality_of_matcher'
16
+ require 'shoulda/matchers/active_model/numericality_matchers/numeric_type_matcher'
16
17
  require 'shoulda/matchers/active_model/numericality_matchers/comparison_matcher'
17
- require 'shoulda/matchers/active_model/numericality_matchers/odd_even_number_matcher'
18
+ require 'shoulda/matchers/active_model/numericality_matchers/odd_number_matcher'
19
+ require 'shoulda/matchers/active_model/numericality_matchers/even_number_matcher'
18
20
  require 'shoulda/matchers/active_model/numericality_matchers/only_integer_matcher'
19
21
  require 'shoulda/matchers/active_model/allow_mass_assignment_of_matcher'
20
22
  require 'shoulda/matchers/active_model/errors'
@@ -44,7 +46,7 @@ module Shoulda
44
46
  # class User < ActiveRecord::Base
45
47
  # validates_presence_of :name
46
48
  # validates_presence_of :phone_number
47
- # validates_inclusion_of :status, :in => %w(Activated Pending), :strict => true
49
+ # validates_inclusion_of :status, in: %w(Activated Pending), strict: true
48
50
  # attr_accessible :name, :phone_number
49
51
  # end
50
52
  module ActiveModel
@@ -13,7 +13,7 @@ module Shoulda # :nodoc:
13
13
  # the test looks for any errors in <tt>errors.on(:attribute)</tt>.
14
14
  # * <tt>strict</tt> - expects the model to raise an exception when the
15
15
  # validation fails rather than adding to the errors collection. Used for
16
- # testing `validates!` and the `:strict => true` validation options.
16
+ # testing `validates!` and the `strict: true` validation options.
17
17
  #
18
18
  # Example:
19
19
  # it { should_not allow_value('bad').for(:isbn) }
@@ -68,7 +68,7 @@ module Shoulda # :nodoc:
68
68
 
69
69
  values_to_match.none? do |value|
70
70
  self.value = value
71
- instance.send("#{attribute_to_set}=", value)
71
+ set_and_double_check_attribute!(attribute_to_set, value)
72
72
  errors_match?
73
73
  end
74
74
  end
@@ -93,6 +93,24 @@ module Shoulda # :nodoc:
93
93
  :instance, :attribute_to_set, :attribute_to_check_message_against,
94
94
  :context, :value, :matched_error
95
95
 
96
+ def set_and_double_check_attribute!(attribute_name, value)
97
+ instance.__send__("#{attribute_name}=", value)
98
+
99
+ if value.nil?
100
+ ensure_attribute_was_cleared!(attribute_name)
101
+ end
102
+ end
103
+
104
+ def ensure_attribute_was_cleared!(attribute_name)
105
+ if instance.respond_to?(attribute_name)
106
+ actual_value = instance.__send__(attribute_name)
107
+
108
+ if !actual_value.nil?
109
+ raise Shoulda::Matchers::ActiveModel::CouldNotClearAttribute.create(actual_value)
110
+ end
111
+ end
112
+ end
113
+
96
114
  def errors_match?
97
115
  has_messages? && errors_for_attribute_match?
98
116
  end
@@ -163,9 +181,9 @@ module Shoulda # :nodoc:
163
181
  def default_attribute_message
164
182
  default_error_message(
165
183
  options[:expected_message],
166
- :model_name => model_name,
167
- :instance => instance,
168
- :attribute => attribute_to_set
184
+ model_name: model_name,
185
+ instance: instance,
186
+ attribute: attribute_to_set
169
187
  )
170
188
  end
171
189
 
@@ -35,10 +35,6 @@ module Shoulda # :nodoc:
35
35
  end
36
36
  alias failure_message_for_should_not failure_message_when_negated
37
37
 
38
- def allowed_types
39
- ''
40
- end
41
-
42
38
  def strict
43
39
  @allow_matcher.strict
44
40
  self
@@ -25,6 +25,25 @@ module Shoulda # :nodoc:
25
25
  ARBITRARY_OUTSIDE_STRING = 'shouldamatchersteststring'
26
26
  ARBITRARY_OUTSIDE_FIXNUM = 123456789
27
27
  ARBITRARY_OUTSIDE_DECIMAL = 0.123456789
28
+ BOOLEAN_ALLOWS_BOOLEAN_MESSAGE = <<EOT
29
+ You are using `ensure_inclusion_of` to assert that a boolean column allows
30
+ boolean values and disallows non-boolean ones. Assuming you are using
31
+ `validates_format_of` in your model, be aware that it is not possible to fully
32
+ test this, and in fact the validation is superfluous, as boolean columns will
33
+ automatically convert non-boolean values to boolean ones. Hence, you should
34
+ consider removing this test and the corresponding validation.
35
+ EOT
36
+ BOOLEAN_ALLOWS_NIL_MESSAGE = <<EOT
37
+ You are using `ensure_inclusion_of` to assert that a boolean column allows nil.
38
+ Be aware that it is not possible to fully test this, as anything other than
39
+ true, false or nil will be converted to false. Hence, you should consider
40
+ removing this test and the corresponding validation.
41
+ EOT
42
+ BOOLEAN_ALLOWS_NIL_WITH_NOT_NULL_MESSAGE = <<EOT
43
+ You have specified that your model's #{@attribute} should ensure inclusion of nil.
44
+ However, #{@attribute} is a boolean column which does not allow null values.
45
+ Hence, this test will fail and there is no way to make it pass.
46
+ EOT
28
47
 
29
48
  def initialize(attribute)
30
49
  super(attribute)
@@ -142,30 +161,63 @@ module Shoulda # :nodoc:
142
161
  end
143
162
 
144
163
  def disallows_value_outside_of_array?
145
- disallows_value_of(value_outside_of_array)
164
+ if attribute_column.type == :boolean
165
+ case @array
166
+ when [true, false]
167
+ Shoulda::Matchers.warn BOOLEAN_ALLOWS_BOOLEAN_MESSAGE
168
+ return true
169
+ when [nil]
170
+ if attribute_column.null
171
+ Shoulda::Matchers.warn BOOLEAN_ALLOWS_NIL_MESSAGE
172
+ return true
173
+ else
174
+ raise NonNullableBooleanError, BOOLEAN_ALLOWS_NIL_WITH_NOT_NULL_MESSAGE
175
+ end
176
+ end
177
+ end
178
+
179
+ !allows_value_of(*values_outside_of_array)
146
180
  end
147
181
 
148
- def value_outside_of_array
149
- if @array.include?(outside_value)
182
+ def values_outside_of_array
183
+ if !(@array & outside_values).empty?
150
184
  raise CouldNotDetermineValueOutsideOfArray
151
185
  else
152
- outside_value
186
+ outside_values
153
187
  end
154
188
  end
155
189
 
156
- def outside_value
157
- @outside_value ||= find_outside_value
190
+ def outside_values
191
+ case attribute_column.type
192
+ when :boolean
193
+ boolean_outside_values
194
+ when :integer, :float
195
+ [ARBITRARY_OUTSIDE_FIXNUM]
196
+ when :decimal
197
+ [ARBITRARY_OUTSIDE_DECIMAL]
198
+ else
199
+ [ARBITRARY_OUTSIDE_STRING]
200
+ end
158
201
  end
159
202
 
160
- def find_outside_value
161
- case @subject.send(@attribute.to_s)
162
- when Fixnum
163
- ARBITRARY_OUTSIDE_FIXNUM
164
- when BigDecimal
165
- ARBITRARY_OUTSIDE_DECIMAL
166
- else
167
- ARBITRARY_OUTSIDE_STRING
203
+ def boolean_outside_values
204
+ values = []
205
+
206
+ values << case @array
207
+ when [true] then false
208
+ when [false] then true
209
+ else raise CouldNotDetermineValueOutsideOfArray
210
+ end
211
+
212
+ if attribute_column.null
213
+ values << nil
168
214
  end
215
+
216
+ values
217
+ end
218
+
219
+ def attribute_column
220
+ @subject.class.columns_hash[@attribute.to_s]
169
221
  end
170
222
  end
171
223
  end