formeze 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +133 -0
- data/Rakefile.rb +7 -0
- data/formeze.gemspec +12 -0
- data/lib/formeze.rb +232 -0
- data/spec/formeze_spec.rb +399 -0
- metadata +50 -0
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
Formeze: A little library for defining classes to handle form data/input
|
2
|
+
========================================================================
|
3
|
+
|
4
|
+
|
5
|
+
Motivation
|
6
|
+
----------
|
7
|
+
|
8
|
+
Most web apps built for end users will need to process urlencoded form data.
|
9
|
+
Registration forms, profile forms, checkout forms, contact forms, and forms
|
10
|
+
for adding/editing application specific data. As developers we would like to
|
11
|
+
process this data safely, to minimise the possibility of security holes
|
12
|
+
within our application that could be exploited. Formeze adopts the approach
|
13
|
+
of being "strict by default", forcing the application code to be explicit in
|
14
|
+
what it accepts as input.
|
15
|
+
|
16
|
+
|
17
|
+
Example usage
|
18
|
+
-------------
|
19
|
+
|
20
|
+
Forms are just "plain old ruby objects". Calling `Formeze.setup` will include
|
21
|
+
some class methods and instance methods, but will otherwise leave the object
|
22
|
+
untouched (i.e. you can define your own initialization). Here is a minimal
|
23
|
+
example, which defines a form with a single "title" field:
|
24
|
+
|
25
|
+
class ExampleForm
|
26
|
+
Formeze.setup(self)
|
27
|
+
|
28
|
+
field :title
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
This form can then be used to parse and validate form/input data as follows:
|
33
|
+
|
34
|
+
form = ExampleForm.new
|
35
|
+
|
36
|
+
form.parse('title=Title')
|
37
|
+
|
38
|
+
form.title # => "Title"
|
39
|
+
|
40
|
+
|
41
|
+
Detecting errors
|
42
|
+
----------------
|
43
|
+
|
44
|
+
Formeze distinguishes between user errors (which are expected in the normal
|
45
|
+
running of your application), and key/value errors (which most likely indicate
|
46
|
+
either developer error, or form tampering).
|
47
|
+
|
48
|
+
For the latter case, the `parse` method that formeze provides will raise a
|
49
|
+
Formeze::KeyError or a Formeze::ValueError exception if the structure of the
|
50
|
+
form data does not match the field definitions.
|
51
|
+
|
52
|
+
After calling `parse` you can check that the form is valid by calling the
|
53
|
+
`#valid?` method. If it isn't you can call the `errors` method which will
|
54
|
+
return an array of error messages to display to the user.
|
55
|
+
|
56
|
+
|
57
|
+
Field options
|
58
|
+
-------------
|
59
|
+
|
60
|
+
By default fields cannot be blank, they are limited to 64 characters,
|
61
|
+
and they cannot contain newlines. These restrictions can be overrided
|
62
|
+
by setting various field options.
|
63
|
+
|
64
|
+
Defining a field without any options works well for a simple text input.
|
65
|
+
If the default character limit is too big or too small you can override
|
66
|
+
it by setting the `char_limit` option. For example:
|
67
|
+
|
68
|
+
field :title, char_limit: 200
|
69
|
+
|
70
|
+
|
71
|
+
If you are dealing with textareas (i.e. multiple lines of text) then you can
|
72
|
+
set the `multiline` option to allow newlines. For example:
|
73
|
+
|
74
|
+
field :description, char_limit: 500, multiline: true
|
75
|
+
|
76
|
+
|
77
|
+
Error messages will include the field label, which by default is set to the
|
78
|
+
field name, capitalized, and with underscores replace by spaces. If you want
|
79
|
+
to override this, set the `label` option. For example:
|
80
|
+
|
81
|
+
field :twitter, label: 'Twitter Username'
|
82
|
+
|
83
|
+
|
84
|
+
If you want to validate that the field value matches a specific pattern you
|
85
|
+
can specify the `pattern` option. This is useful for validating things with
|
86
|
+
well defined formats, like numbers. For example:
|
87
|
+
|
88
|
+
field :number, pattern: /\A[1-9]\d*\z/
|
89
|
+
|
90
|
+
field :card_security_code, char_limit: 5, value: /\A\d+\z/
|
91
|
+
|
92
|
+
|
93
|
+
If you want to validate that the field value belongs to a set of predefined
|
94
|
+
values then you can specify the `values` option. This is useful for dealing
|
95
|
+
with input from select boxes, where the values are known upfront. For example:
|
96
|
+
|
97
|
+
field :card_expiry_month, values: (1..12).map(&:to_s)
|
98
|
+
|
99
|
+
|
100
|
+
The `values` option is also useful for checkboxes. Specify the `key_required`
|
101
|
+
option to handle the case where the checkbox is unchecked. For example:
|
102
|
+
|
103
|
+
field :accept_terms, values: %w(true), key_required: false
|
104
|
+
|
105
|
+
|
106
|
+
Sometimes you'll have a field with multiple values. A multiple select input,
|
107
|
+
a set of checkboxes. For this case you can specify the `multiple` option to
|
108
|
+
allow multiple values. For example:
|
109
|
+
|
110
|
+
field :colour, multiple: true, values: Colour.keys
|
111
|
+
|
112
|
+
|
113
|
+
Unlike all the other examples so far, reading the attribute that corresponds
|
114
|
+
to this field will return an array of strings instead of a single string.
|
115
|
+
|
116
|
+
|
117
|
+
Rails usage
|
118
|
+
-----------
|
119
|
+
|
120
|
+
This is the basic pattern for using a formeze form in a rails controller:
|
121
|
+
|
122
|
+
form = SomeForm.new
|
123
|
+
form.parse(request.raw_post)
|
124
|
+
|
125
|
+
if form.valid?
|
126
|
+
# do something with form data
|
127
|
+
else
|
128
|
+
# display form.errors to user
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
Formeze will automatically define optional "utf8" and "authenticity_token"
|
133
|
+
fields on every form so that you don't have to specify those manually.
|
data/Rakefile.rb
ADDED
data/formeze.gemspec
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'formeze'
|
3
|
+
s.version = '1.0.0'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = ['Tim Craft']
|
6
|
+
s.email = ['mail@timcraft.com']
|
7
|
+
s.homepage = 'http://github.com/timcraft/formeze'
|
8
|
+
s.description = 'A little library for defining classes to handle form data/input'
|
9
|
+
s.summary = 'See description'
|
10
|
+
s.files = Dir.glob('{lib,spec}/**/*') + %w(README.md Rakefile.rb formeze.gemspec)
|
11
|
+
s.require_path = 'lib'
|
12
|
+
end
|
data/lib/formeze.rb
ADDED
@@ -0,0 +1,232 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module Formeze
|
4
|
+
class Label
|
5
|
+
def initialize(name)
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
@name.to_s.tr('_', ' ').capitalize
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Field
|
15
|
+
attr_reader :name
|
16
|
+
|
17
|
+
def initialize(name, options = {})
|
18
|
+
@name, @options = name, options
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate(value, &error)
|
22
|
+
error.call(self, 'is required') if required? && value !~ /\S/
|
23
|
+
|
24
|
+
error.call(self, 'has too many lines') if !multiline? && value.lines.count > 1
|
25
|
+
|
26
|
+
error.call(self, 'has too many characters') if value.chars.count > char_limit
|
27
|
+
|
28
|
+
error.call(self, 'has too many words') if word_limit? && value.scan(/\w+/).length > word_limit
|
29
|
+
|
30
|
+
error.call(self, 'is invalid') if pattern? && value !~ pattern
|
31
|
+
|
32
|
+
error.call(self, 'is invalid') if values? && !values.include?(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
def key
|
36
|
+
@key ||= @name.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def key_required?
|
40
|
+
@options.fetch(:key_required) { true }
|
41
|
+
end
|
42
|
+
|
43
|
+
def label
|
44
|
+
@label ||= @options.fetch(:label) { Label.new(name) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def required?
|
48
|
+
@options.fetch(:required) { true }
|
49
|
+
end
|
50
|
+
|
51
|
+
def multiline?
|
52
|
+
@options.fetch(:multiline) { false }
|
53
|
+
end
|
54
|
+
|
55
|
+
def multiple?
|
56
|
+
@options.fetch(:multiple) { false }
|
57
|
+
end
|
58
|
+
|
59
|
+
def char_limit
|
60
|
+
@options.fetch(:char_limit) { 64 }
|
61
|
+
end
|
62
|
+
|
63
|
+
def word_limit?
|
64
|
+
@options.has_key?(:word_limit)
|
65
|
+
end
|
66
|
+
|
67
|
+
def word_limit
|
68
|
+
@options.fetch(:word_limit)
|
69
|
+
end
|
70
|
+
|
71
|
+
def pattern?
|
72
|
+
@options.has_key?(:pattern)
|
73
|
+
end
|
74
|
+
|
75
|
+
def pattern
|
76
|
+
@options.fetch(:pattern)
|
77
|
+
end
|
78
|
+
|
79
|
+
def values?
|
80
|
+
@options.has_key?(:values)
|
81
|
+
end
|
82
|
+
|
83
|
+
def values
|
84
|
+
@options.fetch(:values)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
module ArrayAttrAccessor
|
89
|
+
def array_attr_reader(name)
|
90
|
+
define_method(name) do
|
91
|
+
ivar = :"@#{name}"
|
92
|
+
|
93
|
+
values = instance_variable_get(ivar)
|
94
|
+
|
95
|
+
if values.nil?
|
96
|
+
values = []
|
97
|
+
|
98
|
+
instance_variable_set(ivar, values)
|
99
|
+
end
|
100
|
+
|
101
|
+
values
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def array_attr_writer(name)
|
106
|
+
define_method(:"#{name}=") do |value|
|
107
|
+
ivar = :"@#{name}"
|
108
|
+
|
109
|
+
values = instance_variable_get(ivar)
|
110
|
+
|
111
|
+
if values.nil?
|
112
|
+
instance_variable_set(ivar, [value])
|
113
|
+
else
|
114
|
+
values << value
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def array_attr_accessor(name)
|
120
|
+
array_attr_reader(name)
|
121
|
+
array_attr_writer(name)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
module ClassMethods
|
126
|
+
include ArrayAttrAccessor
|
127
|
+
|
128
|
+
def fields
|
129
|
+
@fields ||= []
|
130
|
+
end
|
131
|
+
|
132
|
+
def field(*args)
|
133
|
+
field = Field.new(*args)
|
134
|
+
|
135
|
+
fields << field
|
136
|
+
|
137
|
+
if field.multiple?
|
138
|
+
array_attr_accessor field.name
|
139
|
+
else
|
140
|
+
attr_accessor field.name
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def guard(&block)
|
145
|
+
fields << block
|
146
|
+
end
|
147
|
+
|
148
|
+
def checks
|
149
|
+
@checks ||= []
|
150
|
+
end
|
151
|
+
|
152
|
+
def check(&block)
|
153
|
+
checks << block
|
154
|
+
end
|
155
|
+
|
156
|
+
def errors
|
157
|
+
@errors ||= []
|
158
|
+
end
|
159
|
+
|
160
|
+
def error(message)
|
161
|
+
errors << message
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class KeyError < StandardError; end
|
166
|
+
|
167
|
+
class ValueError < StandardError; end
|
168
|
+
|
169
|
+
class UserError < StandardError; end
|
170
|
+
|
171
|
+
module InstanceMethods
|
172
|
+
def parse(encoded_form_data)
|
173
|
+
form_data = CGI.parse(encoded_form_data)
|
174
|
+
|
175
|
+
self.class.fields.each do |field|
|
176
|
+
unless field.respond_to?(:key)
|
177
|
+
instance_eval(&field) ? return : next
|
178
|
+
end
|
179
|
+
|
180
|
+
unless form_data.has_key?(field.key)
|
181
|
+
next if field.multiple? || !field.key_required?
|
182
|
+
|
183
|
+
raise KeyError
|
184
|
+
end
|
185
|
+
|
186
|
+
values = form_data.delete(field.key)
|
187
|
+
|
188
|
+
if values.length > 1
|
189
|
+
raise ValueError unless field.multiple?
|
190
|
+
end
|
191
|
+
|
192
|
+
values.each do |value|
|
193
|
+
field.validate(value) do |error|
|
194
|
+
errors << UserError.new("#{field.label} #{error}")
|
195
|
+
end
|
196
|
+
|
197
|
+
send(:"#{field.name}=", value)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
raise KeyError unless form_data.empty?
|
202
|
+
|
203
|
+
self.class.checks.zip(self.class.errors) do |check, error|
|
204
|
+
instance_eval(&check) ? next : errors << UserError.new(error)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def errors
|
209
|
+
@errors ||= []
|
210
|
+
end
|
211
|
+
|
212
|
+
def valid?
|
213
|
+
errors.empty?
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.setup(form)
|
218
|
+
form.send :include, InstanceMethods
|
219
|
+
|
220
|
+
form.extend ClassMethods
|
221
|
+
|
222
|
+
if on_rails?
|
223
|
+
form.field(:utf8, key_required: false)
|
224
|
+
|
225
|
+
form.field(:authenticity_token, key_required: false)
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.on_rails?
|
230
|
+
defined?(Rails)
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'formeze'
|
4
|
+
|
5
|
+
class FormWithField
|
6
|
+
Formeze.setup(self)
|
7
|
+
|
8
|
+
field :title
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'FormWithField' do
|
12
|
+
before do
|
13
|
+
@form = FormWithField.new
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'title method' do
|
17
|
+
it 'should return nil' do
|
18
|
+
@form.title.must_be_nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'title equals method' do
|
23
|
+
it 'should set the value of the title attribute' do
|
24
|
+
@form.title = 'Untitled'
|
25
|
+
@form.title.must_equal('Untitled')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 'parse method' do
|
30
|
+
it 'should set the value of the title attribute' do
|
31
|
+
@form.parse('title=Untitled')
|
32
|
+
@form.title.must_equal('Untitled')
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should raise an exception when the key is missing' do
|
36
|
+
proc { @form.parse('') }.must_raise(Formeze::KeyError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should raise an exception when there are multiple values for the key' do
|
40
|
+
proc { @form.parse('title=foo&title=bar') }.must_raise(Formeze::ValueError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should raise an exception when there is an unexpected key' do
|
44
|
+
proc { @form.parse('title=Untitled&foo=bar') }.must_raise(Formeze::KeyError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe 'FormWithField after parsing valid input' do
|
50
|
+
before do
|
51
|
+
@form = FormWithField.new
|
52
|
+
@form.parse('title=Untitled')
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'valid query method' do
|
56
|
+
it 'should return true' do
|
57
|
+
@form.valid?.must_equal(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'errors method' do
|
62
|
+
it 'should return an empty array' do
|
63
|
+
@form.errors.must_be_instance_of(Array)
|
64
|
+
@form.errors.must_be_empty
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe 'FormWithField after parsing blank input' do
|
70
|
+
before do
|
71
|
+
@form = FormWithField.new
|
72
|
+
@form.parse('title=')
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'valid query method' do
|
76
|
+
it 'should return false' do
|
77
|
+
@form.valid?.must_equal(false)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'FormWithField after parsing input containing newlines' do
|
83
|
+
before do
|
84
|
+
@form = FormWithField.new
|
85
|
+
@form.parse('title=This+is+a+product.%0AIt+is+very+lovely.')
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'valid query method' do
|
89
|
+
it 'should return false' do
|
90
|
+
@form.valid?.must_equal(false)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class FormWithOptionalField
|
96
|
+
Formeze.setup(self)
|
97
|
+
|
98
|
+
field :title, required: false
|
99
|
+
end
|
100
|
+
|
101
|
+
describe 'FormWithOptionalField after parsing blank input' do
|
102
|
+
before do
|
103
|
+
@form = FormWithOptionalField.new
|
104
|
+
@form.parse('title=')
|
105
|
+
end
|
106
|
+
|
107
|
+
describe 'valid query method' do
|
108
|
+
it 'should return true' do
|
109
|
+
@form.valid?.must_equal(true)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
class FormWithFieldThatCanHaveMultipleLines
|
115
|
+
Formeze.setup(self)
|
116
|
+
|
117
|
+
field :description, multiline: true
|
118
|
+
end
|
119
|
+
|
120
|
+
describe 'FormWithFieldThatCanHaveMultipleLines after parsing input containing newlines' do
|
121
|
+
before do
|
122
|
+
@form = FormWithFieldThatCanHaveMultipleLines.new
|
123
|
+
@form.parse('description=This+is+a+product.%0AIt+is+very+lovely.')
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'valid query method' do
|
127
|
+
it 'should return true' do
|
128
|
+
@form.valid?.must_equal(true)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class FormWithCharacterLimitedField
|
134
|
+
Formeze.setup(self)
|
135
|
+
|
136
|
+
field :title, char_limit: 16
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'FormWithCharacterLimitedField after parsing input with too many characters' do
|
140
|
+
before do
|
141
|
+
@form = FormWithCharacterLimitedField.new
|
142
|
+
@form.parse('title=This+Title+Will+Be+Too+Long')
|
143
|
+
end
|
144
|
+
|
145
|
+
describe 'valid query method' do
|
146
|
+
it 'should return false' do
|
147
|
+
@form.valid?.must_equal(false)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class FormWithWordLimitedField
|
153
|
+
Formeze.setup(self)
|
154
|
+
|
155
|
+
field :title, word_limit: 2
|
156
|
+
end
|
157
|
+
|
158
|
+
describe 'FormWithWordLimitedField after parsing input with too many words' do
|
159
|
+
before do
|
160
|
+
@form = FormWithWordLimitedField.new
|
161
|
+
@form.parse('title=This+Title+Will+Be+Too+Long')
|
162
|
+
end
|
163
|
+
|
164
|
+
describe 'valid query method' do
|
165
|
+
it 'should return false' do
|
166
|
+
@form.valid?.must_equal(false)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class FormWithFieldThatMustMatchPattern
|
172
|
+
Formeze.setup(self)
|
173
|
+
|
174
|
+
field :number, pattern: /\A\d+\z/
|
175
|
+
end
|
176
|
+
|
177
|
+
describe 'FormWithFieldThatMustMatchPattern after parsing input that matches the pattern' do
|
178
|
+
before do
|
179
|
+
@form = FormWithFieldThatMustMatchPattern.new
|
180
|
+
@form.parse('number=12345')
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'valid query method' do
|
184
|
+
it 'should return true' do
|
185
|
+
@form.valid?.must_equal(true)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe 'FormWithFieldThatMustMatchPattern after parsing input that does not match the pattern' do
|
191
|
+
before do
|
192
|
+
@form = FormWithFieldThatMustMatchPattern.new
|
193
|
+
@form.parse('number=notanumber')
|
194
|
+
end
|
195
|
+
|
196
|
+
describe 'valid query method' do
|
197
|
+
it 'should return false' do
|
198
|
+
@form.valid?.must_equal(false)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class FormWithFieldThatCanHaveMultipleValues
|
204
|
+
Formeze.setup(self)
|
205
|
+
|
206
|
+
field :colour, multiple: true
|
207
|
+
end
|
208
|
+
|
209
|
+
describe 'FormWithFieldThatCanHaveMultipleValues' do
|
210
|
+
before do
|
211
|
+
@form = FormWithFieldThatCanHaveMultipleValues.new
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'colour method' do
|
215
|
+
it 'should return an empty array' do
|
216
|
+
@form.colour.must_be_instance_of(Array)
|
217
|
+
@form.colour.must_be_empty
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe 'colour equals method' do
|
222
|
+
it 'should add the argument to the colour attribute array' do
|
223
|
+
@form.colour = 'black'
|
224
|
+
@form.colour.must_include('black')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe 'parse method' do
|
229
|
+
it 'should add the value to the colour attribute array' do
|
230
|
+
@form.parse('colour=black')
|
231
|
+
@form.colour.must_include('black')
|
232
|
+
end
|
233
|
+
|
234
|
+
it 'should not raise an exception when there are multiple values for the key' do
|
235
|
+
@form.parse('colour=black&colour=white')
|
236
|
+
end
|
237
|
+
|
238
|
+
it 'should not raise an exception when the key is missing' do
|
239
|
+
@form.parse('')
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe 'FormWithFieldThatCanHaveMultipleValues after parsing input with multiple values' do
|
245
|
+
before do
|
246
|
+
@form = FormWithFieldThatCanHaveMultipleValues.new
|
247
|
+
@form.parse('colour=black&colour=white')
|
248
|
+
end
|
249
|
+
|
250
|
+
describe 'colour method' do
|
251
|
+
it 'should return an array containing the values' do
|
252
|
+
@form.colour.must_be_instance_of(Array)
|
253
|
+
@form.colour.must_include('black')
|
254
|
+
@form.colour.must_include('white')
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe 'valid query method' do
|
259
|
+
it 'should return true' do
|
260
|
+
@form.valid?.must_equal(true)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe 'FormWithFieldThatCanHaveMultipleValues after parsing input with no values' do
|
266
|
+
before do
|
267
|
+
@form = FormWithFieldThatCanHaveMultipleValues.new
|
268
|
+
@form.parse('')
|
269
|
+
end
|
270
|
+
|
271
|
+
describe 'colour method' do
|
272
|
+
it 'should return an empty array' do
|
273
|
+
@form.colour.must_be_instance_of(Array)
|
274
|
+
@form.colour.must_be_empty
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
describe 'valid query method' do
|
279
|
+
it 'should return true' do
|
280
|
+
@form.valid?.must_equal(true)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
class FormWithFieldThatCanOnlyHaveSpecifiedValues
|
286
|
+
Formeze.setup(self)
|
287
|
+
|
288
|
+
field :answer, values: %w(yes no)
|
289
|
+
end
|
290
|
+
|
291
|
+
describe 'FormWithFieldThatCanOnlyHaveSpecifiedValues after parsing input with an invalid value' do
|
292
|
+
before do
|
293
|
+
@form = FormWithFieldThatCanOnlyHaveSpecifiedValues.new
|
294
|
+
@form.parse('answer=maybe')
|
295
|
+
end
|
296
|
+
|
297
|
+
describe 'valid query method' do
|
298
|
+
it 'should return false' do
|
299
|
+
@form.valid?.must_equal(false)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
class FormWithGuard
|
305
|
+
Formeze.setup(self)
|
306
|
+
|
307
|
+
field :delivery_address
|
308
|
+
|
309
|
+
field :same_address, values: %w(yes no)
|
310
|
+
|
311
|
+
guard { same_address? }
|
312
|
+
|
313
|
+
field :billing_address
|
314
|
+
|
315
|
+
def same_address?
|
316
|
+
same_address == 'yes'
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe 'FormWithGuard after parsing input with same_address set and no billing address' do
|
321
|
+
before do
|
322
|
+
@form = FormWithGuard.new
|
323
|
+
@form.parse('delivery_address=123+Main+St&same_address=yes')
|
324
|
+
end
|
325
|
+
|
326
|
+
describe 'valid query method' do
|
327
|
+
it 'should return true' do
|
328
|
+
@form.valid?.must_equal(true)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
class FormWithCustomValidation
|
334
|
+
Formeze.setup(self)
|
335
|
+
|
336
|
+
field :email
|
337
|
+
|
338
|
+
check { email.include?(?@) }
|
339
|
+
error 'Email is invalid'
|
340
|
+
end
|
341
|
+
|
342
|
+
describe 'FormWithCustomValidation after parsing invalid input' do
|
343
|
+
before do
|
344
|
+
@form = FormWithCustomValidation.new
|
345
|
+
@form.parse('email=alice')
|
346
|
+
end
|
347
|
+
|
348
|
+
describe 'valid query method' do
|
349
|
+
it 'should return false' do
|
350
|
+
@form.valid?.must_equal(false)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
class FormWithOptionalKey
|
356
|
+
Formeze.setup(self)
|
357
|
+
|
358
|
+
field :accept_terms, values: %w(true), key_required: false
|
359
|
+
end
|
360
|
+
|
361
|
+
describe 'FormWithOptionalKey after parsing input without the key' do
|
362
|
+
before do
|
363
|
+
@form = FormWithOptionalKey.new
|
364
|
+
@form.parse('')
|
365
|
+
end
|
366
|
+
|
367
|
+
describe 'valid query method' do
|
368
|
+
it 'should return true' do
|
369
|
+
@form.valid?.must_equal(true)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
Rails = Object.new
|
375
|
+
|
376
|
+
class RailsForm
|
377
|
+
Formeze.setup(self)
|
378
|
+
|
379
|
+
field :title
|
380
|
+
end
|
381
|
+
|
382
|
+
describe 'RailsForm' do
|
383
|
+
before do
|
384
|
+
@form = RailsForm.new
|
385
|
+
end
|
386
|
+
|
387
|
+
describe 'parse method' do
|
388
|
+
it 'should automatically process the utf8 and authenticity_token parameters' do
|
389
|
+
@form.parse('utf8=%E2%9C%93&authenticity_token=5RMc3sPZdR%2BZz4onNS8NfK&title=Test')
|
390
|
+
@form.authenticity_token.wont_be_empty
|
391
|
+
@form.utf8.wont_be_empty
|
392
|
+
end
|
393
|
+
|
394
|
+
it 'should not complain if the utf8 or authenticity_token parameters are missing' do
|
395
|
+
@form.parse('utf8=%E2%9C%93&title=Test')
|
396
|
+
@form.parse('authenticity_token=5RMc3sPZdR%2BZz4onNS8NfK&title=Test')
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
metadata
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: formeze
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Craft
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-04-09 00:00:00.000000000Z
|
13
|
+
dependencies: []
|
14
|
+
description: A little library for defining classes to handle form data/input
|
15
|
+
email:
|
16
|
+
- mail@timcraft.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- lib/formeze.rb
|
22
|
+
- spec/formeze_spec.rb
|
23
|
+
- README.md
|
24
|
+
- Rakefile.rb
|
25
|
+
- formeze.gemspec
|
26
|
+
homepage: http://github.com/timcraft/formeze
|
27
|
+
licenses: []
|
28
|
+
post_install_message:
|
29
|
+
rdoc_options: []
|
30
|
+
require_paths:
|
31
|
+
- lib
|
32
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
requirements: []
|
45
|
+
rubyforge_project:
|
46
|
+
rubygems_version: 1.8.10
|
47
|
+
signing_key:
|
48
|
+
specification_version: 3
|
49
|
+
summary: See description
|
50
|
+
test_files: []
|