formulary 0.0.2 → 0.0.3

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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +65 -34
  3. data/formulary.gemspec +0 -1
  4. data/lib/formulary.rb +1 -5
  5. data/lib/formulary/html_form.rb +31 -13
  6. data/lib/formulary/html_form/fields.rb +16 -0
  7. data/lib/formulary/html_form/fields/checkbox_group.rb +1 -1
  8. data/lib/formulary/html_form/fields/color_input.rb +24 -0
  9. data/lib/formulary/html_form/fields/date_input.rb +59 -0
  10. data/lib/formulary/html_form/fields/email_input.rb +11 -2
  11. data/lib/formulary/html_form/fields/field.rb +15 -3
  12. data/lib/formulary/html_form/fields/field_group.rb +3 -3
  13. data/lib/formulary/html_form/fields/input.rb +1 -1
  14. data/lib/formulary/html_form/fields/month_input.rb +15 -0
  15. data/lib/formulary/html_form/fields/number_input.rb +63 -0
  16. data/lib/formulary/html_form/fields/password_input.rb +7 -0
  17. data/lib/formulary/html_form/fields/range_input.rb +7 -0
  18. data/lib/formulary/html_form/fields/search_input.rb +7 -0
  19. data/lib/formulary/html_form/fields/select.rb +1 -1
  20. data/lib/formulary/html_form/fields/week_input.rb +15 -0
  21. data/lib/formulary/html_form/labels.rb +39 -0
  22. data/lib/formulary/version.rb +1 -1
  23. data/spec/html_form/fields/checkbox_group_spec.rb +23 -10
  24. data/spec/html_form/fields/color_input_spec.rb +58 -0
  25. data/spec/html_form/fields/date_input_spec.rb +60 -0
  26. data/spec/html_form/fields/email_input_spec.rb +16 -6
  27. data/spec/html_form/fields/field_spec.rb +40 -1
  28. data/spec/html_form/fields/hidden_input_spec.rb +3 -1
  29. data/spec/html_form/fields/month_input_spec.rb +60 -0
  30. data/spec/html_form/fields/number_input_spec.rb +67 -0
  31. data/spec/html_form/fields/password_input_spec.rb +29 -0
  32. data/spec/html_form/fields/radio_button_group_spec.rb +27 -8
  33. data/spec/html_form/fields/range_input_spec.rb +20 -0
  34. data/spec/html_form/fields/search_input_spec.rb +18 -0
  35. data/spec/html_form/fields/select_spec.rb +10 -7
  36. data/spec/html_form/fields/tel_input_spec.rb +3 -3
  37. data/spec/html_form/fields/text_input_spec.rb +3 -3
  38. data/spec/html_form/fields/textarea_spec.rb +2 -2
  39. data/spec/html_form/fields/week_input_spec.rb +60 -0
  40. data/spec/html_form_spec.rb +234 -100
  41. data/spec/support/element_helper.rb +1 -1
  42. data/spec/support/shared_examples_for_pattern.rb +4 -2
  43. data/spec/support/shared_examples_for_required.rb +4 -2
  44. metadata +114 -106
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 393050691325dca94776e614a5de5f939fd595de
4
+ data.tar.gz: 825e144f8e5a11d88756318ba32e9e5248088ca1
5
+ SHA512:
6
+ metadata.gz: 51ebdb9b78b2e3bb3be922650daebd152cf302c00074cee83d3e7f350b9eb80ba9dc38e2dad34d113444faf2a53d4c20f8400309bcaa49ba29b5c9f2e4217b7e
7
+ data.tar.gz: f164cece566135c3400ce59ba8042e24cee8736b6469268921f302a9027a02e83f0a0cc2f423a789e84bdad76614cfdc39dc6e7a0207c643c0b651d1e2e02eb8
data/README.md CHANGED
@@ -17,32 +17,32 @@ gem 'formulary'
17
17
 
18
18
  And then execute:
