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 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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,16 @@
1
+ before_install:
2
+ - gem install bundler
3
+
4
+ notifications:
5
+ email: false
6
+
7
+ rvm:
8
+ - 1.9.3
9
+ - 2.0.0
10
+ - 2.1.1
11
+ - 2.1.2
12
+ - ruby-head
13
+ - jruby
14
+
15
+ gemfile:
16
+ - Gemfile.ci
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'guard-rspec', '~> 4.3', require: false
6
+ gem 'byebug'
data/Gemfile.ci ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard :rspec, cmd: 'bundle exec rspec' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/two_way_mapper/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
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 [![Build Status](https://travis-ci.org/timsly/two-way-mapper.svg?branch=master)](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,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task default: :spec
@@ -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,15 @@
1
+ module TwoWayMapper
2
+ class Map
3
+ delegate :[], to: :@maps
4
+
5
+ def initialize
6
+ @maps = {}
7
+ end
8
+
9
+ def register(name)
10
+ mapping = TwoWayMapper::Mapping.new
11
+ yield mapping if block_given?
12
+ @maps[name.to_sym] = mapping
13
+ end
14
+ end
15
+ end
@@ -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,13 @@
1
+ module TwoWayMapper
2
+ module Node
3
+ class ActiveRecord < Object
4
+ def rewind_to?(obj, key)
5
+ super && obj.send(key)
6
+ end
7
+
8
+ def create_node(obj, key)
9
+ obj.send "build_#{key}"
10
+ end
11
+ end
12
+ end
13
+ 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,10 @@
1
+ require 'rails'
2
+
3
+ module TwoWayMapper
4
+ class Railtie < Rails::Railtie
5
+ initializer "two_way_mapper.set_load_path" do |app|
6
+ path = Rails.root.join('app', 'mappings', '*.rb').to_s
7
+ Dir[path].each{ |file| load file }
8
+ end
9
+ end
10
+ 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
@@ -0,0 +1,13 @@
1
+ module TwoWayMapper
2
+ module Tools
3
+ class << self
4
+ def first_item_from_hash!(hash)
5
+ raise ArgumentError unless hash.is_a?(Hash)
6
+ raise ArgumentError unless first = hash.first
7
+
8
+ hash.delete first[0]
9
+ first
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module TwoWayMapper
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,11 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+
4
+ require 'two-way-mapper'
5
+
6
+ require 'rspec/its'
7
+ require 'ostruct'
8
+ require 'debugger' if defined? Debugger
9
+
10
+ RSpec.configure do |config|
11
+ end
@@ -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