foraneus 0.0.15 → 0.0.16

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.
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