formulary 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 46c937b704dd06944ff4591bac2fd43d72a24ec2
4
+ data.tar.gz: 14d9eec2e9d6bd6e81250e6dfa9f8031ff294b24
5
+ SHA512:
6
+ metadata.gz: 786f312821225f0c57ae34344d722fe93107a25f6b9d462eafd1f6da43191fd997e20c8b77aa5c25d8802e807b6c550cce566d6434feed68a8e3d835b47bcaa4
7
+ data.tar.gz: abcd346bebb4a05ef63e1fc5ad2616078f81bf2e1bfe9047efa61f010a2681d5bf1539bcc67c84b0398c2798dad62da5ee3fed58a195844899f49a812ea4ef8d
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in formulary.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Matt Bohme
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,75 @@
1
+ # Formulary
2
+
3
+ > <strong>Formularies</strong> (singular <strong>formulary</strong>; Latin <em>littera(e) formularis, -ares</em>) are medieval collections of models for the execution of documents (acta), public or private; a space being left for the insertion of names, dates, and circumstances peculiar to each case. Their modern equivalent are forms.
4
+ >
5
+ > -- <cite><a href="http://en.wikipedia.org/wiki/Formulary_%28model_documents%29">Wikipedia</a></cite>
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'formulary'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install formulary
20
+
21
+ ## Usage
22
+
23
+ Create a new Formulary Form
24
+
25
+ ```ruby
26
+ require 'formulary'
27
+
28
+ form_html = <<EOF
29
+ <form>
30
+ <input type="email" name="email" required />
31
+ <input type="username" name="username" pattern="[a-z0-9_-]{3,16}" />
32
+ </form>
33
+ EOF
34
+
35
+ html_form = Formulary::HtmlForm.new(form_html)
36
+ ```
37
+
38
+ Validate the form based on HTML5 field types and/or patterns and view which fields are invalid and why.
39
+
40
+ ```ruby
41
+ html_form.valid?({ email: "test@example.com", username: "person" })
42
+ # => true
43
+
44
+ html_form.valid?({ email: "invalid", username: "person" })
45
+ # => false
46
+
47
+ html_form.errors
48
+ # => {"email"=>"not a valid email"}
49
+ ```
50
+
51
+ When an unexpected field is submitted that wasn't in the original markup, it will raise a `Formulary::UnexpectedParameter` exception:
52
+
53
+ ```ruby
54
+ html_form.valid?({ unknown: "value" })
55
+ # => Formulary::UnexpectedParameter: Got unexpected field 'unknown'
56
+ ```
57
+
58
+ ## Currently Supported
59
+
60
+ - type="email"
61
+ - required
62
+ - pattern="REGEX"
63
+
64
+ ## TODO
65
+
66
+ - select, checkbox, radio and multiselect tags have one of the valid options selected
67
+ - validate [other html5 field types](http://www.w3schools.com/html/html5_form_input_types.asp)
68
+
69
+ ## Contributing
70
+
71
+ 1. Fork it
72
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
73
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
74
+ 4. Push to the branch (`git push origin my-new-feature`)
75
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'formulary/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "formulary"
8
+ spec.version = Formulary::VERSION
9
+ spec.authors = ["Matt Bohme", "Don Petersen"]
10
+ spec.email = ["matt.bohme@g5searchmarketing.com", "don.petersen@g5searchmarketing.com"]
11
+ spec.description = %q{Valid form submission based on the HTML5 validation on the form itself}
12
+ spec.summary = %q{Valid form submission based on the HTML5 validation on the form itself}
13
+ spec.homepage = "http://github.com/g5/formulary"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "nokogiri"
22
+ spec.add_dependency "activesupport"
23
+ spec.add_dependency "email_veracity"
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.3"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,6 @@
1
+ require 'nokogiri'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'email_veracity'
4
+
5
+ require "formulary/version"
6
+ require "formulary/html_form"
@@ -0,0 +1,101 @@
1
+ module Formulary
2
+ class HtmlForm
3
+ class Field
4
+ attr_accessor :name, :type, :required, :pattern
5
+
6
+ def initialize(name, type, required, pattern=nil)
7
+ @name = name
8
+ @type = type
9
+ @required = required
10
+ @pattern = pattern
11
+ end
12
+
13
+ def set_value(value)
14
+ @value = value
15
+ end
16
+
17
+ def valid?
18
+ presence_correct && pattern_correct && correct_for_type
19
+ end
20
+
21
+ def error
22
+ return "required" unless presence_correct
23
+ return "format" unless pattern_correct
24
+ return "not a valid #{@type}" unless correct_for_type
25
+ end
26
+
27
+ protected
28
+
29
+ def presence_correct
30
+ !required || @value.present?
31
+ end
32
+
33
+ def pattern_correct
34
+ return true if @pattern.blank? || @value.blank?
35
+ @value.match(Regexp.new(@pattern))
36
+ end
37
+
38
+ def correct_for_type
39
+ return true if @value.blank?
40
+
41
+ case @type
42
+ when "email"
43
+ EmailVeracity::Address.new(@value).valid?
44
+ else
45
+ true
46
+ end
47
+ end
48
+ end
49
+
50
+ def initialize(markup)
51
+ @markup = markup
52
+ end
53
+
54
+ def fields
55
+ @fields ||= build_fields
56
+ end
57
+
58
+ def valid?(params)
59
+ params.each do |key, value|
60
+ raise UnexpectedParameter.new("Got unexpected field '#{key}'") unless find_field(key)
61
+
62
+ find_field(key).set_value(value)
63
+ end
64
+
65
+ fields.all?(&:valid?)
66
+ end
67
+
68
+ def errors
69
+ fields.each_with_object({}) do |field, hash|
70
+ hash[field.name] = field.error unless field.valid?
71
+ end
72
+ end
73
+
74
+ protected
75
+
76
+ def find_field(name)
77
+ fields.detect { |field| field.name == name.to_s }
78
+ end
79
+
80
+ def build_fields
81
+ doc = Nokogiri::HTML(@markup)
82
+
83
+ doc.css("input[type!='submit'], textarea").map do |input|
84
+ type = input.name == "textarea" ? "textarea" : input.attributes["type"].value
85
+ pattern = input.attributes.include?("pattern") ? input.attributes["pattern"].value : nil
86
+
87
+
88
+ Field.new(
89
+ input.attributes["name"].value,
90
+ type,
91
+ input.attributes.include?("required"),
92
+ pattern
93
+ )
94
+ end
95
+ end
96
+ end
97
+
98
+ class UnexpectedParameter < StandardError
99
+
100
+ end
101
+ end
@@ -0,0 +1,3 @@
1
+ module Formulary
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,187 @@
1
+ require 'spec_helper'
2
+
3
+ describe Formulary::HtmlForm do
4
+ let(:markup) do
5
+ <<-EOS
6
+ <div class="lead widget">
7
+
8
+ <p class="heading">Apply</p>
9
+
10
+ <form id="lead_form" action="{{ widget.lead_form.submission_url.value }}" method="POST">
11
+
12
+ <div class="field">
13
+ <label for="first_name">First Name</label>
14
+ <input type="text" name="first_name" required />
15
+ </div>
16
+
17
+ <div class="field">
18
+ <label for="last_name">Last Name</label>
19
+ <input type="text" name="last_name" required />
20
+ </div>
21
+
22
+ <div class="field">
23
+ <label for="email">Email</label>
24
+ <input type="email" name="email" required />
25
+ </div>
26
+
27
+ <div class="field">
28
+ <label for="g5_email">G5 Email</label>
29
+ <input type="email" name="g5_email" pattern="@getg5\.com$" />
30
+ </div>
31
+
32
+ <div class="field">
33
+ <label for="phone">Phone</label>
34
+ <input type="tel" name="phone" />
35
+ </div>
36
+
37
+ <div class="field">
38
+ <label for="street_address">Street Address</label>
39
+ <input type="text" name="street_address" />
40
+ </div>
41
+
42
+ <div class="field">
43
+ <label for="city">City</label>
44
+ <input type="text" name="city" pattern="^[A-Za-z]*$" />
45
+ </div>
46
+
47
+ <div class="field">
48
+ <label for="state">State</label>
49
+ <input type="text" name="state" />
50
+ </div>
51
+
52
+ <div class="field">
53
+ <label for="zip">Zip</label>
54
+ <input type="text" name="zip" />
55
+ </div>
56
+
57
+ <div class="field">
58
+ <label for="message">Message</label>
59
+ <textarea name="message"></textarea>
60
+ </div>
61
+
62
+ <input type="submit" value="Apply" />
63
+
64
+ </form>
65
+ </div>
66
+ EOS
67
+ end
68
+
69
+ def valid_params(hash = {})
70
+ {
71
+ first_name: "First",
72
+ last_name: "Last",
73
+ email: "test@test.com",
74
+ g5_email: "test@getg5.com"
75
+ }.merge(hash)
76
+ end
77
+
78
+ let(:html_form) { Formulary::HtmlForm.new(markup) }
79
+
80
+ describe "#fields" do
81
+ let(:fields) { html_form.fields }
82
+ subject { fields }
83
+
84
+ its(:length) { should eq(10) }
85
+
86
+ describe "a required text input" do
87
+ subject { fields.first }
88
+
89
+ its(:name) { should eq("first_name") }
90
+ its(:type) { should eq("text") }
91
+ its(:required) { should be_true }
92
+ end
93
+
94
+ describe "a textarea that is not required" do
95
+ subject { fields[9] }
96
+
97
+ its(:name) { should eq("message") }
98
+ its(:type) { should eq("textarea") }
99
+ its(:required) { should be_false }
100
+ end
101
+
102
+ describe "an input with a pattern attribute" do
103
+ subject { fields[6] }
104
+
105
+ its(:pattern) { should eq("^[A-Za-z]*$") }
106
+ end
107
+ end
108
+
109
+ describe "#valid?" do
110
+ subject(:valid) { html_form.valid?(params) }
111
+ #before { subject }
112
+
113
+ context "with valid parameters" do
114
+ let(:params) { valid_params }
115
+ it { should be_true }
116
+ end
117
+
118
+ context "with valid parameters" do
119
+ context "phone number" do
120
+ let(:params) { valid_params(phone: "+1 456 123987") }
121
+ it { should be_true }
122
+ end
123
+ end
124
+
125
+ context "with invalid parameters" do
126
+ context "due to missing parameters" do
127
+ let(:params) { { last_name: "" } }
128
+
129
+ it { should be_false }
130
+
131
+ it "has an error for an omitted field" do
132
+ html_form.errors.keys.should include("first_name")
133
+ html_form.errors["first_name"].should include("required")
134
+ end
135
+
136
+ it "has an error for a blank field" do
137
+ html_form.errors.keys.should include("last_name")
138
+ html_form.errors["last_name"].should include("required")
139
+ end
140
+ end
141
+
142
+ context "due to failing regex check" do
143
+ let(:params) { valid_params(city: "New York") }
144
+
145
+ it { should be_false }
146
+
147
+ it "has an error for the field that doesn't match the pattern" do
148
+ valid
149
+ html_form.errors.keys.should include("city")
150
+ html_form.errors["city"].should include("format")
151
+ end
152
+ end
153
+
154
+ context "due to invalid email address" do
155
+ let(:params) { valid_params(email: "testing") }
156
+
157
+ it { should be_false }
158
+
159
+ it "has an error for the email field" do
160
+ valid
161
+ html_form.errors.keys.should include("email")
162
+ html_form.errors["email"].should include("not a valid email")
163
+ end
164
+ end
165
+
166
+ context "due to invalid g5 email address" do
167
+ let(:params) { valid_params(g5_email: "test@test.com") }
168
+
169
+ it { should be_false }
170
+
171
+ it "has an error for the g5 email field" do
172
+ valid
173
+ html_form.errors.keys.should include("g5_email")
174
+ html_form.errors["g5_email"].should include("format")
175
+ end
176
+ end
177
+
178
+ context "due to unexpected parameters" do
179
+ let(:params) { valid_params(extra: "test") }
180
+
181
+ it "raises a Formulary::UnexpectedParameter exception" do
182
+ expect { valid }.to raise_error(Formulary::UnexpectedParameter, /extra/)
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,19 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ RSpec.configure do |config|
8
+ config.treat_symbols_as_metadata_keys_with_true_values = true
9
+ config.run_all_when_everything_filtered = true
10
+ config.filter_run :focus
11
+
12
+ # Run specs in random order to surface order dependencies. If you find an
13
+ # order dependency and want to debug it, you can fix the order by providing
14
+ # the seed, which is printed after each run.
15
+ # --seed 1234
16
+ config.order = 'random'
17
+ end
18
+
19
+ require 'formulary'
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: formulary
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Matt Bohme
8
+ - Don Petersen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-10-23 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - '>='
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: activesupport
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: email_veracity
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - '>='
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: bundler
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: '1.3'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '1.3'
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: rspec
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ description: Valid form submission based on the HTML5 validation on the form itself
99
+ email:
100
+ - matt.bohme@g5searchmarketing.com
101
+ - don.petersen@g5searchmarketing.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - .gitignore
107
+ - .rspec
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - formulary.gemspec
113
+ - lib/formulary.rb
114
+ - lib/formulary/html_form.rb
115
+ - lib/formulary/version.rb
116
+ - spec/html_form_spec.rb
117
+ - spec/spec_helper.rb
118
+ homepage: http://github.com/g5/formulary
119
+ licenses:
120
+ - MIT
121
+ metadata: {}
122
+ post_install_message:
123
+ rdoc_options: []
124
+ require_paths:
125
+ - lib
126
+ required_ruby_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - '>='
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ required_rubygems_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 2.0.8
139
+ signing_key:
140
+ specification_version: 4
141
+ summary: Valid form submission based on the HTML5 validation on the form itself
142
+ test_files:
143
+ - spec/html_form_spec.rb
144
+ - spec/spec_helper.rb