vizjerai-validatable 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/README.rdoc +118 -0
  2. data/Rakefile +54 -0
  3. data/VERSION.yml +4 -0
  4. data/lib/child_validation.rb +15 -0
  5. data/lib/errors.rb +88 -0
  6. data/lib/included_validation.rb +9 -0
  7. data/lib/macros.rb +304 -0
  8. data/lib/object_extension.rb +21 -0
  9. data/lib/requireable.rb +26 -0
  10. data/lib/understandable.rb +31 -0
  11. data/lib/validatable.rb +23 -0
  12. data/lib/validatable_class_methods.rb +85 -0
  13. data/lib/validatable_instance_methods.rb +106 -0
  14. data/lib/validations/validates_acceptance_of.rb +14 -0
  15. data/lib/validations/validates_confirmation_of.rb +23 -0
  16. data/lib/validations/validates_each.rb +14 -0
  17. data/lib/validations/validates_format_of.rb +16 -0
  18. data/lib/validations/validates_inclusion_of.rb +25 -0
  19. data/lib/validations/validates_length_of.rb +30 -0
  20. data/lib/validations/validates_numericality_of.rb +27 -0
  21. data/lib/validations/validates_presence_of.rb +17 -0
  22. data/lib/validations/validates_true_for.rb +13 -0
  23. data/lib/validations/validation_base.rb +86 -0
  24. data/test/all_tests.rb +1 -0
  25. data/test/functional/test_validatable.rb +437 -0
  26. data/test/functional/test_validates_acceptance_of.rb +16 -0
  27. data/test/functional/test_validates_confirmation_of.rb +56 -0
  28. data/test/functional/test_validates_each.rb +14 -0
  29. data/test/functional/test_validates_format_of.rb +34 -0
  30. data/test/functional/test_validates_inclusion_of.rb +53 -0
  31. data/test/functional/test_validates_length_of.rb +64 -0
  32. data/test/functional/test_validates_numericality_of.rb +57 -0
  33. data/test/functional/test_validates_presence_of.rb +16 -0
  34. data/test/functional/test_validates_true_for.rb +27 -0
  35. data/test/test_helper.rb +33 -0
  36. data/test/unit/test_errors.rb +64 -0
  37. data/test/unit/test_understandable.rb +19 -0
  38. data/test/unit/test_validatable.rb +38 -0
  39. data/test/unit/test_validates_acceptance_of.rb +45 -0
  40. data/test/unit/test_validates_confirmation_of.rb +76 -0
  41. data/test/unit/test_validates_format_of.rb +44 -0
  42. data/test/unit/test_validates_inclusion_of.rb +35 -0
  43. data/test/unit/test_validates_length_of.rb +80 -0
  44. data/test/unit/test_validates_numericality_of.rb +76 -0
  45. data/test/unit/test_validates_presence_of.rb +35 -0
  46. data/test/unit/test_validates_true_for.rb +29 -0
  47. data/test/unit/test_validation_base.rb +52 -0
  48. metadata +123 -0
