eapi 0.5.0 → 0.6.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: 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