eapi 0.5.0 → 0.6.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: 65b2149ec39bedde36ad3c6305a7eee7fb8b37b6
4
- data.tar.gz: 6dc257fc779aeb132e289e7e870364e163104118
3
+ metadata.gz: 4673ef60bc4acd96d0ae165935fab2e1b901cf53
4
+ data.tar.gz: d04635f645f8638fdb8a224dc5ae3320e5fe90fd
5
5
  SHA512:
6
- metadata.gz: 1e36790d62876f6cda906ed3f1cf4b1f29338b1acdbb70fadeac7718119ec4382f78655e7026b5cc289b9a8be5df560d467f912f817a34ec6bb58743322bd4ff
7
- data.tar.gz: 440261fc694e382d0664f83f6ecf0f619e7f4922388adea99640b0a9ada69ce31b2f652fc8d376f96d5f59d480fb5a4dbb1ac216a4ec7e2a2bc50f9464dbe7e8
6
+ metadata.gz: 4da861d047452f5057a2cc1f778ea2073198afab91a926fd7cdc1c36e35222ca6f7e02aa547b5683099d21020d2d1171825562a66a94c47663316f2fcf65240d
7
+ data.tar.gz: 8d9f6a3d3d5a8e18a07cf03f22b40724acd98a9a736985bf01093e2204f77a633b7d79c3a2603c4ab1ce1e5378dd32d4be47d3d9c82333aed375f4ff86971203
data/README.md CHANGED
@@ -88,6 +88,106 @@ By default, each property will be converted into a simple element (Array, Hash,
88
88
  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.
89
89
  3. If a value respond to `to_h`, it will be called.
90
90
 
91
+ #### Custom value conversion
92
+
93
+ We can override the default value conversion using the `convert_with` option in the property definition. It will accept:
94
+
95
+ * a symbol (message to be sent to the value)
96
+ * callable object (lambda, proc...) that accepts either 1 parameter (the value) or 2 parameters (the value and the object)
97
+
98
+ ```ruby
99
+ class ExampleItem
100
+ include Eapi::Item
101
+
102
+ property :something, convert_with: :to_s
103
+ property :other, convert_with: ->(val) { "This is #{val}" }
104
+ property :third, convert_with: ->(val, obj) do
105
+ s = obj.something
106
+ c = obj.send :converted_value_for, :something
107
+ "I am #{val} with some #{s.inspect} as #{c.inspect}"
108
+ end
109
+ end
110
+
111
+ x = ExampleItem.new something: :x, other: 1, third: 'the third'
112
+ x.render # =>
113
+ # {
114
+ # # converted to string (`to_s` method called)
115
+ # something: 'x',
116
+ #
117
+ # # converted using the lambda
118
+ # other: 'This is 1',
119
+ #
120
+ # # converted using the lambda, with access to the context object
121
+ # third: "I am the third with some :x as \"x\""
122
+ # }
123
+ ```
124
+
125
+ On a `List` object, it will work as conversion for every element.
126
+
127
+ ```ruby
128
+ class ExampleList
129
+ include Eapi::List
130
+
131
+ elements convert_with: :to_s
132
+ end
133
+
134
+ x = ExampleItem.new.add(1).add(2)
135
+ x.render # => ["1", "2"]
136
+ ```
137
+
138
+ #### Convert values before validation using `convert_before_validation` option
139
+
140
+ If `convert_before_validation` option is enabled, then the conversion will occur before validation
141
+
142
+ ```ruby
143
+ class ExampleItem
144
+ include Eapi::Item
145
+
146
+ property :something, convert_with: :to_i, convert_before_validation: true
147
+ property :normal, convert_with: :to_i
148
+
149
+ validates :something, inclusion: { in: [1, 2, 3] }
150
+ validates :normal, inclusion: { in: [1, 2, 3] }
151
+ end
152
+
153
+ # the conversion happens before validation, so it passes
154
+ x = ExampleItem.new something: '1', normal: 2
155
+ x.valid? # => true
156
+
157
+ # the conversion happens on rendering, after validation, so it fails
158
+ x = ExampleItem.new something: 1, normal: '2'
159
+ x.valid? # => false
160
+ ```
161
+
162
+ In `List` it will work on elements.
163
+
164
+ ```ruby
165
+ class ExampleListConvertBeforeValidationEnabled
166
+ include Eapi::List
167
+
168
+ elements convert_with: :to_i, convert_before_validation: true, validate_with: ->(record, attr, value) do
169
+ record.errors.add(attr, 'must pass my custom validation') unless value.kind_of?(Fixnum)
170
+ end
171
+ end
172
+
173
+ class ExampleListConvertBeforeValidationDisabled
174
+ include Eapi::List
175
+
176
+ elements convert_with: :to_i, validate_with: ->(record, attr, value) do
177
+ record.errors.add(attr, 'must pass my custom validation') unless value.kind_of?(Fixnum)
178
+ end
179
+ end
180
+
181
+
182
+ # the conversion happens before validation, so it passes
183
+ x = ExampleListConvertBeforeValidationEnabled.new.add('1')
184
+ x.valid? # => true
185
+
186
+ # the conversion happens on rendering, after validation, so it fails
187
+ x = ExampleListConvertBeforeValidationDisabled.new.add('1')
188
+ x.valid? # => false
189
+ ```
190
+
91
191
  #### Ignoring values
92
192
 
93
193
  By default, any `nil` values will be omitted in the final structure by the `perform_render` method, in both `Item` and `List`.
@@ -23,6 +23,11 @@ module Eapi
23
23
  raise Eapi::Errors::InvalidElementError, "errors: #{errors.full_messages}, self: #{self.inspect}" unless valid?
24
24
  end
25
25
 
26
+ def valid?(*)
27
+ perform_before_validation
28
+ super
29
+ end
30
+
26
31
  def render
27
32
  validate!
28
33
  perform_render
@@ -94,6 +94,10 @@ module Eapi
94
94
  definition.fetch(:required, false)
95
95
  end
96
96
 
97
+ def allow_raw?
98
+ definition.fetch(:allow_raw, false)
99
+ end
100
+
97
101
  def validate_with
98
102
  definition.fetch(:validate_with, nil)
99
103
  end
@@ -110,9 +114,6 @@ module Eapi
110
114
  definition.fetch(:init_class, nil)
111
115
  end
112
116
 
113
- def allow_raw?
114
- definition.fetch(:allow_raw, false)
115
- end
116
117
  end
117
118
  end
118
119
  end
@@ -16,6 +16,7 @@ module Eapi
16
16
  render
17
17
  end
18
18
 
19
+ private
19
20
  def perform_render
20
21
  {}.tap do |hash|
21
22
  _properties.each do |prop|
@@ -23,5 +24,13 @@ module Eapi
23
24
  end
24
25
  end
25
26
  end
27
+
28
+ def perform_before_validation
29
+ _properties.each do |property|
30
+ if self.class.convert_before_validation?(property)
31
+ self.set(property, converted_value_for(property))
32
+ end
33
+ end
34
+ end
26
35
  end
27
36
  end
@@ -13,6 +13,7 @@ module Eapi
13
13
  def self.add_features(klass)
14
14
  Eapi::Common.add_features klass
15
15
  klass.extend(ClassMethods)
16
+ klass.include(Eapi::Methods::Properties::ListInstanceMethods)
16
17
  klass.extend(Eapi::Methods::Properties::ListCLassMethods)
17
18
  end
18
19
 
@@ -34,10 +35,6 @@ module Eapi
34
35
  render
35
36
  end
36
37
 
37
- def perform_render
38
- _list.map { |val| convert_value val }.reject { |x| to_be_ignored x }
39
- end
40
-
41
38
  def _list
42
39
  @_list ||= []
43
40
  end
@@ -75,8 +72,17 @@ module Eapi
75
72
  protected :initialize_copy
76
73
 
77
74
  private
78
- def to_be_ignored(value)
79
- Eapi::ValueIgnoreChecker.to_be_ignored? value, self.class.elements_ignore_definition
75
+ def perform_render
76
+ _list.reduce([]) do |array, value|
77
+ set_value_in_final_array(array, value)
78
+ array
79
+ end
80
+ end
81
+
82
+ def perform_before_validation
83
+ if self.class.elements_convert_before_validation?
84
+ _list.map! { |v| convert_value_for_element(v) }
85
+ end
80
86
  end
81
87
 
82
88
  # transpose, assoc, rassoc , permutation, combination, repeated_permutation, repeated_combination, product, pack ?? => do not use the methods
@@ -19,11 +19,11 @@ module Eapi
19
19
  end
20
20
 
21
21
  def converted_value_for(prop)
22
- convert_value get(prop)
22
+ convert_value get(prop), self.class.defined_convert_with_for(prop)
23
23
  end
24
24
 
25
- def convert_value(value)
26
- Eapi::ValueConverter.convert_value(value)
25
+ def convert_value(value, convert_with = nil)
26
+ Eapi::ValueConverter.convert_value(value, self, convert_with)
27
27
  end
28
28
 
29
29
  def converted_or_default_value_for(property)
@@ -112,12 +112,40 @@ module Eapi
112
112
  definition_for(property).fetch(:default, nil)
113
113
  end
114
114
 
115
+ def defined_convert_with_for(property)
116
+ definition_for(property).fetch(:convert_with, nil)
117
+ end
118
+
119
+ def convert_before_validation?(property)
120
+ definition_for(property).fetch(:convert_before_validation, false)
121
+ end
122
+
115
123
  private :_property_allow_raw
116
124
  private :_property_definitions
117
125
  private :run_property_definition
118
126
  private :store_property_definition
119
127
  end
120
128
 
129
+ module ListInstanceMethods
130
+ def set_value_in_final_array(array, value)
131
+ yield_final_value_for_elements(value) do |val|
132
+ array << val
133
+ end
134
+ end
135
+
136
+ def yield_final_value_for_elements(value)
137
+ yield convert_value_for_element(value) unless to_be_ignored?(value)
138
+ end
139
+
140
+ def to_be_ignored?(value)
141
+ Eapi::ValueIgnoreChecker.to_be_ignored? value, self.class.elements_ignore_definition
142
+ end
143
+
144
+ def convert_value_for_element(value)
145
+ convert_value(value, self.class.elements_defined_convert_with_for)
146
+ end
147
+ end
148
+
121
149
  module ListCLassMethods
122
150
  def elements_allow_raw
123
151
  property_allow_raw(:_list)
@@ -135,11 +163,19 @@ module Eapi
135
163
  definition_for_elements.fetch(:ignore, :nil?)
136
164
  end
137
165
 
166
+ def elements_convert_before_validation?
167
+ definition_for_elements.fetch(:convert_before_validation, false)
168
+ end
169
+
138
170
  def elements(definition)
139
171
  run_list_definition definition
140
172
  store_list_definition definition
141
173
  end
142
174
 
175
+ def elements_defined_convert_with_for
176
+ definition_for_elements.fetch(:convert_with, nil)
177
+ end
178
+
143
179
  def definition_for_elements
144
180
  @_list_definition ||= {}
145
181
  end
@@ -1,20 +1,47 @@
1
1
  module Eapi
2
2
  module ValueConverter
3
- def self.convert_value(value)
4
- if value.nil?
3
+ def self.convert_value(value, context, convert_with = nil)
4
+
5
+ if convert_with.present?
6
+ value_using_convert_with(value, context, convert_with)
7
+ elsif value.nil?
5
8
  nil
6
9
  elsif can_render? value
7
10
  value_from_render value
8
11
  elsif is_list? value
9
- value_from_list value
12
+ value_from_list value, context
10
13
  elsif is_hash?(value)
11
- value_from_hash value
14
+ value_from_hash value, context
12
15
  else
13
16
  value
14
17
  end
15
18
  end
16
19
 
17
20
  private
21
+ def self.value_using_convert_with(value, context, convert_with)
22
+ if convert_with.respond_to? :call
23
+ value_using_callable value, context, convert_with
24
+ else
25
+ value_using_message value, convert_with
26
+ end
27
+ end
28
+
29
+ def self.value_using_callable(value, context, callable)
30
+ a = callable.try(:arity) || callable.method(:call).arity
31
+ case a
32
+ when 0
33
+ callable.call
34
+ when 1
35
+ callable.call value
36
+ else
37
+ callable.call value, context
38
+ end
39
+ end
40
+
41
+ def self.value_using_message(value, message)
42
+ value.send message
43
+ end
44
+
18
45
  def self.can_render?(value)
19
46
  value.respond_to? :render
20
47
  end
@@ -33,14 +60,14 @@ module Eapi
33
60
  value.render
34
61
  end
35
62
 
36
- def self.value_from_list(value)
37
- value.to_a.map { |e| convert_value e }.compact
63
+ def self.value_from_list(value, context)
64
+ value.to_a.map { |e| convert_value e, context }.compact
38
65
  end
39
66
 
40
- def self.value_from_hash(value)
67
+ def self.value_from_hash(value, context)
41
68
  {}.tap do |hash|
42
69
  value.to_h.each_pair do |k, v|
43
- val = convert_value v
70
+ val = convert_value v, context
44
71
  hash[k] = val unless val.nil?
45
72
  end
46
73
  hash.deep_symbolize_keys!
@@ -1,3 +1,3 @@
1
1
  module Eapi
2
- VERSION = "0.5.0"
2
+ VERSION = "0.6.0"
3
3
  end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Eapi do
4
+
5
+ describe 'convert_with' do
6
+
7
+ context 'Item' do
8
+
9
+ class ExampleItemConvertWith
10
+ include Eapi::Item
11
+
12
+ property :something, convert_with: :to_s
13
+ property :other, convert_with: ->(val) { "This is #{val}" }
14
+ property :third, convert_with: ->(val, obj) do
15
+ s = obj.something
16
+ c = obj.send :converted_value_for, :something
17
+ "I am #{val} with some #{s.inspect} as #{c.inspect}"
18
+ end
19
+ end
20
+
21
+ subject { ExampleItemConvertWith.new something: :x, other: 1, third: 'the third' }
22
+
23
+ context 'message (symbol or string)' do
24
+ it 'in the rendered hash, the value is converted by sending the message to the value' do
25
+ expect(subject.render[:something]).to eq 'x'
26
+ end
27
+ end
28
+
29
+ context 'callable object with 1 argument' do
30
+ it 'in the rendered hash, the value is converted by sending `call` to the callable object and passing the value as single argument' do
31
+ expect(subject.render[:other]).to eq 'This is 1'
32
+ end
33
+ end
34
+
35
+ context 'callable object with 2 arguments' do
36
+ it 'in the rendered hash, the value is converted by sending `call` to the callable object and passing the value as first argument and the context object (item) as second argument' do
37
+ expect(subject.render[:third]).to eq "I am the third with some :x as \"x\""
38
+ end
39
+ end
40
+ end
41
+
42
+ context 'List' do
43
+ class ExampleListConvertWith
44
+ include Eapi::List
45
+
46
+ elements convert_with: :to_s
47
+ end
48
+
49
+ subject { ExampleListConvertWith.new.add(1).add(2) }
50
+
51
+ it 'with a given option for `context_with`, it will use to convert all elements of the list' do
52
+ expect(subject.render).to eq ['1', '2']
53
+ end
54
+ end
55
+ end
56
+
57
+ describe 'convert_before_validation option' do
58
+
59
+ context 'Item' do
60
+ class ExampleItemConvertBeforeValidation
61
+ include Eapi::Item
62
+
63
+ property :something, convert_with: :to_i, convert_before_validation: true
64
+ property :normal, convert_with: :to_i
65
+
66
+ validates :something, inclusion: {in: [1, 2, 3]}
67
+ validates :normal, inclusion: {in: [1, 2, 3]}
68
+ end
69
+
70
+ context 'enabled' do
71
+ subject { ExampleItemConvertBeforeValidation.new something: '1', normal: 2 }
72
+ it 'the conversion happens before validation' do
73
+ expect(subject).to be_valid
74
+ end
75
+ end
76
+
77
+ context 'disabled' do
78
+ subject { ExampleItemConvertBeforeValidation.new something: 1, normal: '2' }
79
+ it 'the conversion happens on rendering, after validation' do
80
+ expect(subject).not_to be_valid
81
+ end
82
+ end
83
+ end
84
+
85
+ context 'List' do
86
+ class ExampleListConvertBeforeValidationEnabled
87
+ include Eapi::List
88
+
89
+ elements convert_with: :to_i, convert_before_validation: true, validate_with: ->(record, attr, value) do
90
+ record.errors.add(attr, 'must pass my custom validation') unless value.kind_of?(Fixnum)
91
+ end
92
+ end
93
+
94
+ class ExampleListConvertBeforeValidationDisabled
95
+ include Eapi::List
96
+
97
+ elements convert_with: :to_i, validate_with: ->(record, attr, value) do
98
+ record.errors.add(attr, 'must pass my custom validation') unless value.kind_of?(Fixnum)
99
+ end
100
+ end
101
+
102
+ context 'enabled' do
103
+ subject { ExampleListConvertBeforeValidationEnabled.new.add('1') }
104
+ it 'the conversion happens before validation' do
105
+ expect(subject).to be_valid
106
+ end
107
+ end
108
+
109
+ context 'disabled' do
110
+ subject { ExampleListConvertBeforeValidationDisabled.new.add('1') }
111
+ it 'the conversion happens on rendering, after validation' do
112
+ expect(subject).not_to be_valid
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: eapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eduardo Turiño
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-07-23 00:00:00.000000000 Z
11
+ date: 2014-07-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -214,6 +214,7 @@ files:
214
214
  - lib/eapi/value_ignore_checker.rb
215
215
  - lib/eapi/version.rb
216
216
  - spec/basic_spec.rb
217
+ - spec/conversion_spec.rb
217
218
  - spec/definition_spec.rb
218
219
  - spec/extension_spec.rb
219
220
  - spec/function_spec.rb
@@ -252,6 +253,7 @@ summary: ruby gem for building complex structures that will end up in hashes (in
252
253
  devised for ElasticSearch search requests)
253
254
  test_files:
254
255
  - spec/basic_spec.rb
256
+ - spec/conversion_spec.rb
255
257
  - spec/definition_spec.rb
256
258
  - spec/extension_spec.rb
257
259
  - spec/function_spec.rb