spine-mappings 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: db444936faae00c79848292520913fe94090e6bb
4
+ data.tar.gz: 77202967f3bb1c391c7ed948ddcc10072efc5242
5
+ SHA512:
6
+ metadata.gz: 8a0d0e05558ec4e7df7f4ab725186f99b5e2d05e88a703471853e88a47630de7497fb80af14f34eea275863b6f82fe1675583e23e1e319653532cb36349d599a
7
+ data.tar.gz: 094f222f822677d16b0499ea781c7f662c07b2c72ceb7a9e024ab2b718b93a1e28f6e900dc47158673e834ae3aafe5cf2c00fc914d0f6d7e2aa7482e588a5055
@@ -0,0 +1,36 @@
1
+ *.gem
2
+ *.rbc
3
+ /.config
4
+ /coverage/
5
+ /InstalledFiles
6
+ /pkg/
7
+ /spec/reports/
8
+ /test/tmp/
9
+ /test/version_tmp/
10
+ /tmp/
11
+
12
+ ## Specific to RubyMotion:
13
+ .dat*
14
+ .repl_history
15
+ build/
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /bin/
25
+ /.bundle/
26
+ /vendor/bundle
27
+ /lib/bundler/man/
28
+
29
+ # for a library or gem, you might want to ignore these files since the code is
30
+ # intended to run in multiple environments; otherwise, check them in:
31
+ # Gemfile.lock
32
+ # .ruby-version
33
+ # .ruby-gemset
34
+
35
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
36
+ .rvmrc
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require config/default
@@ -0,0 +1,6 @@
1
+ Changelog
2
+ =========
3
+
4
+ 0.1.0
5
+ -----
6
+ - First public release
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in spine-mappings.gemspec
4
+ gemspec
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ spine-mappings (0.1.0)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ codeclimate-test-reporter (0.4.7)
10
+ simplecov (>= 0.7.1, < 1.0.0)
11
+ diff-lcs (1.2.5)
12
+ docile (1.1.5)
13
+ json (1.8.2)
14
+ rake (10.4.2)
15
+ rspec (3.2.0)
16
+ rspec-core (~> 3.2.0)
17
+ rspec-expectations (~> 3.2.0)
18
+ rspec-mocks (~> 3.2.0)
19
+ rspec-core (3.2.3)
20
+ rspec-support (~> 3.2.0)
21
+ rspec-expectations (3.2.1)
22
+ diff-lcs (>= 1.2.0, < 2.0)
23
+ rspec-support (~> 3.2.0)
24
+ rspec-mocks (3.2.1)
25
+ diff-lcs (>= 1.2.0, < 2.0)
26
+ rspec-support (~> 3.2.0)
27
+ rspec-support (3.2.2)
28
+ simplecov (0.10.0)
29
+ docile (~> 1.1.0)
30
+ json (~> 1.8)
31
+ simplecov-html (~> 0.10.0)
32
+ simplecov-html (0.10.0)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ bundler (~> 1.7)
39
+ codeclimate-test-reporter (~> 0.4)
40
+ rake (~> 10.0)
41
+ rspec (~> 3.2)
42
+ spine-mappings!
data/LICENSE ADDED
@@ -0,0 +1,12 @@
1
+ Copyright (c) 2015, TOGGL LLC
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
5
+
6
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
7
+
8
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
9
+
10
+ 3. Neither the name of the TOGGL LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,56 @@
1
+ # Spine::Mappings
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/spine-mappings.svg)](http://badge.fury.io/rb/spine-mappings)
4
+ [![Dependency Status](https://gemnasium.com/rspine/mappings.svg)](https://gemnasium.com/rspine/mappings)
5
+ [![Test Coverage](https://codeclimate.com/github/rspine/mappings/badges/coverage.svg)](https://codeclimate.com/github/rspine/mappings/coverage)
6
+ [![Code Climate](https://codeclimate.com/github/rspine/mappings/badges/gpa.svg)](https://codeclimate.com/github/rspine/mappings)
7
+ [![Codeship Status for rspine/mappings](https://codeship.com/projects/90a03900-e41c-0132-2cdb-16773c71d38d/status?branch=master)](https://codeship.com/projects/81788)
8
+
9
+ Maps object to hashes using DSL. You can give different namespaces for mappings
10
+ by extending module or class with `Spine::Mappings::Repository`.
11
+
12
+ ## Installation
13
+
14
+ To install it, add the gem to your Gemfile:
15
+
16
+ ```ruby
17
+ gem 'spine-mappings'
18
+ ```
19
+
20
+ Then run `bundle`. If you're not using Bundler, just `gem install spine-mappings`.
21
+
22
+ ## Usage
23
+
24
+ ```ruby
25
+ Spine::Mappings.define :tag do
26
+ integer :id, from: identity
27
+ string :name
28
+ end
29
+
30
+ Spine::Mappings.define :product do
31
+ integer :id, from: :identity
32
+ string :name
33
+ decimal :price
34
+ boolean :is_available, { |source| source.available? }
35
+ date :available_from
36
+ date :available_until
37
+ tag :tags, nullable: true
38
+ end
39
+
40
+ Spine::Mapings.define :timestamps do
41
+ timestamp :created_at
42
+ timestamp :updated_at
43
+ end
44
+
45
+ Spine::Mappings.find(:product, :timestamps).map(product)
46
+
47
+ Spine::Mappings.find(:product, :timestamps).map_all(products)
48
+ ```
49
+
50
+ You can provide also `comapct` or `strict` strategy for mapping. First adds
51
+ value only when it is not nil, second adds everything what was defined. Default
52
+ is `strict`.
53
+
54
+ ```ruby
55
+ Spine::Mappings.find(:product, :timestamps).map(product, strategy: :compact)
56
+ ```
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,30 @@
1
+ module Spine
2
+ module Mappings
3
+ autoload :Repository, 'spine/mappings/repository'
4
+ autoload :Mapper, 'spine/mappings/mapper'
5
+ autoload :Strategies, 'spine/mappings/strategies'
6
+ autoload :Mapping, 'spine/mappings/mapping'
7
+
8
+ module Syntax
9
+ autoload :Any, 'spine/mappings/syntax/any'
10
+ autoload :String, 'spine/mappings/syntax/string'
11
+ autoload :Integer, 'spine/mappings/syntax/integer'
12
+ autoload :Decimal, 'spine/mappings/syntax/decimal'
13
+ autoload :Boolean, 'spine/mappings/syntax/boolean'
14
+ autoload :Date, 'spine/mappings/syntax/date'
15
+ autoload :DateTime, 'spine/mappings/syntax/datetime'
16
+ autoload :Timestamp, 'spine/mappings/syntax/timestamp'
17
+ autoload :Nested, 'spine/mappings/syntax/nested'
18
+ autoload :Collection, 'spine/mappings/syntax/collection'
19
+ autoload :RegisteredMapper, 'spine/mappings/syntax/registered_mapper'
20
+ end
21
+
22
+ module Commands
23
+ autoload :ActionFetch, 'spine/mappings/commands/action_fetch'
24
+ autoload :Fetch, 'spine/mappings/commands/fetch'
25
+ autoload :Intercept, 'spine/mappings/commands/intercept'
26
+ end
27
+
28
+ extend Repository
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Commands
4
+ class ActionFetch
5
+ attr_reader :action
6
+
7
+ def initialize(action)
8
+ @action = action
9
+ end
10
+
11
+ def execute(source)
12
+ action.call(source)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ module Spine
2
+ module Mappings
3
+ module Commands
4
+ class Fetch
5
+ attr_reader :method_name
6
+
7
+ def initialize(method_name)
8
+ @method_name = method_name
9
+ end
10
+
11
+ def execute(source)
12
+ if source.respond_to?(method_name)
13
+ source.public_send(method_name)
14
+ elsif source.respond_to?('[]')
15
+ source[method_name]
16
+ else
17
+ raise "Source object don't have #{ method_name } method defined."
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Spine
2
+ module Mappings
3
+ module Commands
4
+ class Intercept
5
+ attr_reader :command, :action, :options
6
+
7
+ def initialize(command, action, options = {})
8
+ @command = command
9
+ @options = options
10
+ @action = action
11
+ end
12
+
13
+ def execute(source)
14
+ value = command.execute(source) || options[:default]
15
+ if value || !options.fetch(:nullable, false)
16
+ action.call(value)
17
+ else
18
+ value
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Spine
2
+ module Mappings
3
+ class Mapper
4
+ attr_reader :mappings
5
+
6
+ def initialize(mappings)
7
+ @mappings = mappings
8
+ end
9
+
10
+ def map(source, options = {})
11
+ return nil unless source
12
+
13
+ strategy = Strategies.find(options[:strategy])
14
+ mappings.reduce({}) do |mapped, mapping|
15
+ strategy.run(mapping.commands, source, mapped)
16
+ end
17
+ end
18
+
19
+ def map_all(sources, options = {})
20
+ return nil unless sources || sources.empty?
21
+
22
+ sources.map { |source| map(source, options) }
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,40 @@
1
+ module Spine
2
+ module Mappings
3
+ class Mapping
4
+ include Syntax::Any
5
+ include Syntax::String
6
+ include Syntax::Integer
7
+ include Syntax::Decimal
8
+ include Syntax::Boolean
9
+ include Syntax::Date
10
+ include Syntax::DateTime
11
+ include Syntax::Timestamp
12
+ include Syntax::Nested
13
+ include Syntax::Collection
14
+ include Syntax::RegisteredMapper
15
+
16
+ attr_reader :commands, :repository
17
+
18
+ def initialize(repository)
19
+ @repository = repository
20
+ @commands = {}
21
+ end
22
+
23
+ private
24
+
25
+ def register(destination, command)
26
+ @commands[destination] = command
27
+ end
28
+
29
+ def intercept(command, options, &block)
30
+ Commands::Intercept.new(command, block, options)
31
+ end
32
+
33
+ def build(destination, options, &block)
34
+ return Commands::ActionFetch.new(block) if block_given?
35
+
36
+ Commands::Fetch.new(options.fetch(:from, destination))
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ module Spine
2
+ module Mappings
3
+ # Maps objects to hash.
4
+ #
5
+ # == Types
6
+ # * any - Value as is.
7
+ # * integer - Integer, default value is 0.
8
+ # * string - String, default value is ''.
9
+ # * decimal - Floating point number, default value is 0.0.
10
+ # * date - Date in ISO 8601 format (YYYY-MM-DD).
11
+ # * datetime - Date and time in ISO 8601 format (YYYY-MM-DDTHH:MM:SSZ).
12
+ # * timestamp - Date and time with milliseconds in ISO 8601 format
13
+ # (YYYY-MM-DDTHH:MM:SS.000Z).
14
+ # * boolean - Boolean.
15
+ #
16
+ # Also you can use other serializers by name. They must be registered
17
+ # before usage.
18
+ #
19
+ # == Options
20
+ # * from - Method name for source object. Also you can use closure to fetch
21
+ # value
22
+ # * nullable - Allows value to be nil.
23
+ # * default - Default value.
24
+ #
25
+ # == Examples
26
+ #
27
+ # Serialization.define :tag do
28
+ # integer :id, from: identity
29
+ # string :name
30
+ # end
31
+ #
32
+ # Serialization.define :product do
33
+ # integer :id, from: identity
34
+ # string :name
35
+ # decimal :price
36
+ # boolean :is_available, { |source| source.available? }
37
+ # date :available_from
38
+ # date :avaialable_until
39
+ # tag :tags, nullable: true
40
+ # timestamp :created_at
41
+ # timestamp :updated_at
42
+ # end
43
+ #
44
+ # serialized_product = Serialization.serialize(:product, product)
45
+ # serialized_products = Serialization.serialize_all(:product, products)
46
+ module Repository
47
+ def define(name, &block)
48
+ mappings[name] = parse(block)
49
+ end
50
+
51
+ def find(*names)
52
+ found_mappings = names.map { |name| mappings.fetch(name) }
53
+
54
+ if found_mappings.empty?
55
+ nil
56
+ else
57
+ Mapper.new(found_mappings)
58
+ end
59
+ end
60
+
61
+ def mappings
62
+ @mappings ||= {}
63
+ end
64
+
65
+ def parse(dsl)
66
+ mapping = Mapping.new(self)
67
+ mapping.instance_eval(&dsl)
68
+ mapping
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,28 @@
1
+ module Spine
2
+ module Mappings
3
+ module Strategies
4
+ extend self
5
+
6
+ autoload :Strict, 'spine/mappings/strategies/strict'
7
+ autoload :Compact, 'spine/mappings/strategies/compact'
8
+
9
+ def collection
10
+ @collection ||= default_collection
11
+ end
12
+
13
+ def find(identifier)
14
+ collection[identifier] || collection[default_identifier]
15
+ end
16
+
17
+ private
18
+
19
+ def default_identifier
20
+ :strict
21
+ end
22
+
23
+ def default_collection
24
+ { strict: Strict, compact: Compact }
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Strategies
4
+ module Compact
5
+ extend self
6
+
7
+ def run(commands, source, destination = {})
8
+ commands.reduce(destination) do |result, (key, command)|
9
+ value = command.execute(source)
10
+ result[key] = value if value
11
+ result
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,16 @@
1
+ module Spine
2
+ module Mappings
3
+ module Strategies
4
+ module Strict
5
+ extend self
6
+
7
+ def run(commands, source, destination = {})
8
+ commands.reduce(destination) do |result, (key, command)|
9
+ result[key] = command.execute(source)
10
+ result
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Any
5
+ def any(destination, options = {}, &block)
6
+ register(destination, build(destination, options, &block))
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Boolean
5
+ def boolean(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value| !!value }
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Collection
5
+ def collection(type, destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) {
10
+ |value| repository.find(type).map_all(value)
11
+ }
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Date
5
+ def date(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) {
10
+ |value| value.to_date.iso8601
11
+ }
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module DateTime
5
+ def datetime(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value|
10
+ value.to_datetime.iso8601
11
+ }
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Decimal
5
+ def decimal(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value| value.to_f }
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Integer
5
+ def integer(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value| value.to_i }
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Nested
5
+ def nested(destination, options = {}, &block)
6
+ mapping = repository.parse(block)
7
+ mapper = Mapper.new([mapping])
8
+ command = build(destination, options)
9
+ register(
10
+ destination,
11
+ intercept(command, options) { |value| mapper.map(value, options) }
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module RegisteredMapper
5
+ def method_missing(name, *arguments, &block)
6
+ mapper = repository.find(name.to_sym)
7
+ super and return unless mapper
8
+
9
+ destination = arguments.shift
10
+ options = arguments.shift || {}
11
+ command = build(destination, options, &block)
12
+ register(
13
+ destination,
14
+ intercept(command, options) { |value| mapper.map(value) }
15
+ )
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module String
5
+ def string(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value| value.to_s }
10
+ )
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ module Spine
2
+ module Mappings
3
+ module Syntax
4
+ module Timestamp
5
+ def timestamp(destination, options = {}, &block)
6
+ command = build(destination, options, &block)
7
+ register(
8
+ destination,
9
+ intercept(command, options) { |value|
10
+ value.to_datetime.iso8601(3)
11
+ }
12
+ )
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ module Spine
2
+ module Mappings
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ require './spec/config/default'
2
+
3
+ require 'codeclimate-test-reporter'
4
+ CodeClimate::TestReporter.start
@@ -0,0 +1,20 @@
1
+ RSpec.configure do |config|
2
+ config.filter_run :focus
3
+ config.run_all_when_everything_filtered = true
4
+
5
+ if config.files_to_run.one?
6
+ config.default_formatter = 'doc'
7
+ end
8
+
9
+ config.order = :random
10
+
11
+ Kernel.srand config.seed
12
+
13
+ config.expect_with :rspec do |expectations|
14
+ expectations.syntax = :expect
15
+ end
16
+
17
+ config.mock_with :rspec do |mocks|
18
+ mocks.syntax = :expect
19
+ end
20
+ end
@@ -0,0 +1,263 @@
1
+ require 'ostruct'
2
+ require 'date'
3
+ require 'spine/mappings'
4
+
5
+ module Spine
6
+ module Mappings
7
+ describe Mappings do
8
+ subject { Mappings }
9
+
10
+ it 'defines empty mapper' do
11
+ subject.define(:test) {}
12
+ expect(subject.find(:test)).to be
13
+ end
14
+
15
+ it 'maps data by coping attribute with same name' do
16
+ subject.define :test do
17
+ any :id
18
+ end
19
+
20
+ data = OpenStruct.new(id: 1)
21
+ mapped = subject.find(:test).map(data)
22
+
23
+ expect(mapped[:id]).to eq(1)
24
+ end
25
+
26
+ it 'maps data by coping attribute with different name' do
27
+ subject.define :test do
28
+ any :value, from: :test_value
29
+ end
30
+
31
+ data = OpenStruct.new(test_value: 1)
32
+ mapped = subject.find(:test).map(data)
33
+
34
+ expect(mapped[:value]).to eq(1)
35
+ end
36
+
37
+ it 'maps data by coping attribute from closure' do
38
+ subject.define :test do
39
+ any(:value) { |source| source.test_value }
40
+ end
41
+
42
+ data = OpenStruct.new(test_value: 1)
43
+ mapped = subject.find(:test).map(data)
44
+
45
+ expect(mapped[:value]).to eq(1)
46
+ end
47
+
48
+ it 'maps empty data as nil' do
49
+ subject.define(:test) do
50
+ any :value
51
+ end
52
+
53
+ data = OpenStruct.new(value: nil)
54
+ mapped = subject.find(:test).map(data)
55
+
56
+ expect(mapped[:value]).to eq(nil)
57
+ end
58
+
59
+ it 'maps data with empty mapper' do
60
+ subject.define(:test) {}
61
+ expect(subject.find(:test).map(Object.new)).to eq({})
62
+ end
63
+
64
+ it 'maps hash to hash' do
65
+ subject.define(:test) do
66
+ any :x, from: :y
67
+ end
68
+
69
+ expect(subject.find(:test).map(y: 1)).to eq(x: 1)
70
+ end
71
+
72
+ describe 'when using interceptors' do
73
+ it 'maps data by converting it to string' do
74
+ subject.define :test do
75
+ string :value
76
+ end
77
+
78
+ data = OpenStruct.new(value: 1)
79
+ mapped = subject.find(:test).map(data)
80
+
81
+ expect(mapped[:value]).to eq('1')
82
+ end
83
+
84
+ it 'maps data by converting it to integer' do
85
+ subject.define :test do
86
+ integer :value
87
+ end
88
+
89
+ data = OpenStruct.new(value: "1")
90
+ mapped = subject.find(:test).map(data)
91
+
92
+ expect(mapped[:value]).to eq(1)
93
+ end
94
+
95
+ it 'maps nil data when nullable' do
96
+ subject.define :test do
97
+ integer :value, nullable: true
98
+ end
99
+
100
+ data = OpenStruct.new(value: nil)
101
+ mapped = subject.find(:test).map(data)
102
+
103
+ expect(mapped[:value]).to eq(nil)
104
+ end
105
+
106
+ it 'maps nil data to default' do
107
+ subject.define :test do
108
+ integer :value, default: 1
109
+ end
110
+
111
+ data = OpenStruct.new(value: nil)
112
+ mapped = subject.find(:test).map(data)
113
+
114
+ expect(mapped[:value]).to eq(1)
115
+ end
116
+
117
+ it 'maps data by converting it to decimal' do
118
+ subject.define :test do
119
+ decimal :value
120
+ end
121
+
122
+ data = OpenStruct.new(value: "1.5")
123
+ mapped = subject.find(:test).map(data)
124
+
125
+ expect(mapped[:value]).to eq(1.5)
126
+ end
127
+
128
+ it 'maps data by converting it to boolean' do
129
+ subject.define :test do
130
+ boolean :value
131
+ end
132
+
133
+ data = OpenStruct.new(value: '1')
134
+ mapped = subject.find(:test).map(data)
135
+
136
+ expect(mapped[:value]).to eq(true)
137
+ end
138
+
139
+ it 'maps data by converting it to date' do
140
+ subject.define :test do
141
+ date :value
142
+ end
143
+
144
+ value = Time.now
145
+ data = OpenStruct.new(value: value)
146
+ mapped = subject.find(:test).map(data)
147
+
148
+ expect(mapped[:value]).to eq(value.to_date.iso8601)
149
+ end
150
+
151
+ it 'maps data by converting it to date time' do
152
+ subject.define :test do
153
+ datetime :value
154
+ end
155
+
156
+ value = Time.now
157
+ data = OpenStruct.new(value: value)
158
+ mapped = subject.find(:test).map(data)
159
+
160
+ expect(mapped[:value]).to eq(value.to_datetime.iso8601)
161
+ end
162
+
163
+ it 'maps data by converting it to timestamp' do
164
+ subject.define :test do
165
+ timestamp :value
166
+ end
167
+
168
+ value = Time.now
169
+ data = OpenStruct.new(value: value)
170
+ mapped = subject.find(:test).map(data)
171
+
172
+ expect(mapped[:value]).to eq(value.to_datetime.iso8601(3))
173
+ end
174
+ end
175
+
176
+ describe 'when using other mappings' do
177
+ it 'maps data by converting it by other mapper' do
178
+ subject.define :nested_object do
179
+ any :nested_value
180
+ end
181
+ subject.define(:test) do
182
+ nested_object :value
183
+ end
184
+
185
+ data = double(value: double(nested_value: 1))
186
+ mapped = subject.find(:test).map(data)
187
+
188
+ expect(mapped[:value][:nested_value])
189
+ .to eq(data.value.nested_value)
190
+ end
191
+
192
+ it 'maps data by converting it by other complex mapper' do
193
+ subject.define :nested_object do
194
+ any :nested_value, from: :test_value
195
+ end
196
+ subject.define :test do
197
+ nested_object :value
198
+ end
199
+
200
+ data = double(value: double(test_value: 1))
201
+ mapped = subject.find(:test).map(data)
202
+
203
+ expect(mapped[:value][:nested_value])
204
+ .to eq(data.value.test_value)
205
+ end
206
+
207
+ it 'raises error when nested mapper is not defined' do
208
+ expect {
209
+ subject.define :test do
210
+ not_defined_nested_object :value
211
+ end
212
+ }.to raise_error(KeyError)
213
+ end
214
+ end
215
+
216
+ describe 'when using mapping strategies' do
217
+ it 'maps using strict strategy' do
218
+ subject.define(:test) do
219
+ any :x
220
+ end
221
+
222
+ expect(subject.find(:test).map(y: 1).keys).to include(:x)
223
+ end
224
+
225
+ it 'maps using compact strategy' do
226
+ subject.define(:test) do
227
+ any :x
228
+ end
229
+
230
+ source = { y: 1 }
231
+ mapped = subject.find(:test).map(source, strategy: :compact)
232
+ expect(mapped.keys).not_to include(:x)
233
+ end
234
+ end
235
+
236
+ describe 'when mapping nested objects' do
237
+ it 'maps nested hash' do
238
+ subject.define(:test) do
239
+ nested :x do
240
+ any :y
241
+ end
242
+ end
243
+
244
+ source = OpenStruct.new(x: OpenStruct.new(y: 1))
245
+ mapped = subject.find(:test).map(source)
246
+ expect(mapped[:x][:y]).to eq(source.x.y)
247
+ end
248
+
249
+ it 'maps nested object' do
250
+ subject.define(:test) do
251
+ nested :x do
252
+ any :y
253
+ end
254
+ end
255
+
256
+ source = { x: { y: 1 } }
257
+ mapped = subject.find(:test).map(source)
258
+ expect(mapped[:x][:y]).to eq(source[:x][:y])
259
+ end
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,24 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'spine/mappings/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "spine-mappings"
7
+ spec.version = Spine::Mappings::VERSION
8
+ spec.authors = ["TOGGL LLC"]
9
+ spec.email = ["support@toggl.com"]
10
+ spec.summary = ''
11
+ spec.description = ''
12
+ spec.homepage = 'https://github.com/rspine/mappings'
13
+ spec.license = 'BSD-3-Clause'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency 'bundler', "~> 1.7"
21
+ spec.add_development_dependency 'rake', "~> 10.0"
22
+ spec.add_development_dependency 'rspec', "~> 3.2"
23
+ spec.add_development_dependency 'codeclimate-test-reporter', "~> 0.4"
24
+ end
metadata ADDED
@@ -0,0 +1,137 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: spine-mappings
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - TOGGL LLC
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-05-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
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.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: codeclimate-test-reporter
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.4'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.4'
69
+ description: ''
70
+ email:
71
+ - support@toggl.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - CHANGELOG.md
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE
82
+ - README.md
83
+ - Rakefile
84
+ - lib/spine/mappings.rb
85
+ - lib/spine/mappings/commands/action_fetch.rb
86
+ - lib/spine/mappings/commands/fetch.rb
87
+ - lib/spine/mappings/commands/intercept.rb
88
+ - lib/spine/mappings/mapper.rb
89
+ - lib/spine/mappings/mapping.rb
90
+ - lib/spine/mappings/repository.rb
91
+ - lib/spine/mappings/strategies.rb
92
+ - lib/spine/mappings/strategies/compact.rb
93
+ - lib/spine/mappings/strategies/strict.rb
94
+ - lib/spine/mappings/syntax/any.rb
95
+ - lib/spine/mappings/syntax/boolean.rb
96
+ - lib/spine/mappings/syntax/collection.rb
97
+ - lib/spine/mappings/syntax/date.rb
98
+ - lib/spine/mappings/syntax/datetime.rb
99
+ - lib/spine/mappings/syntax/decimal.rb
100
+ - lib/spine/mappings/syntax/integer.rb
101
+ - lib/spine/mappings/syntax/nested.rb
102
+ - lib/spine/mappings/syntax/registered_mapper.rb
103
+ - lib/spine/mappings/syntax/string.rb
104
+ - lib/spine/mappings/syntax/timestamp.rb
105
+ - lib/spine/mappings/version.rb
106
+ - spec/config/coverage.rb
107
+ - spec/config/default.rb
108
+ - spec/spine/mappings/mappings_spec.rb
109
+ - spine-mappings.gemspec
110
+ homepage: https://github.com/rspine/mappings
111
+ licenses:
112
+ - BSD-3-Clause
113
+ metadata: {}
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubyforge_project:
130
+ rubygems_version: 2.4.5
131
+ signing_key:
132
+ specification_version: 4
133
+ summary: ''
134
+ test_files:
135
+ - spec/config/coverage.rb
136
+ - spec/config/default.rb
137
+ - spec/spine/mappings/mappings_spec.rb