data_translation 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +10 -0
- data/LICENSE +19 -0
- data/README +5 -0
- data/lib/data_translation.rb +268 -0
- data/test/tc_data_translation.rb +159 -0
- data/test/tc_data_translation_destination.rb +124 -0
- data/test/ts_all.rb +4 -0
- metadata +76 -0
data/CHANGELOG
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
Version 1.1 (2011-04-28)
|
2
|
+
|
3
|
+
* Switched order of source checking so that hashes are checked prior to source
|
4
|
+
* Changed #from_source to call processor with different arugments depending
|
5
|
+
upon processor arity
|
6
|
+
|
7
|
+
|
8
|
+
Version 1.0 (2010-07-28)
|
9
|
+
|
10
|
+
* Created initial library.
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Scott Patterson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,268 @@
|
|
1
|
+
# Simple class to provide an easy way to map and transform data.
|
2
|
+
#
|
3
|
+
# ==Example Usage
|
4
|
+
#
|
5
|
+
# agent = DataTranslation.new do |m|
|
6
|
+
# m.option :strict, true
|
7
|
+
#
|
8
|
+
# m.set 'source_id', 1
|
9
|
+
#
|
10
|
+
# m.link 'login', 'Username'
|
11
|
+
# m.link 'first_name', 'FirstName'
|
12
|
+
# m.link 'last_name', 'LastName'
|
13
|
+
# m.link 'phone_number', lambda {|source| "(#{source['Area']}) #{source['Phone']}"}
|
14
|
+
#
|
15
|
+
# m.processor do |values|
|
16
|
+
# Agent.find_or_create_by_source_id_and_login(values['source_id'], values['login'])
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# source = {'Username' => 'spatterson',
|
21
|
+
# 'FirstName' => 'Scott',
|
22
|
+
# 'LastName' => 'Patterson',
|
23
|
+
# 'Area' => '123',
|
24
|
+
# 'Phone' => '456-7890'}
|
25
|
+
#
|
26
|
+
# results = agent.transform(source)
|
27
|
+
#
|
28
|
+
# puts results.inspect # => {"phone_number" => "(123) 456-7890",
|
29
|
+
# # "last_name" => "Patterson",
|
30
|
+
# # "login" => "spatterson",
|
31
|
+
# # "first_name" => "Scott"
|
32
|
+
# # "source_id" => 1}
|
33
|
+
#
|
34
|
+
# new_object = agent.from_source(source)
|
35
|
+
|
36
|
+
class DataTranslation
|
37
|
+
VERSION = '1.1.0'
|
38
|
+
|
39
|
+
attr_reader :mappings, :static_values, :options
|
40
|
+
|
41
|
+
# Includes DataTranslation::Destination in the specified class (klass). If a name
|
42
|
+
# is given, yields and returns a DataTranslation for that name. i.e.
|
43
|
+
# DataTranslation.destination(PlainClass, :source_name) {|dtm| dtm.link ...}
|
44
|
+
def self.destination(klass, name = nil)
|
45
|
+
# No need to include ourself again if we've already extended the class
|
46
|
+
klass.class_eval("include(Destination)") unless klass.include?(Destination)
|
47
|
+
|
48
|
+
if name
|
49
|
+
klass.data_translation_map(name) {|dtm| yield dtm} if block_given?
|
50
|
+
klass.data_translation_map(name)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Constructor, yields self to allow for block-based configuration.
|
55
|
+
# Defaults: strict = true
|
56
|
+
def initialize # yields self
|
57
|
+
@mappings = {}
|
58
|
+
@static_values = {}
|
59
|
+
@options = {:strict => true}
|
60
|
+
@processor = nil
|
61
|
+
|
62
|
+
yield self if block_given?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Sets the option with name to value.
|
66
|
+
# Current options are:
|
67
|
+
# :strict = boolean; true will raise a NonresponsiveSource exception if the source
|
68
|
+
# object does not respond to a mapping.
|
69
|
+
# false ignores non-existant fields in the source object.
|
70
|
+
def option(name, value)
|
71
|
+
@options[name.to_sym] = value
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sets a specified to field to value during transformation without regard to the source object.
|
75
|
+
# If you wish to link to source object data or use a lambda/block, use #link instead.
|
76
|
+
# to may be any object that can be stored as a Hash key.
|
77
|
+
#
|
78
|
+
# set :field_name, 'Static value'
|
79
|
+
def set(to, value)
|
80
|
+
@static_values[to] = value
|
81
|
+
end
|
82
|
+
|
83
|
+
# Links a destination field to a source method, element, or lambda.
|
84
|
+
# to may be a string, symbol, or any object that can be used as a hash key.
|
85
|
+
# If from is a lambda, called with one argument (the source passed to #transform).
|
86
|
+
# Alternatively, can be called with a block instead of a lambda or from.
|
87
|
+
#
|
88
|
+
# link 'field_name', 'FieldName'
|
89
|
+
# link :field_name, 'FieldName'
|
90
|
+
# link :field_name, lambda {|source| source...}
|
91
|
+
# link(:field_name) {|source| source...}
|
92
|
+
def link(to, from = nil, &block)
|
93
|
+
@mappings[to] = block.nil? ? from : block
|
94
|
+
end
|
95
|
+
|
96
|
+
# If called without a block, returns the current block. If called with a block,
|
97
|
+
# sets the block that will be run with the results from #transform when #from_source
|
98
|
+
# is called. The results of the transformation will be passed if the block
|
99
|
+
# expects only one parameter. If the block expects two parameters both the results
|
100
|
+
# and the original source object will be provided.
|
101
|
+
def processor(&block) # |transform_results [, source_data]|
|
102
|
+
if block_given?
|
103
|
+
@processor = block
|
104
|
+
else
|
105
|
+
@processor
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Removes the currently defined processor, if any.
|
110
|
+
def remove_processor
|
111
|
+
@processor = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
# Given a source object, returns a new hash with elements as determined by the
|
115
|
+
# current mapping. Mapping is set by one or more calls to #link. Options passed
|
116
|
+
# to this method override the instance options. See #option for a list of options.
|
117
|
+
# #link values will override #set values.
|
118
|
+
def transform(source, options = {})
|
119
|
+
options = @options.merge(options)
|
120
|
+
|
121
|
+
apply_static_values(options).merge(apply_mappings(source, options))
|
122
|
+
end
|
123
|
+
|
124
|
+
# Given a source object, returns the results of #transform if no processor is defined
|
125
|
+
# or the results of calling the processor block as defined by #processor.
|
126
|
+
def from_source(source, options = {})
|
127
|
+
results = transform(source, options)
|
128
|
+
|
129
|
+
if @processor
|
130
|
+
if @processor.arity == 1
|
131
|
+
@processor.call(results)
|
132
|
+
else
|
133
|
+
@processor.call(results, source)
|
134
|
+
end
|
135
|
+
else
|
136
|
+
results
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Yields the mapping object so that options and links can be applied within a block.
|
141
|
+
def update # yields self
|
142
|
+
yield self
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
# Given a source object and optional options hash, iterates over the current mappings
|
148
|
+
# (defined by #link) and returns a Hash of results.
|
149
|
+
def apply_mappings(source, options = {})
|
150
|
+
results = {}
|
151
|
+
|
152
|
+
@mappings.each do |to, from|
|
153
|
+
if from.respond_to? :call # Lambda
|
154
|
+
results[to] = from.call(source)
|
155
|
+
elsif source.respond_to?(:[]) && # Hash-like Object
|
156
|
+
(options[:strict] == false || source.has_key?(from))
|
157
|
+
results[to] = source[from]
|
158
|
+
elsif source.respond_to?(from.to_sym) # Source Object
|
159
|
+
results[to] = source.send(from.to_sym)
|
160
|
+
else
|
161
|
+
raise NonresponsiveSource,
|
162
|
+
"#{to}: #{source.class} does not respond to '#{from}' (#{from.class})"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
results
|
167
|
+
end
|
168
|
+
|
169
|
+
# Returns a hash of static values as defined by the #set method.
|
170
|
+
def apply_static_values(options = {})
|
171
|
+
@static_values # currently nothing to do to process, so we just pass it along for now.
|
172
|
+
end
|
173
|
+
|
174
|
+
## Mixins ##
|
175
|
+
|
176
|
+
# Provides helper methods for mapping and creating new objects from source data.
|
177
|
+
#
|
178
|
+
# In addition to including the mixin DataTranslation::Destination, a class method
|
179
|
+
# called initialize_from_data_translation may optionally be provided that takes a hash
|
180
|
+
# of translated data. If not present, the from_source will pass a hash of the
|
181
|
+
# transformation results to the new method.
|
182
|
+
#
|
183
|
+
# Multiple mappings may be given for a single destination by using different names
|
184
|
+
# for them. e.g. :source1 and :source2 as names yield different mappings.
|
185
|
+
#
|
186
|
+
# ==Example Usage
|
187
|
+
#
|
188
|
+
# class DestinationObject
|
189
|
+
# include DataTranslation::Destination
|
190
|
+
#
|
191
|
+
# def self.initialize_from_data_translation(results)
|
192
|
+
# end
|
193
|
+
# end
|
194
|
+
#
|
195
|
+
# DestinationObject.data_translation_map(:source_name) do |dtm|
|
196
|
+
# dtm.link 'first_key', 'Key1'
|
197
|
+
# end
|
198
|
+
#
|
199
|
+
# source = {'Key1' => 'Value1'}
|
200
|
+
#
|
201
|
+
# new_object = DestinationObject.from_source(:source_name, source)
|
202
|
+
|
203
|
+
module Destination
|
204
|
+
# Provides our class methods when included.
|
205
|
+
def self.included(base)
|
206
|
+
base.extend(ClassMethods)
|
207
|
+
end
|
208
|
+
|
209
|
+
module ClassMethods
|
210
|
+
# Given the name of a mapping and a source object, transforms the source object
|
211
|
+
# based upon the specified mapping and attempts to process the results using one of
|
212
|
+
# several methods that are checked in the following order:
|
213
|
+
# * DataTranslation#processor defined block
|
214
|
+
# * Destination class initialize_from_data_translation
|
215
|
+
# * Destination class default constructor
|
216
|
+
# with the resulting hash if that method is not defined.
|
217
|
+
# Returns an instance of the class based upon the source data.
|
218
|
+
def from_source(name, source)
|
219
|
+
dtm = data_translation_map(name)
|
220
|
+
|
221
|
+
if dtm.processor
|
222
|
+
dtm.from_source(source)
|
223
|
+
elsif respond_to?(:initialize_from_data_translation)
|
224
|
+
initialize_from_data_translation(dtm.transform(source))
|
225
|
+
else
|
226
|
+
new(dtm.transform(source))
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Given the name of a mapping, returns the existing mapping with that name
|
231
|
+
# or creates one with that name and yields it if a block is given.
|
232
|
+
# Returns the DataTranslation mapping for the specified name.
|
233
|
+
def data_translation_map(name) # yields DataTranslation
|
234
|
+
@dt_mappings ||= {}
|
235
|
+
@dt_mappings[name] ||= DataTranslation.new
|
236
|
+
|
237
|
+
yield @dt_mappings[name] if block_given?
|
238
|
+
|
239
|
+
@dt_mappings[name]
|
240
|
+
end
|
241
|
+
|
242
|
+
# Returns a string containing a sample DataTranslation mapping for this instance
|
243
|
+
# based upon actual column names (assuming this is an ActiveRecord class or
|
244
|
+
# another class that provides an array of string column/attribute names via a
|
245
|
+
# column_names class method).
|
246
|
+
#
|
247
|
+
# Passing the name argument sets it as the translation name in the output.
|
248
|
+
def stub_data_translation_from_column_names(name = 'name')
|
249
|
+
map = ["DataTranslation.destination(#{self}, :#{name}) do |dtm|"]
|
250
|
+
|
251
|
+
column_names.each do |col|
|
252
|
+
map << "\tdtm.link :#{col}, :#{col}"
|
253
|
+
end
|
254
|
+
|
255
|
+
map << "end"
|
256
|
+
|
257
|
+
map.join("\n")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
## Exceptions ##
|
264
|
+
|
265
|
+
# Raised when the source object does not respond to the from link.
|
266
|
+
class NonresponsiveSource < Exception
|
267
|
+
end
|
268
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
require 'data_translation'
|
6
|
+
|
7
|
+
class TC_DataTranslation < Test::Unit::TestCase
|
8
|
+
def test_should_set_options
|
9
|
+
dt = DataTranslation.new do |m|
|
10
|
+
m.option :strict, true
|
11
|
+
end
|
12
|
+
|
13
|
+
assert_equal true, dt.options[:strict]
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_should_link_string_key
|
17
|
+
dt = DataTranslation.new do |m|
|
18
|
+
m.link 'To', 'From'
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal 'From', dt.mappings['To']
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_should_link_to_lambda
|
25
|
+
dt = DataTranslation.new do |m|
|
26
|
+
m.link 'To', lambda {|source| 'Lambda'}
|
27
|
+
end
|
28
|
+
|
29
|
+
assert_equal 'Lambda', dt.mappings['To'].call({})
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_should_link_to_block
|
33
|
+
dt = DataTranslation.new do |m|
|
34
|
+
m.link('To') {|source| 'Block'}
|
35
|
+
end
|
36
|
+
|
37
|
+
assert_equal 'Block', dt.mappings['To'].call({})
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_should_set_static_value
|
41
|
+
dt = DataTranslation.new do |m|
|
42
|
+
m.set 'To', 'StaticFrom'
|
43
|
+
end
|
44
|
+
|
45
|
+
assert_equal 'StaticFrom', dt.static_values['To']
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_should_transform_with_lambda
|
49
|
+
dt = DataTranslation.new do |m|
|
50
|
+
m.link 'Phone', lambda {|source| "(#{source['Area']}) #{source['PhoneNumber']}"}
|
51
|
+
end
|
52
|
+
|
53
|
+
source = {'Area' => 123, 'PhoneNumber' => '456-7890'}
|
54
|
+
|
55
|
+
assert_equal '(123) 456-7890', dt.transform(source)['Phone']
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_should_transform_with_block
|
59
|
+
dt = DataTranslation.new do |m|
|
60
|
+
m.link('Phone') {|source| "(#{source['Area']}) #{source['PhoneNumber']}"}
|
61
|
+
end
|
62
|
+
|
63
|
+
source = {'Area' => 123, 'PhoneNumber' => '456-7890'}
|
64
|
+
|
65
|
+
assert_equal '(123) 456-7890', dt.transform(source)['Phone']
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_should_transform_hash_source
|
69
|
+
source = {'Key1' => 'Value1', 'Key2' => 'Value2'}
|
70
|
+
|
71
|
+
dt = DataTranslation.new do |m|
|
72
|
+
m.link 'Dest1', 'Key1'
|
73
|
+
m.link 'Dest2', 'Key2'
|
74
|
+
end
|
75
|
+
|
76
|
+
results = dt.transform(source)
|
77
|
+
|
78
|
+
assert_equal 'Value1', results['Dest1']
|
79
|
+
assert_equal 'Value2', results['Dest2']
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_should_transform_object_source
|
83
|
+
source = mock('Key1' => 'Value1', 'Key2' => 'Value2')
|
84
|
+
|
85
|
+
dt = DataTranslation.new do |m|
|
86
|
+
m.link 'Dest1', 'Key1'
|
87
|
+
m.link 'Dest2', 'Key2'
|
88
|
+
end
|
89
|
+
|
90
|
+
results = dt.transform(source)
|
91
|
+
|
92
|
+
assert_equal 'Value1', results['Dest1']
|
93
|
+
assert_equal 'Value2', results['Dest2']
|
94
|
+
end
|
95
|
+
|
96
|
+
# Ran into an issue when mapping US address data from a hash with
|
97
|
+
# symbol keys. :zip was specified as a key, but because we checked
|
98
|
+
# for object methods first, enumerable#zip was being called instead
|
99
|
+
# of hash#[].
|
100
|
+
def test_should_check_for_hashlike_object_before_source
|
101
|
+
source = {:zip => '99999'}
|
102
|
+
|
103
|
+
dt = DataTranslation.new do |m|
|
104
|
+
m.link 'ZipCode', :zip
|
105
|
+
end
|
106
|
+
|
107
|
+
source.expects(:zip).never
|
108
|
+
|
109
|
+
results = dt.transform(source)
|
110
|
+
|
111
|
+
assert_equal '99999', results['ZipCode']
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_should_call_processor_on_transform
|
115
|
+
dt = DataTranslation.new do |m|
|
116
|
+
m.link :name, 'Name'
|
117
|
+
|
118
|
+
m.processor do |results|
|
119
|
+
"Construct called with #{results[:name]}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
assert_equal 'Construct called with value', dt.from_source({'Name' => 'value'})
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_should_raise_exception_when_strict
|
127
|
+
source = {}
|
128
|
+
|
129
|
+
dt = DataTranslation.new {|m| m.link 'Key1', 'Value1'}
|
130
|
+
|
131
|
+
assert_raises(DataTranslation::NonresponsiveSource) do
|
132
|
+
dt.transform(source)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_should_transform_with_static_value
|
137
|
+
dt = DataTranslation.new {|m| m.set 'To', 'StaticValue'}
|
138
|
+
|
139
|
+
assert_equal({'To' => 'StaticValue'}, dt.transform({}))
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_should_not_raise_exception_when_not_strict
|
143
|
+
source = {}
|
144
|
+
|
145
|
+
dt = DataTranslation.new {|m| m.link 'Key1', 'Value1'}
|
146
|
+
|
147
|
+
assert_nothing_raised do
|
148
|
+
dt.transform(source, :strict => false)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_should_update_dt
|
153
|
+
dt = DataTranslation.new {|m| m.option :strict, true}
|
154
|
+
assert dt.options[:strict]
|
155
|
+
|
156
|
+
dt.update {|m| m.option :strict, false}
|
157
|
+
assert ! dt.options[:strict]
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha'
|
5
|
+
require 'data_translation'
|
6
|
+
|
7
|
+
## Test Objects
|
8
|
+
|
9
|
+
class TestObject
|
10
|
+
include DataTranslation::Destination
|
11
|
+
|
12
|
+
attr_reader :name, :options
|
13
|
+
|
14
|
+
def self.initialize_from_data_translation(params)
|
15
|
+
TestObject.new(params.delete('name'), params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(name, options = {})
|
19
|
+
@name = name
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.column_names
|
24
|
+
['id', 'first_name', 'last_name']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class PlainObject
|
29
|
+
attr_reader :options
|
30
|
+
|
31
|
+
def initialize(options)
|
32
|
+
@options = options
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
class TC_DataTranslationDestination < Test::Unit::TestCase
|
38
|
+
def setup
|
39
|
+
@source = {'Name' => 'test object', 'Key1' => 'Value1', 'Key2' => 'Value2'}
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_should_create_map(klass = TestObject)
|
43
|
+
klass.data_translation_map(:hash_source) do |m|
|
44
|
+
m.option :strict, true
|
45
|
+
|
46
|
+
m.link 'name', 'Name'
|
47
|
+
m.link 'first_key', 'Key1'
|
48
|
+
m.link 'second_key', 'Key2'
|
49
|
+
|
50
|
+
m.remove_processor
|
51
|
+
yield m if block_given?
|
52
|
+
end
|
53
|
+
|
54
|
+
assert klass.data_translation_map(:hash_source).options[:strict]
|
55
|
+
assert_equal 'Name', klass.data_translation_map(:hash_source).mappings['name']
|
56
|
+
assert_equal 'Key1', klass.data_translation_map(:hash_source).mappings['first_key']
|
57
|
+
assert_equal 'Key2', klass.data_translation_map(:hash_source).mappings['second_key']
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_should_create_new_object_using_custom_constructor
|
61
|
+
test_should_create_map
|
62
|
+
|
63
|
+
to = TestObject.from_source(:hash_source, @source)
|
64
|
+
|
65
|
+
assert_equal 'test object', to.name
|
66
|
+
assert_equal 'Value1', to.options['first_key']
|
67
|
+
assert_equal 'Value2', to.options['second_key']
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_should_create_new_object_using_default_constructor
|
71
|
+
DataTranslation.destination(PlainObject)
|
72
|
+
test_should_create_map(PlainObject)
|
73
|
+
|
74
|
+
to = PlainObject.from_source(:hash_source, @source)
|
75
|
+
|
76
|
+
assert_equal 'test object', to.options['name']
|
77
|
+
assert_equal 'Value1', to.options['first_key']
|
78
|
+
assert_equal 'Value2', to.options['second_key']
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_should_call_processor_if_given
|
82
|
+
DataTranslation.destination(PlainObject)
|
83
|
+
test_should_create_map(PlainObject) do |m|
|
84
|
+
m.processor do |results, source|
|
85
|
+
results.values.sort # for consistency we sort our values
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
assert_equal ['Value1', 'Value2', 'test object'],
|
90
|
+
PlainObject.from_source(:hash_source, @source)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_should_return_mapping_for_name
|
94
|
+
test_should_create_map
|
95
|
+
|
96
|
+
assert TestObject.data_translation_map(:hash_source).kind_of?(DataTranslation)
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_should_make_class_destination
|
100
|
+
DataTranslation.destination(PlainObject)
|
101
|
+
|
102
|
+
assert PlainObject.include?(DataTranslation::Destination)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_should_make_class_destination_and_yield
|
106
|
+
DataTranslation.destination(PlainObject, :hash_source) do |dtm|
|
107
|
+
dtm.link 'first_key', 'Key1'
|
108
|
+
end
|
109
|
+
|
110
|
+
assert_equal 'Key1', PlainObject.data_translation_map(:hash_source).mappings['first_key']
|
111
|
+
assert PlainObject.respond_to?(:from_source)
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_should_stub_map_from_column_names
|
115
|
+
assert_equal "DataTranslation.destination(TestObject, :my_name) do |dtm|\n\tdtm.link :id, :id\n\tdtm.link :first_name, :first_name\n\tdtm.link :last_name, :last_name\nend",
|
116
|
+
TestObject.stub_data_translation_from_column_names(:my_name)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_should_not_include_destination_multiple_times
|
120
|
+
DataTranslation::Destination.expects(:included).never
|
121
|
+
|
122
|
+
DataTranslation.destination(TestObject)
|
123
|
+
end
|
124
|
+
end
|
data/test/ts_all.rb
ADDED
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: data_translation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 19
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 1.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Scott Patterson
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-28 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Provides a means to write data translation maps in Ruby and transform data from a source object.
|
23
|
+
email: scott.patterson@digitalaun.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README
|
30
|
+
- LICENSE
|
31
|
+
- CHANGELOG
|
32
|
+
files:
|
33
|
+
- lib/data_translation.rb
|
34
|
+
- test/tc_data_translation.rb
|
35
|
+
- test/tc_data_translation_destination.rb
|
36
|
+
- test/ts_all.rb
|
37
|
+
- README
|
38
|
+
- LICENSE
|
39
|
+
- CHANGELOG
|
40
|
+
has_rdoc: true
|
41
|
+
homepage:
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --main
|
47
|
+
- README
|
48
|
+
require_paths:
|
49
|
+
- lib
|
50
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
requirements: []
|
69
|
+
|
70
|
+
rubyforge_project:
|
71
|
+
rubygems_version: 1.5.0
|
72
|
+
signing_key:
|
73
|
+
specification_version: 3
|
74
|
+
summary: Generic data mapping and translation expressed in Ruby.
|
75
|
+
test_files:
|
76
|
+
- test/ts_all.rb
|