eapi 0.0.1 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b1e95dc0187bc0b633dd39f04862c3f57f1c412f
4
- data.tar.gz: 35a9ef27b0186c91592e4a7d1bee6d43a717e13e
3
+ metadata.gz: 4eaa6b7386dfe974f725d80fa008f3d8dbe5ee46
4
+ data.tar.gz: a9b8fb3d839460128e7595c11db9754b18ac0347
5
5
  SHA512:
6
- metadata.gz: e68a34cf0a19910cda0eebdd3e855bcf3fe70fa4ab34de5c3e6f0c6bc7b67b25493f8d0a8acdf77a54fb25bffe06c841d889331abefaee245614668560d38508
7
- data.tar.gz: 3f0961bb4964307784f5e251640b39c52ee6e488592462712b41c3b0996e75f327b9726454ec1d340dc3ce27b24a8089475201fa39946b108a7869260988a53c
6
+ metadata.gz: f6cd68cc945520b4e4eaf62a8f60be1f53e06bd21226befb7817cfcefeb01168d04dd75cb7491eae835cd2aa1b24cf3321d838fa56bae973f709b05b191c5db2
7
+ data.tar.gz: d5688563cada1584200aa2ca6febca6303fba83115484a3dfe80e103dda2282fdb5a7dc3871aa9858415a9eb8f478669ffd255ed674bbb6b63a9a5d00fc67d06
data/.travis.yml CHANGED
@@ -1,6 +1,11 @@
1
1
  language: ruby
2
2
  cache: bundler
3
3
 
4
+ addons:
5
+ code_climate:
6
+ repo_token: 9915e76dfd68b6208b36d41238a429cd8b48fa534e287dbf20c8edf4b2fd5abf
7
+
8
+
4
9
  rvm:
5
10
  - 2.1.1
6
11
  - 2.0.0
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in eapi.gemspec
4
4
  gemspec
5
+ gem 'codeclimate-test-reporter', group: :test, require: nil
data/README.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  ruby gem for building complex structures that will end up in hashes (initially devised for ElasticSearch search requests)
4
4
 