data/README.rdoc ADDED
@@ -0,0 +1,118 @@
1
+ = Validatable
2
+
3
+ Validatable is a library for adding validations.
4
+
5
+ by Jay[http://jayfields.blogspot.com] Fields[http://jayfields.blogspot.com]
6
+
7
+ == Download and Installation
8
+
9
+ You can download Validatable from here[http://rubyforge.org/projects/validatable] or install it with the following command.
10
+
11
+ $ gem install validatable
12
+
13
+ == License
14
+
15
+ You may use, copy and redistribute this library under the same terms as Ruby itself (see http://www.ruby-lang.org/en/LICENSE.txt).
16
+
17
+ == Examples
18
+
19
+ Validation of an entire hierarchy of objects with errors aggregated at the root object.
20
+
21
+ class Person
22
+ include Validatable
23
+ validates_presence_of :name
24
+ attr_accessor :name
25
+ end
26
+
27
+ class PersonPresenter
28
+ include Validatable
29
+ include_validations_for :person
30
+ attr_accessor :person
31
+
32
+ def initialize(person)
33
+ @person = person
34
+ end
35
+ end
36
+
37
+ presenter = PersonPresenter.new(Person.new)
38
+ presenter.valid? #=> false
39
+ presenter.errors.on(:name) #=> "can't be blank"
40
+
41
+ Validations that turn off after X times of failed attempts.
42
+
43
+ class Person
44
+ include Validatable
45
+ validates_presence_of :name, :times => 1
46
+ attr_accessor :name
47
+ end
48
+
49
+ person = Person.new
50
+ person.valid? #=> false
51
+ person.valid? #=> true
52
+
53
+ Validations can be given levels. If a validation fails on a level the validations for subsequent levels will not be executed.
54
+
55
+ class Person
56
+ include Validatable
57
+ validates_presence_of :name, :level => 1, :message => "name message"
58
+ validates_presence_of :address, :level => 2
59
+ attr_accessor :name, :address
60
+ end
61
+
62
+ person = Person.new
63
+ person.valid? #=> false
64
+ person.errors.on(:name) #=> "name message"
65
+ person.errors.on(:address) #=> nil
66
+
67
+ Validations can also be given groups. Groups can be used to validate an object when it can be valid in various states. For example a mortgage application may be valid for saving (saving a partial application), but that same mortgage application would not be valid for underwriting. In our example a application can be saved as long as a Social Security Number is present; however, an application can not be underwritten unless the name attribute contains a value.
68
+
69
+ class MortgageApplication
70
+ include Validatable
71
+ validates_presence_of :ssn, :groups => [:saving, :underwriting]
72
+ validates_presence_of :name, :groups => :underwriting
73
+ attr_accessor :name, :ssn
74
+ end
75
+
76
+ application = MortgageApplication.new
77
+ application.ssn = 377990118
78
+ application.valid_for_saving? #=> true
79
+ application.valid_for_underwriting? #=> false
80
+
81
+ As you can see, you can use an array if the validation needs to be part of various groups. However, if the validation only applies to one group you can simply use a symbol for the group name.
82
+
83
+ Similar to Rails, Validatable also supports conditional validation.
84
+
85
+ class Person
86
+ include Validatable
87
+ attr_accessor :name
88
+ validates_format_of :name, :with => /.+/, :if => Proc.new { !name.nil? }
89
+ end
90
+ Person.new.valid? #=> true
91
+
92
+ Validatable also exposes an after_validate hook method.
93
+
94
+ class Person
95
+ include Validatable
96
+ validates_presence_of :name
97
+ attr_accessor :name
98
+ end
99
+
100
+ class ValidatesPresenceOf
101
+ after_validate do |result, instance, attribute|
102
+ instance.errors.add("#{attribute} can't be blank") unless result
103
+ end
104
+ end
105
+
106
+ person = Person.new
107
+ person.valid? #=> false
108
+ person.errors.on(:name) #=> "name can't be blank"
109
+
110
+ The after_validate hook yields the result of the validation being run,
111
+ the instance the validation was run on, and the attribute that was validated
112
+
113
+ In the above example the attribute "name" is appended to the message.
114
+
115
+ See the tests for more examples
116
+
117
+ == Contributors
118
+ Rick Bradley, Anonymous Z, Jason Miller, Ali Aghareza, Xavier Shay, Dan Manges, Karthik Krishnan and Venkat, Clint Bishop, Chris Didyk, Yi Wen
data/Rakefile ADDED
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "validatable"
8
+ gem.summary = %Q{Validatable is a library for adding validations.}
9
+ gem.email = "nunemaker@gmail.com"
10
+ gem.homepage = "http://github.com/jnunemaker/validatable"
11
+ gem.authors = ['Jay Fields', 'John Nunemaker']
12
+ gem.files = FileList['lib/**/*.rb', '[A-Z]*', 'test/**/*'].to_a
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ require 'rake/testtask'
20
+ Rake::TestTask.new(:test) do |test|
21
+ test.libs << 'lib' << 'test'
22
+ test.pattern = 'test/**/test_*.rb'
23
+ test.verbose = true
24
+ end
25
+
26
+ begin
27
+ require 'rcov/rcovtask'
28
+ Rcov::RcovTask.new do |test|
29
+ test.libs << 'test'
30
+ test.pattern = 'test/**/test_*.rb'
31
+ test.verbose = true
32
+ end
33
+ rescue LoadError
34
+ task :rcov do
35
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
36
+ end
37
+ end
38
+
39
+ task :default => :test
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ if File.exist?('VERSION.yml')
44
+ config = YAML.load(File.read('VERSION.yml'))
45
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
46
+ else
47
+ version = ""
48
+ end
49
+
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = "validatable #{version}"
52
+ rdoc.rdoc_files.include('README*')
53
+ rdoc.rdoc_files.include('lib/**/*.rb')
54
+ end
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :patch: 0
3
+ :major: 1
4
+ :minor: 8
@@ -0,0 +1,15 @@
1
+ module Validatable
2
+ class ChildValidation #:nodoc:
3
+ attr_accessor :attribute, :map, :should_validate_proc
4
+
5
+ def initialize(attribute, map, should_validate_proc)
6
+ @attribute = attribute
7
+ @map = map
8
+ @should_validate_proc = should_validate_proc
9
+ end
10
+
11
+ def should_validate?(instance)
12
+ instance.instance_eval &should_validate_proc
13
+ end
14
+ end
15
+ end
data/lib/errors.rb ADDED
@@ -0,0 +1,88 @@
1
+ module Validatable
2
+ class Errors
3
+ extend Forwardable
4
+ include Enumerable
5
+
6
+ def_delegators :errors, :clear, :each, :each_pair, :empty?, :length, :size
7
+
8
+ # Returns true if the specified +attribute+ has errors associated with it.
9
+ #
10
+ # class Company < ActiveRecord::Base
11
+ # validates_presence_of :name, :address, :email
12
+ # validates_length_of :name, :in => 5..30
13
+ # end
14
+ #
15
+ # company = Company.create(:address => '123 First St.')
16
+ # company.errors.invalid?(:name) # => true
17
+ # company.errors.invalid?(:address) # => false
18
+ def invalid?(attribute)
19
+ !@errors[attribute.to_sym].nil?
20
+ end
21
+
22
+ # call-seq: on(attribute)
23
+ #
24
+ # * Returns nil, if no errors are associated with the specified +attribute+.
25
+ # * Returns the error message, if one error is associated with the specified +attribute+.
26
+ # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
27
+ def on(attribute)
28
+ return nil if errors[attribute.to_sym].nil?
29
+ errors[attribute.to_sym].size == 1 ? errors[attribute.to_sym].first : errors[attribute.to_sym]
30
+ end
31
+
32
+ def add(attribute, message) #:nodoc:
33
+ errors[attribute.to_sym] = [] if errors[attribute.to_sym].nil?
34
+ errors[attribute.to_sym] << message
35
+ end
36
+
37
+ def merge!(errors) #:nodoc:
38
+ errors.each_pair{|k, v| add(k,v)}
39
+ self
40
+ end
41
+
42
+ # call-seq: replace(attribute)
43
+ #
44
+ # * Replaces the errors value for the given +attribute+
45
+ def replace(attribute, value)
46
+ errors[attribute.to_sym] = value
47
+ end
48
+
49
+ # call-seq: raw(attribute)
50
+ #
51
+ # * Returns an array of error messages associated with the specified +attribute+.
52
+ def raw(attribute)
53
+ errors[attribute.to_sym]
54
+ end
55
+
56
+ def errors #:nodoc:
57
+ @errors ||= {}
58
+ end
59
+
60
+ def count #:nodoc:
61
+ errors.values.flatten.size
62
+ end
63
+
64
+ # call-seq: full_messages -> an_array_of_messages
65
+ #
66
+ # Returns an array containing the full list of error messages.
67
+ def full_messages
68
+ full_messages = []
69
+
70
+ errors.each_key do |attribute|
71
+ errors[attribute].each do |msg|
72
+ next if msg.nil?
73
+
74
+ if attribute.to_s == "base"
75
+ full_messages << msg
76
+ else
77
+ full_messages << humanize(attribute.to_s) + " " + msg
78
+ end
79
+ end
80
+ end
81
+ full_messages
82
+ end
83
+
84
+ def humanize(lower_case_and_underscored_word) #:nodoc:
85
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,9 @@
1
+ module Validatable
2
+ class IncludedValidation #:nodoc:
3
+ attr_accessor :attribute
4
+
5
+ def initialize(attribute)
6
+ @attribute = attribute
7
+ end
8
+ end
9
+ end
data/lib/macros.rb ADDED
@@ -0,0 +1,304 @@
1
+ module Validatable
2
+ module Macros
3
+ # call-seq: validates_each(*args)
4
+ #
5
+ # Validates that the logic evaluates to true
6
+ #
7
+ # class Address
8
+ # include Validatable
9
+ # validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") if ZipCodeService.allows(zip_code) }
10
+ # end
11
+ #
12
+ # The logic option is required.
13
+ #
14
+ # Configuration options:
15
+ #
16
+ # * after_validate - A block that executes following the run of a validation
17
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
18
+ # * if - A block that when executed must return true of the validation will not occur
19
+ # * level - The level at which the validation should occur
20
+ # * logic - A block that executes to perform the validation
21
+ # * message - The message to add to the errors collection when the validation fails
22
+ # * times - The number of times the validation applies
23
+ def validates_each(*args)
24
+ add_validations(args, ValidatesEach)
25
+ end
26
+
27
+ # call-seq: validates_format_of(*args)
28
+ #
29
+ # Validates whether the value of the specified attribute is of the
30
+ # correct form by matching it against the regular expression provided.
31
+ #
32
+ # class Person
33
+ # include Validatable
34
+ # validates_format_of :first_name, :with => /[ A-Za-z]/
35
+ # end
36
+ #
37
+ # A regular expression must be provided or else an exception will be raised.
38
+ #
39
+ # Configuration options:
40
+ #
41
+ # * after_validate - A block that executes following the run of a validation
42
+ # * message - The message to add to the errors collection when the validation fails
43
+ # * times - The number of times the validation applies
44
+ # * level - The level at which the validation should occur
45
+ # * if - A block that when executed must return true of the validation will not occur
46
+ # * with - The regular expression used to validate the format
47
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
48
+ def validates_format_of(*args)
49
+ add_validations(args, ValidatesFormatOf)
50
+ end
51
+
52
+ # call-seq: validates_inclusion_of(*args)
53
+ #
54
+ # Validates whether the value of the specified attribute is available in a particular enumerable object.
55
+ #
56
+ # class Person
57
+ # include Validatable
58
+ # validates_inclusion_of :gender, :in => %w( m f ), :message => 'whoa! what are you then!??!!'
59
+ # validates_inclusion_of :age, :in => 0..99
60
+ # validates_inclusion_of :format, :in => %w ( jpg gif png ), :message => "extension {{value}} is not included in teh list"
61
+ # end
62
+ #
63
+ # Configuration options:
64
+ #
65
+ # * in - An enumerable object of available items.
66
+ # * message - The message to add to the errors collection when the validation fails
67
+ # * allow_nil - If set to true, skips this validation if the attribute is nil (default is false)
68
+ # * allow_blank - If set to true, skips this validation if the attribute is blank (default is false)
69
+ def validates_inclusion_of(*args)
70
+ add_validations(args, ValidatesInclusionOf)
71
+ end
72
+
73
+ # call-seq: validates_length_of(*args)
74
+ #
75
+ # Validates that the specified attribute matches the length restrictions supplied.
76
+ #
77
+ # class Person
78
+ # include Validatable
79
+ # validates_length_of :first_name, :maximum=>30
80
+ # validates_length_of :last_name, :minimum=>30
81
+ # end
82
+ #
83
+ # Configuration options:
84
+ #
85
+ # * after_validate - A block that executes following the run of a validation
86
+ # * message - The message to add to the errors collection when the validation fails
87
+ # * times - The number of times the validation applies
88
+ # * level - The level at which the validation should occur
89
+ # * if - A block that when executed must return true of the validation will not occur
90
+ # * minimum - The minimum size of the attribute
91
+ # * maximum - The maximum size of the attribute
92
+ # * is - The size the attribute must be
93
+ # * within - A range that the size of the attribute must fall within
94
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
95
+ def validates_length_of(*args)
96
+ add_validations(args, ValidatesLengthOf)
97
+ end
98
+
99
+ # call-seq: validates_numericality_of(*args)
100
+ #
101
+ # Validates that the specified attribute is numeric.
102
+ #
103
+ # class Person
104
+ # include Validatable
105
+ # validates_numericality_of :age
106
+ # end
107
+ #
108
+ # Configuration options:
109
+ #
110
+ # * after_validate - A block that executes following the run of a validation
111
+ # * message - The message to add to the errors collection when the validation fails
112
+ # * times - The number of times the validation applies
113
+ # * level - The level at which the validation should occur
114
+ # * if - A block that when executed must return true of the validation will not occur
115
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
116
+ # * only_integer - Whether the attribute must be an integer (default is false)
117
+ def validates_numericality_of(*args)
118
+ add_validations(args, ValidatesNumericalityOf)
119
+ end
120
+
121
+ # call-seq: validates_acceptance_of(*args)
122
+ #
123
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
124
+ #
125
+ # class Person
126
+ # include Validatable
127
+ # validates_acceptance_of :terms_of_service
128
+ # validates_acceptance_of :eula, :message => "must be abided"
129
+ # end
130
+ #
131
+ # Configuration options:
132
+ #
133
+ # * after_validate - A block that executes following the run of a validation
134
+ # * message - The message to add to the errors collection when the validation fails
135
+ # * times - The number of times the validation applies
136
+ # * level - The level at which the validation should occur
137
+ # * if - A block that when executed must return true of the validation will not occur
138
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
139
+ def validates_acceptance_of(*args)
140
+ add_validations(args, ValidatesAcceptanceOf)
141
+ end
142
+
143
+ # call-seq: validates_confirmation_of(*args)
144
+ #
145
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
146
+ #
147
+ # Class:
148
+ # class PersonPresenter
149
+ # include Validatable
150
+ # validates_confirmation_of :user_name, :password
151
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
152
+ # end
153
+ #
154
+ # View:
155
+ # <%= password_field "person", "password" %>
156
+ # <%= password_field "person", "password_confirmation" %>
157
+ #
158
+ # Configuration options:
159
+ #
160
+ # * after_validate - A block that executes following the run of a validation
161
+ # * case_sensitive - Whether or not to apply case-sensitivity on the comparison. Defaults to true.
162
+ # * message - The message to add to the errors collection when the validation fails
163
+ # * times - The number of times the validation applies
164
+ # * level - The level at which the validation should occur
165
+ # * if - A block that when executed must return true of the validation will not occur
166
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
167
+ def validates_confirmation_of(*args)
168
+ add_validations(args, ValidatesConfirmationOf)
169
+ end
170
+
171
+ # call-seq: validates_presence_of(*args)
172
+ #
173
+ # Validates that the specified attributes are not nil or an empty string
174
+ #
175
+ # class Person
176
+ # include Validatable
177
+ # validates_presence_of :first_name
178
+ # end
179
+ #
180
+ # The first_name attribute must be in the object and it cannot be nil or empty.
181
+ #
182
+ # Configuration options:
183
+ #
184
+ # * after_validate - A block that executes following the run of a validation
185
+ # * message - The message to add to the errors collection when the validation fails
186
+ # * times - The number of times the validation applies
187
+ # * level - The level at which the validation should occur
188
+ # * if - A block that when executed must return true of the validation will not occur
189
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
190
+ def validates_presence_of(*args)
191
+ add_validations(args, ValidatesPresenceOf)
192
+ end
193
+
194
+ # call-seq: validates_true_for(*args)
195
+ #
196
+ # Validates that the logic evaluates to true
197
+ #
198
+ # class Person
199
+ # include Validatable
200
+ # validates_true_for :first_name, :logic => lambda { first_name == 'Jamie' }
201
+ # end
202
+ #
203
+ # The logic option is required.
204
+ #
205
+ # Configuration options:
206
+ #
207
+ # * after_validate - A block that executes following the run of a validation
208
+ # * message - The message to add to the errors collection when the validation fails
209
+ # * times - The number of times the validation applies
210
+ # * level - The level at which the validation should occur
211
+ # * if - A block that when executed must return true of the validation will not occur
212
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
213
+ # * logic - A block that executes to perform the validation
214
+ def validates_true_for(*args)
215
+ add_validations(args, ValidatesTrueFor)
216
+ end
217
+
218
+ # call-seq: include_validations_from(attribute)
219
+ #
220
+ # Includes all the validations that are defined on the attribute.
221
+ # class Person
222
+ # include Validatable
223
+ # validates_presence_of :name
224
+ # end
225
+ #
226
+ # class PersonPresenter
227
+ # include Validatable
228
+ # include_validataions_from :person
229
+ # attr_accessor :person
230
+ # def name
231
+ # person.name
232
+ # end
233
+ #
234
+ # def initialize(person)
235
+ # @person = person
236
+ # end
237
+ # end
238
+ #
239
+ # presenter = PersonPresenter.new(Person.new)
240
+ # presenter.valid? #=> false
241
+ # presenter.errors.on(:name) #=> "can't be blank"
242
+ #
243
+ # The name attribute whose validations should be added.
244
+ def include_validations_from(attribute_to_validate, options = {})
245
+ validations_to_include << IncludedValidation.new(attribute_to_validate)
246
+ end
247
+
248
+ # call-seq: include_errors_from(attribute_to_validate, options = {})
249
+ #
250
+ # Validates the specified attributes.
251
+ # class Person
252
+ # include Validatable
253
+ # validates_presence_of :name
254
+ # attr_accessor :name
255
+ # end
256
+ #
257
+ # class PersonPresenter
258
+ # include Validatable
259
+ # include_errors_from :person, :map => { :name => :namen }, :if => lambda { not person.nil? }
260
+ # attr_accessor :person
261
+ #
262
+ # def initialize(person)
263
+ # @person = person
264
+ # end
265
+ # end
266
+ #
267
+ # presenter = PersonPresenter.new(Person.new)
268
+ # presenter.valid? #=> false
269
+ # presenter.errors.on(:namen) #=> "can't be blank"
270
+ #
271
+ # The person attribute will be validated.
272
+ # If person is invalid the errors will be added to the PersonPresenter errors collection.
273
+ #
274
+ # Configuration options:
275
+ #
276
+ # * map - A hash that maps attributes of the child to attributes of the parent.
277
+ # * if - A block that when executed must return true of the validation will not occur.
278
+ def include_errors_from(attribute_to_validate, options = {})
279
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
280
+ end
281
+
282
+ def include_validations_for(attribute_to_validate, options = {}) #:nodoc:
283
+ puts "include_validations_for is deprecated; use include_errors_from instead"
284
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
285
+ end
286
+
287
+ # call-seq: before_validation(&block)
288
+ #
289
+ # Is called before valid? or valid_for_*?
290
+ #
291
+ # class Person
292
+ # include Validatable
293
+ # before_validation do
294
+ # self.name = "default name"
295
+ # end
296
+ #
297
+ # attr_accessor :name
298
+ # end
299
+ #
300
+ def before_validation(&block)
301
+ before_validations << block
302
+ end
303
+ end
304
+ end
@@ -0,0 +1,21 @@
1
+ class Object #:nodoc:
2
+ module InstanceExecHelper #:nodoc:
3
+ end
4
+ include InstanceExecHelper
5
+ def instance_eval_with_params(*args, &block)
6
+ begin
7
+ old_critical, Thread.critical = Thread.critical, true
8
+ n = 0
9
+ n += 1 while respond_to?(mname="__instance_exec#{n}")
10
+ InstanceExecHelper.module_eval{ define_method(mname, &block) }
11
+ ensure
12
+ Thread.critical = old_critical
13
+ end
14
+ begin
15
+ ret = send(mname, *args)
16
+ ensure
17
+ InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
18
+ end
19
+ ret
20
+ end
21
+ end
@@ -0,0 +1,26 @@
1
+ module Validatable
2
+ module Requireable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def requires(*args)
5
+ required_options.concat args
6
+ end
7
+
8
+ def required_options
9
+ @required_options ||= []
10
+ end
11
+ end
12
+
13
+ def self.included(klass)
14
+ klass.extend ClassMethods
15
+ end
16
+
17
+ def requires(options)
18
+ required_options = self.class.required_options.inject([]) do |errors, attribute|
19
+ errors << attribute.to_s unless options.has_key?(attribute)
20
+ errors
21
+ end
22
+ raise ArgumentError.new("#{self.class} requires options: #{required_options.join(', ')}") if required_options.any?
23
+ true
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,31 @@
1
+ module Validatable
2
+ module Understandable #:nodoc:
3
+ module ClassMethods #:nodoc:
4
+ def understands(*args)
5
+ understandings.concat args
6
+ end
7
+
8
+ def understandings
9
+ @understandings ||= []
10
+ end
11
+
12
+ def all_understandings
13
+ return understandings + self.superclass.all_understandings if self.superclass.respond_to? :all_understandings
14
+ understandings
15
+ end
16
+ end
17
+
18
+ def self.included(klass)
19
+ klass.extend ClassMethods
20
+ end
21
+
22
+ def must_understand(hash)
23
+ invalid_options = hash.inject([]) do |errors, (key, value)|
24
+ errors << key.to_s unless self.class.all_understandings.include?(key)
25
+ errors
26
+ end
27
+ raise ArgumentError.new("invalid options: #{invalid_options.join(', ')}") if invalid_options.any?
28
+ true
29
+ end
30
+ end
31
+ end