foraneus 0.0.15 → 0.0.16

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d581bebcc546182ffb84546ff4fb8ad667ee126f
4
- data.tar.gz: 241b1dfc78a1837a6ec6404904ab1a4b1f6d3125
3
+ metadata.gz: 026d8cac77a7a732c918a057f4eac4b965a92180
4
+ data.tar.gz: 2fd7da19d82c382269c1cfd1425defbfe97a5253
5
5
  SHA512:
6
- metadata.gz: 457b8493632fb86f01ea610b164f05df68c0f85f156ab26d3c7fe98ee62a2262c9a2281dd037f636ffb31fac42b49f68ad66386dcd264db5023134d9ef0fbe15
7
- data.tar.gz: 4c668906d942e090e563b7ec8a8241c4cfa1cb42096bad58146c168aca89672cdd82b1a92c99896fd8bcf7af7c354d5a9e525fb7a32122f9900103fc4060d52d
6
+ metadata.gz: 8a97fdb7d2fcc3940fb788f7783c4525dc4b32305a732c2d736a0a2fb436e81460020a23faadcf4346f4284b1dfd54722edc964dbbf18fd6b2517fdcbd0755b6
7
+ data.tar.gz: f76ff7fe5ca5b27971cfa1cdad7b725303d4428d44ee5a737cdbc3699522e2f6c508f1e5282c47344941a483377bb2cbb6b3c941bc05da5ddb15c92eb1ce8bc6
data/README.md CHANGED
@@ -47,7 +47,10 @@ raw and parsed data.
47
47
  ```
48
48
 
49
49
  ``` ruby
50
+ # the parsed attributes
50
51
  form.data # => { :delay => 5, :duration => 2.14 }
52
+
53
+ # the received attributes
51
54
  form[] # => { :delay => '5', :duration => '2.14' }
52
55
  ```
53
56
 
@@ -163,6 +166,24 @@ If an external value is not fed into a required field, an error with key `KeyErr
163
166
  form.errors[:delay].message # => 'required parameter not found: "delay"'
164
167
  ```
165
168
 
169
+ ## Absence of optional fields
170
+
171
+ Absent fields are treated as `nil` when invoking accessor methods.
172
+
173
+ ``` ruby
174
+ MyForm = Class.new(Foraneus) { string :name }
175
+ form = MyForm.parse
176
+
177
+ form.name # => nil
178
+ ```
179
+
180
+ Data accessors don't include any absent field.
181
+
182
+ ``` ruby
183
+ form.data # => {}
184
+ form[] # => {}
185
+ ```
186
+
166
187
  ## Blank values
167
188
 
168
189
  By default, any blank value is treated as nil.
@@ -197,8 +218,12 @@ Parse data from the ouside:
197
218
  form = MyForm.parse
198
219
 
199
220
  form.name # => 'Alice'
221
+ form.data # => { :name => 'Alice' }
222
+
200
223
  form[:name] # => nil, because data from the outside
201
224
  # don't include any value
225
+
226
+ form[] # => {}
202
227
  ```
203
228
 
204
229
  Convert values back from the inside:
@@ -235,6 +260,165 @@ It is possible to rename methods `#errors` and `#data` so it will not conflict w
235
260
  form.non_clashing_data # { :errors => 'some errors', :data => 'some data' }
