carlosbrando-remarkable 0.0.99 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/History.txt +5 -5
  2. data/Manifest.txt +53 -35
  3. data/PostInstall.txt +1 -6
  4. data/README.rdoc +109 -15
  5. data/Rakefile +29 -29
  6. data/lib/remarkable/active_record/README.markdown +378 -0
  7. data/lib/remarkable/active_record/active_record.rb +12 -11
  8. data/lib/remarkable/active_record/helpers.rb +215 -5
  9. data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
  10. data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
  11. data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
  12. data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
  13. data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
  14. data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
  15. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
  16. data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
  17. data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
  18. data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
  19. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
  20. data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
  21. data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
  22. data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
  23. data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
  24. data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
  25. data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
  26. data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
  27. data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
  28. data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
  29. data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
  30. data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
  31. data/lib/remarkable/active_record/macros.rb +52 -0
  32. data/lib/remarkable/assertions.rb +29 -0
  33. data/lib/remarkable/controller/README.markdown +147 -0
  34. data/lib/remarkable/controller/controller.rb +11 -6
  35. data/lib/remarkable/controller/helpers.rb +4 -38
  36. data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
  37. data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
  38. data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
  39. data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
  40. data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
  41. data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
  42. data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
  43. data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
  44. data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
  45. data/lib/remarkable/controller/macros.rb +78 -0
  46. data/lib/remarkable/dsl.rb +239 -0
  47. data/lib/remarkable/example/example_methods.rb +27 -7
  48. data/lib/remarkable/helpers.rb +28 -0
  49. data/lib/remarkable/matcher_base.rb +64 -0
  50. data/lib/remarkable/private_helpers.rb +10 -115
  51. data/lib/remarkable/rails.rb +27 -0
  52. data/lib/remarkable.rb +13 -5
  53. data/remarkable.gemspec +43 -0
  54. data/spec/controllers/posts_controller_spec.rb +58 -4
  55. data/spec/controllers/users_controller_spec.rb +1 -0
  56. data/spec/fixtures/fleas.yml +10 -0
  57. data/spec/fixtures/users.yml +7 -0
  58. data/spec/models/address_spec.rb +44 -0
  59. data/spec/models/dog_spec.rb +64 -3
  60. data/spec/models/flea_spec.rb +30 -0
  61. data/spec/models/post_spec.rb +36 -2
  62. data/spec/models/product_spec.rb +73 -8
  63. data/spec/models/tag_spec.rb +2 -2
  64. data/spec/models/tagging_spec.rb +24 -0
  65. data/spec/models/user_spec.rb +206 -21
  66. data/spec/other/custom_macros_spec.rb +27 -0
  67. data/spec/other/my_own_matcher_spec.rb +11 -0
  68. data/spec/other/private_helpers_spec.rb +31 -0
  69. data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
  70. data/spec/rails_root/app/models/address.rb +2 -2
  71. data/spec/rails_root/app/models/flea.rb +4 -0
  72. data/spec/rails_root/app/models/pets/dog.rb +12 -0
  73. data/spec/rails_root/app/models/product.rb +7 -5
  74. data/spec/rails_root/app/models/tagging.rb +2 -0
  75. data/spec/rails_root/app/models/user.rb +20 -5
  76. data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
  77. data/spec/rails_root/config/database.yml +1 -2
  78. data/spec/rails_root/config/environment.rb +3 -1
  79. data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
  80. data/spec/rails_root/config/locales/en.yml +8 -0
  81. data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
  82. data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
  83. data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
  84. data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
  85. data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
  86. data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
  87. data/spec/spec_helper.rb +0 -2
  88. metadata +63 -43
  89. data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
  90. data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
  91. data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
  92. data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
  93. data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
  94. data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
  95. data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
  96. data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
  97. data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
  98. data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
  99. data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
  100. data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
  101. data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
  102. data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
  103. data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
  104. data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
  105. data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
  106. data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
  107. data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
  108. data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
  109. data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
  110. data/lib/remarkable/controller/macros/assign_to.rb +0 -110
  111. data/lib/remarkable/controller/macros/filter_params.rb +0 -52
  112. data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
  113. data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
  114. data/lib/remarkable/controller/macros/render_template.rb +0 -18
  115. data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
  116. data/lib/remarkable/controller/macros/respond_with.rb +0 -86
  117. data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
  118. data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
  119. data/lib/remarkable/controller/macros/route.rb +0 -91
  120. data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
  121. data/spec/rails_root/app/models/dog.rb +0 -5
