ramsingla-validatable 1.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. data/README.rdoc +123 -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 +105 -0
  6. data/lib/included_validation.rb +9 -0
  7. data/lib/macros.rb +284 -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 +24 -0
  12. data/lib/validatable_class_methods.rb +85 -0
  13. data/lib/validatable_instance_methods.rb +116 -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_length_of.rb +30 -0
  19. data/lib/validations/validates_numericality_of.rb +27 -0
  20. data/lib/validations/validates_presence_of.rb +17 -0
  21. data/lib/validations/validates_true_for.rb +13 -0
  22. data/lib/validations/validation_base.rb +86 -0
  23. data/test/all_tests.rb +1 -0
  24. data/test/functional/test_validatable.rb +553 -0
  25. data/test/functional/test_validates_acceptance_of.rb +16 -0
  26. data/test/functional/test_validates_confirmation_of.rb +56 -0
  27. data/test/functional/test_validates_each.rb +14 -0
  28. data/test/functional/test_validates_format_of.rb +34 -0
  29. data/test/functional/test_validates_length_of.rb +64 -0
  30. data/test/functional/test_validates_numericality_of.rb +57 -0
  31. data/test/functional/test_validates_presence_of.rb +16 -0
  32. data/test/functional/test_validates_true_for.rb +27 -0
  33. data/test/test_helper.rb +35 -0
  34. data/test/unit/test_errors.rb +64 -0
  35. data/test/unit/test_understandable.rb +19 -0
  36. data/test/unit/test_validatable.rb +38 -0
  37. data/test/unit/test_validates_acceptance_of.rb +45 -0
  38. data/test/unit/test_validates_confirmation_of.rb +76 -0
  39. data/test/unit/test_validates_format_of.rb +44 -0
  40. data/test/unit/test_validates_length_of.rb +80 -0
  41. data/test/unit/test_validates_numericality_of.rb +76 -0
  42. data/test/unit/test_validates_presence_of.rb +35 -0
  43. data/test/unit/test_validates_true_for.rb +29 -0
  44. data/test/unit/test_validation_base.rb +52 -0
  45. metadata +119 -0
