formvalidator 0.1.3 → 0.1.5
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.
- data/AUTHORS +1 -1
- data/CHANGELOG +14 -2
- data/README +4 -1
- data/formvalidator.gemspec +3 -3
- data/formvalidator.rb +128 -59
- data/tests/regress.rb +10 -7
- metadata +62 -50
- data/TODO +0 -5
data/AUTHORS
CHANGED
@@ -1 +1 @@
|
|
1
|
-
Travis Whitton <
|
1
|
+
Travis Whitton <tinymountain@gmail.com>
|
data/CHANGELOG
CHANGED
@@ -22,7 +22,19 @@ o Wrote additional unit test.
|
|
22
22
|
--------------------------------------------------------------------------------
|
23
23
|
8/14/2003 version 0.1.3
|
24
24
|
|
25
|
-
o Fixed credit card type detection bugs
|
26
|
-
o Fixed a bug regarding invalid field accounting
|
25
|
+
o Fixed credit card type detection bugs.
|
26
|
+
o Fixed a bug regarding invalid field accounting.
|
27
27
|
|
28
28
|
--------------------------------------------------------------------------------
|
29
|
+
10/26/2004 version 0.1.4
|
30
|
+
|
31
|
+
o Changed obsolete to_a() calls to use Array().
|
32
|
+
o Changed type() calls to use class().
|
33
|
+
o Fixed apply_string_constraint() to handle multiple elements.
|
34
|
+
o Fixed apply_regexp_constraint() to handle multiple elements.
|
35
|
+
o Remembered to increment version number.
|
36
|
+
|
37
|
+
--------------------------------------------------------------------------------
|
38
|
+
8/28/2010 version 0.1.5
|
39
|
+
|
40
|
+
o Bugfix for passing hash as profile to constructor
|
data/README
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
FormValidator Library
|
3
3
|
|
4
|
-
|
4
|
+
Aug. 28, 2010 Travis Whitton <tinymountain@gmail.com>
|
5
5
|
|
6
6
|
FormValidator is a full featured form validation library written in pure ruby.
|
7
7
|
See README.rdoc for more details.
|
@@ -12,6 +12,9 @@ See README.rdoc for more details.
|
|
12
12
|
build the docs if you want to
|
13
13
|
3. rdoc --main README.rdoc formvalidator.rb README.rdoc
|
14
14
|
|
15
|
+
[Alternate installation]
|
16
|
+
1. gem install formvalidator
|
17
|
+
|
15
18
|
[Copying]
|
16
19
|
FormValidator extension library is copywrited free software by Travis Whitton
|
17
20
|
<whitton@atlantic.net>. You can redistribute it under the terms specified in
|
data/formvalidator.gemspec
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = %q{formvalidator}
|
3
|
-
s.version = "0.1.
|
3
|
+
s.version = "0.1.5"
|
4
4
|
s.date = Time.now
|
5
5
|
s.summary = %q{FormValidator is a Ruby port of Perl's Data::FormValidator library.}
|
6
6
|
s.author = %q{Travis Whitton}
|
7
|
-
s.email = %q{
|
8
|
-
s.homepage = %q{http://
|
7
|
+
s.email = %q{tinymountain@gmail.com}
|
8
|
+
s.homepage = %q{http://github.com/tmountain/FormValidator}
|
9
9
|
s.require_path = %q{.}
|
10
10
|
s.autorequire = %q{formvalidator}
|
11
11
|
s.files = Dir.glob('**/*')
|
data/formvalidator.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class FormValidator
|
2
|
-
VERSION = "0.1.
|
2
|
+
VERSION = "0.1.5"
|
3
3
|
|
4
4
|
# Constructor.
|
5
5
|
def initialize(profile=nil)
|
@@ -76,7 +76,7 @@ class FormValidator
|
|
76
76
|
if Hash === profile
|
77
77
|
@profile = profile
|
78
78
|
else
|
79
|
-
load_profiles
|
79
|
+
load_profiles if @profile_file
|
80
80
|
@profile = @profiles[profile]
|
81
81
|
end
|
82
82
|
check_profile_syntax(@profile)
|
@@ -103,7 +103,7 @@ class FormValidator
|
|
103
103
|
|
104
104
|
# [:a, :b, :c, [:d, :e, [:f, :g]]] -> ["a", "b", "c", ["d", "e", ["f", "g"]]]
|
105
105
|
def strify_array(array)
|
106
|
-
array.
|
106
|
+
Array(array).map do |m|
|
107
107
|
m = (Array === m) ? strify_array(m) : m
|
108
108
|
m = (Hash === m) ? strify_hash(m) : m
|
109
109
|
Symbol === m ? m.to_s : m
|
@@ -175,7 +175,7 @@ class FormValidator
|
|
175
175
|
#
|
176
176
|
# :required => [:name, :age, :phone]
|
177
177
|
def required
|
178
|
-
@profile[:required].
|
178
|
+
Array(@profile[:required]).each do |field|
|
179
179
|
@required_fields << field
|
180
180
|
@missing_fields.push(field) if @form[field].to_s.empty?
|
181
181
|
end
|
@@ -191,7 +191,7 @@ class FormValidator
|
|
191
191
|
#
|
192
192
|
# :optional => [:name, :age, :phone]
|
193
193
|
def optional
|
194
|
-
@profile[:optional].
|
194
|
+
Array(@profile[:optional]).each do |field|
|
195
195
|
@optional_fields << field unless @optional_fields.include?(field)
|
196
196
|
end
|
197
197
|
@optional_fields
|
@@ -206,7 +206,7 @@ class FormValidator
|
|
206
206
|
# :required_regexp => /name/
|
207
207
|
def required_regexp
|
208
208
|
@form.keys.each do |elem|
|
209
|
-
@profile[:required_regexp].
|
209
|
+
Array(@profile[:required_regexp]).each do |regexp|
|
210
210
|
regexp = Regexp.new(regexp)
|
211
211
|
if elem =~ regexp
|
212
212
|
@required_fields << elem unless @required_fields.include?(elem)
|
@@ -225,7 +225,7 @@ class FormValidator
|
|
225
225
|
# :required_regexp => /name/
|
226
226
|
def optional_regexp
|
227
227
|
@form.keys.each do |elem|
|
228
|
-
@profile[:optional_regexp].
|
228
|
+
Array(@profile[:optional_regexp]).each do |regexp|
|
229
229
|
regexp = Regexp.new(regexp)
|
230
230
|
if elem =~ regexp
|
231
231
|
@optional_fields << elem unless @optional_fields.include?(elem)
|
@@ -297,14 +297,14 @@ class FormValidator
|
|
297
297
|
if Hash === deps
|
298
298
|
deps.keys.each do |key|
|
299
299
|
if @form[field].to_s == key
|
300
|
-
deps[key].
|
300
|
+
Array(deps[key]).each do |dep|
|
301
301
|
@missing_fields.push(dep) if @form[dep].to_s.empty?
|
302
302
|
end
|
303
303
|
end
|
304
304
|
end
|
305
305
|
else
|
306
306
|
if not @form[field].to_s.empty?
|
307
|
-
deps.
|
307
|
+
Array(deps).each do |dep|
|
308
308
|
@missing_fields.push(dep) if @form[dep].to_s.empty?
|
309
309
|
end
|
310
310
|
end
|
@@ -341,11 +341,11 @@ class FormValidator
|
|
341
341
|
#
|
342
342
|
# :filters => :strip
|
343
343
|
def filters
|
344
|
-
@profile[:filters].
|
344
|
+
Array(@profile[:filters]).each do |filter|
|
345
345
|
if respond_to?("filter_#{filter}".intern)
|
346
346
|
@form.keys.each do |field|
|
347
347
|
# If a key has multiple elements, apply filter to each element
|
348
|
-
if @form[field].
|
348
|
+
if Array(@form[field]).length > 1
|
349
349
|
@form[field].each_index do |i|
|
350
350
|
elem = @form[field][i]
|
351
351
|
@form[field][i] = self.send("filter_#{filter}".intern, elem)
|
@@ -369,11 +369,11 @@ class FormValidator
|
|
369
369
|
#
|
370
370
|
# :field_filters => { :home_phone => :phone }
|
371
371
|
def field_filters
|
372
|
-
@profile[:field_filters].
|
373
|
-
filters.
|
372
|
+
Array(@profile[:field_filters]).each do |field,filters|
|
373
|
+
Array(filters).each do |filter|
|
374
374
|
if respond_to?("filter_#{filter}".intern)
|
375
375
|
# If a key has multiple elements, apply filter to each element
|
376
|
-
if @form[field].
|
376
|
+
if Array(@form[field]).length > 1
|
377
377
|
@form[field].each_index do |i|
|
378
378
|
elem = @form[field][i]
|
379
379
|
@form[field][i] = self.send("filter_#{filter}".intern, elem)
|
@@ -394,12 +394,12 @@ class FormValidator
|
|
394
394
|
#
|
395
395
|
# :field_filter_regexp_map => { /name/ => :capitalize }
|
396
396
|
def field_filter_regexp_map
|
397
|
-
@profile[:field_filter_regexp_map].
|
398
|
-
filters.
|
397
|
+
Array(@profile[:field_filter_regexp_map]).each do |re,filters|
|
398
|
+
Array(filters).each do |filter|
|
399
399
|
if respond_to?("filter_#{filter}".intern)
|
400
400
|
@form.keys.select {|key| key =~ re}.each do |match|
|
401
401
|
# If a key has multiple elements, apply filter to each element
|
402
|
-
if @form[match].
|
402
|
+
if Array(@form[match]).length > 1
|
403
403
|
@form[match].each_index do |i|
|
404
404
|
elem = @form[match][i]
|
405
405
|
@form[match][i] = self.send("filter_#{filter}".intern, elem)
|
@@ -435,7 +435,7 @@ class FormValidator
|
|
435
435
|
#
|
436
436
|
# :untaint_constraint_fields => %w{ name age }
|
437
437
|
def untaint_constraint_fields
|
438
|
-
@profile[:untaint_constraint_fields].
|
438
|
+
Array(@profile[:untaint_constraint_fields]).each do |field|
|
439
439
|
@untaint_fields.push(field)
|
440
440
|
end
|
441
441
|
end
|
@@ -505,7 +505,7 @@ class FormValidator
|
|
505
505
|
# Valid constraint objects are String, Hash, Array, Proc, and Regexp.
|
506
506
|
def do_constraint(key, constraints)
|
507
507
|
constraints.each do |constraint|
|
508
|
-
type = constraint.
|
508
|
+
type = constraint.class.to_s.intern
|
509
509
|
case type
|
510
510
|
when :String
|
511
511
|
apply_string_constraint(key, constraint)
|
@@ -540,39 +540,83 @@ class FormValidator
|
|
540
540
|
|
541
541
|
# Applies a builtin constraint to form[key]
|
542
542
|
def apply_string_constraint(key, constraint)
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
@form[key]
|
548
|
-
|
543
|
+
### New code to handle multiple elements (beware!)
|
544
|
+
if Array(@form[key]).length > 1
|
545
|
+
index = 0
|
546
|
+
Array(@form[key]).each do |value|
|
547
|
+
res = self.send("match_#{constraint}".intern, @form[key][index].to_s)
|
548
|
+
if res
|
549
|
+
if untaint?(key)
|
550
|
+
@form[key][index] = res
|
551
|
+
@form[key][index].untaint
|
552
|
+
end
|
553
|
+
else
|
554
|
+
@form[key].delete_at(index)
|
555
|
+
@invalid_fields[key] ||= []
|
556
|
+
unless @invalid_fields[key].include?(constraint)
|
557
|
+
@invalid_fields[key].push(constraint)
|
558
|
+
end
|
559
|
+
nil
|
560
|
+
end
|
561
|
+
index += 1
|
549
562
|
end
|
563
|
+
### End new code
|
550
564
|
else
|
551
|
-
@form.
|
552
|
-
|
553
|
-
|
554
|
-
|
565
|
+
res = self.send("match_#{constraint}".intern, @form[key].to_s)
|
566
|
+
if res
|
567
|
+
if untaint?(key)
|
568
|
+
@form[key] = res
|
569
|
+
@form[key].untaint
|
570
|
+
end
|
571
|
+
else
|
572
|
+
@form.delete(key)
|
573
|
+
@invalid_fields[key] ||= []
|
574
|
+
unless @invalid_fields[key].include?(constraint)
|
575
|
+
@invalid_fields[key].push(constraint)
|
576
|
+
end
|
577
|
+
nil
|
555
578
|
end
|
556
|
-
nil
|
557
579
|
end
|
558
580
|
end
|
559
581
|
|
560
582
|
# Applies regexp constraint to form[key]
|
561
583
|
def apply_regexp_constraint(key, constraint)
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
@form[key]
|
567
|
-
|
584
|
+
### New code to handle multiple elements (beware!)
|
585
|
+
if Array(@form[key]).length > 1
|
586
|
+
index = 0
|
587
|
+
Array(@form[key]).each do |value|
|
588
|
+
m = constraint.match(@form[key][index].to_s)
|
589
|
+
if m
|
590
|
+
if untaint?(key)
|
591
|
+
@form[key][index] = m[0]
|
592
|
+
@form[key][index].untaint
|
593
|
+
end
|
594
|
+
else
|
595
|
+
@form[key].delete_at(index)
|
596
|
+
@invalid_fields[key] ||= []
|
597
|
+
unless @invalid_fields[key].include?(constraint.inspect)
|
598
|
+
@invalid_fields[key].push(constraint.inspect)
|
599
|
+
end
|
600
|
+
nil
|
601
|
+
end
|
602
|
+
index += 1
|
568
603
|
end
|
604
|
+
### End new code
|
569
605
|
else
|
570
|
-
@form.
|
571
|
-
|
572
|
-
|
573
|
-
|
606
|
+
m = constraint.match(@form[key].to_s)
|
607
|
+
if m
|
608
|
+
if untaint?(key)
|
609
|
+
@form[key] = m[0]
|
610
|
+
@form[key].untaint
|
611
|
+
end
|
612
|
+
else
|
613
|
+
@form.delete(key)
|
614
|
+
@invalid_fields[key] ||= []
|
615
|
+
unless @invalid_fields[key].include?(constraint.inspect)
|
616
|
+
@invalid_fields[key].push(constraint.inspect)
|
617
|
+
end
|
618
|
+
nil
|
574
619
|
end
|
575
|
-
nil
|
576
620
|
end
|
577
621
|
end
|
578
622
|
|
@@ -599,10 +643,11 @@ class FormValidator
|
|
599
643
|
# If an optional name field is specified then it will be listed as
|
600
644
|
# the failed constraint in the invalid_fields hash.
|
601
645
|
def apply_hash_constraint(key, constraint)
|
602
|
-
name
|
603
|
-
action
|
604
|
-
params
|
605
|
-
res
|
646
|
+
name = constraint["name"]
|
647
|
+
action = constraint["constraint"]
|
648
|
+
params = constraint["params"]
|
649
|
+
res = false
|
650
|
+
skip_end = false
|
606
651
|
|
607
652
|
# In order to call a builtin or proc, params and action must be present.
|
608
653
|
if action and params
|
@@ -615,21 +660,45 @@ class FormValidator
|
|
615
660
|
end
|
616
661
|
|
617
662
|
if Regexp === action
|
618
|
-
|
619
|
-
|
620
|
-
|
663
|
+
### New code to handle multiple elements (beware!)
|
664
|
+
if Array(@form[key]).length > 1
|
665
|
+
index = 0
|
666
|
+
skip_end = true
|
667
|
+
Array(@form[key]).each do |value|
|
668
|
+
m = action.match(value)
|
669
|
+
res = m[0] if m
|
670
|
+
if res
|
671
|
+
@form[key][index] = res if untaint?(key)
|
672
|
+
else
|
673
|
+
@form[key].delete_at(index)
|
674
|
+
constraint = (name) ? name : constraint
|
675
|
+
@invalid_fields[key] ||= []
|
676
|
+
unless @invalid_fields[key].include?(constraint)
|
677
|
+
@invalid_fields[key].push(constraint)
|
678
|
+
end
|
679
|
+
nil
|
680
|
+
end
|
681
|
+
index += 1
|
682
|
+
end
|
683
|
+
### End new code
|
684
|
+
else
|
685
|
+
m = action.match(@form[key].to_s)
|
686
|
+
res = m[0] if m
|
687
|
+
end
|
621
688
|
end
|
622
689
|
|
623
|
-
if
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
@invalid_fields[key].
|
690
|
+
if not skip_end
|
691
|
+
if res
|
692
|
+
@form[key] = res if untaint?(key)
|
693
|
+
else
|
694
|
+
@form.delete(key)
|
695
|
+
constraint = (name) ? name : constraint
|
696
|
+
@invalid_fields[key] ||= []
|
697
|
+
unless @invalid_fields[key].include?(constraint)
|
698
|
+
@invalid_fields[key].push(constraint)
|
699
|
+
end
|
700
|
+
nil
|
631
701
|
end
|
632
|
-
nil
|
633
702
|
end
|
634
703
|
end
|
635
704
|
end # module ConstraintHelpers
|
@@ -758,14 +827,14 @@ class FormValidator
|
|
758
827
|
|
759
828
|
# Matches a US state.
|
760
829
|
def match_state(state)
|
761
|
-
state = (state.
|
830
|
+
state = (state.class == String) ? state.intern : state
|
762
831
|
index = STATES.index(state)
|
763
832
|
(index) ? STATES[index].to_s : nil
|
764
833
|
end
|
765
834
|
|
766
835
|
# Matches a Canadian province.
|
767
836
|
def match_province(prov)
|
768
|
-
prov = (prov.
|
837
|
+
prov = (prov.class == String) ? prov.intern : prov
|
769
838
|
index = PROVINCES.index(prov)
|
770
839
|
(index) ? PROVINCES[index].to_s : nil
|
771
840
|
end
|
data/tests/regress.rb
CHANGED
@@ -294,6 +294,16 @@ class TestValidator < Test::Unit::TestCase
|
|
294
294
|
assert_equal(false, form["ip"].tainted?)
|
295
295
|
end
|
296
296
|
|
297
|
+
def test_hash_constructor
|
298
|
+
profile = {
|
299
|
+
:test => {
|
300
|
+
:required => [ :foo ]
|
301
|
+
}
|
302
|
+
}
|
303
|
+
fv = FormValidator.new(profile)
|
304
|
+
assert(fv.validate({'foo' => 'bar'}, :test))
|
305
|
+
end
|
306
|
+
|
297
307
|
def test_constraint_regexp_map
|
298
308
|
form = {
|
299
309
|
"zipcode" => "32608"
|
@@ -492,13 +502,6 @@ class TestValidator < Test::Unit::TestCase
|
|
492
502
|
assert_nil(@fv.match_american_phone("(abc) abc-defg"))
|
493
503
|
end
|
494
504
|
|
495
|
-
def test_match_cc_number()
|
496
|
-
# Not a real credit card number! It simply matches the checksum.
|
497
|
-
assert_equal("378282246310005",
|
498
|
-
@fv.match_cc_number("378282246310005", :AMEX))
|
499
|
-
assert_nil(@fv.match_cc_number("378282256310005", :AMEX))
|
500
|
-
end
|
501
|
-
|
502
505
|
def test_match_cc_exp()
|
503
506
|
year = Time.new.year
|
504
507
|
month = Time.new.month
|
metadata
CHANGED
@@ -1,59 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: "0.8"
|
3
|
-
specification_version: 1
|
4
2
|
name: formvalidator
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.1.
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
- "."
|
11
|
-
author: Travis Whitton
|
12
|
-
email: whitton@atlantic.net
|
13
|
-
homepage: http://grub.ath.cx/formvalidator/
|
14
|
-
rubyforge_project:
|
15
|
-
description:
|
4
|
+
version: 0.1.5
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Travis Whitton
|
16
8
|
autorequire: formvalidator
|
17
|
-
default_executable:
|
18
9
|
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-08-28 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: tinymountain@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.rdoc
|
24
|
+
files:
|
25
|
+
- install.rb
|
26
|
+
- README
|
27
|
+
- CHANGELOG
|
28
|
+
- README.rdoc
|
29
|
+
- tests/testprofile.rb
|
30
|
+
- tests/regress.rb
|
31
|
+
- examples/simple.rb
|
32
|
+
- examples/README
|
33
|
+
- examples/profiles/my_profile.rb
|
34
|
+
- examples/profiles/extension.rb
|
35
|
+
- examples/extend.rb
|
36
|
+
- examples/standard.rb
|
37
|
+
- examples/file.rb
|
38
|
+
- formvalidator.rb
|
39
|
+
- formvalidator.gemspec
|
40
|
+
- AUTHORS
|
19
41
|
has_rdoc: true
|
20
|
-
|
42
|
+
homepage: http://github.com/tmountain/FormValidator
|
43
|
+
licenses: []
|
44
|
+
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options:
|
47
|
+
- --main
|
48
|
+
- README.rdoc
|
49
|
+
require_paths:
|
50
|
+
- .
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
21
52
|
requirements:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
26
62
|
version:
|
27
|
-
platform: ruby
|
28
|
-
files:
|
29
|
-
- tests
|
30
|
-
- README
|
31
|
-
- examples
|
32
|
-
- CHANGELOG
|
33
|
-
- TODO
|
34
|
-
- formvalidator.rb
|
35
|
-
- README.rdoc
|
36
|
-
- install.rb
|
37
|
-
- AUTHORS
|
38
|
-
- formvalidator.gemspec
|
39
|
-
- tests/testprofile.rb
|
40
|
-
- tests/regress.rb
|
41
|
-
- examples/file.rb
|
42
|
-
- examples/standard.rb
|
43
|
-
- examples/profiles
|
44
|
-
- examples/extend.rb
|
45
|
-
- examples/simple.rb
|
46
|
-
- examples/README
|
47
|
-
- examples/profiles/my_profile.rb
|
48
|
-
- examples/profiles/extension.rb
|
49
|
-
test_files:
|
50
|
-
- tests/regress.rb
|
51
|
-
rdoc_options:
|
52
|
-
- "--main"
|
53
|
-
- README.rdoc
|
54
|
-
extra_rdoc_files:
|
55
|
-
- README.rdoc
|
56
|
-
executables: []
|
57
|
-
extensions: []
|
58
63
|
requirements: []
|
59
|
-
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: FormValidator is a Ruby port of Perl's Data::FormValidator library.
|
70
|
+
test_files:
|
71
|
+
- tests/regress.rb
|
data/TODO
DELETED
@@ -1,5 +0,0 @@
|
|
1
|
-
Patches are Welcome!
|
2
|
-
--------------------------------------------------------------------------------
|
3
|
-
o Make constraints handle form elements with multiple fields.
|
4
|
-
o Make field filters accept proc objects.
|
5
|
-
o Make a tutorial on using FormValidator on the Ruby Wiki.
|