eapi 0.0.1 → 0.1.0

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