two-way-mapper 0.0.1 → 0.3.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
- 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