two-way-mapper 0.0.1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/Gemfile +6 -0
- data/Gemfile.ci +3 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +79 -0
- data/Rakefile +5 -0
- data/lib/two-way-mapper.rb +1 -0
- data/lib/two_way_mapper.rb +24 -0
- data/lib/two_way_mapper/map.rb +15 -0
- data/lib/two_way_mapper/mapping.rb +56 -0
- data/lib/two_way_mapper/node.rb +10 -0
- data/lib/two_way_mapper/node/abstract.rb +61 -0
- data/lib/two_way_mapper/node/active_record.rb +13 -0
- data/lib/two_way_mapper/node/hash.rb +25 -0
- data/lib/two_way_mapper/node/object.rb +24 -0
- data/lib/two_way_mapper/railtie.rb +10 -0
- data/lib/two_way_mapper/rule.rb +39 -0
- data/lib/two_way_mapper/tools.rb +13 -0
- data/lib/two_way_mapper/version.rb +3 -0
- data/spec/map_spec.rb +12 -0
- data/spec/mapping_spec.rb +96 -0
- data/spec/node/abstract_spec.rb +23 -0
- data/spec/node/active_record_spec.rb +31 -0
- data/spec/node/hash_spec.rb +68 -0
- data/spec/node/object_spec.rb +43 -0
- data/spec/rule_spec.rb +128 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/tools_spec.rb +29 -0
- data/two-way-mapper.gemspec +27 -0
- metadata +155 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: bcbb9ddd6ab08af20ca40eec44b53bf3ac2109e9
|
4
|
+
data.tar.gz: 914738db9bb087708fc3603c1297738137d6226e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9aad37686d32b6c02c163addb8d1a99bcdbbb39c5e5e770de7978825c354051872e114f4468c415b61d1574e83f7ef633bdb008c92d4bdd269bafa841cca000c
|
7
|
+
data.tar.gz: 24b29a8195033c489f9314620e5191c5be144c4d6b4bf15fb4280039ac686117569fabefa293d31081095beca0b7508324224006d1239328b7aa9ec8b77adc3e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.ci
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Tima Maslyuchenko
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Two Way Mapper [](https://travis-ci.org/timsly/two-way-mapper)
|
2
|
+
|
3
|
+
Two way data mapping
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'two-way-mapper'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install two-way-mapper
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
First, we need to define mapping
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
TwoWayMapper.register :customer do |mapping|
|
27
|
+
mapping.left :object # set left plugin to object
|
28
|
+
mapping.right :hash, stringify_keys: true # set right plugin to hash
|
29
|
+
|
30
|
+
# define transformation rules
|
31
|
+
mapping.rule 'first_name', 'FirstName'
|
32
|
+
mapping.rule 'last_name', 'LastName'
|
33
|
+
mapping.rule 'gender', 'sex',
|
34
|
+
map: {
|
35
|
+
'M' => 'male',
|
36
|
+
'F' => 'female'
|
37
|
+
}, default: ''
|
38
|
+
end
|
39
|
+
```
|
40
|
+
|
41
|
+
Once mapping is defined we can convert one object to another and vice versa
|
42
|
+
|
43
|
+
```ruby
|
44
|
+
Customer = Struct.new :first_name, :last_name, :gender
|
45
|
+
|
46
|
+
customer = Customer.new
|
47
|
+
api_response = { 'FirstName' => 'Evee', 'LastName' => 'Fjord', 'sex' => 'female' }
|
48
|
+
|
49
|
+
TwoWayMapper[:customer].from_right_to_left customer, api_response
|
50
|
+
puts customer.first_name # => 'Evee'
|
51
|
+
puts customer.last_name # => 'Fjord'
|
52
|
+
puts customer.gender # => 'F'
|
53
|
+
|
54
|
+
request_data = {}
|
55
|
+
|
56
|
+
another_customer = Customer.new
|
57
|
+
another_customer.first_name = 'Step'
|
58
|
+
another_customer.last_name = 'Bander'
|
59
|
+
another_customer.gender = 'M'
|
60
|
+
|
61
|
+
TwoWayMapper[:customer].from_left_to_right another_customer, request_data
|
62
|
+
puts request_data # => { 'FirstName' => 'Step', 'LastName' => 'Bander', sex: 'male' }
|
63
|
+
```
|
64
|
+
|
65
|
+
On rails, you can put all mappings into `app/mappings` folder
|
66
|
+
|
67
|
+
### Available plugins
|
68
|
+
|
69
|
+
* hash
|
70
|
+
* object
|
71
|
+
* active_record (same as object, but for keys like `user.email`, it will try to build `user` before updating `email` on write)
|
72
|
+
|
73
|
+
## Contributing
|
74
|
+
|
75
|
+
1. Fork it
|
76
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
77
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
78
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
79
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'two_way_mapper'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'active_support/dependencies/autoload'
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
require 'active_support/inflector'
|
4
|
+
|
5
|
+
module TwoWayMapper
|
6
|
+
extend ActiveSupport::Autoload
|
7
|
+
|
8
|
+
autoload :Map
|
9
|
+
autoload :Mapping
|
10
|
+
autoload :Rule
|
11
|
+
autoload :Node
|
12
|
+
autoload :Tools
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def map
|
16
|
+
@map ||= Map.new
|
17
|
+
end
|
18
|
+
|
19
|
+
delegate :register, to: :map
|
20
|
+
delegate :[], to: :map
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
require 'two_way_mapper/railtie' if defined? Rails
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
class Mapping
|
3
|
+
attr_reader :rules, :left_class, :left_options, :right_class, :right_options
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@rules = []
|
7
|
+
end
|
8
|
+
|
9
|
+
[:left, :right].each do |method|
|
10
|
+
class_eval <<-CODE, __FILE__, __LINE__ + 1
|
11
|
+
def #{method}(plugin, options = {})
|
12
|
+
@#{method}_class = node_class plugin
|
13
|
+
@#{method}_options = options
|
14
|
+
end
|
15
|
+
CODE
|
16
|
+
end
|
17
|
+
|
18
|
+
def node_class(plugin)
|
19
|
+
TwoWayMapper::Node.const_get plugin.to_s.camelize
|
20
|
+
rescue NameError
|
21
|
+
raise NameError, 'Cannot find node'
|
22
|
+
end
|
23
|
+
|
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
|
27
|
+
|
28
|
+
opt = options.dup
|
29
|
+
left_opt = opt.delete(:left) || {}
|
30
|
+
right_opt = opt.delete(:right) || {}
|
31
|
+
|
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
|
39
|
+
end
|
40
|
+
|
41
|
+
left = left_class.new left_selector, left_options.merge(left_opt)
|
42
|
+
right = right_class.new right_selector, right_options.merge(right_opt)
|
43
|
+
|
44
|
+
@rules << Rule.new(left, right, opt)
|
45
|
+
end
|
46
|
+
|
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
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
module Node
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :Abstract, 'two_way_mapper/node/abstract'
|
6
|
+
autoload :Hash, 'two_way_mapper/node/hash'
|
7
|
+
autoload :Object, 'two_way_mapper/node/object'
|
8
|
+
autoload :ActiveRecord, 'two_way_mapper/node/active_record'
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
module Node
|
3
|
+
class Abstract
|
4
|
+
DIVIDER = '.'
|
5
|
+
|
6
|
+
attr_accessor :selector, :options
|
7
|
+
|
8
|
+
def initialize(selector, options = {})
|
9
|
+
@selector, @options = selector, options
|
10
|
+
end
|
11
|
+
|
12
|
+
def keys(&block)
|
13
|
+
unless block
|
14
|
+
block = options[:stringify_keys] ? :to_s : :to_sym
|
15
|
+
block = block.to_proc
|
16
|
+
end
|
17
|
+
selector.split(DIVIDER).map &block
|
18
|
+
end
|
19
|
+
|
20
|
+
def read(source)
|
21
|
+
rewind_forward(source) { |obj, key| return nil }
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(obj, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def writable?(current_value, new_value)
|
28
|
+
!options[:write_if] ||
|
29
|
+
!options[:write_if].respond_to?(:call) ||
|
30
|
+
options[:write_if].call(current_value, new_value)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def rewind_forward(obj, margin = 0)
|
36
|
+
to = -(1 + margin.to_i.abs)
|
37
|
+
keys[0..to].each do |key|
|
38
|
+
unless rewind_to?(obj, key)
|
39
|
+
if block_given?
|
40
|
+
yield obj, key
|
41
|
+
else
|
42
|
+
create_node obj, key
|
43
|
+
end
|
44
|
+
end
|
45
|
+
obj = next_key obj, key
|
46
|
+
end
|
47
|
+
|
48
|
+
obj
|
49
|
+
end
|
50
|
+
|
51
|
+
def rewind_to?(obj, key)
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_node(obj, key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def next_key(obj, key)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
module Node
|
3
|
+
class Hash < Abstract
|
4
|
+
def write(source, value)
|
5
|
+
rewinded = rewind_forward source, 1
|
6
|
+
|
7
|
+
rewinded[keys.last] = value if writable? rewinded[keys.last], value
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def rewind_to?(obj, key)
|
13
|
+
obj.is_a?(::Hash) && obj.key?(key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_node(obj, key)
|
17
|
+
obj[key] = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def next_key(obj, key)
|
21
|
+
obj[key]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
module Node
|
3
|
+
class Object < Abstract
|
4
|
+
def write(source, value)
|
5
|
+
rewinded = rewind_forward source, 1
|
6
|
+
|
7
|
+
rewinded.send "#{keys.last}=", value if writable? rewinded.send(keys.last), value
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def rewind_to?(obj, key)
|
13
|
+
obj.respond_to? key
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_node(obj, key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_key(obj, key)
|
20
|
+
obj.send key
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module TwoWayMapper
|
2
|
+
class Rule
|
3
|
+
attr_reader :left, :right
|
4
|
+
|
5
|
+
def initialize(left, right, opt = {})
|
6
|
+
@left = left
|
7
|
+
@right = right
|
8
|
+
@options = opt
|
9
|
+
end
|
10
|
+
|
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
|
20
|
+
|
21
|
+
#{to}_obj
|
22
|
+
end
|
23
|
+
CODE
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def map_value(value, left_to_right = true)
|
29
|
+
map = @options[:map]
|
30
|
+
if map && map.is_a?(Hash)
|
31
|
+
map = map.invert unless left_to_right
|
32
|
+
default_key = "default_#{left_to_right ? 'left' : 'right'}".to_sym
|
33
|
+
value = map[value] || @options[default_key] || @options[:default]
|
34
|
+
else
|
35
|
+
value
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/map_spec.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
describe TwoWayMapper::Map do
|
2
|
+
describe '#register' do
|
3
|
+
let(:map) { TwoWayMapper::Map.new }
|
4
|
+
|
5
|
+
it 'should register new mapping' do
|
6
|
+
map.register :import
|
7
|
+
|
8
|
+
mapping = map[:import]
|
9
|
+
expect(mapping).to be_instance_of TwoWayMapper::Mapping
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
describe TwoWayMapper::Mapping do
|
2
|
+
[:left, :right].each do |method|
|
3
|
+
describe "##{method}" do
|
4
|
+
let(:mapping) { TwoWayMapper::Mapping.new }
|
5
|
+
|
6
|
+
it "should set #{method} with options" do
|
7
|
+
mapping.send method, :object, opt1: ''
|
8
|
+
|
9
|
+
expect(mapping.send("#{method}_class")).to eql TwoWayMapper::Node::Object
|
10
|
+
expect(mapping.send("#{method}_options")).to include opt1: ''
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#rule' do
|
16
|
+
let(:mapping) { TwoWayMapper::Mapping.new }
|
17
|
+
before :each do
|
18
|
+
mapping.left :object
|
19
|
+
mapping.right :hash
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'left and right validation' do
|
23
|
+
let(:mapping_without_both) { TwoWayMapper::Mapping.new }
|
24
|
+
let(:mapping_without_left) { TwoWayMapper::Mapping.new }
|
25
|
+
let(:mapping_without_right) { TwoWayMapper::Mapping.new }
|
26
|
+
before :each do
|
27
|
+
mapping_without_left.right :hash
|
28
|
+
mapping_without_right.left :hash
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should raise error when no left or right nodes' do
|
32
|
+
expect{mapping_without_left.rule 'key', 'key'}.to raise_error
|
33
|
+
expect{mapping_without_right.rule 'key', 'key'}.to raise_error
|
34
|
+
expect{mapping_without_both.rule 'key', 'key'}.to raise_error
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should add item to rules hash' do
|
39
|
+
expect{mapping.rule 'key1', 'Key1'}.to change{mapping.rules.count}.from(0).to(1)
|
40
|
+
|
41
|
+
rule = mapping.rules.first
|
42
|
+
expect(rule).to be_instance_of TwoWayMapper::Rule
|
43
|
+
expect(rule.left.selector).to eql 'key1'
|
44
|
+
expect(rule.right.selector).to eql 'Key1'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should allow to pass hash' do
|
48
|
+
expect{mapping.rule 'key1' => { opt1: 'val' }}.to raise_error
|
49
|
+
|
50
|
+
mapping.rule 'key1' => { opt1: 'val' }, 'Key2' => {}
|
51
|
+
rule = mapping.rules.first
|
52
|
+
|
53
|
+
expect(rule.left.selector).to eql 'key1'
|
54
|
+
expect(rule.right.selector).to eql 'Key2'
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should allow to pass left abd right options ' do
|
58
|
+
mapping.rule 'key1', 'Key2', left: { opt1: 'val' }, right: { opt2: 'val' }
|
59
|
+
rule = mapping.rules.first
|
60
|
+
|
61
|
+
expect(rule.left.options).to include opt1: 'val'
|
62
|
+
expect(rule.right.options).to include opt2: 'val'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should work with options copy' do
|
66
|
+
options = { left: { opt1: 'val' }, right: { opt2: 'val' } }
|
67
|
+
mapping.rule 'key1', 'Key2', options
|
68
|
+
|
69
|
+
expect(options).to eql left: { opt1: 'val' }, right: { opt2: 'val' }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'convertion methods' do
|
74
|
+
let(:mapping) { TwoWayMapper::Mapping.new }
|
75
|
+
let(:rule1) { double from_left_to_right: nil, from_right_to_left: nil }
|
76
|
+
let(:rule2) { double from_left_to_right: nil, from_right_to_left: nil }
|
77
|
+
let(:left_obj) { double() }
|
78
|
+
let(:right_obj) { double() }
|
79
|
+
before :each do
|
80
|
+
mapping.left :object
|
81
|
+
mapping.right :hash
|
82
|
+
allow(mapping).to receive(:rules).and_return [rule1, rule2]
|
83
|
+
end
|
84
|
+
|
85
|
+
[:from_left_to_right, :from_right_to_left].each do |method|
|
86
|
+
describe "##{method}" do
|
87
|
+
it 'should proxy to all rules' do
|
88
|
+
expect(rule1).to receive(method).with left_obj, right_obj
|
89
|
+
expect(rule2).to receive(method).with left_obj, right_obj
|
90
|
+
|
91
|
+
mapping.send method, left_obj, right_obj
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
describe TwoWayMapper::Node::Abstract do
|
2
|
+
describe '#keys' do
|
3
|
+
subject { TwoWayMapper::Node::Abstract.new 'key1.key11.key111' }
|
4
|
+
|
5
|
+
its(:keys) { should eql [:key1, :key11, :key111] }
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'writable?' do
|
9
|
+
it 'should be truthy if write_if options not set' do
|
10
|
+
node = TwoWayMapper::Node::Abstract.new 'key1.key11.key111'
|
11
|
+
|
12
|
+
expect(node).to be_writable 'current', 'new'
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should be truthy if write_if option set' do
|
16
|
+
node = TwoWayMapper::Node::Abstract.new 'key1.key11.key111', write_if: ->(c, n) { c == 'current' || n == 'new' }
|
17
|
+
|
18
|
+
expect(node).to be_writable 'current', 'new1'
|
19
|
+
expect(node).to be_writable 'current1', 'new'
|
20
|
+
expect(node).not_to be_writable 'current1', 'new1'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
describe TwoWayMapper::Node::ActiveRecord do
|
2
|
+
let(:node) { TwoWayMapper::Node::ActiveRecord.new 'user.email' }
|
3
|
+
|
4
|
+
describe '#write' do
|
5
|
+
it 'should try to build before write' do
|
6
|
+
user = double(email: '')
|
7
|
+
obj = double()
|
8
|
+
allow(obj).to receive :build_user do
|
9
|
+
allow(obj).to receive(:user).and_return user
|
10
|
+
end
|
11
|
+
|
12
|
+
expect(obj).to receive :build_user
|
13
|
+
expect(user).to receive(:email=).with 'test@email.com'
|
14
|
+
|
15
|
+
node.write obj, 'test@email.com'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should try to build even if respond_to but obj itself is nil' do
|
19
|
+
user = double(email: '')
|
20
|
+
obj = double(user: nil)
|
21
|
+
allow(obj).to receive :build_user do
|
22
|
+
allow(obj).to receive(:user).and_return user
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(obj).to receive :build_user
|
26
|
+
expect(user).to receive(:email=).with 'test@email.com'
|
27
|
+
|
28
|
+
node.write obj, 'test@email.com'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
describe TwoWayMapper::Node::Hash do
|
2
|
+
context 'normal keys' do
|
3
|
+
let(:node) { TwoWayMapper::Node::Hash.new 'key1.key11.key111' }
|
4
|
+
|
5
|
+
describe '#read' do
|
6
|
+
it 'should return nil when path is not avaiable' do
|
7
|
+
expect(node.read({})).to be_nil
|
8
|
+
expect(node.read(key1: 1)).to be_nil
|
9
|
+
expect(node.read(key1: { key11: 1 })).to be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should read from passed object' do
|
13
|
+
expect(node.read(key1: { key11: { key111: 'value' } })).to eql 'value'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#write' do
|
18
|
+
let(:obj) { {} }
|
19
|
+
|
20
|
+
it 'should write by path' do
|
21
|
+
node.write obj, 'value'
|
22
|
+
expect(obj).to eql key1: { key11: { key111: 'value' } }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'string keys' do
|
28
|
+
let(:node) { TwoWayMapper::Node::Hash.new 'key1.key11.key111', stringify_keys: true }
|
29
|
+
|
30
|
+
describe '#read' do
|
31
|
+
it 'should return nil when path is not avaiable' do
|
32
|
+
expect(node.read({})).to be_nil
|
33
|
+
expect(node.read('key1' => 1)).to be_nil
|
34
|
+
expect(node.read('key1' => { 'key11' => 1 })).to be_nil
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should read from passed object' do
|
38
|
+
expect(node.read('key1' => { 'key11' => { 'key111' => 'value' } })).to eql 'value'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#write' do
|
43
|
+
let(:obj) { {} }
|
44
|
+
|
45
|
+
it 'should write by path' do
|
46
|
+
node.write obj, 'value'
|
47
|
+
expect(obj).to eql 'key1' => { 'key11' => { 'key111' => 'value' } }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'write_if option' do
|
53
|
+
let(:node) { TwoWayMapper::Node::Hash.new 'key1.key11', write_if: ->(c, n) { c.empty? || n == 'value1' } }
|
54
|
+
let(:writable_obj1) { { key1: { key11: '' } } }
|
55
|
+
let(:writable_obj2) { { key1: { key11: 'smth' } } }
|
56
|
+
let(:not_writable_obj) { { key1: { key11: 'smth' } } }
|
57
|
+
|
58
|
+
it 'should not write if such option passed and it satisfy condition' do
|
59
|
+
node.write writable_obj1, 'value'
|
60
|
+
node.write writable_obj2, 'value1'
|
61
|
+
node.write not_writable_obj, 'value'
|
62
|
+
|
63
|
+
expect(writable_obj1[:key1][:key11]).to eql 'value'
|
64
|
+
expect(writable_obj2[:key1][:key11]).to eql 'value1'
|
65
|
+
expect(not_writable_obj[:key1][:key11]).not_to eql 'value'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
describe TwoWayMapper::Node::Object do
|
2
|
+
let(:node) { TwoWayMapper::Node::Object.new 'key1.key11.key111' }
|
3
|
+
|
4
|
+
describe '#read' do
|
5
|
+
it 'should return nil when path is not avaiable' do
|
6
|
+
expect(node.read(OpenStruct.new)).to be_nil
|
7
|
+
expect(node.read(OpenStruct.new(key1: 1))).to be_nil
|
8
|
+
expect(node.read(OpenStruct.new(key1: OpenStruct.new(key11: 1)))).to be_nil
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should read from passed object' do
|
12
|
+
obj = OpenStruct.new(key1: OpenStruct.new(key11: OpenStruct.new(key111: 'value')))
|
13
|
+
|
14
|
+
expect(node.read(obj)).to eql 'value'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#write' do
|
19
|
+
let(:obj) { OpenStruct.new(key1: OpenStruct.new(key11: OpenStruct.new(key111: nil))) }
|
20
|
+
|
21
|
+
it 'should write by path' do
|
22
|
+
node.write obj, 'value'
|
23
|
+
expect(obj.key1.key11.key111).to eql 'value'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'write_if option' do
|
28
|
+
let(:node) { TwoWayMapper::Node::Object.new 'key', write_if: ->(c, n) { c.empty? || n == 'value1'} }
|
29
|
+
let(:writable_obj1) { OpenStruct.new key: '' }
|
30
|
+
let(:writable_obj2) { OpenStruct.new key: 'smth' }
|
31
|
+
let(:not_writable_obj) { OpenStruct.new key: 'smth' }
|
32
|
+
|
33
|
+
it 'should not write if such option passed and it satisfy condition' do
|
34
|
+
node.write writable_obj1, 'value'
|
35
|
+
node.write writable_obj2, 'value1'
|
36
|
+
node.write not_writable_obj, 'value'
|
37
|
+
|
38
|
+
expect(writable_obj1.key).to eql 'value'
|
39
|
+
expect(writable_obj2.key).to eql 'value1'
|
40
|
+
expect(not_writable_obj.key).not_to eql 'value'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/spec/rule_spec.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
describe TwoWayMapper::Rule do
|
2
|
+
context 'transformation methods' do
|
3
|
+
let(:left_node) { TwoWayMapper::Node::Object.new 'key1' }
|
4
|
+
let(:right_node) { TwoWayMapper::Node::Hash.new 'Kk.Key1' }
|
5
|
+
let(:left_object) { OpenStruct.new }
|
6
|
+
let(:map) { { 'value' => 'VALUE' } }
|
7
|
+
|
8
|
+
context 'without options' do
|
9
|
+
let(:rule) { TwoWayMapper::Rule.new left_node, right_node }
|
10
|
+
|
11
|
+
describe '#from_left_to_right' do
|
12
|
+
it 'should read from left node and write to right node' do
|
13
|
+
left_object.key1 = 'value1'
|
14
|
+
right_object = {}
|
15
|
+
rule.from_left_to_right left_object, right_object
|
16
|
+
|
17
|
+
expect(right_object).to eql Kk: { Key1: 'value1' }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#from_right_to_left' do
|
22
|
+
it 'should read from right node and write to left node' do
|
23
|
+
left_object.key1 = nil
|
24
|
+
right_object = { Kk: { Key1: 'value1' } }
|
25
|
+
rule.from_right_to_left left_object, right_object
|
26
|
+
|
27
|
+
expect(left_object.key1).to eql 'value1'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'with map option' do
|
33
|
+
let(:rule) { TwoWayMapper::Rule.new left_node, right_node, map: map, default: 'not found' }
|
34
|
+
|
35
|
+
describe '#from_left_to_right' do
|
36
|
+
it 'should read from left node and write to right node' do
|
37
|
+
left_object.key1 = 'value'
|
38
|
+
right_object = {}
|
39
|
+
rule.from_left_to_right left_object, right_object
|
40
|
+
|
41
|
+
expect(right_object).to eql Kk: { Key1: 'VALUE' }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#from_right_to_left' do
|
46
|
+
it 'should read from right node and write to left node' do
|
47
|
+
left_object.key1 = nil
|
48
|
+
right_object = { Kk: { Key1: 'VALUE' } }
|
49
|
+
rule.from_right_to_left left_object, right_object
|
50
|
+
|
51
|
+
expect(left_object.key1).to eql 'value'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with default option' do
|
57
|
+
describe '#from_left_to_right' do
|
58
|
+
it 'should return default value if not found' do
|
59
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, map: map, default: 'not found'
|
60
|
+
|
61
|
+
left_object.key1 = 'value1'
|
62
|
+
right_object = {}
|
63
|
+
rule.from_left_to_right left_object, right_object
|
64
|
+
|
65
|
+
expect(right_object).to eql Kk: { Key1: 'not found' }
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should return use default_left if present and value not found' do
|
69
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, map: map, default: 'not found', default_left: 'not found on left', default_right: 'not found on right'
|
70
|
+
|
71
|
+
left_object.key1 = 'value1'
|
72
|
+
right_object = {}
|
73
|
+
rule.from_left_to_right left_object, right_object
|
74
|
+
|
75
|
+
expect(right_object).to eql Kk: { Key1: 'not found on left' }
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#from_right_to_left' do
|
80
|
+
it 'should return default value if not found' do
|
81
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, map: map, default: 'not found'
|
82
|
+
|
83
|
+
left_object.key1 = nil
|
84
|
+
right_object = { Kk: { Key1: 'VALUE1' } }
|
85
|
+
rule.from_right_to_left left_object, right_object
|
86
|
+
|
87
|
+
expect(left_object.key1).to eql 'not found'
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'should return use default_right if present and value not found' do
|
91
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, map: map, default: 'not found', default_left: 'not found on left', default_right: 'not found on right'
|
92
|
+
|
93
|
+
left_object.key1 = nil
|
94
|
+
right_object = { Kk: { Key1: 'VALUE1' } }
|
95
|
+
rule.from_right_to_left left_object, right_object
|
96
|
+
|
97
|
+
expect(left_object.key1).to eql 'not found on right'
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'with callback option' do
|
103
|
+
describe 'on_left_to_right' do
|
104
|
+
it 'should transform value if such options passed' do
|
105
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, on_left_to_right: ->(v) { v.upcase }
|
106
|
+
|
107
|
+
left_object.key1 = 'value1'
|
108
|
+
right_object = {}
|
109
|
+
rule.from_left_to_right left_object, right_object
|
110
|
+
|
111
|
+
expect(right_object).to eql Kk: { Key1: 'VALUE1' }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'on_right_to_left' do
|
116
|
+
it 'should transform value if such options passed' do
|
117
|
+
rule = TwoWayMapper::Rule.new left_node, right_node, on_right_to_left: ->(v) { v.downcase }
|
118
|
+
|
119
|
+
left_object.key1 = nil
|
120
|
+
right_object = { Kk: { Key1: 'VALUE1' } }
|
121
|
+
rule.from_right_to_left left_object, right_object
|
122
|
+
|
123
|
+
expect(left_object.key1).to eql 'value1'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
data/spec/spec_helper.rb
ADDED
data/spec/tools_spec.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
describe TwoWayMapper::Tools do
|
2
|
+
let(:tools) { TwoWayMapper::Tools }
|
3
|
+
|
4
|
+
describe '.first_item_from_hash!' do
|
5
|
+
it 'should raise error unless hash passed' do
|
6
|
+
expect{tools.first_item_from_hash!}.to raise_error
|
7
|
+
expect{tools.first_item_from_hash! ''}.to raise_error
|
8
|
+
expect{tools.first_item_from_hash! 1}.to raise_error
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should raise error if emplt hash passed' do
|
12
|
+
expect{tools.first_item_from_hash! {}}.to raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should return first hash key and value' do
|
16
|
+
k, v = tools.first_item_from_hash! a: 1
|
17
|
+
|
18
|
+
expect(k).to eql :a
|
19
|
+
expect(v).to eql 1
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should delete first item' do
|
23
|
+
hash = { a: 1, b: 2 }
|
24
|
+
k, v = tools.first_item_from_hash! hash
|
25
|
+
|
26
|
+
expect(hash).not_to include a: 1
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'two_way_mapper/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "two-way-mapper"
|
8
|
+
spec.version = TwoWayMapper::VERSION
|
9
|
+
spec.authors = ["Tima Maslyuchenko"]
|
10
|
+
spec.email = ["insside@gmail.com"]
|
11
|
+
spec.description = %q{Two way data mapping}
|
12
|
+
spec.summary = %q{Two way data mapping}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
25
|
+
spec.add_development_dependency "rspec-its"
|
26
|
+
spec.add_development_dependency "rake"
|
27
|
+
end
|
metadata
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: two-way-mapper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tima Maslyuchenko
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-its
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Two way data mapping
|
84
|
+
email:
|
85
|
+
- insside@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".rspec"
|
92
|
+
- ".travis.yml"
|
93
|
+
- Gemfile
|
94
|
+
- Gemfile.ci
|
95
|
+
- Guardfile
|
96
|
+
- LICENSE.txt
|
97
|
+
- README.md
|
98
|
+
- Rakefile
|
99
|
+
- lib/two-way-mapper.rb
|
100
|
+
- lib/two_way_mapper.rb
|
101
|
+
- lib/two_way_mapper/map.rb
|
102
|
+
- lib/two_way_mapper/mapping.rb
|
103
|
+
- lib/two_way_mapper/node.rb
|
104
|
+
- lib/two_way_mapper/node/abstract.rb
|
105
|
+
- lib/two_way_mapper/node/active_record.rb
|
106
|
+
- lib/two_way_mapper/node/hash.rb
|
107
|
+
- lib/two_way_mapper/node/object.rb
|
108
|
+
- lib/two_way_mapper/railtie.rb
|
109
|
+
- lib/two_way_mapper/rule.rb
|
110
|
+
- lib/two_way_mapper/tools.rb
|
111
|
+
- lib/two_way_mapper/version.rb
|
112
|
+
- spec/map_spec.rb
|
113
|
+
- spec/mapping_spec.rb
|
114
|
+
- spec/node/abstract_spec.rb
|
115
|
+
- spec/node/active_record_spec.rb
|
116
|
+
- spec/node/hash_spec.rb
|
117
|
+
- spec/node/object_spec.rb
|
118
|
+
- spec/rule_spec.rb
|
119
|
+
- spec/spec_helper.rb
|
120
|
+
- spec/tools_spec.rb
|
121
|
+
- two-way-mapper.gemspec
|
122
|
+
homepage: ''
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
metadata: {}
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - ">="
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '0'
|
140
|
+
requirements: []
|
141
|
+
rubyforge_project:
|
142
|
+
rubygems_version: 2.2.2
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: Two way data mapping
|
146
|
+
test_files:
|
147
|
+
- spec/map_spec.rb
|
148
|
+
- spec/mapping_spec.rb
|
149
|
+
- spec/node/abstract_spec.rb
|
150
|
+
- spec/node/active_record_spec.rb
|
151
|
+
- spec/node/hash_spec.rb
|
152
|
+
- spec/node/object_spec.rb
|
153
|
+
- spec/rule_spec.rb
|
154
|
+
- spec/spec_helper.rb
|
155
|
+
- spec/tools_spec.rb
|