formeze 1.0.0
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/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: []
|