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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bcbb9ddd6ab08af20ca40eec44b53bf3ac2109e9
4
- data.tar.gz: 914738db9bb087708fc3603c1297738137d6226e
2
+ SHA256:
3
+ metadata.gz: 27c5a5f04384aebefba51e9ec085042cdf6d1ca40ff7d82aafba522b3b8eb457
4
+ data.tar.gz: 52d3d15700603d35a82e12f53c64765c71053644238bbe02cb44e9666934b6f6
5
5
  SHA512:
6
- metadata.gz: 9aad37686d32b6c02c163addb8d1a99bcdbbb39c5e5e770de7978825c354051872e114f4468c415b61d1574e83f7ef633bdb008c92d4bdd269bafa841cca000c
7
- data.tar.gz: 24b29a8195033c489f9314620e5191c5be144c4d6b4bf15fb4280039ac686117569fabefa293d31081095beca0b7508324224006d1239328b7aa9ec8b77adc3e
6
+ metadata.gz: 7d1319a99792280e03ea43e6b8ecb294cb6da12d443e4b7f4a1211d4ef44515589de48307244330044baa666c4691f4816b7466ac56401ced959321afb263685
7
+ data.tar.gz: 4e99bb150c889eb4eafcb966ba7f3756cee4d64e63f2ec0b0040f19f2d830e75d1f433234018ca11a7506bb7b35a5962a74cf491b5a0166b3783bf60793f25b1
@@ -5,10 +5,10 @@ notifications:
5
5
  email: false
6
6
 
7
7
  rvm:
8
- - 1.9.3
9
- - 2.0.0
10
- - 2.1.1
11
- - 2.1.2
8
+ - 2.3
9
+ - 2.4
10
+ - 2.5
11
+ - 2.6
12
12
  - ruby-head
13
13
  - jruby
14
14
 
data/README.md CHANGED
@@ -38,7 +38,30 @@ TwoWayMapper.register :customer do |mapping|
38
38
  end
39
39
  ```
40
40
 
41
- Once mapping is defined we can convert one object to another and vice versa
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 customer, api_response
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 another_customer, request_data
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
- On rails, you can put all mappings into `app/mappings` folder
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, but for keys like `user.email`, it will try to build `user` before updating `email` on write)
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
 
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'two_way_mapper'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/dependencies/autoload'
2
4
  require 'active_support/core_ext/module/delegation'
3
5
  require 'active_support/inflector'
@@ -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
- attr_reader :rules, :left_class, :left_options, :right_class, :right_options
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
- @rules = []
10
+ @rules_list = []
7
11
  end
8
12
 
9
- [:left, :right].each do |method|
13
+ [DIRECTIONS, DIRECTIONS.reverse].each do |from, to|
10
14
  class_eval <<-CODE, __FILE__, __LINE__ + 1
11
- def #{method}(plugin, options = {})
12
- @#{method}_class = node_class plugin
13
- @#{method}_options = options
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
- def node_class(plugin)
19
- TwoWayMapper::Node.const_get plugin.to_s.camelize
20
- rescue NameError
21
- raise NameError, 'Cannot find node'
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(left_selector, right_selector = {}, options = {})
25
- raise 'You need to set left before calling rule' unless left_class
26
- raise 'You need to set right before calling rule' unless right_class
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 left_selector.is_a?(Hash)
33
- raise ArgumentError if left_selector.count < 2
34
- opt = left_selector
35
- left_selector = opt.keys.first
36
- left_opt.merge! opt.delete left_selector
37
- right_selector = opt.keys.first
38
- right_opt.merge! opt.delete right_selector
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
- left = left_class.new left_selector, left_options.merge(left_opt)
42
- right = right_class.new right_selector, right_options.merge(right_opt)
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
- @rules << Rule.new(left, right, opt)
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
- { left: :right, right: :left }.each do |from, to|
48
- class_eval <<-CODE, __FILE__, __LINE__ + 1
49
- def from_#{from}_to_#{to}(left_obj, right_obj)
50
- rules.each { |r| r.from_#{from}_to_#{to} left_obj, right_obj }
51
- #{to}_obj
52
- end
53
- CODE
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
@@ -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 :Abstract, 'two_way_mapper/node/abstract'
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,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TwoWayMapper
2
4
  module Node
3
5
  class ActiveRecord < Object
@@ -6,7 +8,7 @@ module TwoWayMapper
6
8
  end
7
9
 
8
10
  def create_node(obj, key)
9
- obj.send "build_#{key}"
11
+ obj.send("build_#{key}")
10
12
  end
11
13
  end
12
14
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TwoWayMapper
2
4
  module Node
3
- class Abstract
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 &block
19
+ selector.split(DIVIDER).map(&block)
18
20
  end
19
21
 
20
22
  def read(source)
21
- rewind_forward(source) { |obj, key| return nil }
23
+ rewind_forward(source) { |_obj, _key| return nil }
22
24
  end
23
25
 
24
- def write(obj, value)
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
- !options[:write_if].respond_to?(:call) ||
30
- options[:write_if].call(current_value, new_value)
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
- if block_given?
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 obj, key
45
+ obj = next_key(obj, key)
46
46
  end
47
47
 
48
48
  obj
49
49
  end
50
50
 
51
- def rewind_to?(obj, key)
51
+ def rewind_to?(_obj, _key)
52
+ raise NotImplementedError
52
53
  end
53
54
 
54
- def create_node(obj, key)
55
+ def create_node(_obj, _key)
56
+ raise NotImplementedError
55
57
  end
56
58
 
57
- def next_key(obj, 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 < Abstract
5
+ class Hash < Base
4
6
  def write(source, value)
5
7
  rewinded = rewind_forward source, 1
6
8
 
7
- rewinded[keys.last] = value if writable? rewinded[keys.last], value
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 < Abstract
5
+ class Object < Base
4
6
  def write(source, value)
5
- rewinded = rewind_forward source, 1
7
+ rewinded = rewind_forward(source, 1)
8
+
9
+ return unless writable?(rewinded.send(keys.last), value)
6
10
 
7
- rewinded.send "#{keys.last}=", value if writable? rewinded.send(keys.last), value
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? key
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 key
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 "two_way_mapper.set_load_path" do |app|
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
@@ -1,36 +1,72 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TwoWayMapper
2
4
  class Rule
3
- attr_reader :left, :right
5
+ attr_reader :left_nodes, :right_nodes
4
6
 
5
- def initialize(left, right, opt = {})
6
- @left = left
7
- @right = right
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
- { left: :right, right: :left }.each do |from, to|
12
- class_eval <<-CODE, __FILE__, __LINE__ + 1
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
- #{to}_obj
22
- end
23
- CODE
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 map_value(value, left_to_right = true)
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 && map.is_a?(Hash)
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
- value = map[value] || @options[default_key] || @options[:default]
69
+ map[value] || @options[default_key] || @options[:default]
34
70
  else
35
71
  value
36
72
  end