236
261
  ```
237
262
 
263
+ ## Nesting
264
+ Forms can also have form fields.
265
+
266
+ ``` ruby
267
+ class Profile < Foraneus
268
+ string :email
269
+
270
+ form :coords do
271
+ integer :x
272
+ integer :y
273
+ end
274
+ end
275
+ ```
276
+
277
+ ``` ruby
278
+ profile = Profile.parse(:email => 'mail@example.org', :coords => { :x => '1', :y => '2' })
279
+
280
+ profile.email # => mail@example.org
281
+
282
+ profile.coords.x # => 1
283
+ profile.coords.y # => 2
284
+
285
+ profile.coords[:x] # => '1'
286
+ profile.coords[:y] # => '2'
287
+
288
+ ```
289
+
290
+ ``` ruby
291
+ profile.coords.data # => { :x => 1, :y => 2 }
292
+ profile.coords[] # => { :x => '1', :y => '2' }
293
+ ```
294
+
295
+ ``` ruby
296
+ profile[:coords] # => { :x => '1', :y => '2' }
297
+ ```
298
+
299
+ ``` ruby
300
+ profile.data # => { :email => 'mail.example.org', :coords => { :x => 1, :y => 2 } }
301
+ profile[] # => { :email => 'mail@example.org', :coords => { :x => '1', :y => '2' } }
302
+ ```
303
+
304
+ - Absence
305
+ ``` ruby
306
+ profile = Profile.parse
307
+
308
+ profile.coords # => nil
309
+ profile.data # => {}
310
+ profile[] # => {}
311
+ ```
312
+
313
+ - .Nullity
314
+ ``` ruby
315
+ profile = Profile.parse(:coords => nil)
316
+
317
+ profile.coords # => nil
318
+ profile.data # => { :coords => nil }
319
+ profile[] # => { :coords => nil }
320
+ ```
321
+
322
+ - Emptiness
323
+ ``` ruby
324
+ profile = Profile.parse(:coords => {})
325
+
326
+ profile.coords.x # => nil
327
+ profile.coords.y # => nil
328
+
329
+ profile.coords.data # => {}
330
+ profile.coords[] # => {}
331
+
332
+ profile.data # => { :coords => {} }
333
+ profile[] # => { :coords => {} }
334
+ ```
335
+
336
+ - Validations
337
+ ``` ruby
338
+ profile = Profile.parse(:coords => { :x => '0', :y => '0' })
339
+
340
+ profile.coords.valid? # => true
341
+ profile.coords.errors # => {}
342
+ ```
343
+
344
+ ``` ruby
345
+ profile = Profile.parse(:coords => { :x => 'FIVE' })
346
+
347
+ profile.coords.x # => nil
348
+
349
+ profile.coords.valid? # => false
350
+
351
+ profile.coords.errors[:x].key # => 'ArgumentError'
352
+ profile.coords.errors[:x].message # => 'invalid value for Integer(): "FIVE"'
353
+
354
+ profile.valid? # => false
355
+
356
+ profile.errors[:coords].key # => 'NestedFormError'
357
+ profile.errors[:coords].message # => 'Invalid nested form: coords'
358
+ ```
359
+
360
+ ``` ruby
361
+ profile = Profile.parse(:coords => 'FIVE,SIX')
362
+
363
+ profile.coords # => nil
364
+
365
+
366
+ profile.valid? # => false
367
+ profile.errors[:coords].key # => 'NestedFormError'
368
+ profile.errors[:coords].message # => 'Invalid nested form: coords'
369
+ ```
370
+
371
+ - From the inside:
372
+ ``` ruby
373
+ profile = Profile.raw(:email => 'mail@example.org', :coords => { :x => 1, :y => 2 })
374
+ ```
375
+
376
+ ``` ruby
377
+ profile.coords.x # => 1
378
+ profile.coords.data # => { :x => 1, :y => 2 }
379
+
380
+ profile.coords[:x] # => '1'
381
+ profile.coords[] # => { :x => '1', :y => '2' }
382
+ ```
383
+
384
+ ``` ruby
385
+ profile.data # => { :email => 'email.example.org', :coords => { :x => 0, :y => 0 } }
386
+ profile[] # => { :email => 'email@example.org', :coords => { :x => '0', :y => '0' } }
387
+ ```
388
+ - .Absence
389
+
390
+ ```
391
+ profile = Profile.raw
392
+
393
+ profile.coords # => nil
394
+ profile.data # => {}
395
+ profile[] # => {}
396
+ ```
397
+
398
+ - .Nullity
399
+ ``` ruby
400
+ profile = Profile.raw(:coords => nil)
401
+
402
+ profile.coords # => nil
403
+ profile.data # => { :coords => nil }
404
+ profile[] # => { :coords => nil }
405
+ ```
406
+
407
+ - .Emptiness
408
+ ``` ruby
409
+ profile = Profile.raw(:coords => {})
410
+
411
+ profile.coords.x # => nil
412
+ profile.coords.y # => nil
413
+
414
+ profile.coords.data # => {}
415
+ profile.coords[] # => {}
416
+
417
+ profile.data # => { :coords => {} }
418
+ profile[] # => { :coords => {} }
419
+ ```
420
+
421
+
238
422
  ## Installation
239
423
 
240
424
  - Install `foraneus` as a gem.
data/lib/foraneus.rb CHANGED
@@ -1,3 +1,7 @@
1
+ # TODO refactor
2
+ class NestedFormError < StandardError
3
+ end
4
+
1
5
  if RUBY_VERSION == '1.8.7'
2
6
  require 'foraneus/compatibility/ruby-1.8.7'
3
7
  end
@@ -7,6 +11,7 @@ require 'foraneus/converters/date'
7
11
  require 'foraneus/converters/decimal'
8
12
  require 'foraneus/converters/float'
9
13
  require 'foraneus/converters/integer'
14
+ require 'foraneus/converters/nested'
10
15
  require 'foraneus/converters/noop'
11
16
  require 'foraneus/converters/string'
12
17
  require 'foraneus/errors'
@@ -55,6 +60,15 @@ class Foraneus
55
60
  field(name, converter)
56
61
  end
57
62
 
63
+ # Declares a nested form field
64
+ #
65
+ # @param [Symbol] name The name of the field.
66
+ # @yield Yields to a nested foraneus spec.
67
+ def self.form(name, &block)
68
+ converter = Class.new(Foraneus::Converters::Nested, &block)
69
+ field(name, converter)
70
+ end
71
+
58
72
  # Declares an integer field.
59
73
  #
60
74
  # @param [Symbol] name The name of the field.
@@ -180,9 +194,6 @@ class Foraneus
180
194
  # @param [Symbol] k Field name.
181
195
  # @param [String] v Raw value.
182
196
  def []=(k, v)
183
- #raw_data = @_
184
-
185
- #raw_data[k] = v
186
197
  @_[k] = v
187
198
  end
188
199
 
@@ -196,12 +207,25 @@ class Foraneus
196
207
 
197
208
  v, error = Utils.parse_datum(field, s, converter)
198
209
 
199
- unless error
210
+ if error.nil? && !v.nil? && Foraneus::Utils.nested_converter?(converter)
211
+ instance.send(self.accessors[:data])[field.to_sym] = v.data if is_present
212
+ instance.send("#{field}=", v)
213
+ unless v.valid?
214
+ error = Foraneus::Error.new('NestedFormError', "Invalid nested form: #{field}")
215
+ instance.send(self.accessors[:errors])[field.to_sym] = error
216
+ end
217
+
218
+ elsif error.nil?
200
219
  instance.send("#{field}=", v)
201
220
  instance.send(self.accessors[:data])[field.to_sym] = v if is_present || converter.opts.include?(:default)
221
+ else
222
+ if Foraneus::Utils.nested_converter?(converter)
223
+ error = Foraneus::Error.new('NestedFormError', "Invalid nested form: #{field}")
224
+ instance.send(self.accessors[:errors])[field.to_sym] = error
225
+ else
226
+ instance.send(self.accessors[:errors])[field.to_sym] = error if error
227
+ end
202
228
  end
203
-
204
- instance.send(self.accessors[:errors])[field.to_sym] = error if error
205
229
  end
206
230
  private_class_method :__parse_field
207
231
 
@@ -216,7 +240,17 @@ class Foraneus
216
240
 
217
241
  s = Utils.raw_datum(v, converter)
218
242
 
219
- instance[field.to_sym] = s if is_present || converter.opts.include?(:default)
243
+ if Foraneus::Utils.nested_converter?(converter)
244
+ instance.send("#{field}=", s)
245
+ end
246
+
247
+ if is_present || converter.opts.include?(:default)
248
+ if Foraneus::Utils.nested_converter?(converter) && !s.nil?
249
+ instance[field.to_sym] = s[]
250
+ else
251
+ instance[field.to_sym] = s
252
+ end
253
+ end
220
254
  end
221
255
  private_class_method :__raw_field
222
256
 
@@ -0,0 +1,9 @@
1
+ class Foraneus
2
+ module Converters
3
+ class Nested < Foraneus
4
+
5
+ def self.opts; {}; end
6
+
7
+ end
8
+ end
9
+ end
@@ -67,6 +67,10 @@ class Foraneus
67
67
  converter.raw(v) unless v.nil?
68
68
  end
69
69
 
70
+ def self.nested_converter?(converter)
71
+ Class === converter && converter.ancestors.include?(Foraneus)
72
+ end
73
+
70
74
  # Creates a singleton attribute accessor on an instance.
71
75
  #
72
76
  # @param [Foraneus] instance
@@ -0,0 +1,231 @@
1
+ require 'spec_helper'
2
+
3
+ describe Foraneus do
4
+
5
+ let(:converter) { Foraneus::Converters::Integer.new }
6
+
7
+ let(:form_spec) {
8
+ c = converter
9
+ Class.new(Foraneus) do
10
+ string :email
11
+
12
+ form :coords do
13
+ field :x, c
14
+ integer :y
15
+ end
16
+ end
17
+ }
18
+
19
+ describe '.parse' do
20
+ describe 'with parseable data' do
21
+ subject { form_spec.parse(:email => 'mail@example.org', :coords => { :x => '1', :y => '2' }) }
22
+
23
+ it 'parses the nested form' do
24
+ assert_equal 'mail@example.org', subject.email
25
+
26
+ assert_equal 1, subject.coords.x
27
+ assert_equal 2, subject.coords.y
28
+
29
+ assert_equal '1', subject.coords[:x]
30
+ assert_equal '2', subject.coords[:y]
31
+
32
+ assert_equal({ :x => 1, :y => 2 }, subject.coords.data)
33
+
34
+ assert_equal({ :x => '1', :y => '2' }, subject.coords[])
35
+
36
+ assert subject.coords.valid?
37
+
38
+ assert_empty subject.coords.errors
39
+ end
40
+
41
+ it 'treats the field as a regular one from the point of view of the parent form' do
42
+ assert_equal({ :email => 'mail@example.org', :coords => { :x => 1, :y => 2 } }, subject.data)
43
+
44
+ assert_equal({ :x => '1', :y => '2' }, subject[:coords])
45
+ assert_equal({ :email => 'mail@example.org', :coords => { :x => '1', :y => '2' } }, subject[])
46
+
47
+ assert subject.valid?
48
+
49
+ assert_empty subject.errors
50
+ end
51
+ end
52
+
53
+ describe 'with absent data' do
54
+ subject { form_spec.parse }
55
+
56
+ it 'parses' do
57
+ assert_nil subject.coords
58
+
59
+ refute_includes subject.data, :coords
60
+ refute_includes subject[], :coords
61
+ end
62
+ end
63
+
64
+ describe 'with nil data' do
65
+ subject { form_spec.parse(:coords => nil) }
66
+
67
+ it 'parses' do
68
+ assert_nil subject.coords
69
+
70
+ assert_equal({ :coords => nil }, subject.data)
71
+ assert_equal({ :coords => nil }, subject[])
72
+ end
73
+ end
74
+
75
+ describe 'with empty data' do
76
+ subject { form_spec.parse(:coords => {}) }
77
+
78
+ it 'parses' do
79
+ assert_nil subject.coords.x
80
+ assert_nil subject.coords.y
81
+
82
+ assert_empty subject.coords.data
83
+ assert_empty subject.coords[]
84
+
85
+ assert_equal({ :coords => {} }, subject.data)
86
+ assert_equal({ :coords => {} }, subject[])
87
+ end
88
+ end
89
+
90
+
91
+ describe 'with unparseable nested data' do
92
+ subject { form_spec.parse(:coords => { :x => 'FIVE' }) }
93
+
94
+ it 'parses' do
95
+ assert_nil subject.coords.x
96
+
97
+ assert_equal 'FIVE', subject.coords[:x]
98
+ assert_equal({ :x => 'FIVE' }, subject.coords[])
99
+
100
+
101
+ refute subject.coords.valid?
102
+ assert_includes subject.coords.errors, :x
103
+ end
104
+
105
+ describe 'an error' do
106
+ let(:error) { subject.coords.errors.values.first }
107
+
108
+ let(:converter_exception) do
109
+ begin
110
+ converter.parse('FIVE')
111
+ rescue
112
+ e = $!
113
+ end
114
+
115
+ e
116
+ end
117
+
118
+ it 'provides a key' do
119
+ assert_equal converter_exception.class.name, error.key
120
+ end
121
+
122
+ it 'provides a message' do
123
+ assert_equal converter_exception.message, error.message
124
+ end
125
+ end
126
+
127
+ describe 'the parent form' do
128
+ it 'is marked as invalid with errors' do
129
+ refute subject.valid?
130
+
131
+ assert_includes subject.errors, :coords
132
+ end
133
+
134
+ describe 'the error' do
135
+ let(:error) { subject.errors[:coords] }
136
+
137
+ it 'provides a key' do
138
+ assert_equal 'NestedFormError', error.key
139
+ end
140
+
141
+ it 'provides a message' do
142
+ assert_equal 'Invalid nested form: coords', error.message
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ describe 'with non enclosing parseable data' do
149
+ subject { form_spec.parse(:coords => 'FIVE,SIX') }
150
+
151
+ it 'parses' do
152
+ assert_nil subject.coords
153
+
154
+ refute subject.valid?
155
+ end
156
+
157
+ describe 'the error' do
158
+ let(:error) { subject.errors[:coords] }
159
+
160
+ it 'provides a key' do
161
+ assert_equal 'NestedFormError', error.key
162
+ end
163
+
164
+ it 'provides a message' do
165
+ assert_equal 'Invalid nested form: coords', error.message
166
+ end
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '.raw' do
172
+ subject { form_spec.raw(:email => 'mail@example.org', :coords => { :x => 1, :y => 2 }) }
173
+
174
+ it 'parses' do
175
+ assert_equal 1, subject.coords.x
176
+ assert_equal 2, subject.coords.y
177
+
178
+ assert_equal({ :x => 1, :y => 2 }, subject.coords.data)
179
+
180
+ assert_equal '1', subject.coords[:x]
181
+ assert_equal '2', subject.coords[:y]
182
+
183
+ assert_equal({ :x => '1', :y => '2' }, subject.coords[])
184
+ end
185
+
186
+ describe 'parent form' do
187
+ it 'parses' do
188
+ assert_equal({:email => 'mail@example.org', :coords => {:x => 1, :y => 2}}, subject.data)
189
+
190
+ assert_equal({ :email => 'mail@example.org', :coords => { :x => '1', :y => '2' } }, subject[])
191
+ end
192
+ end
193
+
194
+ describe 'with absent data' do
195
+ subject { form_spec.raw }
196
+
197
+ it 'parses' do
198
+ assert_nil subject.coords
199
+
200
+ assert_empty subject.data
201
+ assert_empty subject[]
202
+ end
203
+ end
204
+
205
+ describe 'with nil data' do
206
+ subject { form_spec.raw(:coords => nil) }
207
+
208
+ it 'parses' do
209
+ assert_nil subject.coords
210
+
211
+ assert_equal({ :coords => nil }, subject.data)
212
+ assert_equal({ :coords => nil }, subject[])
213
+ end
214
+ end
215
+
216
+ describe 'with empty data' do
217
+ subject { form_spec.raw(:coords => {}) }
218
+
219
+ it 'parses' do
220
+ assert_nil subject.coords.x
221
+ assert_nil subject.coords.y
222
+
223
+ assert_empty subject.coords.data
224
+ assert_empty subject.coords[]
225
+
226
+ assert_equal({ :coords => {} }, subject.data)
227
+ assert_equal({ :coords => {} }, subject[])
228
+ end
229
+ end
230
+ end
231
+ end
@@ -145,11 +145,11 @@ describe Foraneus do
145
145
  end
146
146
 
147
147
  it 'provides a key' do
148
- assert_equal error.key, converter_exception.class.name
148
+ assert_equal converter_exception.class.name, error.key
149
149
  end
150
150
 
151
151
  it 'provides a message' do
152
- assert_equal error.message, converter_exception.message
152
+ assert_equal converter_exception.message, error.message
153
153
  end
154
154
  end
155
155
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,3 @@
1
- #require 'rubygems'
2
-
3
1
  require 'minitest/autorun'
4
2
  require 'minitest/spec'
5
3
  require 'minitest/unit'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foraneus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.15
4
+ version: 0.0.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gianfranco Zas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-25 00:00:00.000000000 Z
11
+ date: 2015-06-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -68,6 +68,7 @@ files:
68
68
  - lib/foraneus/converters/decimal.rb
69
69
  - lib/foraneus/converters/float.rb
70
70
  - lib/foraneus/converters/integer.rb
71
+ - lib/foraneus/converters/nested.rb
71
72
  - lib/foraneus/converters/noop.rb
72
73
  - lib/foraneus/converters/string.rb
73
74
  - lib/foraneus/errors.rb
@@ -79,6 +80,7 @@ files:
79
80
  - spec/lib/foraneus/converters/integer_converter_spec.rb
80
81
  - spec/lib/foraneus/converters/noop_converter_spec.rb
81
82
  - spec/lib/foraneus/converters/string_converter_spec.rb
83
+ - spec/lib/foraneus_nesting_spec.rb
82
84
  - spec/lib/foraneus_spec.rb
83
85
  - spec/runner.rb
84
86
  - spec/spec_helper.rb
@@ -114,6 +116,7 @@ test_files:
114
116
  - spec/lib/foraneus/converters/integer_converter_spec.rb
115
117
  - spec/lib/foraneus/converters/boolean_converter_spec.rb
116
118
  - spec/lib/foraneus/converters/string_converter_spec.rb
119
+ - spec/lib/foraneus_nesting_spec.rb
117
120
  - spec/lib/foraneus_spec.rb
118
121
  - spec/runner.rb
119
122
  - spec/spec_helper.rb