eapi 0.2.1 → 0.3.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.
- checksums.yaml +4 -4
- data/README.md +316 -237
- data/lib/eapi/definition_runners/property.rb +21 -6
- data/lib/eapi/definition_runners/runner.rb +4 -0
- data/lib/eapi/errors.rb +6 -0
- data/lib/eapi/methods/accessor.rb +25 -2
- data/lib/eapi/methods/names.rb +4 -0
- data/lib/eapi/methods/properties.rb +17 -15
- data/lib/eapi/methods/types.rb +14 -2
- data/lib/eapi/type_checker.rb +23 -7
- data/lib/eapi/value_converter.rb +10 -0
- data/lib/eapi/version.rb +1 -1
- data/spec/list_elements_spec.rb +149 -48
- data/spec/validations_spec.rb +33 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: af49a7ace939612da586885e144e872e26f9bab6
|
|
4
|
+
data.tar.gz: a55532d8fc6a2eb3350b19768e5ac403f3329b2b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df2402e3c80c6d2ec64291d61a31f3c9e8c50dff4383f29b1f72e56bd801473a40daea2b9e97199f87a16a959ec63670af210fedbd4a343b68c0a5c7cbd34ecb
|
|
7
|
+
data.tar.gz: 5dd6bcb8988fb8a1add9932e038fd84df732f5c84c494efcfbe00be5ac3f549bc6f70a07cb34506fd23dd1ef30e7e0e274a7b53e1e3bbab281cf03a45ee4d3dc
|
data/README.md
CHANGED
|
@@ -1,114 +1,85 @@
|
|
|
1
1
|
# Eapi (Elastic API)
|
|
2
2
|
|
|
3
|
-
ruby gem for building complex structures that will end up in hashes (initially devised for ElasticSearch search requests)
|
|
4
|
-
|
|
5
3
|
[](http://badge.fury.io/rb/eapi)
|
|
6
4
|
[](https://travis-ci.org/eturino/eapi)
|
|
7
5
|
[](https://codeclimate.com/github/eturino/eapi)
|
|
8
6
|
[](https://codeclimate.com/github/eturino/eapi)
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Add this line to your application's Gemfile:
|
|
13
|
-
|
|
14
|
-
gem 'eapi'
|
|
15
|
-
|
|
16
|
-
And then execute:
|
|
17
|
-
|
|
18
|
-
$ bundle
|
|
19
|
-
|
|
20
|
-
Or install it yourself as:
|
|
21
|
-
|
|
22
|
-
$ gem install eapi
|
|
23
|
-
|
|
24
|
-
## Dependencies
|
|
25
|
-
|
|
26
|
-
### Ruby version
|
|
27
|
-
|
|
28
|
-
Works with ruby 2.1, tested with MRI 2.1.1
|
|
29
|
-
|
|
30
|
-
### Gem dependencies
|
|
8
|
+
Ruby gem for building complex structures that will end up in hashes or arrays
|
|
31
9
|
|
|
32
|
-
|
|
10
|
+
Main features:
|
|
33
11
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
12
|
+
* property definition
|
|
13
|
+
* automatic fluent accessors
|
|
14
|
+
* list support
|
|
15
|
+
* validation
|
|
16
|
+
* rendering to `Hash` or `Array`
|
|
17
|
+
* raw `Hash` or `Array` support to skip type check validations
|
|
18
|
+
* omit `nil` values automatically
|
|
40
19
|
|
|
41
20
|
## Usage
|
|
42
21
|
|
|
43
|
-
|
|
22
|
+
Eapi work by exposing a couple of modules to include in your classes.
|
|
44
23
|
|
|
45
|
-
|
|
24
|
+
* `Eapi::Item` module to create objects with multiple properties that will render into hashes.
|
|
25
|
+
* `Eapi::List` module to create lists that will render into arrays.
|
|
46
26
|
|
|
47
27
|
```ruby
|
|
48
|
-
|
|
28
|
+
# ITEM
|
|
29
|
+
class MyItem
|
|
49
30
|
include Eapi::Item
|
|
50
31
|
|
|
32
|
+
# defining some properties
|
|
51
33
|
property :something
|
|
34
|
+
property :other, type: Fixnum
|
|
35
|
+
property :third, multiple: true
|
|
52
36
|
end
|
|
53
|
-
```
|
|
54
37
|
|
|
55
|
-
|
|
38
|
+
i = MyItem.new something: 1
|
|
39
|
+
i.something # => 1
|
|
40
|
+
i.other(2).add_third(3)
|
|
41
|
+
i.render # => {something: 1, other: 2, third: [3]}
|
|
42
|
+
i.to_h # => {something: 1, other: 2, third: [3]}
|
|
56
43
|
|
|
57
|
-
|
|
44
|
+
# LIST
|
|
45
|
+
class MyList
|
|
46
|
+
include Eapi::List
|
|
47
|
+
|
|
48
|
+
elements required: true
|
|
49
|
+
end
|
|
58
50
|
|
|
59
|
-
|
|
51
|
+
l = MyList.new
|
|
52
|
+
l.add(1).add(2)
|
|
53
|
+
l.render # => [ 1, 2 ]
|
|
54
|
+
l.to_a # => [ 1, 2 ]
|
|
55
|
+
```
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
class MyTestKlass
|
|
63
|
-
include Eapi::Item
|
|
57
|
+
This will provide:
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
59
|
+
* a *DSL to define properties* and elements *validations* and *rules*
|
|
60
|
+
* *fluent accessor* methods for each property
|
|
61
|
+
* a *keyword arguments* enabled `initialize` method
|
|
62
|
+
* a shortcut for object creation sending messages to the `Eapi` module directly with the name of the class.
|
|
67
63
|
|
|
68
|
-
|
|
69
|
-
x.something # => 1
|
|
70
|
-
```
|
|
64
|
+
We'll se this in detail.
|
|
71
65
|
|
|
72
|
-
|
|
66
|
+
## `Eapi::Item`: Property based Item objects
|
|
73
67
|
|
|
74
|
-
|
|
68
|
+
### `initialize` method
|
|
75
69
|
|
|
76
|
-
|
|
70
|
+
`Eapi::Item` will add a `initialize` method to your class that will accept a hash. It will recognise the defined properties in that hash and will set them.
|
|
77
71
|
|
|
78
|
-
|
|
72
|
+
*important*: For now any unrecognised property in the hash will be ignored. This may change in the future.
|
|
79
73
|
|
|
80
|
-
```ruby
|
|
81
|
-
class
|
|
74
|
+
```ruby
|
|
75
|
+
class MyTestKlass
|
|
82
76
|
include Eapi::Item
|
|
83
77
|
|
|
84
78
|
property :something
|
|
85
79
|
end
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
include Eapi::Item
|
|
90
|
-
|
|
91
|
-
property :something
|
|
92
|
-
end
|
|
93
|
-
end
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
As shown by rspec run:
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
initialise using method calls to Eapi
|
|
100
|
-
Eapi.MyTestKlassOutside(...)
|
|
101
|
-
calls MyTestKlassOutside.new
|
|
102
|
-
Eapi.my_test_klass_outside(...)
|
|
103
|
-
calls MyTestKlassOutside.new
|
|
104
|
-
Eapi.Somewhere__TestKlassInModule(...)
|
|
105
|
-
calls Somewhere::TestKlassInModule.new
|
|
106
|
-
Eapi.somewhere__test_klass_in_module(...)
|
|
107
|
-
calls Somewhere::TestKlassInModule.new
|
|
108
|
-
Eapi.Somewhere_TestKlassInModule(...)
|
|
109
|
-
calls Somewhere::TestKlassInModule.new
|
|
110
|
-
Eapi.somewhere_test_klass_in_module(...)
|
|
111
|
-
calls Somewhere::TestKlassInModule.new
|
|
81
|
+
x = MyTestKlass.new something: 1
|
|
82
|
+
x.something # => 1
|
|
112
83
|
```
|
|
113
84
|
|
|
114
85
|
### Defining properties
|
|
@@ -123,7 +94,7 @@ class MyTestKlass
|
|
|
123
94
|
property :two
|
|
124
95
|
end
|
|
125
96
|
```
|
|
126
|
-
#### Setting
|
|
97
|
+
#### Setting properties on object creation
|
|
127
98
|
We can then assign the properties on object creation:
|
|
128
99
|
```ruby
|
|
129
100
|
x = MyTestKlass.new one: 1, two: 2
|
|
@@ -180,15 +151,15 @@ All Eapi classes respond to `render` and return a hash (for `Item` classes) or a
|
|
|
180
151
|
|
|
181
152
|
Inside, `render` will call `valid?`, raise an error of type `Eapi::Errors::InvalidElementError` if something is not right, and if everything is ok it will call `create_hash`.
|
|
182
153
|
|
|
183
|
-
The `create_hash` method will create a hash with the properties as keys. Each value will be "converted".
|
|
154
|
+
The `create_hash` method will create a hash with the properties as keys. Each value will be "converted" (see "Values conversion" section).
|
|
184
155
|
|
|
185
156
|
#### Values conversion
|
|
186
157
|
|
|
187
158
|
By default, each property will be converted into a simple element (Array, Hash, or simple value).
|
|
188
159
|
|
|
189
|
-
If a value
|
|
190
|
-
|
|
191
|
-
If a value respond to `to_h`, it will be called.
|
|
160
|
+
1. If a value responds to `render`, it will call that method. That way, Eapi objects that are values of some properties or lists will be validated and rendered (converted) themselves (render / value conversion cascade).
|
|
161
|
+
2. If a value is an Array or a Set, `to_a` will be invoked and all values will be converted in the same way.
|
|
162
|
+
3. If a value respond to `to_h`, it will be called.
|
|
192
163
|
|
|
193
164
|
important: *any nil value will be omitted* in the final hash.
|
|
194
165
|
|
|
@@ -246,6 +217,24 @@ When defining the property, we can specify some options to specify what values a
|
|
|
246
217
|
|
|
247
218
|
It uses `ActiveModel::Validations`. When `to_h` is called in an Eapi object, the `valid?` method will be called and if the object is not valid an `Eapi::Errors::InvalidElementError` error will raise.
|
|
248
219
|
|
|
220
|
+
#### Validations from `ActiveModel::Validations`
|
|
221
|
+
|
|
222
|
+
All other ActiveModel::Validations can be used:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
class TestKlass
|
|
226
|
+
include Eapi::Item
|
|
227
|
+
|
|
228
|
+
property :something
|
|
229
|
+
validates :something, numericality: true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
eapi = TestKlass.new something: 'something'
|
|
233
|
+
eapi.valid? # => false
|
|
234
|
+
eapi.errors.full_messages # => ["Something is not a number"]
|
|
235
|
+
eapi.errors.messages # => {something: ["must is not a number"]}
|
|
236
|
+
```
|
|
237
|
+
|
|
249
238
|
#### Mark a property as Required with `required` option
|
|
250
239
|
|
|
251
240
|
A required property will fail if the value is not present. It will use `ActiveModel::Validations` inside and will effectively do a `validates_presence_of :property_name`.
|
|
@@ -267,7 +256,7 @@ eapi.errors.messages # => {something: ["can't be blank"]}
|
|
|
267
256
|
|
|
268
257
|
#### Specify the property's Type with `type` option
|
|
269
258
|
|
|
270
|
-
If a property is defined to be of a specific type, the value will be validated to meet that criteria. It means that the value must be of the specified type. It will use `value.kind_of?(type)` and if that fails it will use `value.is?(type)` if defined.
|
|
259
|
+
If a property is defined to be of a specific type, the value will be validated to meet that criteria. It means that the value must be of the specified type. It will use `value.kind_of?(type)` (if type represents an actual class), and if that fails it will use `value.is?(type)` if defined.
|
|
271
260
|
|
|
272
261
|
example:
|
|
273
262
|
|
|
@@ -284,13 +273,164 @@ eapi.errors.full_messages # => ["Something must be a Hash"]
|
|
|
284
273
|
eapi.errors.messages # => {something: ["must be a Hash"]}
|
|
285
274
|
```
|
|
286
275
|
|
|
287
|
-
|
|
276
|
+
#### Custom validation with `validate_with` option
|
|
277
|
+
|
|
278
|
+
A more specific validation can be used using `validate_with`, that works the same way as `ActiveModel::Validations`.
|
|
288
279
|
|
|
280
|
+
example:
|
|
281
|
+
|
|
289
282
|
```ruby
|
|
290
283
|
class TestKlass
|
|
291
284
|
include Eapi::Item
|
|
292
285
|
|
|
293
|
-
property :something,
|
|
286
|
+
property :something, validate_with: ->(record, attr, value) do
|
|
287
|
+
record.errors.add(attr, "must pass my custom validation") unless value == :valid_val
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
eapi = TestKlass.new something: 1
|
|
292
|
+
eapi.valid? # => false
|
|
293
|
+
eapi.errors.full_messages # => ["Something must pass my custom validation"]
|
|
294
|
+
eapi.errors.messages # => {something: ["must pass my custom validation"]}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
### List properties
|
|
299
|
+
|
|
300
|
+
A property can be defined as a multiple property. This will affect the methods defined in the class (it will create a fluent 'adder' method `add_property_name` and a fluent 'clearer' method `clear_property_name`), and also the automatic initialisation.
|
|
301
|
+
|
|
302
|
+
#### Define property as multiple with `multiple` option
|
|
303
|
+
|
|
304
|
+
A property marked as `multiple` will be initialised with an empty array. If no `init_class` is specified then it will use Array as a `init_class`, for purposes of the `init_property_name` method.
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
class TestKlass
|
|
308
|
+
include Eapi::Item
|
|
309
|
+
|
|
310
|
+
property :something, multiple: true
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
#### Fluent adder method `add_property_name`
|
|
315
|
+
|
|
316
|
+
For a property marked as multiple, an extra fluent method called `add_property_name` will be created. This work very similar to the fluent setter `set_property_name` but inside it will append the value (using the shovel method `<<`) instead of setting it.
|
|
317
|
+
|
|
318
|
+
If the property is `nil` when `add_property_name` is called, then it will call `init_property_name` before.
|
|
319
|
+
|
|
320
|
+
```ruby
|
|
321
|
+
class TestKlass
|
|
322
|
+
include Eapi::Item
|
|
323
|
+
|
|
324
|
+
property :something, multiple: true
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
x = TestKlass.new
|
|
328
|
+
x.add_something(1).add_something(2)
|
|
329
|
+
x.something # => [1, 2]
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
#### Fluent clearer method `clear_property_name`
|
|
334
|
+
|
|
335
|
+
For a property marked as multiple, an extra fluent method called `clear_property_name` will be created. This method will call `clear` into the existing property value if it is present and respond to it. If that is not the case, it will init the property again calling `init_property_name`.
|
|
336
|
+
|
|
337
|
+
```ruby
|
|
338
|
+
class TestKlass
|
|
339
|
+
include Eapi::Item
|
|
340
|
+
|
|
341
|
+
property :something, multiple: true
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
x = TestKlass.new
|
|
345
|
+
x.add_something(1).add_something(2)
|
|
346
|
+
x.something # => [1, 2]
|
|
347
|
+
x.clear_something.something # => []
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Implicit `multiple` depending on `init_class` or `type`
|
|
351
|
+
|
|
352
|
+
Even without `multiple` option specified, if the `init_class` option is:
|
|
353
|
+
* `Array`
|
|
354
|
+
* `Set`
|
|
355
|
+
* a class that responds to `is_multiple?` with true
|
|
356
|
+
|
|
357
|
+
then the property is marked as multiple.
|
|
358
|
+
|
|
359
|
+
It will also work if the `type` option is given with a class or a class name that complies with the above restrictions.
|
|
360
|
+
|
|
361
|
+
example: (all `TestKlass` properties are marked as multiple)
|
|
362
|
+
```ruby
|
|
363
|
+
class MyCustomList
|
|
364
|
+
def self.is_multiple?
|
|
365
|
+
true
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def <<(val)
|
|
369
|
+
@list |= []
|
|
370
|
+
@list << val
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
class TestKlass
|
|
375
|
+
include Eapi::Item
|
|
376
|
+
|
|
377
|
+
property :p1, multiple: true
|
|
378
|
+
property :p2, init_class: Array
|
|
379
|
+
property :p3, init_class: "Set"
|
|
380
|
+
property :p4, type: Set
|
|
381
|
+
property :p5, type: "MyCustomList"
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
x = TestKlass.new
|
|
385
|
+
x.add_p1(1).add_p2(2).add_p3(3).add_p4(4)
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Element validation
|
|
389
|
+
|
|
390
|
+
Same as property validation, but for specific the elements in the list.
|
|
391
|
+
|
|
392
|
+
We can use `element_type` option in the definition, and it will check the type of each element in the list, same as `type` option does with the type of the property's value.
|
|
393
|
+
|
|
394
|
+
We can also specify `validate_element_with` option, and it will act the same as `validate_with` but for each element in the list.
|
|
395
|
+
|
|
396
|
+
```ruby
|
|
397
|
+
class TestKlass
|
|
398
|
+
include Eapi::Item
|
|
399
|
+
|
|
400
|
+
property :something, multiple: true, element_type: Hash
|
|
401
|
+
property :other, multiple: true, validate_element_with: ->(record, attr, value) do
|
|
402
|
+
record.errors.add(attr, "element must pass my custom validation") unless value == :valid_val
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
eapi = TestKlass.new
|
|
407
|
+
eapi.add_something 1
|
|
408
|
+
|
|
409
|
+
eapi.valid? # => false
|
|
410
|
+
eapi.errors.full_messages # => ["Something element must be a Hash"]
|
|
411
|
+
eapi.errors.messages # => {something: ["must element be a Hash"]}
|
|
412
|
+
|
|
413
|
+
eapi.something [{a: :b}]
|
|
414
|
+
eapi.valid? # => true
|
|
415
|
+
|
|
416
|
+
eapi.add_other 1
|
|
417
|
+
eapi.valid? # => false
|
|
418
|
+
eapi.errors.full_messages # => ["Other element must pass my custom validation"]
|
|
419
|
+
eapi.errors.messages # => {other: ["element must pass my custom validation"]}
|
|
420
|
+
|
|
421
|
+
eapi.other [:valid_val]
|
|
422
|
+
eapi.valid? # => true
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### Automatic property initialisation with `init_class` option
|
|
426
|
+
|
|
427
|
+
If a property is marked to be initialised using a specific class, then a `init_property_name` method is created that will set a new object of the given class in the property.
|
|
428
|
+
|
|
429
|
+
```ruby
|
|
430
|
+
class TestKlass
|
|
431
|
+
include Eapi::Item
|
|
432
|
+
|
|
433
|
+
property :something, init_class: Hash
|
|
294
434
|
end
|
|
295
435
|
|
|
296
436
|
eapi = TestKlass.new
|
|
@@ -299,7 +439,7 @@ eapi.init_something
|
|
|
299
439
|
eapi.something # => {}
|
|
300
440
|
```
|
|
301
441
|
|
|
302
|
-
A symbol or a string can also be specified as class name in `
|
|
442
|
+
A symbol or a string can also be specified as class name in `init_class` option, and it will be loaded on type check. This can be helpful to avoid loading problems. Using the same example as before:
|
|
303
443
|
|
|
304
444
|
```ruby
|
|
305
445
|
class TestKlass
|
|
@@ -316,7 +456,7 @@ eapi.something # => {}
|
|
|
316
456
|
|
|
317
457
|
To trigger the error, the value must not be an instance of the given Type, and also must not respond `true` to `value.is?(type)`
|
|
318
458
|
|
|
319
|
-
|
|
459
|
+
### Skip type validation with 'raw' values with `allow_raw` option
|
|
320
460
|
|
|
321
461
|
If we want to check for the type of the elements, but still want the flexibility of using raw `Hash` or `Array` in case we want something specific there, we can specify it with the `allow_raw` option.
|
|
322
462
|
|
|
@@ -397,44 +537,7 @@ TestList.elements_disallow_raw
|
|
|
397
537
|
TestList.elements_allow_raw? # => false
|
|
398
538
|
```
|
|
399
539
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
A more specific validation can be used using `validate_with`, that works the same way as `ActiveModel::Validations`.
|
|
403
|
-
|
|
404
|
-
example:
|
|
405
|
-
|
|
406
|
-
```ruby
|
|
407
|
-
class TestKlass
|
|
408
|
-
include Eapi::Item
|
|
409
|
-
|
|
410
|
-
property :something, validate_with: ->(record, attr, value) do
|
|
411
|
-
record.errors.add(attr, "must pass my custom validation") unless value == :valid_val
|
|
412
|
-
end
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
eapi = TestKlass.new something: 1
|
|
416
|
-
eapi.valid? # => false
|
|
417
|
-
eapi.errors.full_messages # => ["Something must pass my custom validation"]
|
|
418
|
-
eapi.errors.messages # => {something: ["must pass my custom validation"]}
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
#### Validations from `ActiveModel::Validations`
|
|
422
|
-
|
|
423
|
-
All other ActiveModel::Validations can be used:
|
|
424
|
-
|
|
425
|
-
```ruby
|
|
426
|
-
class TestKlass
|
|
427
|
-
include Eapi::Item
|
|
428
|
-
|
|
429
|
-
property :something
|
|
430
|
-
validates :something, numericality: true
|
|
431
|
-
end
|
|
432
|
-
|
|
433
|
-
eapi = TestKlass.new something: 'something'
|
|
434
|
-
eapi.valid? # => false
|
|
435
|
-
eapi.errors.full_messages # => ["Something is not a number"]
|
|
436
|
-
eapi.errors.messages # => {something: ["must is not a number"]}
|
|
437
|
-
```
|
|
540
|
+
### Definition
|
|
438
541
|
|
|
439
542
|
#### Unrecognised property definition options
|
|
440
543
|
|
|
@@ -460,113 +563,7 @@ definition[:type] = Array
|
|
|
460
563
|
TestKlass.definition_for :something # => { type: Hash, unrecognised_option: 1 }
|
|
461
564
|
```
|
|
462
565
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
a property can be defined as a multiple property. This will affect the methods defined in the class (it will create a fluent 'adder' method `add_property_name`), and also the automatic initialisation.
|
|
466
|
-
|
|
467
|
-
#### Define property as multiple with `multiple` option
|
|
468
|
-
|
|
469
|
-
A property marked as `multiple` will be initialised with an empty array. If no type is specified then it will use Array as a type, only for purposes of the `init_property_name` method.
|
|
470
|
-
|
|
471
|
-
```ruby
|
|
472
|
-
class TestKlass
|
|
473
|
-
include Eapi::Item
|
|
474
|
-
|
|
475
|
-
property :something, multiple: true
|
|
476
|
-
end
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
#### Adder method `add_property_name`
|
|
480
|
-
|
|
481
|
-
For a property marked as multiple, an extra fluent method called `add_property_name` will be created. This work very similar to the fluent setter `set_property_name` but inside it will append the value (using the shovel method `<<`) instead of setting it.
|
|
482
|
-
|
|
483
|
-
If the property is `nil` when `add_property_name` is called, then it will call `init_property_name` before.
|
|
484
|
-
|
|
485
|
-
```ruby
|
|
486
|
-
class TestKlass
|
|
487
|
-
include Eapi::Item
|
|
488
|
-
|
|
489
|
-
property :something, multiple: true
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
x = TestKlass.new
|
|
493
|
-
x.add_something(1).add_something(2)
|
|
494
|
-
x.something # => [1, 2]
|
|
495
|
-
```
|
|
496
|
-
|
|
497
|
-
#### Implicit `multiple` depending on Type
|
|
498
|
-
|
|
499
|
-
Even without `multiple` option specified, if the `type` option is:
|
|
500
|
-
* `Array`
|
|
501
|
-
* `Set`
|
|
502
|
-
* a class that responds to `is_multiple?` with true
|
|
503
|
-
|
|
504
|
-
then the property is marked as multiple.
|
|
505
|
-
|
|
506
|
-
example: (all `TestKlass` properties are marked as multiple)
|
|
507
|
-
```ruby
|
|
508
|
-
class MyCustomList
|
|
509
|
-
def self.is_multiple?
|
|
510
|
-
true
|
|
511
|
-
end
|
|
512
|
-
|
|
513
|
-
def <<(val)
|
|
514
|
-
@list |= []
|
|
515
|
-
@list << val
|
|
516
|
-
end
|
|
517
|
-
end
|
|
518
|
-
|
|
519
|
-
class TestKlass
|
|
520
|
-
include Eapi::Item
|
|
521
|
-
|
|
522
|
-
property :p1, multiple: true
|
|
523
|
-
property :p2, type: Array
|
|
524
|
-
property :p3, type: Set
|
|
525
|
-
property :p4, type: MyCustomList
|
|
526
|
-
end
|
|
527
|
-
|
|
528
|
-
x = TestKlass.new
|
|
529
|
-
x.add_p1(1).add_p2(2).add_p3(3).add_p4(4)
|
|
530
|
-
```
|
|
531
|
-
|
|
532
|
-
#### Element validation
|
|
533
|
-
|
|
534
|
-
Same as property validation, but for specific the elements in the list.
|
|
535
|
-
|
|
536
|
-
We can use `element_type` option in the definition, and it will check the type of each element in the list, same as `type` option does with the type of the property's value.
|
|
537
|
-
|
|
538
|
-
We can also specify `validate_element_with` option, and it will act the same as `validate_with` but for each element in the list.
|
|
539
|
-
|
|
540
|
-
```ruby
|
|
541
|
-
class TestKlass
|
|
542
|
-
include Eapi::Item
|
|
543
|
-
|
|
544
|
-
property :something, multiple: true, element_type: Hash
|
|
545
|
-
property :other, multiple: true, validate_element_with: ->(record, attr, value) do
|
|
546
|
-
record.errors.add(attr, "element must pass my custom validation") unless value == :valid_val
|
|
547
|
-
end
|
|
548
|
-
end
|
|
549
|
-
|
|
550
|
-
eapi = TestKlass.new
|
|
551
|
-
eapi.add_something 1
|
|
552
|
-
|
|
553
|
-
eapi.valid? # => false
|
|
554
|
-
eapi.errors.full_messages # => ["Something element must be a Hash"]
|
|
555
|
-
eapi.errors.messages # => {something: ["must element be a Hash"]}
|
|
556
|
-
|
|
557
|
-
eapi.something [{a: :b}]
|
|
558
|
-
eapi.valid? # => true
|
|
559
|
-
|
|
560
|
-
eapi.add_other 1
|
|
561
|
-
eapi.valid? # => false
|
|
562
|
-
eapi.errors.full_messages # => ["Other element must pass my custom validation"]
|
|
563
|
-
eapi.errors.messages # => {other: ["element must pass my custom validation"]}
|
|
564
|
-
|
|
565
|
-
eapi.other [:valid_val]
|
|
566
|
-
eapi.valid? # => true
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
### `List` classes
|
|
566
|
+
## `Eapi::List`: list based objects
|
|
570
567
|
|
|
571
568
|
An Eapi `List` is to an Array as an Eapi `Item` is to a Hash.
|
|
572
569
|
|
|
@@ -574,10 +571,14 @@ It will render itself into an array of elements. It can store a list of elements
|
|
|
574
571
|
|
|
575
572
|
It works using an internal list of elements, to whom it delegates most of the behaviour. Its interface is compatible with an Array, including ActiveSupport methods.
|
|
576
573
|
|
|
577
|
-
|
|
574
|
+
*important*: Right now a `List` can also have properties like an `Item`, but this could change for a stable release.
|
|
575
|
+
|
|
576
|
+
### accessor to internal element list: `_list`
|
|
578
577
|
|
|
579
578
|
The internal list of elements of an Eapi `List` object can be accessed using the `_list` method, that is always an `Array`.
|
|
580
579
|
|
|
580
|
+
### Methods
|
|
581
|
+
|
|
581
582
|
#### fluent adder: `add`
|
|
582
583
|
|
|
583
584
|
Similar to the `set_x` methods for properties, this method will add an element to the internal list and return `self`.
|
|
@@ -593,7 +594,7 @@ The options for that definition is:
|
|
|
593
594
|
* `element_type` or `type`: it will provoke the list validation to fail if an element does not complies with the given type validation (see type validation on `Item`)
|
|
594
595
|
* `validate_element_with` or `validate_with`: it will execute the given callable object to validate each element, similar to the `validate_element_with` option in the property definition.
|
|
595
596
|
|
|
596
|
-
|
|
597
|
+
### example
|
|
597
598
|
|
|
598
599
|
```ruby
|
|
599
600
|
class MyListKlass
|
|
@@ -620,6 +621,10 @@ l.add(1)
|
|
|
620
621
|
l.valid? # => false
|
|
621
622
|
```
|
|
622
623
|
|
|
624
|
+
## Common to `Item` and `List`
|
|
625
|
+
|
|
626
|
+
The following features are shared between `List`s and `Item`s.
|
|
627
|
+
|
|
623
628
|
### Pose as other types
|
|
624
629
|
|
|
625
630
|
An Eapi class can poses as other types, for purposes of `type` checking in a property definition. We use the class method `is` for this.
|
|
@@ -671,9 +676,51 @@ obj.is_an_one_thing? # => true
|
|
|
671
676
|
obj.is_a_super_duper_thing? # => false
|
|
672
677
|
```
|
|
673
678
|
|
|
674
|
-
###
|
|
679
|
+
### Object creation shortcut: calling methods in Eapi
|
|
680
|
+
|
|
681
|
+
Calling a method with the desired class name in `Eapi` module will do the same as `DesiredClass.new(...)`. The name can be the same as the class, or an underscorised version, or a simple underscored one.
|
|
682
|
+
|
|
683
|
+
The goal is to use `Eapi.esr_search(name: 'Paco')` as a shortcut to `Esr::Search.new(name: 'Paco')`. We can also use `Eapi.Esr_Search(...)` and other combinations.
|
|
684
|
+
|
|
685
|
+
To show this feature and all the combinations for method names, we'll use the 2 example classes that are used in the actual test rspec.
|
|
686
|
+
|
|
687
|
+
```ruby
|
|
688
|
+
class MyTestKlassOutside
|
|
689
|
+
include Eapi::Item
|
|
690
|
+
|
|
691
|
+
property :something
|
|
692
|
+
end
|
|
693
|
+
|
|
694
|
+
module Somewhere
|
|
695
|
+
class TestKlassInModule
|
|
696
|
+
include Eapi::Item
|
|
697
|
+
|
|
698
|
+
property :something
|
|
699
|
+
end
|
|
700
|
+
end
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
As shown by rspec run:
|
|
704
|
+
|
|
705
|
+
```
|
|
706
|
+
initialise using method calls to Eapi
|
|
707
|
+
Eapi.MyTestKlassOutside(...)
|
|
708
|
+
calls MyTestKlassOutside.new
|
|
709
|
+
Eapi.my_test_klass_outside(...)
|
|
710
|
+
calls MyTestKlassOutside.new
|
|
711
|
+
Eapi.Somewhere__TestKlassInModule(...)
|
|
712
|
+
calls Somewhere::TestKlassInModule.new
|
|
713
|
+
Eapi.somewhere__test_klass_in_module(...)
|
|
714
|
+
calls Somewhere::TestKlassInModule.new
|
|
715
|
+
Eapi.Somewhere_TestKlassInModule(...)
|
|
716
|
+
calls Somewhere::TestKlassInModule.new
|
|
717
|
+
Eapi.somewhere_test_klass_in_module(...)
|
|
718
|
+
calls Somewhere::TestKlassInModule.new
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
## Using Eapi in your own library
|
|
675
722
|
|
|
676
|
-
You can add the functionality of Eapi to your own library module, and use it instead of `Eapi::
|
|
723
|
+
You can add the functionality of Eapi to your own library module, and use it instead of `Eapi::Item` or `Eapi::List`.
|
|
677
724
|
|
|
678
725
|
Method-call-initialise shortcut can ignore the base name:
|
|
679
726
|
|
|
@@ -683,7 +730,7 @@ module MyExtension
|
|
|
683
730
|
end
|
|
684
731
|
|
|
685
732
|
class TestKlass
|
|
686
|
-
include MyExtension::
|
|
733
|
+
include MyExtension::Item
|
|
687
734
|
property :something
|
|
688
735
|
end
|
|
689
736
|
|
|
@@ -695,7 +742,7 @@ obj.something # => 1
|
|
|
695
742
|
|
|
696
743
|
module MyExtension
|
|
697
744
|
class TestKlassInside
|
|
698
|
-
include MyExtension::
|
|
745
|
+
include MyExtension::Item
|
|
699
746
|
property :something
|
|
700
747
|
end
|
|
701
748
|
end
|
|
@@ -711,9 +758,41 @@ obj.something # => 1
|
|
|
711
758
|
|
|
712
759
|
As it works now, the children of your extension will be also children of `Eapi`, so calling `Eapi.your_klass` and `YourExtension.your_klass` will do the same.
|
|
713
760
|
|
|
761
|
+
## Installation
|
|
762
|
+
|
|
763
|
+
Add this line to your application's Gemfile:
|
|
764
|
+
|
|
765
|
+
gem 'eapi'
|
|
766
|
+
|
|
767
|
+
And then execute:
|
|
768
|
+
|
|
769
|
+
$ bundle
|
|
770
|
+
|
|
771
|
+
Or install it yourself as:
|
|
772
|
+
|
|
773
|
+
$ gem install eapi
|
|
774
|
+
|
|
775
|
+
## Dependencies
|
|
776
|
+
|
|
777
|
+
### Ruby version
|
|
778
|
+
|
|
779
|
+
Works with ruby 2.1, tested with MRI 2.1.1
|
|
780
|
+
|
|
781
|
+
### Gem dependencies
|
|
782
|
+
|
|
783
|
+
This gem uses ActiveSupport (version 4) and also the ActiveModel Validations (version 4). It also uses fluent_accessors gem.
|
|
784
|
+
|
|
785
|
+
Extracted from the gemspec:
|
|
786
|
+
```
|
|
787
|
+
spec.add_dependency 'fluent_accessors', '~> 1'
|
|
788
|
+
spec.add_dependency 'activesupport', '~> 4'
|
|
789
|
+
spec.add_dependency 'activemodel', '~> 4'
|
|
790
|
+
```
|
|
791
|
+
|
|
714
792
|
## TODO
|
|
715
793
|
|
|
716
794
|
1. `type` option in property definition to accept symbol -> if a class can be recognised by that name, it works ok. If not, it still uses that for type validation (using `is?`) but it does not use that in the `init_` method.
|
|
795
|
+
2. `type` option to be divided in `init_type` (must be a class or a class name) and `check_type` (class / class name or type validation using `is?`)
|
|
717
796
|
|
|
718
797
|
## Contributing
|
|
719
798
|
|