vulcanize 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +280 -0
- data/Rakefile +9 -0
- data/lib/vulcanize.rb +6 -0
- data/lib/vulcanize/check_box.rb +13 -0
- data/lib/vulcanize/form.rb +63 -0
- data/lib/vulcanize/version.rb +3 -0
- data/test/check_box_test.rb +17 -0
- data/test/default_attribute_test.rb +32 -0
- data/test/private_attribute_test.rb +30 -0
- data/test/renamed_attribute_test.rb +22 -0
- data/test/required_attribute_test.rb +53 -0
- data/test/standard_attribute_test.rb +98 -0
- data/test/test_config.rb +14 -0
- data/vulcanize.gemspec +25 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 462824bb0ed48d86a0dd6eb81d85a14959a77f4c
|
4
|
+
data.tar.gz: 43adbd9afbd615f43658ba3611dc00cdff919047
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9af8b2b4cf35288cbf6c4e5f5b9819d08cba4e69b34d68cd635f4c33da3f806f7591ae735fcd42146fb2d5bfffeb1588d275ba6c2147131dcdadf984e210fb62
|
7
|
+
data.tar.gz: e86a618092527ff2a320ea9afb9c7aaad857a5360eb9cf983c0036a3fd9fb7b2c0d27d586056f9d092af45c1d04ad0ac492a232ebfa3d631f477df0814db3712
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Peter Saxton
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,280 @@
|
|
1
|
+
# Vulcanize
|
2
|
+
|
3
|
+
Form objects to handle coercing user input.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'vulcanize'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install vulcanize
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
Forms consist of one or more attributes that are defined with a name, type and optional named parameters. Types are used to coerce the raw input and should be responsible for deciding a given inputs validity. If we have a `Name` domain object we can use it in a vulcanize form as follows. *Notes below on creating domain object*
|
24
|
+
|
25
|
+
```rb
|
26
|
+
class Form < Vulcanize::Form
|
27
|
+
attribute :name, Name
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
The default behavior of an attribute is to coerce the input when it is valid, return `nil` when there is no input and to raise an error for invalid input.
|
32
|
+
|
33
|
+
```rb
|
34
|
+
# Create a form with a valid name
|
35
|
+
form = Form.new :name => 'Peter'
|
36
|
+
|
37
|
+
form.valid?
|
38
|
+
# => true
|
39
|
+
|
40
|
+
form.email
|
41
|
+
# => #<Name:0x00000002a579e8 @value="Peter">
|
42
|
+
```
|
43
|
+
|
44
|
+
```rb
|
45
|
+
# Create a form with a null name
|
46
|
+
form = Form.new :name => nil
|
47
|
+
|
48
|
+
form.valid?
|
49
|
+
# => true
|
50
|
+
|
51
|
+
form.email
|
52
|
+
# => nil
|
53
|
+
```
|
54
|
+
|
55
|
+
```rb
|
56
|
+
# Create a form with an invalid name
|
57
|
+
form = Form.new :name => '<DANGER!!>'
|
58
|
+
|
59
|
+
form.valid?
|
60
|
+
# => false
|
61
|
+
|
62
|
+
form.email
|
63
|
+
# !! ArgumentError
|
64
|
+
```
|
65
|
+
|
66
|
+
### Error Handling
|
67
|
+
|
68
|
+
Forms are designed to offer flexible error handling while limiting the ways that invalid can pass through to the core program. Each attribute method raises an error if the coercion fails or if raw input is missing from an attribute that was required. If a block is passed to the method then instead of failing the block will be called with the raw value and the error that would have been raised. This allows several different ways to handle bad input.
|
69
|
+
|
70
|
+
Usecase 1: return raw input and error so the user can edit the raw value.
|
71
|
+
|
72
|
+
```rb
|
73
|
+
form = Form.new :name => '<DANGER!!>'
|
74
|
+
|
75
|
+
value = form.email { |raw, _| raw }
|
76
|
+
error = form.email { |_, error| error }
|
77
|
+
```
|
78
|
+
|
79
|
+
Usecase 2: return a default value and error which a user may use.
|
80
|
+
|
81
|
+
```rb
|
82
|
+
form = Form.new :start_date => 'bad input'
|
83
|
+
|
84
|
+
value = form.start_date { |raw, _| DateTime.now }
|
85
|
+
error = form.start_date { |_, error| error }
|
86
|
+
```
|
87
|
+
### Note
|
88
|
+
|
89
|
+
All Ruby methods can take a block, this allows you to use the form in place of a domain object.
|
90
|
+
|
91
|
+
```rb
|
92
|
+
user.email { |raw, error| #Never called, user email method does not use a block }
|
93
|
+
```
|
94
|
+
|
95
|
+
### Default attribute
|
96
|
+
Attributes may have a default value that will be returned instead of nil when the raw value is nil or empty.
|
97
|
+
|
98
|
+
```rb
|
99
|
+
class NullName
|
100
|
+
def value
|
101
|
+
'No name'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class DefaultForm < Vulcanize::Form
|
106
|
+
attribute :name, Name, :default => NullName.new
|
107
|
+
end
|
108
|
+
|
109
|
+
form = DefaultForm.new :name => ''
|
110
|
+
|
111
|
+
form.valid?
|
112
|
+
# => true
|
113
|
+
|
114
|
+
form.name
|
115
|
+
# => #<NullName:0x00000002aafb98>
|
116
|
+
```
|
117
|
+
|
118
|
+
### Required attribute
|
119
|
+
Attributes can be specified as being required. When the raw value is nil or empty retrieving the attribute vale will raise an `AttributeRequired` error and the form will be invalid.
|
120
|
+
|
121
|
+
```rb
|
122
|
+
class RequiredForm < Vulcanize::Form
|
123
|
+
attribute :name, Name, :required => true
|
124
|
+
end
|
125
|
+
|
126
|
+
form = RequiredForm.new :name => ''
|
127
|
+
|
128
|
+
form.valid?
|
129
|
+
# => false
|
130
|
+
|
131
|
+
form.name
|
132
|
+
# !! Vulcanize::AttributeRequired
|
133
|
+
```
|
134
|
+
|
135
|
+
### Private attribute
|
136
|
+
Sometimes input needs to be coerced or validated but should not be available outside the form. The classic example is password confirmation in a form.
|
137
|
+
|
138
|
+
```rb
|
139
|
+
class PasswordForm < Vulcanize::Form
|
140
|
+
attribute :password, Password, :required => true
|
141
|
+
attribute :password_confirmation, Password, :private => true
|
142
|
+
|
143
|
+
def valid?
|
144
|
+
unless password == password_confirmation
|
145
|
+
return false
|
146
|
+
end
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
151
|
+
|
152
|
+
### Renamed attribute
|
153
|
+
Vulcanize can also be used to handle any input that can be cast as a hash. JSON data for example. It may be that input fields need renaming. That can be done with the 'from' parameter.
|
154
|
+
|
155
|
+
```rb
|
156
|
+
class RenameForm < Vulcanize::Form
|
157
|
+
attribute :name, Name, :from => 'display_name'
|
158
|
+
end
|
159
|
+
|
160
|
+
form = RenameForm.new 'display_name' => 'Peter'
|
161
|
+
|
162
|
+
form.valid?
|
163
|
+
# => true
|
164
|
+
|
165
|
+
form.values
|
166
|
+
# => {:name => #<Name:0x00000002a579e8 @value="Peter">}
|
167
|
+
```
|
168
|
+
|
169
|
+
> Possible extension symbol option. if set true recases to symbol. if not leaves as input. default true. from could as be called field.
|
170
|
+
|
171
|
+
### Domain objects
|
172
|
+
|
173
|
+
The first step is to create your domain object. It should throw an ArgumentError if initialized with invalid arguments.
|
174
|
+
|
175
|
+
As an example here is a very simple implementation of a name object which has these conditions.
|
176
|
+
- It will always be capitalized
|
177
|
+
- It must be between 2 and 20 characters
|
178
|
+
|
179
|
+
```rb
|
180
|
+
class Name
|
181
|
+
def initialize(raw)
|
182
|
+
section = raw[/^\w{2,20}$/]
|
183
|
+
raise ArgumentError unless section
|
184
|
+
@value = section.capitalize
|
185
|
+
end
|
186
|
+
|
187
|
+
attr_reader :value
|
188
|
+
end
|
189
|
+
```
|
190
|
+
### Check boxes
|
191
|
+
A common requirement is handling check boxes in HTML forms. There are two distinct requirements when handling these inputs. They are the 'optional check box' and the 'agreement check box'. Vulcanize provides a `CheckBox` coercer to handle these inputs.
|
192
|
+
|
193
|
+
#### Optional check box
|
194
|
+
With this check box the user is indicating true when it is checked and false when it is left unchecked. This can be achieved sing the default option
|
195
|
+
|
196
|
+
```rb
|
197
|
+
class OptionForm < Vulcanize::Form
|
198
|
+
attribute :recieve_mail, Vulcanize::Checkbox, :default => false
|
199
|
+
end
|
200
|
+
|
201
|
+
form = OptionForm.new
|
202
|
+
|
203
|
+
form.recieve_mail
|
204
|
+
# => false
|
205
|
+
|
206
|
+
form.valid?
|
207
|
+
# => true
|
208
|
+
```
|
209
|
+
|
210
|
+
#### Agreement checkbox
|
211
|
+
This check box a user must select to continue. The check box checked should return a value of true. The check box left unchecked should invalidate the form.
|
212
|
+
|
213
|
+
```rb
|
214
|
+
class AgreementForm < Vulcanize::Form
|
215
|
+
attribute :agree_to_terms, Vulcanize::Checkbox, :required => true
|
216
|
+
end
|
217
|
+
|
218
|
+
form = AgreementForm.new
|
219
|
+
|
220
|
+
form.agree_to_terms?
|
221
|
+
# !! #<Vulcanize::AttributeRequired: is not present>
|
222
|
+
|
223
|
+
form.valid?
|
224
|
+
# => false
|
225
|
+
```
|
226
|
+
|
227
|
+
#### Note on Checkbox
|
228
|
+
*`Vulcanize::CheckBox` returns true for an input of `'on'`. For all other values it raises an `ArgumentError`. This is to help debug if fields are incorrectly named.*
|
229
|
+
|
230
|
+
## Standard types
|
231
|
+
**NOT IMPLEMENTED**
|
232
|
+
Vulcanize encourages using custom domain objects over ruby primitives. it is often miss-guided to use the primitives. I.e. 12 June 2030 is not a valid D.O.B for your users and '<|X!#' is not valid article body. However sometimes it is appropriate or prudent to use the basic types and for that reason you can specify the following as types of attributes.
|
233
|
+
|
234
|
+
- String
|
235
|
+
- Integer
|
236
|
+
- Float
|
237
|
+
|
238
|
+
##### Note on using standard types
|
239
|
+
Often a reason to use standard types is because domain limitations on an input have not yet been defined. Instead of staying with strings consider using this minimal implementation.
|
240
|
+
|
241
|
+
```rb
|
242
|
+
class ArticleBody < String
|
243
|
+
end
|
244
|
+
```
|
245
|
+
## TODO
|
246
|
+
- section on testing
|
247
|
+
- actual api docs, perhaps formatted as in [AllSystems](https://github.com/CrowdHailer/AllSystems#user-content-docs)
|
248
|
+
- Handling collections, not necessary if always using custom collections
|
249
|
+
- question mark methods
|
250
|
+
- Pretty printing
|
251
|
+
- equality
|
252
|
+
- docs
|
253
|
+
form.each { |attribute, value| puts "#{attribute}, #{value}" }
|
254
|
+
# => :name, #<Name:0x00000002a579e8 @value="Peter">
|
255
|
+
- symbolize input
|
256
|
+
|
257
|
+
## Questions
|
258
|
+
- Form object with required and default might make sense if default acts as null object?
|
259
|
+
- Form object should have overwritable conditions on empty
|
260
|
+
- Check out virtus Array and Hash they might need to be included in awesomeness
|
261
|
+
- There is no need for and array or hash type if Always defining collections
|
262
|
+
- general nesting structure checkout useuful music batch
|
263
|
+
|
264
|
+
## Change log
|
265
|
+
|
266
|
+
Developed using Documentation Driven Development.
|
267
|
+
Few notes on Documentation Driven Development.
|
268
|
+
- [one](http://tom.preston-werner.com/2010/08/23/readme-driven-development.html)
|
269
|
+
- [two](http://24ways.org/2010/documentation-driven-design-for-apis)
|
270
|
+
|
271
|
+
**[Pull request for block errors](https://github.com/CrowdHailer/vulcanize/pull/1)**
|
272
|
+
|
273
|
+
## Contributing
|
274
|
+
There is no code here yet. So at the moment feel free to contribute around discussing these docs. pull request with your suggestion sounds perfect.
|
275
|
+
|
276
|
+
1. Fork it ( https://github.com/[my-github-username]/vulcanize/fork )
|
277
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
278
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
279
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
280
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/vulcanize.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Vulcanize
|
2
|
+
# Used to coerce input from <input type="checkbox" name="name">
|
3
|
+
# which by befault will send the following
|
4
|
+
# checked: {'name' => 'on'}
|
5
|
+
# unchecked: {}
|
6
|
+
|
7
|
+
CheckBox = Object.new
|
8
|
+
|
9
|
+
def CheckBox.new(raw)
|
10
|
+
return true if raw == 'on'
|
11
|
+
fail ArgumentError
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Vulcanize
|
2
|
+
AttributeRequired = Class.new(ArgumentError)
|
3
|
+
class Form
|
4
|
+
def self.attributes
|
5
|
+
@attributes ||= {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.attribute(attribute_name, type, required: false, default: nil, from: nil, private: false)
|
9
|
+
attributes[attribute_name] = private
|
10
|
+
|
11
|
+
from = from || attribute_name
|
12
|
+
|
13
|
+
define_method attribute_name do |&block|
|
14
|
+
raw = input[from]
|
15
|
+
|
16
|
+
missing = raw.nil? || raw.empty?
|
17
|
+
|
18
|
+
fail AttributeRequired if required and missing
|
19
|
+
return default if missing
|
20
|
+
|
21
|
+
begin
|
22
|
+
return type.new raw
|
23
|
+
rescue ArgumentError => err
|
24
|
+
return block.call raw, err if block
|
25
|
+
raise
|
26
|
+
end
|
27
|
+
end
|
28
|
+
tmp_attributes = attributes
|
29
|
+
private attribute_name if private
|
30
|
+
|
31
|
+
define_method :attributes do
|
32
|
+
tmp_attributes
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize(input={})
|
37
|
+
input.keys.each do |key|
|
38
|
+
input[(key.to_sym rescue key) || key] = input.delete(key)
|
39
|
+
end
|
40
|
+
@input = input
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
return enum_for(:each) unless block_given?
|
45
|
+
|
46
|
+
attributes.each do |attribute, private|
|
47
|
+
unless private
|
48
|
+
yield attribute, public_send(attribute)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid?
|
54
|
+
attributes.keys.each(&method(:send))
|
55
|
+
true
|
56
|
+
rescue
|
57
|
+
false
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :input
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class CheckBoxTest < MiniTest::Test
|
5
|
+
|
6
|
+
def test_returns_true_for_checked_input
|
7
|
+
value = CheckBox.new 'on'
|
8
|
+
assert_equal true, value
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_raises_argument_error_for_invalid_input
|
12
|
+
assert_raises ArgumentError do
|
13
|
+
CheckBox.new 'bad'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class DefaultAttributeTest < MiniTest::Test
|
5
|
+
|
6
|
+
def klass
|
7
|
+
return @klass if @klass
|
8
|
+
klass = Class.new Form
|
9
|
+
klass.attribute :item, TestType, :default => :default
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@klass = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_attribute_returns_default_for_missing_input
|
18
|
+
form = klass.new
|
19
|
+
assert_equal :default, form.item
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_attribute_returns_default_for_nil_input
|
23
|
+
form = klass.new :item => nil
|
24
|
+
assert_equal :default, form.item
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_attribute_returns_default_for_blank_input
|
28
|
+
form = klass.new :item => ''
|
29
|
+
assert_equal :default, form.item
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class PrivateAttributeTest < MiniTest::Test
|
5
|
+
|
6
|
+
def klass
|
7
|
+
return @klass if @klass
|
8
|
+
klass = Class.new Form
|
9
|
+
klass.attribute :item, TestType, :private => true
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@klass = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_attribute_method_is_private
|
18
|
+
form = klass.new :item => 'valid'
|
19
|
+
assert_raises NoMethodError do
|
20
|
+
form.item
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_private_attributes_do_not_show_up_in_each
|
25
|
+
form = klass.new :item => 'valid'
|
26
|
+
enumerator = form.each
|
27
|
+
assert_equal 0, enumerator.count
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class RenamedAttributeTest < MiniTest::Test
|
5
|
+
|
6
|
+
def klass
|
7
|
+
return @klass if @klass
|
8
|
+
klass = Class.new Form
|
9
|
+
klass.attribute :item, TestType, :from => :other
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@klass = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_attribute_is_fetched_from_optional_from_parameter
|
18
|
+
form = klass.new :other => 'valid'
|
19
|
+
assert_equal TestType, form.item.class
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class RequiredAttributeTest < MiniTest::Test
|
5
|
+
|
6
|
+
def klass
|
7
|
+
return @klass if @klass
|
8
|
+
klass = Class.new Form
|
9
|
+
klass.attribute :item, TestType, :required => true
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def teardown
|
14
|
+
@klass = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_attribute_raises_exception_for_missing_input
|
18
|
+
form = klass.new
|
19
|
+
assert_raises Vulcanize::AttributeRequired do
|
20
|
+
form.item
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_form_is_invalid_for_missing_input
|
25
|
+
form = klass.new
|
26
|
+
assert_equal false, form.valid?
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_attribute_raises_exception_for_nil_input
|
30
|
+
form = klass.new :item => nil
|
31
|
+
assert_raises Vulcanize::AttributeRequired do
|
32
|
+
form.item
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_form_is_invalid_for_nil_input
|
37
|
+
form = klass.new :item => nil
|
38
|
+
assert_equal false, form.valid?
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_attribute_raises_exception_for_blank_input
|
42
|
+
form = klass.new :item => ''
|
43
|
+
assert_raises Vulcanize::AttributeRequired do
|
44
|
+
form.item
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_form_is_invalid_for_blank_input
|
49
|
+
form = klass.new :item => ''
|
50
|
+
assert_equal false, form.valid?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require_relative './test_config'
|
2
|
+
|
3
|
+
module Vulcanize
|
4
|
+
class StandardAttributeTest < MiniTest::Test
|
5
|
+
|
6
|
+
def klass
|
7
|
+
return @klass if @klass
|
8
|
+
klass = Class.new Form
|
9
|
+
klass.attribute :item, TestType
|
10
|
+
@klass = klass
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_form
|
14
|
+
@valid_form ||= klass.new :item => 'valid'
|
15
|
+
end
|
16
|
+
|
17
|
+
def invalid_form
|
18
|
+
@invalid_form ||= klass.new :item => 'invalid'
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
@klass = nil
|
23
|
+
@valid_form = nil
|
24
|
+
@invalid_form = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_attribute_returns_coerced_item_for_valid_input
|
28
|
+
assert_equal TestType, valid_form.item.class
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_form_is_valid_with_valid_input
|
32
|
+
assert_equal true, valid_form.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_passes_attribute_name_and_attribute_value_to_each_block
|
36
|
+
array = []
|
37
|
+
valid_form.each { |name, value| array << name << value.class }
|
38
|
+
assert_equal [:item, TestType], array
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_returns_an_enumerable_for_each_value
|
42
|
+
enumerator = valid_form.each
|
43
|
+
attribute_name, attribute_value = enumerator.next
|
44
|
+
assert_equal :item, attribute_name
|
45
|
+
assert_equal TestType, attribute_value.class
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_attribute_returns_coerced_item_for_valid_input_from_string_key
|
49
|
+
form = klass.new 'item' => 'valid'
|
50
|
+
assert_equal TestType, form.item.class
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_attribute_raises_error_if_input_was_invalid
|
54
|
+
assert_raises ArgumentError do
|
55
|
+
invalid_form.item
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_attribute_with_invalid_input_passes_error_to_block
|
60
|
+
raw, error = invalid_form.item { |r, e| return r, e }
|
61
|
+
assert_equal ArgumentError.new, error
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_attribute_with_invalid_input_passes_raw_value_to_block
|
65
|
+
raw, error = invalid_form.item { |r, e| return r, e }
|
66
|
+
assert_equal 'invalid', raw
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_form_is_invalid_with_invalid_input
|
70
|
+
assert_equal false, invalid_form.valid?
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_attribute_returns_nil_for_missing_input
|
74
|
+
form = klass.new
|
75
|
+
assert_equal nil, form.item
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_attribute_returns_nil_for_nil_input
|
79
|
+
form = klass.new :item => nil
|
80
|
+
assert_equal nil, form.item
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_attribute_returns_nil_for_blank_input
|
84
|
+
form = klass.new :item => ''
|
85
|
+
assert_equal nil, form.item
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_attribute_name_can_be_defined_as_a_string
|
89
|
+
# This test is not necessary. In fact removing the symbolize coercion would allow any setup.
|
90
|
+
# klass = Class.new Form
|
91
|
+
# klass.attribute 'item', TestType
|
92
|
+
# form = klass.new :item => 'valid'
|
93
|
+
# puts form.attributes
|
94
|
+
# assert_equal TestType, valid_form.item.class
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/test/test_config.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'vulcanize'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'minitest/reporters'
|
4
|
+
|
5
|
+
reporter_options = {color: true, slow_count: 5}
|
6
|
+
Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new(reporter_options)]
|
7
|
+
|
8
|
+
module Vulcanize
|
9
|
+
class TestType
|
10
|
+
def initialize(value)
|
11
|
+
fail ArgumentError unless value == 'valid'
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/vulcanize.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'vulcanize/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "vulcanize"
|
8
|
+
spec.version = Vulcanize::VERSION
|
9
|
+
spec.authors = ["Peter Saxton"]
|
10
|
+
spec.email = ["vulcanize@workshop14.io"]
|
11
|
+
spec.summary = %q{Form objects for coercing user input to domain specific objects}
|
12
|
+
spec.description = %q{Forms consist of one or more attributes that are defined with a name, type and optional parameters. Plain ruby classes are used to represent attribute type and should be responsible for coercing the raw input and deciding an inputs validity.}
|
13
|
+
spec.homepage = "https://github.com/CrowdHailer/vulcanize"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
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_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
spec.add_development_dependency "minitest", "~> 5.4"
|
24
|
+
spec.add_development_dependency "minitest-reporters", "~> 1.0"
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vulcanize
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Saxton
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest-reporters
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.0'
|
69
|
+
description: Forms consist of one or more attributes that are defined with a name,
|
70
|
+
type and optional parameters. Plain ruby classes are used to represent attribute
|
71
|
+
type and should be responsible for coercing the raw input and deciding an inputs
|
72
|
+
validity.
|
73
|
+
email:
|
74
|
+
- vulcanize@workshop14.io
|
75
|
+
executables: []
|
76
|
+
extensions: []
|
77
|
+
extra_rdoc_files: []
|
78
|
+
files:
|
79
|
+
- ".gitignore"
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- README.md
|
83
|
+
- Rakefile
|
84
|
+
- lib/vulcanize.rb
|
85
|
+
- lib/vulcanize/check_box.rb
|
86
|
+
- lib/vulcanize/form.rb
|
87
|
+
- lib/vulcanize/version.rb
|
88
|
+
- test/check_box_test.rb
|
89
|
+
- test/default_attribute_test.rb
|
90
|
+
- test/private_attribute_test.rb
|
91
|
+
- test/renamed_attribute_test.rb
|
92
|
+
- test/required_attribute_test.rb
|
93
|
+
- test/standard_attribute_test.rb
|
94
|
+
- test/test_config.rb
|
95
|
+
- vulcanize.gemspec
|
96
|
+
homepage: https://github.com/CrowdHailer/vulcanize
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.4.5
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Form objects for coercing user input to domain specific objects
|
120
|
+
test_files:
|
121
|
+
- test/check_box_test.rb
|
122
|
+
- test/default_attribute_test.rb
|
123
|
+
- test/private_attribute_test.rb
|
124
|
+
- test/renamed_attribute_test.rb
|
125
|
+
- test/required_attribute_test.rb
|
126
|
+
- test/standard_attribute_test.rb
|
127
|
+
- test/test_config.rb
|