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.
- data/History.txt +5 -5
- data/Manifest.txt +53 -35
- data/PostInstall.txt +1 -6
- data/README.rdoc +109 -15
- data/Rakefile +29 -29
- data/lib/remarkable/active_record/README.markdown +378 -0
- data/lib/remarkable/active_record/active_record.rb +12 -11
- data/lib/remarkable/active_record/helpers.rb +215 -5
- data/lib/remarkable/active_record/macros/associations/association_matcher.rb +242 -0
- data/lib/remarkable/active_record/macros/callbacks/callback_matcher.rb +46 -0
- data/lib/remarkable/active_record/macros/database/column_matcher.rb +122 -0
- data/lib/remarkable/active_record/macros/database/index_matcher.rb +103 -0
- data/lib/remarkable/active_record/macros/validations/allow_mass_assignment_of_matcher.rb +52 -0
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_list_matcher.rb +83 -0
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_range_matcher.rb +172 -0
- data/lib/remarkable/active_record/macros/validations/have_class_methods_matcher.rb +54 -0
- data/lib/remarkable/active_record/macros/validations/have_instance_methods_matcher.rb +54 -0
- data/lib/remarkable/active_record/macros/validations/have_named_scope_matcher.rb +94 -0
- data/lib/remarkable/active_record/macros/validations/have_readonly_attributes_matcher.rb +48 -0
- data/lib/remarkable/active_record/macros/validations/protect_attributes_matcher.rb +51 -0
- data/lib/remarkable/active_record/macros/validations/validate_acceptance_of_matcher.rb +79 -0
- data/lib/remarkable/active_record/macros/validations/validate_associated_matcher.rb +177 -0
- data/lib/remarkable/active_record/macros/validations/validate_confirmation_of_matcher.rb +74 -0
- data/lib/remarkable/active_record/macros/validations/validate_exclusion_of_matcher.rb +38 -0
- data/lib/remarkable/active_record/macros/validations/validate_format_of_matcher.rb +33 -0
- data/lib/remarkable/active_record/macros/validations/validate_inclusion_of_matcher.rb +45 -0
- data/lib/remarkable/active_record/macros/validations/validate_length_of_matcher.rb +248 -0
- data/lib/remarkable/active_record/macros/validations/validate_numericality_of_matcher.rb +206 -0
- data/lib/remarkable/active_record/macros/validations/validate_presence_of_matcher.rb +72 -0
- data/lib/remarkable/active_record/macros/validations/validate_uniqueness_of_matcher.rb +222 -0
- data/lib/remarkable/active_record/macros.rb +52 -0
- data/lib/remarkable/assertions.rb +29 -0
- data/lib/remarkable/controller/README.markdown +147 -0
- data/lib/remarkable/controller/controller.rb +11 -6
- data/lib/remarkable/controller/helpers.rb +4 -38
- data/lib/remarkable/controller/macros/assign_matcher.rb +85 -0
- data/lib/remarkable/controller/macros/filter_params_matcher.rb +63 -0
- data/lib/remarkable/controller/macros/metadata_matcher.rb +63 -0
- data/lib/remarkable/controller/macros/render_with_layout_matcher.rb +75 -0
- data/lib/remarkable/controller/macros/respond_with_content_type_matcher.rb +60 -0
- data/lib/remarkable/controller/macros/respond_with_matcher.rb +62 -0
- data/lib/remarkable/controller/macros/return_from_session_matcher.rb +58 -0
- data/lib/remarkable/controller/macros/route_matcher.rb +75 -0
- data/lib/remarkable/controller/macros/set_the_flash_to_matcher.rb +60 -0
- data/lib/remarkable/controller/macros.rb +78 -0
- data/lib/remarkable/dsl.rb +239 -0
- data/lib/remarkable/example/example_methods.rb +27 -7
- data/lib/remarkable/helpers.rb +28 -0
- data/lib/remarkable/matcher_base.rb +64 -0
- data/lib/remarkable/private_helpers.rb +10 -115
- data/lib/remarkable/rails.rb +27 -0
- data/lib/remarkable.rb +13 -5
- data/remarkable.gemspec +43 -0
- data/spec/controllers/posts_controller_spec.rb +58 -4
- data/spec/controllers/users_controller_spec.rb +1 -0
- data/spec/fixtures/fleas.yml +10 -0
- data/spec/fixtures/users.yml +7 -0
- data/spec/models/address_spec.rb +44 -0
- data/spec/models/dog_spec.rb +64 -3
- data/spec/models/flea_spec.rb +30 -0
- data/spec/models/post_spec.rb +36 -2
- data/spec/models/product_spec.rb +73 -8
- data/spec/models/tag_spec.rb +2 -2
- data/spec/models/tagging_spec.rb +24 -0
- data/spec/models/user_spec.rb +206 -21
- data/spec/other/custom_macros_spec.rb +27 -0
- data/spec/other/my_own_matcher_spec.rb +11 -0
- data/spec/other/private_helpers_spec.rb +31 -0
- data/spec/rails_root/app/controllers/posts_controller.rb +2 -0
- data/spec/rails_root/app/models/address.rb +2 -2
- data/spec/rails_root/app/models/flea.rb +4 -0
- data/spec/rails_root/app/models/pets/dog.rb +12 -0
- data/spec/rails_root/app/models/product.rb +7 -5
- data/spec/rails_root/app/models/tagging.rb +2 -0
- data/spec/rails_root/app/models/user.rb +20 -5
- data/spec/rails_root/app/views/layouts/posts.rhtml +8 -6
- data/spec/rails_root/config/database.yml +1 -2
- data/spec/rails_root/config/environment.rb +3 -1
- data/spec/rails_root/config/environments/{sqlite3.rb → test.rb} +0 -0
- data/spec/rails_root/config/locales/en.yml +8 -0
- data/spec/rails_root/db/migrate/001_create_users.rb +2 -2
- data/spec/rails_root/db/migrate/005_create_dogs.rb +1 -0
- data/spec/rails_root/db/migrate/011_add_fleas_color.rb +10 -0
- data/spec/rails_root/db/migrate/012_add_fleas_address.rb +10 -0
- data/spec/rails_root/spec/remarkable_macros/.keep +0 -0
- data/spec/rails_root/vendor/plugins/my_plugin/remarkable_macros/.keep +0 -0
- data/spec/spec_helper.rb +0 -2
- metadata +63 -43
- data/lib/remarkable/active_record/macros/associations/belong_to.rb +0 -81
- data/lib/remarkable/active_record/macros/associations/have_and_belong_to_many.rb +0 -77
- data/lib/remarkable/active_record/macros/associations/have_many.rb +0 -160
- data/lib/remarkable/active_record/macros/associations/have_one.rb +0 -133
- data/lib/remarkable/active_record/macros/database/have_db_column.rb +0 -81
- data/lib/remarkable/active_record/macros/database/have_db_columns.rb +0 -73
- data/lib/remarkable/active_record/macros/database/have_indices.rb +0 -75
- data/lib/remarkable/active_record/macros/validations/allow_values_for.rb +0 -103
- data/lib/remarkable/active_record/macros/validations/ensure_length_at_least.rb +0 -97
- data/lib/remarkable/active_record/macros/validations/ensure_length_in_range.rb +0 -134
- data/lib/remarkable/active_record/macros/validations/ensure_length_is.rb +0 -106
- data/lib/remarkable/active_record/macros/validations/ensure_value_in_range.rb +0 -117
- data/lib/remarkable/active_record/macros/validations/have_class_methods.rb +0 -74
- data/lib/remarkable/active_record/macros/validations/have_instance_methods.rb +0 -74
- data/lib/remarkable/active_record/macros/validations/have_named_scope.rb +0 -148
- data/lib/remarkable/active_record/macros/validations/have_readonly_attributes.rb +0 -81
- data/lib/remarkable/active_record/macros/validations/only_allow_numeric_values_for.rb +0 -89
- data/lib/remarkable/active_record/macros/validations/protect_attributes.rb +0 -89
- data/lib/remarkable/active_record/macros/validations/require_acceptance_of.rb +0 -94
- data/lib/remarkable/active_record/macros/validations/require_attributes.rb +0 -94
- data/lib/remarkable/active_record/macros/validations/require_unique_attributes.rb +0 -146
- data/lib/remarkable/controller/macros/assign_to.rb +0 -110
- data/lib/remarkable/controller/macros/filter_params.rb +0 -52
- data/lib/remarkable/controller/macros/redirect_to.rb +0 -24
- data/lib/remarkable/controller/macros/render_a_form.rb +0 -23
- data/lib/remarkable/controller/macros/render_template.rb +0 -18
- data/lib/remarkable/controller/macros/render_with_layout.rb +0 -61
- data/lib/remarkable/controller/macros/respond_with.rb +0 -86
- data/lib/remarkable/controller/macros/respond_with_content_type.rb +0 -45
- data/lib/remarkable/controller/macros/return_from_session.rb +0 -45
- data/lib/remarkable/controller/macros/route.rb +0 -91
- data/lib/remarkable/controller/macros/set_the_flash_to.rb +0 -58
- 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
|