@@ -0,0 +1,177 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class ValidateAssociatedMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ undef_method :allow_nil, :allow_nil?, :allow_blank, :allow_blank?
8
+
9
+ def initialize(*associations)
10
+ load_options(associations.extract_options!)
11
+ @associations = associations
12
+ end
13
+
14
+ def builder(value)
15
+ @options[:builder] = value
16
+ self
17
+ end
18
+
19
+ def matches?(subject)
20
+ @subject = get_instance_of(subject)
21
+
22
+ assert_matcher_for(@associations) do |association|
23
+ @association = association
24
+ build_association? && valid?
25
+ end
26
+ end
27
+
28
+ def description
29
+ "require association #{@associations.to_sentence} to be valid"
30
+ end
31
+
32
+ private
33
+
34
+ # Try to build the association. First we check if the user sent
35
+ # a builder as proc. An example of proc given could be:
36
+ #
37
+ # proc { |subject| subject.associations.build }
38
+ #
39
+ # Second we attempt to get a instance variable with the singular name
40
+ # of the association. If not possible, we finally try:
41
+ #
42
+ # subject.associations.build
43
+ #
44
+ # Our last option is:
45
+ #
46
+ # subject.build_association
47
+ #
48
+ def build_association?
49
+ @plural = @association.to_s.pluralize
50
+ @singular = @plural.singularize
51
+ @association_object = nil
52
+
53
+ if @options[:builder].is_a? Proc
54
+ @association_object = @options[:builder].call(@subject)
55
+ elsif !@spec.instance_variable_get("@#{@singular}").nil?
56
+ @association_object = @spec.instance_variable_get("@#{@singular}")
57
+ end
58
+
59
+ if plural?
60
+ if @association_object # Append the association if already found
61
+ @subject.send(@plural).send('<<', @association_object)
62
+ elsif @subject.send(@plural).respond_to?(:build)
63
+ @association_object = @subject.send(@plural).send(:build)
64
+ end
65
+ elsif singular?
66
+ if @association_object # Set the association if already found
67
+ @subject.send("#{@singular}=", @association_object)
68
+ elsif @subject.respond_to?("build_#{@singular}")
69
+ @association_object = @subject.send("build_#{@singular}")
70
+ end
71
+ end
72
+
73
+ return true if @association_object
74
+
75
+ @missing = "cannot build association, tried to find instance variable " +
76
+ "@#{subject_name.downcase}, then tried @#{subject_name.downcase}.#{@plural}.build " +
77
+ "and @#{subject_name.downcase}.build_#{@singular} without success."
78
+
79
+ @missing << " Please give a proc as optional :builder to build the association." unless @options[:builder].is_a? Proc
80
+
81
+ return false
82
+ end
83
+
84
+ def valid?
85
+ # Try to save the association
86
+ association_saved = if plural?
87
+ @subject.send(@plural).last.save
88
+ elsif singular?
89
+ @subject.send(@singular).save
90
+ end
91
+
92
+ if association_saved
93
+ @missing = "the given association #{@association} cannot be saved with errors"
94
+ return false
95
+ end
96
+
97
+ @subject.save
98
+
99
+ error_message_to_expect = error_message_from_model(@subject, :base, @options[:message])
100
+ return true if assert_contains(@subject.errors.on(plural? ? @plural : @singular), error_message_to_expect)
101
+
102
+ @missing = "#{subject_class} is not invalid when #{@association} is invalid"
103
+ false
104
+ end
105
+
106
+ # Receives a Hash
107
+ def load_options(options = {})
108
+ @options = {
109
+ :message => :invalid
110
+ }.merge(options)
111
+ end
112
+
113
+ def plural?
114
+ @subject.respond_to? @plural
115
+ end
116
+
117
+ def singular?
118
+ @subject.respond_to? @singular
119
+ end
120
+
121
+ def expectation
122
+ "that #{subject_name} is invalid if #{@association} is invalid"
123
+ end
124
+ end
125
+
126
+ # Ensures that the model is invalid if one of the associations given is
127
+ # invalid.
128
+ #
129
+ # If an instance variable has been created in the setup named after the
130
+ # model being tested, then this method will use that. Otherwise, it will
131
+ # create a new instance to test against.
132
+ #
133
+ # It tries to build an instance of the association by two ways. Let's
134
+ # suppose a user that has many projects and you want to validate it:
135
+ #
136
+ # it { should validate_associated(:projects) }
137
+ #
138
+ # The first attempt to build the association would be:
139
+ #
140
+ # @user.projects.build
141
+ #
142
+ # If not possible, then we try:
143
+ #
144
+ # @user.build_project
145
+ #
146
+ # Then it tries to save the associated object. If the object can be saved
147
+ # if success (in this case, it allows all attributes as blank), we won't
148
+ # be able to verify the validation and then an error will be raised. In
149
+ # such cases, you should instantiate the association before calling the
150
+ # matcher:
151
+ #
152
+ # it do
153
+ # @user = User.new
154
+ # @project = @user.projects.build
155
+ # should validate_associated(:projects)
156
+ # end
157
+ #
158
+ # Or give :builder as option:
159
+ #
160
+ # should_validate_associated :projects, :builder => proc { |user| user.projects.build }
161
+ #
162
+ # Options:
163
+ # * <tt>:builder</tt> - a proc to build the association.
164
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
165
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
166
+ #
167
+ # Example:
168
+ #
169
+ # it { should validate_associated(:projects, :account) }
170
+ # it { should validate_associated(:projects, :builder => proc { |user| user.projects.build }) }
171
+ #
172
+ def validate_associated(*attributes)
173
+ ValidateAssociatedMatcher.new(*attributes)
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,74 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class ValidateConfirmationOfMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ undef_method :allow_nil?, :allow_nil, :allow_blank?, :allow_blank
8
+
9
+ def initialize(*attributes)
10
+ load_options(attributes.extract_options!)
11
+ @attributes = attributes
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = get_instance_of(subject)
16
+
17
+ assert_matcher_for(@attributes) do |attribute|
18
+ @attribute = attribute
19
+ confirmed?
20
+ end
21
+ end
22
+
23
+ def description
24
+ "validate confirmation of #{@attributes.to_sentence}"
25
+ end
26
+
27
+ private
28
+
29
+ def confirmed?
30
+ confirmation_assignment = "#{@attribute}_confirmation="
31
+
32
+ if @subject.respond_to? confirmation_assignment
33
+ @subject.send(confirmation_assignment, 'something')
34
+ return true if bad?('different')
35
+
36
+ @missing = "#{subject_name} is valid even if confirmation does not match"
37
+ return false
38
+ else
39
+ @missing = "#{subject_name} does not respond to #{confirmation_assignment}"
40
+ return false
41
+ end
42
+ end
43
+
44
+ def load_options(options = {})
45
+ @options = {
46
+ :message => :confirmation
47
+ }.merge(options)
48
+ end
49
+
50
+ def expectation
51
+ "#{@attribute} to be confirmed"
52
+ end
53
+ end
54
+
55
+ # Ensures that the model cannot be saved if one of the attributes is not confirmed.
56
+ #
57
+ # If an instance variable has been created in the setup named after the
58
+ # model being tested, then this method will use that. Otherwise, it will
59
+ # create a new instance to test against.
60
+ #
61
+ # Options:
62
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
63
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.confirmation')</tt>
64
+ #
65
+ # Example:
66
+ # it { should validate_confirmation_of(:email, :password) }
67
+ #
68
+ def validate_confirmation_of(*attributes)
69
+ ValidateConfirmationOfMatcher.new(*attributes)
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,38 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ # Ensures that given values are not valid for the attribute. If a range
6
+ # is given, ensures that the attribute is not valid in the given range.
7
+ #
8
+ # If an instance variable has been created in the setup named after the
9
+ # model being tested, then this method will use that. Otherwise, it will
10
+ # create a new instance to test against.
11
+ #
12
+ # Note: this matcher accepts at once just one attribute to test.
13
+ #
14
+ # Options:
15
+ #
16
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
17
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
18
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
19
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.exclusion')</tt>
20
+ #
21
+ # Example:
22
+ #
23
+ # it { should validate_exclusion_of(:username, "admin", "user") }
24
+ # it { should_not validate_exclusion_of(:username, "clark_kent", "peter_park") }
25
+ #
26
+ # it { should validate_exclusion_of(:age, 30..60) }
27
+ #
28
+ def validate_exclusion_of(attribute, *good_values)
29
+ if good_values.first.is_a? Range
30
+ EnsureValueInRangeMatcher.new(attribute, :exclusion, *good_values)
31
+ else
32
+ EnsureValueInListMatcher.new(attribute, :exclusion, *good_values)
33
+ end
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ # Ensures that the attribute can be set to the given values.
6
+ #
7
+ # If an instance variable has been created in the setup named after the
8
+ # model being tested, then this method will use that. Otherwise, it will
9
+ # create a new instance to test against.
10
+ #
11
+ # Note: this matcher accepts at once just one attribute to test.
12
+ # Note: this matcher is also aliased as "allow_values_for"
13
+ #
14
+ # Options:
15
+ #
16
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
17
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
18
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
19
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.invalid')</tt>
20
+ #
21
+ # Example:
22
+ #
23
+ # it { should validate_format_of(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
24
+ # it { should_not validate_format_of(:isbn, "bad 1", "bad 2") }
25
+ #
26
+ def validate_format_of(attribute, *good_values)
27
+ EnsureValueInListMatcher.new(attribute, :invalid, *good_values)
28
+ end
29
+ alias :allow_values_for :validate_format_of
30
+
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,45 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+
5
+ # Ensures that given values are valid for the attribute. If a range
6
+ # is given, ensures that the attribute is valid in the given range.
7
+ #
8
+ # If an instance variable has been created in the setup named after the
9
+ # model being tested, then this method will use that. Otherwise, it will
10
+ # create a new instance to test against.
11
+ #
12
+ # Note: this matcher accepts at once just one attribute to test.
13
+ #
14
+ # Options:
15
+ #
16
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
17
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
18
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
19
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.inclusion')</tt>
20
+ #
21
+ # Example:
22
+ #
23
+ # it { should validate_inclusion_of(:isbn, "isbn 1 2345 6789 0", "ISBN 1-2345-6789-0") }
24
+ # it { should_not validate_inclusion_of(:isbn, "bad 1", "bad 2") }
25
+ #
26
+ # it { should validate_inclusion_of(:age, 18..100) }
27
+ #
28
+ def validate_inclusion_of(attribute, *good_values)
29
+ if good_values.first.is_a? Range
30
+ EnsureValueInRangeMatcher.new(attribute, :inclusion, *good_values)
31
+ else
32
+ EnsureValueInListMatcher.new(attribute, :inclusion, *good_values)
33
+ end
34
+ end
35
+
36
+ # TODO This one is for shoulda compatibility. Deprecate it?
37
+ def ensure_inclusion_of(attribute, *good_values) #:nodoc:
38
+ warn "[DEPRECATION] should_ensure_inclusion_of is deprecated. " <<
39
+ "Use should_validate_inclusion_of instead."
40
+ validate_inclusion_of(attribute, *good_values)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,248 @@
1
+ module Remarkable # :nodoc:
2
+ module ActiveRecord # :nodoc:
3
+ module Matchers # :nodoc:
4
+ class ValidateLengthOfMatcher < Remarkable::Matcher::Base
5
+ include Remarkable::ActiveRecord::Helpers
6
+
7
+ def initialize(attributes, range, behavior, options = {})
8
+ @attributes = attributes
9
+ @behavior = behavior
10
+
11
+ # Set the values, for example:
12
+ #
13
+ # send(:within, 0..10)
14
+ #
15
+ send(@behavior, range)
16
+
17
+ load_options(options)
18
+ end
19
+
20
+ # If message is supplied, reassign it properly to :short_message
21
+ # and :long_message. This is ActiveRecord default behavior when
22
+ # the validation is :maximum, :minimum or :is.
23
+ #
24
+ def message(message)
25
+ if [:is, :minimum, :maximum].include? @behavior
26
+ short_message(message)
27
+ long_message(message)
28
+ end
29
+ self
30
+ end
31
+
32
+ def within(range)
33
+ @behavior = :within
34
+ @minimum = range.first
35
+ @maximum = range.last
36
+ self
37
+ end
38
+ alias :in :within
39
+
40
+ def minimum(value)
41
+ @minimum = value
42
+ self
43
+ end
44
+
45
+ def maximum(value)
46
+ @maximum = value
47
+ self
48
+ end
49
+
50
+ def is(value)
51
+ @minimum = value
52
+ @maximum = value
53
+ self
54
+ end
55
+
56
+ def short_message(message)
57
+ @options[:short_message] = message
58
+ @options[:message] = message # make a copy in @options[:message], for
59
+ # allow_blank and allow_nil work properly.
60
+ self
61
+ end
62
+
63
+ def long_message(message)
64
+ @options[:long_message] = message
65
+ self
66
+ end
67
+
68
+ def matches?(subject)
69
+ @subject = subject
70
+
71
+ assert_matcher_for(@attributes) do |attribute|
72
+ @attribute = attribute
73
+
74
+ less_than_min_length? && exactly_min_length? && allow_nil?(:message, @minimum) &&
75
+ more_than_max_length? && exactly_max_length? && allow_blank?(:message, @minimum)
76
+ end
77
+ end
78
+
79
+ def description
80
+ "ensure #{expectation}"
81
+ end
82
+
83
+ def failure_message
84
+ "Expected #{expectation} (#{@missing})"
85
+ end
86
+
87
+ def negative_failure_message
88
+ "Did not expect #{expectation}"
89
+ end
90
+
91
+ private
92
+
93
+ def less_than_min_length?
94
+ return true if @behavior == :maximum || @minimum <= 0
95
+ return true if bad?(value_for_length(@minimum - 1), :short_message, @minimum)
96
+
97
+ @missing = "allow #{@attribute} to be less than #{@minimum} chars long"
98
+ return false
99
+ end
100
+
101
+ def exactly_min_length?
102
+ return true if @behavior == :maximum || @minimum <= 0
103
+ return true if good?(value_for_length(@minimum), :short_message, @minimum)
104
+
105
+ @missing = "not allow #{@attribute} to be exactly #{@minimum} chars long"
106
+ return false
107
+ end
108
+
109
+ def more_than_max_length?
110
+ return true if @behavior == :minimum
111
+ return true if bad?(value_for_length(@maximum + 1), :long_message, @maximum)
112
+
113
+ @missing = "allow #{@attribute} to be more than #{@maximum} chars long"
114
+ return false
115
+ end
116
+
117
+ def exactly_max_length?
118
+ return true if @behavior == :minimum || @minimum == @maximum
119
+ return true if good?(value_for_length(@maximum), :long_message, @maximum)
120
+
121
+ @missing = "not allow #{@attribute} to be exactly #{@maximum} chars long"
122
+ return false
123
+ end
124
+
125
+ def load_options(options)
126
+ if @behavior == :is
127
+ @options = {
128
+ :short_message => :wrong_length,
129
+ :long_message => :wrong_length
130
+ }.merge(options)
131
+ else
132
+ @options = {
133
+ :short_message => :too_short,
134
+ :long_message => :too_long
135
+ }.merge(options)
136
+ end
137
+
138
+ # Reassign messages properly
139
+ message(@options[:message]) if @options[:message]
140
+ long_message(@options[:long_message])
141
+ short_message(@options[:short_message])
142
+ end
143
+
144
+ def expectation
145
+ message = "that the length of the #{@attribute} is "
146
+
147
+ message << if @behavior == :within
148
+ "between #{@minimum} and #{@maximum}"
149
+ elsif @behavior == :minimum
150
+ "more than #{@minimum}"
151
+ elsif @behavior == :maximum
152
+ "less than #{@maximum}"
153
+ else #:is
154
+ "equal to #{@minimum}"
155
+ end
156
+
157
+ message << " or nil" if @options[:allow_nil]
158
+ message << " or blank" if @options[:allow_blank]
159
+ message
160
+ end
161
+
162
+ def value_for_length(value)
163
+ "x" * value
164
+ end
165
+ end
166
+
167
+ # Validates the length of the given attributes. You have also to supply
168
+ # one of the following options: minimum, maximum, is or within.
169
+ #
170
+ # If an instance variable has been created in the setup named after the
171
+ # model being tested, then this method will use that. Otherwise, it will
172
+ # create a new instance to test against.
173
+ #
174
+ # Note: this method is also aliased as <tt>validate_size_of</tt>.
175
+ #
176
+ # Options:
177
+ #
178
+ # * <tt>:minimum</tt> - The minimum size of the attribute.
179
+ # * <tt>:maximum</tt> - The maximum size of the attribute.
180
+ # * <tt>:is</tt> - The exact size of the attribute.
181
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of the attribute.
182
+ # * <tt>:in</tt> - A synonym(or alias) for :within.
183
+ # * <tt>:allow_nil</tt> - when supplied, validates if it allows nil or not.
184
+ # * <tt>:allow_blank</tt> - when supplied, validates if it allows blank or not.
185
+ # * <tt>:short_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
186
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_short') % range.first</tt>
187
+ # * <tt>:long_message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt>.
188
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.too_long') % range.last</tt>
189
+ # * <tt>:message</tt> - value the test expects to find in <tt>errors.on(:attribute)</tt> only when :minimum, :maximum or :is is given.
190
+ # Regexp, string or symbol. Default = <tt>I18n.translate('activerecord.errors.messages.wrong_length') % value</tt>
191
+ #
192
+ # Example:
193
+ #
194
+ # it { should validate_length_of(:password, :within => 6..20) }
195
+ # it { should validate_length_of(:password, :maximum => 20) }
196
+ # it { should validate_length_of(:password, :minimum => 6) }
197
+ # it { should validate_length_of(:age, :is => 18) }
198
+ #
199
+ # it { should validate_length_of(:password).within(6..20) }
200
+ # it { should validate_length_of(:password).maximum(20) }
201
+ # it { should validate_length_of(:password).minimum(6) }
202
+ # it { should validate_length_of(:age).is(18) }
203
+ #
204
+ def validate_length_of(*attributes)
205
+ matcher = nil
206
+ options = attributes.extract_options!
207
+
208
+ [:within, :in, :maximum, :minimum, :is].each do |behavior|
209
+ if options.key? behavior
210
+ matcher ||= ValidateLengthOfMatcher.new(attributes, options.delete(behavior), behavior, options)
211
+ end
212
+ end
213
+
214
+ raise ArgumentError, 'You have to give one of these options: :within, :is, :maximum or :minimum.' if matcher.nil?
215
+ matcher
216
+ end
217
+ alias :validate_size_of :validate_length_of
218
+
219
+ # TODO This one is for shoulda compatibility. Deprecate it?
220
+ def ensure_length_of(*attributes) #:nodoc:
221
+ warn "[DEPRECATION] should_ensure_length_of is deprecated. " <<
222
+ "Use should_validate_length_of instead."
223
+ validate_length_of(*attributes)
224
+ end
225
+
226
+ # TODO Deprecate me
227
+ def ensure_length_in_range(attribute, range, options = {}) #:nodoc:
228
+ warn "[DEPRECATION] should_ensure_length_in_range is deprecated. " <<
229
+ "Use should_validate_length_of(#{attribute.inspect}, :in => #{range.inspect}) instead."
230
+ ValidateLengthOfMatcher.new([attribute], range, :within, options)
231
+ end
232
+
233
+ # TODO Deprecate me
234
+ def ensure_length_at_least(attribute, range, options = {}) #:nodoc:
235
+ warn "[DEPRECATION] should_ensure_length_at_least is deprecated. " <<
236
+ "Use should_validate_length_of(#{attribute.inspect}, :minimum => #{range.inspect}) instead."
237
+ ValidateLengthOfMatcher.new([attribute], range, :minimum, options)
238
+ end
239
+
240
+ # TODO Deprecate me
241
+ def ensure_length_is(attribute, range, options = {}) #:nodoc:
242
+ warn "[DEPRECATION] should_ensure_length_is is deprecated. " <<
243
+ "Use should_validate_length_of(#{attribute.inspect}, :is => #{range.inspect}) instead."
244
+ ValidateLengthOfMatcher.new([attribute], range, :is, options)
245
+ end
246
+ end
247
+ end
248
+ end