data/README.rdoc ADDED
@@ -0,0 +1,123 @@
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) #=> "attribute.not.present"
40
+
41
+ The error message on an attribute are, by default, in the language independent format. (Just like I18n strings).
42
+ These messages can then be translated by the application at whatever level.
43
+
44
+ The default messages exists in Validatable::Errors::MessageCodeFor hash.
45
+
46
+ Validations that turn off after X times of failed attempts.
47
+
48
+ class Person
49
+ include Validatable
50
+ validates_presence_of :name, :times => 1
51
+ attr_accessor :name
52
+ end
53
+
54
+ person = Person.new
55
+ person.valid? #=> false
56
+ person.valid? #=> true
57
+
58
+ Validations can be given levels. If a validation fails on a level the validations for subsequent levels will not be executed.
59
+
60
+ class Person
61
+ include Validatable
62
+ validates_presence_of :name, :level => 1, :message => "name message"
63
+ validates_presence_of :address, :level => 2
64
+ attr_accessor :name, :address
65
+ end
66
+
67
+ person = Person.new
68
+ person.valid? #=> false
69
+ person.errors.on(:name) #=> "name message"
70
+ person.errors.on(:address) #=> nil
71
+
72
+ 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.
73
+
74
+ class MortgageApplication
75
+ include Validatable
76
+ validates_presence_of :ssn, :groups => [:saving, :underwriting]
77
+ validates_presence_of :name, :groups => :underwriting
78
+ attr_accessor :name, :ssn
79
+ end
80
+
81
+ application = MortgageApplication.new
82
+ application.ssn = 377990118
83
+ application.valid_for_saving? #=> true
84
+ application.valid_for_underwriting? #=> false
85
+
86
+ 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.
87
+
88
+ Similar to Rails, Validatable also supports conditional validation.
89
+
90
+ class Person
91
+ include Validatable
92
+ attr_accessor :name
93
+ validates_format_of :name, :with => /.+/, :if => Proc.new { !name.nil? }
94
+ end
95
+ Person.new.valid? #=> true
96
+
97
+ Validatable also exposes an after_validate hook method.
98
+
99
+ class Person
100
+ include Validatable
101
+ validates_presence_of :name
102
+ attr_accessor :name
103
+ end
104
+
105
+ class ValidatesPresenceOf
106
+ after_validate do |result, instance, attribute|
107
+ instance.errors.add("#{attribute} can't be blank") unless result
108
+ end
109
+ end
110
+
111
+ person = Person.new
112
+ person.valid? #=> false
113
+ person.errors.on(:name) #=> "name can't be blank"
114
+
115
+ The after_validate hook yields the result of the validation being run,
116
+ the instance the validation was run on, and the attribute that was validated
117
+
118
+ In the above example the attribute "name" is appended to the message.
119
+
120
+ See the tests for more examples
121
+
122
+ == Contributors
123
+ Rick Bradley, Anonymous Z, Jason Miller, Ali Aghareza, Xavier Shay, Dan Manges, Karthik Krishnan and Venkat, Clint Bishop, Chris Didyk, Yi Wen, Ram Singla
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
+ :major: 1
3
+ :minor: 7
4
+ :patch: 2
@@ -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,105 @@
1
+ module Validatable
2
+
3
+ class Errors
4
+
5
+ MessageCodeFor = {
6
+ :unique => "attribute.duplicate.entry".freeze,
7
+ :format => "attribute.format.invalid".freeze,
8
+ :length => "attribute.length.invalid".freeze,
9
+ :valid => "attribute.document.invalid".freeze,
10
+ :logic => "attribute.logic.false".freeze,
11
+ :accept => "attribute.not.accepted".freeze,
12
+ :required => "attribute.not.present".freeze,
13
+ :exclusion => "attribute.is.reserved".freeze,
14
+ :inclusion => "attribute.is.out.of.list".freeze,
15
+ :confirmation => "attribute.match.failed".freeze,
16
+ :integer => "attribute.not.an.integer".freeze,
17
+ :numeric => "attribute.not.a.number".freeze
18
+ }
19
+
20
+ extend Forwardable
21
+ include Enumerable
22
+
23
+ def_delegators :errors, :clear, :each, :each_pair, :empty?, :length, :size
24
+
25
+ # Returns true if the specified +attribute+ has errors associated with it.
26
+ #
27
+ # class Company < ActiveRecord::Base
28
+ # validates_presence_of :name, :address, :email
29
+ # validates_length_of :name, :in => 5..30
30
+ # end
31
+ #
32
+ # company = Company.create(:address => '123 First St.')
33
+ # company.errors.invalid?(:name) # => true
34
+ # company.errors.invalid?(:address) # => false
35
+ def invalid?(attribute)
36
+ !@errors[attribute.to_sym].nil?
37
+ end
38
+
39
+ # call-seq: on(attribute)
40
+ #
41
+ # * Returns nil, if no errors are associated with the specified +attribute+.
42
+ # * Returns the error message, if one error is associated with the specified +attribute+.
43
+ # * Returns an array of error messages, if more than one error is associated with the specified +attribute+.
44
+ def on(attribute)
45
+ return nil if errors[attribute.to_sym].nil?
46
+ errors[attribute.to_sym].size == 1 ? errors[attribute.to_sym].first : errors[attribute.to_sym]
47
+ end
48
+
49
+ def add(attribute, message) #:nodoc:
50
+ errors[attribute.to_sym] = [] if errors[attribute.to_sym].nil?
51
+ errors[attribute.to_sym] << message
52
+ end
53
+
54
+ def merge!(errors) #:nodoc:
55
+ errors.each_pair{|k, v| add(k,v)}
56
+ self
57
+ end
58
+
59
+ # call-seq: replace(attribute)
60
+ #
61
+ # * Replaces the errors value for the given +attribute+
62
+ def replace(attribute, value)
63
+ errors[attribute.to_sym] = value
64
+ end
65
+
66
+ # call-seq: raw(attribute)
67
+ #
68
+ # * Returns an array of error messages associated with the specified +attribute+.
69
+ def raw(attribute)
70
+ errors[attribute.to_sym]
71
+ end
72
+
73
+ def errors #:nodoc:
74
+ @errors ||= {}
75
+ end
76
+
77
+ def count #:nodoc:
78
+ errors.values.flatten.size
79
+ end
80
+
81
+ # call-seq: full_messages -> an_array_of_messages
82
+ #
83
+ # Returns an array containing the full list of error messages.
84
+ def full_messages
85
+ full_messages = []
86
+
87
+ errors.each_key do |attribute|
88
+ errors[attribute].each do |msg|
89
+ next if msg.nil?
90
+
91
+ if attribute.to_s == "base"
92
+ full_messages << msg
93
+ else
94
+ full_messages << humanize(attribute.to_s) + " " + msg
95
+ end
96
+ end
97
+ end
98
+ full_messages
99
+ end
100
+
101
+ def humanize(lower_case_and_underscored_word) #:nodoc:
102
+ lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize
103
+ end
104
+ end
105
+ 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,284 @@
1
+ module Validatable
2
+
3
+ module Macros
4
+ # call-seq: validates_each(*args)
5
+ #
6
+ # Validates that the logic evaluates to true
7
+ #
8
+ # class Address
9
+ # include Validatable
10
+ # validates_each :zip_code, :logic => lambda { errors.add(:zip_code, "is not valid") if ZipCodeService.allows(zip_code) }
11
+ # end
12
+ #
13
+ # The logic option is required.
14
+ #
15
+ # Configuration options:
16
+ #
17
+ # * after_validate - A block that executes following the run of a validation
18
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
19
+ # * if - A block that when executed must return true of the validation will not occur
20
+ # * level - The level at which the validation should occur
21
+ # * logic - A block that executes to perform the validation
22
+ # * message - The message to add to the errors collection when the validation fails
23
+ # * times - The number of times the validation applies
24
+ def validates_each(*args)
25
+ add_validations(args, ValidatesEach)
26
+ end
27
+
28
+ # call-seq: validates_format_of(*args)
29
+ #
30
+ # Validates whether the value of the specified attribute is of the
31
+ # correct form by matching it against the regular expression provided.
32
+ #
33
+ # class Person
34
+ # include Validatable
35
+ # validates_format_of :first_name, :with => /[ A-Za-z]/
36
+ # end
37
+ #
38
+ # A regular expression must be provided or else an exception will be raised.
39
+ #
40
+ # Configuration options:
41
+ #
42
+ # * after_validate - A block that executes following the run of a validation
43
+ # * message - The message to add to the errors collection when the validation fails
44
+ # * times - The number of times the validation applies
45
+ # * level - The level at which the validation should occur
46
+ # * if - A block that when executed must return true of the validation will not occur
47
+ # * with - The regular expression used to validate the format
48
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
49
+ def validates_format_of(*args)
50
+ add_validations(args, ValidatesFormatOf)
51
+ end
52
+
53
+ # call-seq: validates_length_of(*args)
54
+ #
55
+ # Validates that the specified attribute matches the length restrictions supplied.
56
+ #
57
+ # class Person
58
+ # include Validatable
59
+ # validates_length_of :first_name, :maximum=>30
60
+ # validates_length_of :last_name, :minimum=>30
61
+ # end
62
+ #
63
+ # Configuration options:
64
+ #
65
+ # * after_validate - A block that executes following the run of a validation
66
+ # * message - The message to add to the errors collection when the validation fails
67
+ # * times - The number of times the validation applies
68
+ # * level - The level at which the validation should occur
69
+ # * if - A block that when executed must return true of the validation will not occur
70
+ # * minimum - The minimum size of the attribute
71
+ # * maximum - The maximum size of the attribute
72
+ # * is - The size the attribute must be
73
+ # * within - A range that the size of the attribute must fall within
74
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
75
+ def validates_length_of(*args)
76
+ add_validations(args, ValidatesLengthOf)
77
+ end
78
+
79
+ # call-seq: validates_numericality_of(*args)
80
+ #
81
+ # Validates that the specified attribute is numeric.
82
+ #
83
+ # class Person
84
+ # include Validatable
85
+ # validates_numericality_of :age
86
+ # end
87
+ #
88
+ # Configuration options:
89
+ #
90
+ # * after_validate - A block that executes following the run of a validation
91
+ # * message - The message to add to the errors collection when the validation fails
92
+ # * times - The number of times the validation applies
93
+ # * level - The level at which the validation should occur
94
+ # * if - A block that when executed must return true of the validation will not occur
95
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
96
+ # * only_integer - Whether the attribute must be an integer (default is false)
97
+ def validates_numericality_of(*args)
98
+ add_validations(args, ValidatesNumericalityOf)
99
+ end
100
+
101
+ # call-seq: validates_acceptance_of(*args)
102
+ #
103
+ # Encapsulates the pattern of wanting to validate the acceptance of a terms of service check box (or similar agreement). Example:
104
+ #
105
+ # class Person
106
+ # include Validatable
107
+ # validates_acceptance_of :terms_of_service
108
+ # validates_acceptance_of :eula, :message => "must be abided"
109
+ # end
110
+ #
111
+ # Configuration options:
112
+ #
113
+ # * after_validate - A block that executes following the run of a validation
114
+ # * message - The message to add to the errors collection when the validation fails
115
+ # * times - The number of times the validation applies
116
+ # * level - The level at which the validation should occur
117
+ # * if - A block that when executed must return true of the validation will not occur
118
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
119
+ def validates_acceptance_of(*args)
120
+ add_validations(args, ValidatesAcceptanceOf)
121
+ end
122
+
123
+ # call-seq: validates_confirmation_of(*args)
124
+ #
125
+ # Encapsulates the pattern of wanting to validate a password or email address field with a confirmation. Example:
126
+ #
127
+ # Class:
128
+ # class PersonPresenter
129
+ # include Validatable
130
+ # validates_confirmation_of :user_name, :password
131
+ # validates_confirmation_of :email_address, :message => "should match confirmation"
132
+ # end
133
+ #
134
+ # View:
135
+ # <%= password_field "person", "password" %>
136
+ # <%= password_field "person", "password_confirmation" %>
137
+ #
138
+ # Configuration options:
139
+ #
140
+ # * after_validate - A block that executes following the run of a validation
141
+ # * case_sensitive - Whether or not to apply case-sensitivity on the comparison. Defaults to true.
142
+ # * message - The message to add to the errors collection when the validation fails
143
+ # * times - The number of times the validation applies
144
+ # * level - The level at which the validation should occur
145
+ # * if - A block that when executed must return true of the validation will not occur
146
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
147
+ def validates_confirmation_of(*args)
148
+ add_validations(args, ValidatesConfirmationOf)
149
+ end
150
+
151
+ # call-seq: validates_presence_of(*args)
152
+ #
153
+ # Validates that the specified attributes are not nil or an empty string
154
+ #
155
+ # class Person
156
+ # include Validatable
157
+ # validates_presence_of :first_name
158
+ # end
159
+ #
160
+ # The first_name attribute must be in the object and it cannot be nil or empty.
161
+ #
162
+ # Configuration options:
163
+ #
164
+ # * after_validate - A block that executes following the run of a validation
165
+ # * message - The message to add to the errors collection when the validation fails
166
+ # * times - The number of times the validation applies
167
+ # * level - The level at which the validation should occur
168
+ # * if - A block that when executed must return true of the validation will not occur
169
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
170
+ def validates_presence_of(*args)
171
+ add_validations(args, ValidatesPresenceOf)
172
+ end
173
+
174
+ # call-seq: validates_true_for(*args)
175
+ #
176
+ # Validates that the logic evaluates to true
177
+ #
178
+ # class Person
179
+ # include Validatable
180
+ # validates_true_for :first_name, :logic => lambda { first_name == 'Jamie' }
181
+ # end
182
+ #
183
+ # The logic option is required.
184
+ #
185
+ # Configuration options:
186
+ #
187
+ # * after_validate - A block that executes following the run of a validation
188
+ # * message - The message to add to the errors collection when the validation fails
189
+ # * times - The number of times the validation applies
190
+ # * level - The level at which the validation should occur
191
+ # * if - A block that when executed must return true of the validation will not occur
192
+ # * group - The group that this validation belongs to. A validation can belong to multiple groups
193
+ # * logic - A block that executes to perform the validation
194
+ def validates_true_for(*args)
195
+ add_validations(args, ValidatesTrueFor)
196
+ end
197
+
198
+ # call-seq: include_validations_from(attribute)
199
+ #
200
+ # Includes all the validations that are defined on the attribute.
201
+ # class Person
202
+ # include Validatable
203
+ # validates_presence_of :name
204
+ # end
205
+ #
206
+ # class PersonPresenter
207
+ # include Validatable
208
+ # include_validataions_from :person
209
+ # attr_accessor :person
210
+ # def name
211
+ # person.name
212
+ # end
213
+ #
214
+ # def initialize(person)
215
+ # @person = person
216
+ # end
217
+ # end
218
+ #
219
+ # presenter = PersonPresenter.new(Person.new)
220
+ # presenter.valid? #=> false
221
+ # presenter.errors.on(:name) #=> "can't be blank"
222
+ #
223
+ # The name attribute whose validations should be added.
224
+ def include_validations_from(attribute_to_validate, options = {})
225
+ validations_to_include << IncludedValidation.new(attribute_to_validate)
226
+ end
227
+
228
+ # call-seq: include_errors_from(attribute_to_validate, options = {})
229
+ #
230
+ # Validates the specified attributes.
231
+ # class Person
232
+ # include Validatable
233
+ # validates_presence_of :name
234
+ # attr_accessor :name
235
+ # end
236
+ #
237
+ # class PersonPresenter
238
+ # include Validatable
239
+ # include_errors_from :person, :map => { :name => :namen }, :if => lambda { not person.nil? }
240
+ # attr_accessor :person
241
+ #
242
+ # def initialize(person)
243
+ # @person = person
244
+ # end
245
+ # end
246
+ #
247
+ # presenter = PersonPresenter.new(Person.new)
248
+ # presenter.valid? #=> false
249
+ # presenter.errors.on(:namen) #=> "can't be blank"
250
+ #
251
+ # The person attribute will be validated.
252
+ # If person is invalid the errors will be added to the PersonPresenter errors collection.
253
+ #
254
+ # Configuration options:
255
+ #
256
+ # * map - A hash that maps attributes of the child to attributes of the parent.
257
+ # * if - A block that when executed must return true of the validation will not occur.
258
+ def include_errors_from(attribute_to_validate, options = {})
259
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
260
+ end
261
+
262
+ def include_validations_for(attribute_to_validate, options = {}) #:nodoc:
263
+ puts "include_validations_for is deprecated; use include_errors_from instead"
264
+ children_to_validate << ChildValidation.new(attribute_to_validate, options[:map] || {}, options[:if] || lambda { true })
265
+ end
266
+
267
+ # call-seq: before_validation(&block)
268
+ #
269
+ # Is called before valid? or valid_for_*?
270
+ #
271
+ # class Person
272
+ # include Validatable
273
+ # before_validation do
274
+ # self.name = "default name"
275
+ # end
276
+ #
277
+ # attr_accessor :name
278
+ # end
279
+ #
280
+ def before_validation(&block)
281
+ before_validations << block
282
+ end
283
+ end
284
+ 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