two-way-mapper 0.0.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 +5 -5
- data/.travis.yml +4 -4
- data/README.md +28 -5
- data/lib/two-way-mapper.rb +2 -0
- data/lib/two_way_mapper.rb +2 -0
- data/lib/two_way_mapper/map.rb +4 -0
- data/lib/two_way_mapper/mapping.rb +58 -32
- data/lib/two_way_mapper/node.rb +3 -1
- data/lib/two_way_mapper/node/active_record.rb +3 -1
- data/lib/two_way_mapper/node/{abstract.rb → base.rb} +18 -15
- data/lib/two_way_mapper/node/hash.rb +6 -2
- data/lib/two_way_mapper/node/object.rb +9 -5
- data/lib/two_way_mapper/railtie.rb +4 -2
- data/lib/two_way_mapper/rule.rb +55 -19
- data/lib/two_way_mapper/tools.rb +4 -2
- data/lib/two_way_mapper/version.rb +3 -1
- data/spec/map_spec.rb +3 -1
- data/spec/mapping_spec.rb +118 -30
- data/spec/node/active_record_spec.rb +6 -2
- data/spec/node/{abstract_spec.rb → base_spec.rb} +10 -5
- data/spec/node/hash_spec.rb +7 -3
- data/spec/node/object_spec.rb +4 -2
- data/spec/rule_spec.rb +263 -85
- data/spec/tools_spec.rb +8 -8
- data/two-way-mapper.gemspec +16 -15
- metadata +26 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 27c5a5f04384aebefba51e9ec085042cdf6d1ca40ff7d82aafba522b3b8eb457
|
4
|
+
data.tar.gz: 52d3d15700603d35a82e12f53c64765c71053644238bbe02cb44e9666934b6f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7d1319a99792280e03ea43e6b8ecb294cb6da12d443e4b7f4a1211d4ef44515589de48307244330044baa666c4691f4816b7466ac56401ced959321afb263685
|
7
|
+
data.tar.gz: 4e99bb150c889eb4eafcb966ba7f3756cee4d64e63f2ec0b0040f19f2d830e75d1f433234018ca11a7506bb7b35a5962a74cf491b5a0166b3783bf60793f25b1
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -38,7 +38,30 @@ TwoWayMapper.register :customer do |mapping|
|
|
38
38
|
end
|
39
39
|
```
|
40
40
|
|
41
|
-
|
41
|
+
For simple rules use
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
TwoWayMapper.register :customer do |mapping|
|
45
|
+
mapping.left :object # set left plugin to object
|
46
|
+
mapping.right :hash, stringify_keys: true # set right plugin to hash
|
47
|
+
|
48
|
+
mapping.rules(
|
49
|
+
'first_name' => 'FirstName',
|
50
|
+
'last_name' => 'LastName'
|
51
|
+
)
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
Mapping can be defined explicitly without registration
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
mapping = TwoWayMapper::Mapping.new
|
59
|
+
mapping.left :object
|
60
|
+
mapping.right :hash, stringify_keys: true
|
61
|
+
# ...
|
62
|
+
```
|
63
|
+
|
64
|
+
Once the mapping is defined it can be used to convert one object to another and vice versa
|
42
65
|
|
43
66
|
```ruby
|
44
67
|
Customer = Struct.new :first_name, :last_name, :gender
|
@@ -46,7 +69,7 @@ Customer = Struct.new :first_name, :last_name, :gender
|
|
46
69
|
customer = Customer.new
|
47
70
|
api_response = { 'FirstName' => 'Evee', 'LastName' => 'Fjord', 'sex' => 'female' }
|
48
71
|
|
49
|
-
TwoWayMapper[:customer].from_right_to_left
|
72
|
+
TwoWayMapper[:customer].from_right_to_left(customer, api_response)
|
50
73
|
puts customer.first_name # => 'Evee'
|
51
74
|
puts customer.last_name # => 'Fjord'
|
52
75
|
puts customer.gender # => 'F'
|
@@ -58,17 +81,17 @@ another_customer.first_name = 'Step'
|
|
58
81
|
another_customer.last_name = 'Bander'
|
59
82
|
another_customer.gender = 'M'
|
60
83
|
|
61
|
-
TwoWayMapper[:customer].from_left_to_right
|
84
|
+
TwoWayMapper[:customer].from_left_to_right(another_customer, request_data)
|
62
85
|
puts request_data # => { 'FirstName' => 'Step', 'LastName' => 'Bander', sex: 'male' }
|
63
86
|
```
|
64
87
|
|
65
|
-
|
88
|
+
In rails, mappings can be defined in `app/mappings` folder
|
66
89
|
|
67
90
|
### Available plugins
|
68
91
|
|
69
92
|
* hash
|
70
93
|
* object
|
71
|
-
* active_record (same as object
|
94
|
+
* active_record (same as `object`, but for keys like `user.email`, it builds `user` before updating `email` on write)
|
72
95
|
|
73
96
|
## Contributing
|
74
97
|
|
data/lib/two-way-mapper.rb
CHANGED
data/lib/two_way_mapper.rb
CHANGED
data/lib/two_way_mapper/map.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
class Map
|
3
5
|
delegate :[], to: :@maps
|
@@ -8,7 +10,9 @@ module TwoWayMapper
|
|
8
10
|
|
9
11
|
def register(name)
|
10
12
|
mapping = TwoWayMapper::Mapping.new
|
13
|
+
|
11
14
|
yield mapping if block_given?
|
15
|
+
|
12
16
|
@maps[name.to_sym] = mapping
|
13
17
|
end
|
14
18
|
end
|
@@ -1,56 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
class Mapping
|
3
|
-
|
5
|
+
DIRECTIONS = [:left, :right].freeze
|
6
|
+
|
7
|
+
attr_reader :rules_list, :left_class, :left_options, :right_class, :right_options
|
4
8
|
|
5
9
|
def initialize
|
6
|
-
@
|
10
|
+
@rules_list = []
|
7
11
|
end
|
8
12
|
|
9
|
-
[
|
13
|
+
[DIRECTIONS, DIRECTIONS.reverse].each do |from, to|
|
10
14
|
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
11
|
-
def #{
|
12
|
-
@#{
|
13
|
-
@#{
|
15
|
+
def #{from}(plugin, options = {})
|
16
|
+
@#{from}_class = node_class(plugin)
|
17
|
+
@#{from}_options = options
|
14
18
|
end
|
15
|
-
CODE
|
16
|
-
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
def #{from}_selectors(mappable: false)
|
21
|
+
rules_list.flat_map do |rule|
|
22
|
+
if mappable && rule.from_#{from}_to_#{to}_only?
|
23
|
+
[]
|
24
|
+
else
|
25
|
+
rule.#{from}_nodes.map(&:selector)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def from_#{from}_to_#{to}(left_obj, right_obj)
|
31
|
+
rules_list.each { |rule| rule.from_#{from}_to_#{to}(left_obj, right_obj) }
|
32
|
+
#{to}_obj
|
33
|
+
end
|
34
|
+
CODE
|
22
35
|
end
|
23
36
|
|
24
|
-
def rule(
|
25
|
-
raise '
|
26
|
-
raise '
|
37
|
+
def rule(left_selectors, right_selectors = {}, options = {})
|
38
|
+
raise 'left is not defined' unless left_class
|
39
|
+
raise 'right is not defined' unless right_class
|
27
40
|
|
28
41
|
opt = options.dup
|
29
42
|
left_opt = opt.delete(:left) || {}
|
30
43
|
right_opt = opt.delete(:right) || {}
|
31
44
|
|
32
|
-
if
|
33
|
-
raise ArgumentError if
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
45
|
+
if left_selectors.is_a?(Hash)
|
46
|
+
raise ArgumentError if left_selectors.count < 2
|
47
|
+
|
48
|
+
opt = left_selectors
|
49
|
+
left_selectors = opt.keys.first
|
50
|
+
left_opt.merge! opt.delete(left_selectors)
|
51
|
+
right_selectors = opt.keys.first
|
52
|
+
right_opt.merge!(opt.delete(right_selectors))
|
39
53
|
end
|
40
54
|
|
41
|
-
|
42
|
-
|
55
|
+
@rules_list << Rule.new(
|
56
|
+
build_nodes(left_selectors, left_class, left_options.merge(left_opt)),
|
57
|
+
build_nodes(right_selectors, right_class, right_options.merge(right_opt)),
|
58
|
+
opt
|
59
|
+
)
|
60
|
+
end
|
43
61
|
|
44
|
-
|
62
|
+
def rules(hash)
|
63
|
+
hash.each do |left_selector, right_selector|
|
64
|
+
rule(left_selector, right_selector)
|
65
|
+
end
|
45
66
|
end
|
46
67
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
68
|
+
private
|
69
|
+
|
70
|
+
def node_class(plugin)
|
71
|
+
TwoWayMapper::Node.const_get(plugin.to_s.camelize)
|
72
|
+
rescue NameError
|
73
|
+
raise NameError, 'Cannot find node'
|
74
|
+
end
|
75
|
+
|
76
|
+
def build_nodes(selectors, klass, opt)
|
77
|
+
Array(selectors).map do |selector|
|
78
|
+
klass.new(selector, opt)
|
79
|
+
end
|
54
80
|
end
|
55
81
|
end
|
56
82
|
end
|
data/lib/two_way_mapper/node.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
module Node
|
3
5
|
extend ActiveSupport::Autoload
|
4
6
|
|
5
|
-
autoload :
|
7
|
+
autoload :Base, 'two_way_mapper/node/base'
|
6
8
|
autoload :Hash, 'two_way_mapper/node/hash'
|
7
9
|
autoload :Object, 'two_way_mapper/node/object'
|
8
10
|
autoload :ActiveRecord, 'two_way_mapper/node/active_record'
|
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
module Node
|
3
|
-
class
|
5
|
+
class Base
|
4
6
|
DIVIDER = '.'
|
5
7
|
|
6
8
|
attr_accessor :selector, :options
|
@@ -14,47 +16,48 @@ module TwoWayMapper
|
|
14
16
|
block = options[:stringify_keys] ? :to_s : :to_sym
|
15
17
|
block = block.to_proc
|
16
18
|
end
|
17
|
-
selector.split(DIVIDER).map
|
19
|
+
selector.split(DIVIDER).map(&block)
|
18
20
|
end
|
19
21
|
|
20
22
|
def read(source)
|
21
|
-
rewind_forward(source) { |
|
23
|
+
rewind_forward(source) { |_obj, _key| return nil }
|
22
24
|
end
|
23
25
|
|
24
|
-
def write(
|
26
|
+
def write(_obj, _value)
|
27
|
+
raise NotImplementedError
|
25
28
|
end
|
26
29
|
|
27
30
|
def writable?(current_value, new_value)
|
28
31
|
!options[:write_if] ||
|
29
|
-
|
30
|
-
|
32
|
+
!options[:write_if].respond_to?(:call) ||
|
33
|
+
options[:write_if].call(current_value, new_value)
|
31
34
|
end
|
32
35
|
|
33
36
|
private
|
34
37
|
|
35
38
|
def rewind_forward(obj, margin = 0)
|
36
39
|
to = -(1 + margin.to_i.abs)
|
40
|
+
|
37
41
|
keys[0..to].each do |key|
|
38
42
|
unless rewind_to?(obj, key)
|
39
|
-
|
40
|
-
yield obj, key
|
41
|
-
else
|
42
|
-
create_node obj, key
|
43
|
-
end
|
43
|
+
block_given? ? yield(obj, key) : create_node(obj, key)
|
44
44
|
end
|
45
|
-
obj = next_key
|
45
|
+
obj = next_key(obj, key)
|
46
46
|
end
|
47
47
|
|
48
48
|
obj
|
49
49
|
end
|
50
50
|
|
51
|
-
def rewind_to?(
|
51
|
+
def rewind_to?(_obj, _key)
|
52
|
+
raise NotImplementedError
|
52
53
|
end
|
53
54
|
|
54
|
-
def create_node(
|
55
|
+
def create_node(_obj, _key)
|
56
|
+
raise NotImplementedError
|
55
57
|
end
|
56
58
|
|
57
|
-
def next_key(
|
59
|
+
def next_key(_obj, _key)
|
60
|
+
raise NotImplementedError
|
58
61
|
end
|
59
62
|
end
|
60
63
|
end
|
@@ -1,10 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
module Node
|
3
|
-
class Hash <
|
5
|
+
class Hash < Base
|
4
6
|
def write(source, value)
|
5
7
|
rewinded = rewind_forward source, 1
|
6
8
|
|
7
|
-
|
9
|
+
return unless writable?(rewinded[keys.last], value)
|
10
|
+
|
11
|
+
rewinded[keys.last] = value
|
8
12
|
end
|
9
13
|
|
10
14
|
private
|
@@ -1,23 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
module Node
|
3
|
-
class Object <
|
5
|
+
class Object < Base
|
4
6
|
def write(source, value)
|
5
|
-
rewinded = rewind_forward
|
7
|
+
rewinded = rewind_forward(source, 1)
|
8
|
+
|
9
|
+
return unless writable?(rewinded.send(keys.last), value)
|
6
10
|
|
7
|
-
rewinded.send
|
11
|
+
rewinded.send("#{keys.last}=", value)
|
8
12
|
end
|
9
13
|
|
10
14
|
private
|
11
15
|
|
12
16
|
def rewind_to?(obj, key)
|
13
|
-
obj.respond_to?
|
17
|
+
obj.respond_to?(key)
|
14
18
|
end
|
15
19
|
|
16
20
|
def create_node(obj, key)
|
17
21
|
end
|
18
22
|
|
19
23
|
def next_key(obj, key)
|
20
|
-
obj.send
|
24
|
+
obj.send(key)
|
21
25
|
end
|
22
26
|
end
|
23
27
|
end
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rails'
|
2
4
|
|
3
5
|
module TwoWayMapper
|
4
6
|
class Railtie < Rails::Railtie
|
5
|
-
initializer
|
7
|
+
initializer 'two_way_mapper.set_load_path' do |app|
|
6
8
|
path = Rails.root.join('app', 'mappings', '*.rb').to_s
|
7
|
-
Dir[path].each{ |file| load file }
|
9
|
+
Dir[path].each { |file| load file }
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
data/lib/two_way_mapper/rule.rb
CHANGED
@@ -1,36 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TwoWayMapper
|
2
4
|
class Rule
|
3
|
-
attr_reader :
|
5
|
+
attr_reader :left_nodes, :right_nodes
|
4
6
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
7
|
+
def initialize(left_nodes, right_nodes, opt = {})
|
8
|
+
@left_nodes = left_nodes
|
9
|
+
@right_nodes = right_nodes
|
8
10
|
@options = opt
|
9
11
|
end
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
def from_#{from}_to_#{to}(left_obj, right_obj)
|
14
|
-
value = #{from}.read #{from}_obj
|
15
|
-
value = map_value value, #{(from == :left).inspect}
|
16
|
-
if @options[:on_#{from}_to_#{to}].respond_to? :call
|
17
|
-
value = @options[:on_#{from}_to_#{to}].call value
|
18
|
-
end
|
19
|
-
#{to}.write #{to}_obj, value
|
13
|
+
def from_left_to_right(left_obj, right_obj)
|
14
|
+
return right_obj if from_right_to_left_only?
|
20
15
|
|
21
|
-
|
22
|
-
|
23
|
-
|
16
|
+
value = read(left_nodes, [left_obj, right_obj], true)
|
17
|
+
|
18
|
+
write(right_nodes, right_obj, value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def from_right_to_left(left_obj, right_obj)
|
22
|
+
return left_obj if from_left_to_right_only?
|
23
|
+
|
24
|
+
value = read(right_nodes, [left_obj, right_obj], false)
|
25
|
+
|
26
|
+
write(left_nodes, left_obj, value)
|
27
|
+
end
|
28
|
+
|
29
|
+
def from_right_to_left_only?
|
30
|
+
@options[:from_right_to_left_only]
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_left_to_right_only?
|
34
|
+
@options[:from_left_to_right_only]
|
24
35
|
end
|
25
36
|
|
26
37
|
private
|
27
38
|
|
28
|
-
def
|
39
|
+
def read(nodes, objects, left_to_right)
|
40
|
+
callback = left_to_right ? :on_left_to_right : :on_right_to_left
|
41
|
+
obj = left_to_right ? objects.first : objects.last
|
42
|
+
value = nil
|
43
|
+
|
44
|
+
nodes.each do |node|
|
45
|
+
value = node.read(obj)
|
46
|
+
value = map_value(value, left_to_right)
|
47
|
+
if @options[callback].respond_to?(:call)
|
48
|
+
args = [value] + objects + [node]
|
49
|
+
value = @options[callback].call(*args)
|
50
|
+
end
|
51
|
+
|
52
|
+
break if value
|
53
|
+
end
|
54
|
+
|
55
|
+
value
|
56
|
+
end
|
57
|
+
|
58
|
+
def write(nodes, obj, value)
|
59
|
+
nodes.each { |node| node.write(obj, value) }
|
60
|
+
|
61
|
+
obj
|
62
|
+
end
|
63
|
+
|
64
|
+
def map_value(value, left_to_right)
|
29
65
|
map = @options[:map]
|
30
|
-
if map
|
66
|
+
if map.is_a?(Hash)
|
31
67
|
map = map.invert unless left_to_right
|
32
68
|
default_key = "default_#{left_to_right ? 'left' : 'right'}".to_sym
|
33
|
-
|
69
|
+
map[value] || @options[default_key] || @options[:default]
|
34
70
|
else
|
35
71
|
value
|
36
72
|
end
|