19
19
 
20
- $ bundle
20
+ $ bundle
21
21
 
22
22
  Or install it yourself as:
23
23
 
24
- $ gem install formulary
24
+ $ gem install formulary
25
25
 
26
26
 
27
27
  ## Usage
28
28
 
29
29
  Create a new Formulary Form
30
-
30
+
31
31
  ```ruby
32
32
  require 'formulary'
33
33
 
34
34
  form_html = <<EOF
35
35
  <form>
36
- <input type="email" name="email" required />
37
- <input type="username" name="username" pattern="[a-z0-9_-]{3,16}" />
36
+ <input type="email" name="email" required />
37
+ <input type="username" name="username" pattern="[a-z0-9_-]{3,16}" />
38
38
  </form>
39
39
  EOF
40
-
40
+
41
41
  html_form = Formulary::HtmlForm.new(form_html)
42
42
  ```
43
43
 
44
44
  Validate the form based on HTML5 field types and/or patterns and view which fields are invalid and why.
45
-
45
+
46
46
  ```ruby
47
47
  html_form.valid?({ email: "test@example.com", username: "person" })
48
48
  # => true
@@ -61,25 +61,55 @@ html_form.valid?({ unknown: "value" })
61
61
  # => Formulary::UnexpectedParameter: Got unexpected field 'unknown'
62
62
  ```
63
63
 
64
-
65
64
  ## Currently Supported
66
65
 
67
- - type="email"
66
+ **Supported Input Types**
67
+ - checkbox
68
+ - color
69
+ - date
70
+ - email
71
+ - hidden
72
+ - month
73
+ - number
74
+ - password
75
+ - radio
76
+ - range
77
+ - search
78
+ - tel
79
+ - text
80
+ - week
81
+
82
+ **Ignored Field Types (not validated but does not make things explode)
83
+ - button
84
+ - image
85
+ - reset
86
+ - submit
87
+
88
+ **Supported Input Attributes**
89
+ - max (number, range, date)
90
+ - min (number, range, date)
91
+ - pattern
68
92
  - required
69
- - pattern="REGEX"
70
- - selects, selected value is one of the options
93
+ - step (number, range)
71
94
 
95
+ **Other Supported Tags**
96
+ - select
97
+ - textarea
72
98
 
73
- ## TODO
74
99
 
