data_translation 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|