5
- [![Coverage Status](https://coveralls.io/repos/eturino/eapi/badge.png)](https://coveralls.io/r/eturino/eapi)
5
+ [![Gem Version](https://badge.fury.io/rb/eapi.svg)](http://badge.fury.io/rb/eapi)
6
6
  [![Build Status](https://travis-ci.org/eturino/eapi.svg?branch=master)](https://travis-ci.org/eturino/eapi)
7
+ [![Code Climate](https://codeclimate.com/github/eturino/eapi.png)](https://codeclimate.com/github/eturino/eapi)
8
+ [![Code Climate Coverage](https://codeclimate.com/github/eturino/eapi/coverage.png)](https://codeclimate.com/github/eturino/eapi)
7
9
 
8
10
  ## Installation
9
11
 
@@ -19,9 +21,500 @@ Or install it yourself as:
19
21
 
20
22
  $ gem install eapi
21
23
 
24
+ ## Dependencies
25
+
26
+ ### Ruby version
27
+
28
+ Works with ruby 2. Tested with MRI 2.1.1 and 2.0.0
29
+
30
+ ### Gem dependencies
31
+
32
+ This gem uses ActiveSupport (version 4) and also the ActiveModel Validations (version 4)
33
+
34
+ Extracted from the gemspec:
35
+ ```
36
+ spec.add_dependency 'activesupport', '~> 4'
37
+ spec.add_dependency 'activemodel', '~> 4'
38
+ ```
39
+
22
40
  ## Usage
23
41
 
24
- TODO: Write usage instructions here
42
+ ### including EAPI into your class
43
+
44
+ Just include the module `Eapi::Common` into your class.
45
+
46
+ ```ruby
47
+ class MyTestKlass
48
+ include Eapi::Common
49
+
50
+ property :something
51
+ end
52
+ ```
53
+
54
+ ### Initialize
55
+
56
+ `Eapi::Common` 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.
57
+
58
+ For now any unrecognised property in the hash will be ignored. This may change in the future.
59
+
60
+ ```ruby
61
+ class MyTestKlass
62
+ include Eapi::Common
63
+
64
+ property :something
65
+ end
66
+
67
+ x = MyTestKlass.new something: 1
68
+ x.something # => 1
69
+ ```
70
+
71
+ ### Object creation shortcut: calling methods in Eapi
72
+
73
+ 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.
74
+
75
+ 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.
76
+
77
+ 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.
78
+
79
+ ```ruby
80
+ class MyTestKlassOutside
81
+ include Eapi::Common
82
+
83
+ property :something
84
+ end
85
+
86
+ module Somewhere
87
+ class TestKlassInModule
88
+ include Eapi::Common
89
+
90
+ property :something
91
+ end
92
+ end
93
+ ```
94
+
95
+ As shown by rspec run:
96
+
97
+ ```
98
+ initialise using method calls to Eapi
99
+ Eapi.MyTestKlassOutside(...)
100
+ calls MyTestKlassOutside.new
101
+ Eapi.my_test_klass_outside(...)
102
+ calls MyTestKlassOutside.new
103
+ Eapi.Somewhere__TestKlassInModule(...)
104
+ calls Somewhere::TestKlassInModule.new
105
+ Eapi.somewhere__test_klass_in_module(...)
106
+ calls Somewhere::TestKlassInModule.new
107
+ Eapi.Somewhere_TestKlassInModule(...)
108
+ calls Somewhere::TestKlassInModule.new
109
+ Eapi.somewhere_test_klass_in_module(...)
110
+ calls Somewhere::TestKlassInModule.new
111
+ ```
112
+
113
+ ### Defining properties
114
+
115
+ We define properties in our class with the instruction `property` as shown:
116
+
117
+ ```ruby
118
+ class MyTestKlass
119
+ include Eapi::Common
120
+
121
+ property :one
122
+ property :two
123
+ end
124
+ ```
125
+ #### Setting proeprties on object creation
126
+ We can then assign the properties on object creation:
127
+ ```ruby
128
+ x = MyTestKlass.new one: 1, two: 2
129
+ ```
130
+ #### Getters
131
+
132
+ A getter method will be created for each property
133
+ ```ruby
134
+ x = MyTestKlass.new one: 1, two: 2
135
+ x.one # => 1
136
+ x.two # => 2
137
+ ```
138
+
139
+ #### Setters
140
+
141
+ Also, a setter will be created for each property
142
+ ```ruby
143
+ x = MyTestKlass.new one: 1, two: 2
144
+ x.one = :other
145
+ x.one # => :other
146
+ ```
147
+
148
+ #### Fluent setters (for method chaining)
149
+ Besides the normal setter, a fluent setter (`set_my_prop`) will be created for each property. `self` is returned on this setters, enabling Method Chaining.
150
+
151
+ ```ruby
152
+ x = MyTestKlass.new one: 1, two: 2
153
+ res = x.set_one(:other)
154
+ x.one # => :other
155
+ res.equal? x # => true
156
+
157
+ x.set_one(:hey).set_two(:you)
158
+ x.one # => :hey
159
+ x.two # => :you
160
+ ```
161
+
162
+ #### Getter method as fluent setter
163
+
164
+ The getter method also works as fluent setter. If we pass an argument to it, it will call the fluent setter
165
+ ```ruby
166
+ x = MyTestKlass.new
167
+ res = x.one :fluent
168
+ x.one # => :fluent
169
+ res.equal? x # => true
170
+ ```
171
+
172
+ ### Convert to hashes: `to_h` and `create_hash`
173
+
174
+ All Eapi classes respond to `to_h` and return a hash, as it is the main purpose of this gem. It will execute any validation (see property definition), and if everything is ok, it will convert it to a simple hash structure.
175
+
176
+ #### Methods involved
177
+
178
+ Inside, `to_h` 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`.
179
+
180
+ The `create_hash` method will create a hash with the properties as keys. Each value will be "converted".
181
+
182
+ #### Values conversion
183
+
184
+ By default, each property will be converted into a simple element (Array, Hash, or simple value).
185
+
186
+ If a value is an Array or a Set, `to_a` will be invoked and all values will be "converted" in the same way.
187
+
188
+ If a value respond to `to_h`, it will be called. That way, if the value of a property (or an element of an Array) is an Eapi object, it will be validated and converted into a simple hash structure.
189
+
190
+ important: *any nil value will be omitted* in the final hash.
191
+
192
+ #### Example
193
+
194
+ To demonstrate this behaviour we'll have an Eapi enabled class `ExampleEapi` and another `ComplexValue` class that responds to `to_h`. We'll set into the `ExampleEapi` object complex properties to demonstrate the conversion into a simple structure.
195
+
196
+ ```ruby
197
+ class ComplexValue
198
+ def to_h
199
+ {
200
+ a: Set.new(['hello', 'world', MyTestObject.new])
201
+ }
202
+ end
203
+ end
204
+
205
+ class ExampleEapi
206
+ include Eapi::Common
207
+
208
+ property :something, required: true
209
+ property :other
210
+ end
211
+
212
+ # TESTING `to_h`
213
+
214
+ list = Set.new [
215
+ OpenStruct.new(a: 1, 'b' => 2),
216
+ {c: 3, 'd' => 4},
217
+ nil
218
+ ]
219
+
220
+ eapi = ExampleEapi.new something: list, other: ComplexValue.new
221
+ eapi.to_h # =>
222
+ # {
223
+ # something: [
224
+ # {a: 1, b: 2},
225
+ # {c: 3, d: 4},
226
+ # ],
227
+ #
228
+ # other: {
229
+ # a: [
230
+ # 'hello',
231
+ # 'world',
232
+ # {a: 'hello'}
233
+ # ]
234
+ # }
235
+ # }
236
+ ```
237
+
238
+ ### Property definition
239
+
240
+ When defining the property, we can specify some options to specify what values are expected in that property. This serves for validation and automatic initialisation.
241
+
242
+ 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.
243
+
244
+ #### Mark a property as Required with `required` option
245
+
246
+ 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`.
247
+
248
+ example:
249
+
250
+ ```ruby
251
+ class TestKlass
252
+ include Eapi::Common
253
+
254
+ property :something, required: true
255
+ end
256
+
257
+ eapi = TestKlass.new
258
+ eapi.valid? # => false
259
+ eapi.errors.full_messages # => ["Something can't be blank"]
260
+ eapi.errors.messages # => {something: ["can't be blank"]}
261
+ ```
262
+
263
+ #### Specify the property's Type with `type` option
264
+
265
+ 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.
266
+
267
+ example:
268
+
269
+ ```ruby
270
+ class TestKlass
271
+ include Eapi::Common
272
+
273
+ property :something, type: Hash
274
+ end
275
+
276
+ eapi = TestKlass.new something: 1
277
+ eapi.valid? # => false
278
+ eapi.errors.full_messages # => ["Something must be a Hash"]
279
+ eapi.errors.messages # => {something: ["must be a Hash"]}
280
+ ```
281
+
282
+ Also, if a type is specified, then a `init_property_name` method is created that will set a new object of the given type in the property.
283
+
284
+ ```ruby
285
+ class TestKlass
286
+ include Eapi::Common
287
+
288
+ property :something, type: Hash
289
+ end
290
+
291
+ eapi = TestKlass.new
292
+ eapi.something # => nil
293
+ eapi.init_something
294
+ eapi.something # => {}
295
+ ```
296
+
297
+ 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)`
298
+
299
+ #### Custom validation with `validate_with` option
300
+
301
+ A more specific validation can be used using `validate_with`, that works the same way as `ActiveModel::Validations`.
302
+
303
+ example:
304
+
305
+ ```ruby
306
+ class TestKlass
307
+ include Eapi::Common
308
+
309
+ property :something, validate_with: ->(record, attr, value) do
310
+ record.errors.add(attr, "must pass my custom validation") unless value == :valid_val
311
+ end
312
+ end
313
+
314
+ eapi = TestKlass.new something: 1
315
+ eapi.valid? # => false
316
+ eapi.errors.full_messages # => ["Something must pass my custom validation"]
317
+ eapi.errors.messages # => {something: ["must pass my custom validation"]}
318
+ ```
319
+
320
+ #### Validations from `ActiveModel::Validations`
321
+
322
+ All other ActiveModel::Validations can be used:
323
+
324
+ ```ruby
325
+ class TestKlass
326
+ include Eapi::Common
327
+
328
+ property :something
329
+ validates :something, numericality: true
330
+ end
331
+
332
+ eapi = TestKlass.new something: 'something'
333
+ eapi.valid? # => false
334
+ eapi.errors.full_messages # => ["Something is not a number"]
335
+ eapi.errors.messages # => {something: ["must is not a number"]}
336
+ ```
337
+
338
+ #### Unrecognised property definition options
339
+
340
+ If the definition contained any unrecognised options, it will still be stored. No error is reported yet, but this behaviour may change in the future.
341
+
342
+ #### See property definition with `.definition_for` class method
343
+
344
+ You can see (but not edit) the definition of a property calling the `definition_for` class method. It will also contain the unrecognised options.
345
+
346
+ ```ruby
347
+ class TestKlass
348
+ include Eapi::Common
349
+
350
+ property :something, type: Hash, unrecognised_option: 1
351
+ end
352
+
353
+ definition = TestKlass.definition_for :something # => { type: Hash, unrecognised_option: 1 }
354
+
355
+ # attempt to change the definition...
356
+ definition[:type] = Array
357
+
358
+ # ...has no effect
359
+ TestKlass.definition_for :something # => { type: Hash, unrecognised_option: 1 }
360
+ ```
361
+
362
+ ### List properties
363
+
364
+ 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.
365
+
366
+ #### Define property as multiple with `multiple` option
367
+
368
+ 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.
369
+
370
+ ```ruby
371
+ class TestKlass
372
+ include Eapi::Common
373
+
374
+ property :something, multiple: true
375
+ end
376
+ ```
377
+
378
+ #### Adder method `add_property_name`
379
+
380
+ 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.
381
+
382
+ If the property is `nil` when `add_property_name` is called, then it will call `init_property_name` before.
383
+
384
+ ```ruby
385
+ class TestKlass
386
+ include Eapi::Common
387
+
388
+ property :something, multiple: true
389
+ end
390
+
391
+ x = TestKlass.new
392
+ x.add_something(1).add_something(2)
393
+ x.something # => [1, 2]
394
+ ```
395
+
396
+ #### Implicit `multiple` depending on Type
397
+
398
+ Even without `multiple` option specified, if the `type` option is:
399
+ * `Array`
400
+ * `Set`
401
+ * a class that responds to `is_multiple?` with true
402
+
403
+ then the property is marked as multiple.
404
+
405
+ example: (all `TestKlass` properties are marked as multiple)
406
+ ```ruby
407
+ class MyCustomList
408
+ def self.is_multiple?
409
+ true
410
+ end
411
+
412
+ def <<(val)
413
+ @list |= []
414
+ @list << val
415
+ end
416
+ end
417
+
418
+ class TestKlass
419
+ include Eapi::Common
420
+
421
+ property :p1, multiple: true
422
+ property :p2, type: Array
423
+ property :p3, type: Set
424
+ property :p4, type: MyCustomList
425
+ end
426
+
427
+ x = TestKlass.new
428
+ x.add_p1(1).add_p2(2).add_p3(3).add_p4(4)
429
+ ```
430
+
431
+ #### Element validation
432
+
433
+ Same as property validation, but for specific the elements in the list.
434
+
435
+ 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.
436
+
437
+ We can also specify `validate_element_with` option, and it will act the same as `validate_with` but for each element in the list.
438
+
439
+ ```ruby
440
+ class TestKlass
441
+ include Eapi::Common
442
+
443
+ property :something, multiple: true, element_type: Hash
444
+ property :other, multiple: true, validate_element_with: ->(record, attr, value) do
445
+ record.errors.add(attr, "element must pass my custom validation") unless value == :valid_val
446
+ end
447
+ end
448
+
449
+ eapi = TestKlass.new
450
+ eapi.add_something 1
451
+
452
+ eapi.valid? # => false
453
+ eapi.errors.full_messages # => ["Something element must be a Hash"]
454
+ eapi.errors.messages # => {something: ["must element be a Hash"]}
455
+
456
+ eapi.something [{a: :b}]
457
+ eapi.valid? # => true
458
+
459
+ eapi.add_other 1
460
+ eapi.valid? # => false
461
+ eapi.errors.full_messages # => ["Other element must pass my custom validation"]
462
+ eapi.errors.messages # => {other: ["element must pass my custom validation"]}
463
+
464
+ eapi.other [:valid_val]
465
+ eapi.valid? # => true
466
+ ```
467
+
468
+ ### Pose as other types
469
+
470
+ 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.
471
+
472
+ the `is?` method is also available as an instance method.
473
+
474
+ Eapi also creates specific instance and class methods like `is_a_some_type?` or `is_an_another_type?`.
475
+
476
+ example:
477
+
478
+ ```ruby
479
+ class SuperTestKlass
480
+ include Eapi::Common
481
+ end
482
+
483
+ class TestKlass < SuperTestKlass
484
+ is :one_thing, :other_thing, OtherType
485
+ end
486
+
487
+ TestKlass.is? TestKlass # => true
488
+ TestKlass.is? 'TestKlass' # => true
489
+ TestKlass.is? :TestKlass # => true
490
+
491
+ TestKlass.is? SuperTestKlass # => true
492
+ TestKlass.is? 'SuperTestKlass' # => true
493
+ TestKlass.is? :SuperTestKlass # => true
494
+
495
+ TestKlass.is? :one_thing # => true
496
+ TestKlass.is? :other_thing # => true
497
+ TestKlass.is? :other_thing # => true
498
+ TestKlass.is? OtherType # => true
499
+ TestKlass.is? :OtherType # => true
500
+
501
+ TestKlass.is? SomethingElse # => false
502
+ TestKlass.is? :SomethingElse # => false
503
+
504
+ # also works on instance
505
+ obj = TestKlass.new
506
+ obj.is? TestKlass # => true
507
+ obj.is? :one_thing # => true
508
+
509
+ # specific type test methods
510
+ TestKlass.is_a_test_klass? # => true
511
+ TestKlass.is_an_one_thing? # => true
512
+ TestKlass.is_a_super_duper_thing? # => false
513
+
514
+ obj.is_a_test_klass? # => true
515
+ obj.is_an_one_thing? # => true
516
+ obj.is_a_super_duper_thing? # => false
517
+ ```
25
518
 
26
519
  ## Contributing
27
520