75
- - checkbox, radio and multiselect tags have one of the valid options selected
76
- - validate [other html5 field types](http://www.w3schools.com/html/html5_form_input_types.asp)
100
+ ## TODO
77
101
 
102
+ **Add Unsupported Input Types**
103
+ - datetime
104
+ - datetime-local
105
+ - file
106
+ - time
107
+ - url
78
108
 
79
109
  ## Authors
80
110
 
81
- * Don Petersen / [@dpetersen](https://github.com/dpetersen)
82
- * Matt Bohme / [@quady](https://github.com/quady)
111
+ * Matt Bohme / [@quady](https://github.com/quady)
112
+ * Don Petersen / [@dpetersen](https://github.com/dpetersen)
83
113
 
84
114
 
85
115
  ## Contributing
@@ -95,25 +125,26 @@ html_form.valid?({ unknown: "value" })
95
125
 
96
126
  ## License
97
127
 
98
- Copyright (c) 2013 G5
128
+ Copyright (c) 2013 G5
129
+
130
+ MIT License
99
131
 
100
- MIT License
132
+ Permission is hereby granted, free of charge, to any person obtaining
133
+ a copy of this software and associated documentation files (the
134
+ "Software"), to deal in the Software without restriction, including
135
+ without limitation the rights to use, copy, modify, merge, publish,
136
+ distribute, sublicense, and/or sell copies of the Software, and to
137
+ permit persons to whom the Software is furnished to do so, subject to
138
+ the following conditions:
101
139
 
102
- Permission is hereby granted, free of charge, to any person obtaining
103
- a copy of this software and associated documentation files (the
104
- "Software"), to deal in the Software without restriction, including
105
- without limitation the rights to use, copy, modify, merge, publish,
106
- distribute, sublicense, and/or sell copies of the Software, and to
107
- permit persons to whom the Software is furnished to do so, subject to
108
- the following conditions:
140
+ The above copyright notice and this permission notice shall be
141
+ included in all copies or substantial portions of the Software.
109
142
 
110
- The above copyright notice and this permission notice shall be
111
- included in all copies or substantial portions of the Software.
143
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
144
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
145
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
146
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
147
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
148
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
149
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
112
150
 
113
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
114
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
115
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
116
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
117
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
118
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
119
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -20,7 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "nokogiri"
22
22
  spec.add_dependency "activesupport"
23
- spec.add_dependency "email_veracity"
24
23
 
25
24
  spec.add_development_dependency "bundler", "~> 1.3"
26
25
  spec.add_development_dependency "rake"
@@ -1,11 +1,6 @@
1
1
  require 'nokogiri'
2
2
  require 'active_support/core_ext/object/blank'
3
3
  require 'active_support/core_ext/object/try'
4
- require 'email_veracity'
5
-
6
- # Don't try and determine if this email address is legitimate, just validate
7
- # the provided address.
8
- EmailVeracity::Config[:skip_lookup] = true
9
4
 
10
5
  module Formulary
11
6
  class HtmlForm
@@ -16,4 +11,5 @@ end
16
11
 
17
12
  require "formulary/version"
18
13
  require "formulary/html_form/fields"
14
+ require "formulary/html_form/labels"
19
15
  require "formulary/html_form"
@@ -1,7 +1,9 @@
1
1
  module Formulary
2
2
  class HtmlForm
3
+ include Labels
4
+
3
5
  SINGULAR_FIELD_SELECTOR = <<-EOS
4
- input[type!='submit'][type!='radio'][type!='checkbox'],
6
+ input[type!='submit'][type!='button'][type!='reset'][type!='image'][type!='radio'][type!='checkbox'],
5
7
  textarea,
6
8
  select
7
9
  EOS
@@ -18,9 +20,13 @@ module Formulary
18
20
 
19
21
  def valid?(params)
20
22
  params.each do |key, value|
21
- raise UnexpectedParameter.new("Got unexpected field '#{key}'") unless find_field(key)
22
-
23
- find_field(key).set_value(value)
23
+ if value.kind_of?(Hash)
24
+ value.each do |nested_key, nested_value|
25
+ set_field_value("#{key}[#{nested_key}]", nested_value)
26
+ end
27
+ else
28
+ set_field_value(key, value)
29
+ end
24
30
  end
25
31
 
26
32
  fields.all?(&:valid?)
@@ -34,6 +40,16 @@ module Formulary
34
40
 
35
41
  protected
36
42
 
43
+ def document
44
+ @document ||= Nokogiri::HTML(@markup)
45
+ end
46
+
47
+ def set_field_value(field_name, value)
48
+ field = find_field(field_name)
49
+ raise UnexpectedParameter.new("Got unexpected field '#{field_name}'") unless field
50
+ field.set_value(value)
51
+ end
52
+
37
53
  def fields
38
54
  @fields || build_fields
39
55
  end
@@ -44,24 +60,23 @@ module Formulary
44
60
 
45
61
  def build_fields
46
62
  @fields = []
47
- doc = Nokogiri::HTML(@markup)
48
63
 
49
- build_singular_fields_from(doc)
50
- build_grouped_fields_from(doc)
64
+ build_singular_fields_from
65
+ build_grouped_fields_from
51
66
  end
52
67
 
53
- def build_singular_fields_from(doc)
54
- doc.css(SINGULAR_FIELD_SELECTOR.strip).map do |element|
68
+ def build_singular_fields_from
69
+ document.css(SINGULAR_FIELD_SELECTOR.strip).map do |element|
55
70
  field_klass = FIELD_TYPES.detect { |k| k.compatible_with?(element) }
56
71
  if field_klass.nil?
57
72
  raise UnsupportedFieldType.new("I can't handle this field: #{element.inspect}")
58
73
  end
59
- @fields << field_klass.new(element)
74
+ @fields << field_klass.new(self, element)
60
75
  end
61
76
  end
62
77
 
63
- def build_grouped_fields_from(doc)
64
- grouped_elements = doc.css(GROUPED_FIELD_SELECTOR.strip).group_by do |element|
78
+ def build_grouped_fields_from
79
+ grouped_elements = document.css(GROUPED_FIELD_SELECTOR.strip).group_by do |element|
65
80
  element.attributes["name"].value
66
81
  end
67
82
 
@@ -69,7 +84,10 @@ module Formulary
69
84
  group_name, elements = *element_group
70
85
 
71
86
  group_klass = FIELD_GROUP_TYPES.detect { |k| k.compatible_with?(elements) }
72
- @fields << group_klass.new(group_name, elements)
87
+ if group_klass.nil?
88
+ raise UnsupportedFieldType.new("I can't handle these fields: #{elements.inspect}")
89
+ end
90
+ @fields << group_klass.new(self, group_name, elements)
73
91
  end
74
92
  end
75
93
  end
@@ -4,12 +4,28 @@ require 'formulary/html_form/fields/input'
4
4
 
5
5
  require 'formulary/html_form/fields/text_input'
6
6
  Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::TextInput
7
+ require 'formulary/html_form/fields/search_input'
8
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::SearchInput
9
+ require 'formulary/html_form/fields/password_input'
10
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::PasswordInput
11
+ require 'formulary/html_form/fields/color_input'
12
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::ColorInput
13
+ require 'formulary/html_form/fields/number_input'
14
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::NumberInput
15
+ require 'formulary/html_form/fields/range_input'
16
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::RangeInput
7
17
  require 'formulary/html_form/fields/email_input'
8
18
  Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::EmailInput
9
19
  require 'formulary/html_form/fields/tel_input'
10
20
  Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::TelInput
11
21
  require 'formulary/html_form/fields/hidden_input'
12
22
  Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::HiddenInput
23
+ require 'formulary/html_form/fields/date_input'
24
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::DateInput
25
+ require 'formulary/html_form/fields/month_input'
26
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::MonthInput
27
+ require 'formulary/html_form/fields/week_input'
28
+ Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::WeekInput
13
29
  require 'formulary/html_form/fields/textarea'
14
30
  Formulary::HtmlForm::FIELD_TYPES << Formulary::HtmlForm::Fields::Textarea
15
31
  require 'formulary/html_form/fields/select'
@@ -8,7 +8,7 @@ module Formulary::HtmlForm::Fields
8
8
  true
9
9
  end
10
10
 
11
- def initialize(group_name, elements)
11
+ def initialize(html_form, group_name, elements)
12
12
  super
13
13
  @values = []
14
14
  end
@@ -0,0 +1,24 @@
1
+ module Formulary::HtmlForm::Fields
2
+ class ColorInput < Input
3
+ def self.compatible_type
4
+ "color"
5
+ end
6
+
7
+ def valid?
8
+ super && color_correct?
9
+ end
10
+
11
+ def error
12
+ return super if super.present?
13
+ return "'#{label}' is not a valid color hex value" unless color_correct?
14
+ end
15
+
16
+ protected
17
+
18
+ def color_correct?
19
+ return true if @value.blank?
20
+ return true if @value.match(/\A#[0-9A-F]{6}\z/)
21
+ false
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,59 @@
1
+ module Formulary::HtmlForm::Fields
2
+ class DateInput < Input
3
+ def self.compatible_type
4
+ "date"
5
+ end
6
+
7
+ def display_format
8
+ "YYYY-MM-DD"
9
+ end
10
+
11
+ def datetime_format
12
+ "%Y-%m-%d"
13
+ end
14
+
15
+ def initialize(html_form, element)
16
+ @html_form, @element = html_form, element
17
+ @min = @element.attributes['min'].try('value')
18
+ @max = @element.attributes['max'].try('value')
19
+ end
20
+
21
+ def valid?
22
+ super && date_correct? && min_correct? && max_correct?
23
+ end
24
+
25
+ def error
26
+ return super if super.present?
27
+ return "'#{label}' is not a properly formatted #{self.class.compatible_type}, please use #{display_format}" unless date_correct?
28
+ return "'#{label}' must be a #{self.class.compatible_type} after #{@min}" unless min_correct?
29
+ return "'#{label}' must be a #{self.class.compatible_type} before #{@max}" unless max_correct?
30
+ end
31
+
32
+ protected
33
+
34
+ def date_correct?
35
+ return true if @value.blank?
36
+ Date.strptime(@value, datetime_format)
37
+ rescue ArgumentError => e
38
+ if e.message.include?("invalid date")
39
+ return false
40
+ else
41
+ raise
42
+ end
43
+ end
44
+
45
+ def min_correct?
46
+ return true if @value.blank?
47
+ return true if @min.blank?
48
+ return true if @value >= @min
49
+ false
50
+ end
51
+
52
+ def max_correct?
53
+ return true if @value.blank?
54
+ return true if @max.blank?
55
+ return true if @value <= @max
56
+ false
57
+ end
58
+ end
59
+ end
@@ -1,5 +1,14 @@
1
1
  module Formulary::HtmlForm::Fields
2
2
  class EmailInput < Input
3
+ # The acceptable email pattern in the standard is defined in a language
4
+ # that might not be possible to use in Ruby. Or if it is, we haven't spent
5
+ # the time to find out or not. We did find this supposedly compatible
6
+ # regex on StackOverflow and it passes our tests so we're rolling with that
7
+ # for now.
8
+ #
9
+ # http://stackoverflow.com/questions/4940120/is-there-a-java-implementation-of-the-html5-input-email-validation
10
+ REGEX = /[A-Za-z0-9!#$%&'*+-\/=?^_`{|}~]+@[A-Za-z0-9-]+(.[A-Za-z0-9-]+)*/
11
+
3
12
  def self.compatible_type
4
13
  "email"
5
14
  end
@@ -10,14 +19,14 @@ module Formulary::HtmlForm::Fields
10
19
 
11
20
  def error
12
21
  return super if super.present?
13
- return "email" unless email_correct?
22
+ return "'#{label}' is not a valid email address" unless email_correct?
14
23
  end
15
24
 
16
25
  protected
17
26
 
18
27
  def email_correct?
19
28
  return true if @value.blank?
20
- EmailVeracity::Address.new(@value).valid?
29
+ return true if @value.match(REGEX)
21
30
  end
22
31
  end
23
32
  end
@@ -4,8 +4,8 @@ module Formulary::HtmlForm::Fields
4
4
  false
5
5
  end
6
6
 
7
- def initialize(element)
8
- @element = element
7
+ def initialize(html_form, element)
8
+ @html_form, @element = html_form, element
9
9
  end
10
10
 
11
11
  def name
@@ -21,7 +21,19 @@ module Formulary::HtmlForm::Fields
21
21
  end
22
22
 
23
23
  def error
24
- return "required" if supports_required? && !presence_correct?
24
+ return "'#{label}' is required" if supports_required? && !presence_correct?
25
+ end
26
+
27
+ def label
28
+ @label ||= \
29
+ begin
30
+ l = @html_form.label_for_field(name)
31
+
32
+ if l.nil? then nil
33
+ elsif l.is_a?(String) then l
34
+ else l["fieldset"]
35
+ end
36
+ end
25
37
  end
26
38
 
27
39
  protected