remi 0.2.42 → 0.3.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.
- checksums.yaml +4 -4
- data/.yardopts +7 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +13 -26
- data/README.md +1 -1
- data/features/step_definitions/remi_step.rb +33 -13
- data/features/sub_job_example.feature +24 -0
- data/features/sub_transform_example.feature +35 -0
- data/features/sub_transform_many_to_many.feature +49 -0
- data/features/support/env_app.rb +1 -1
- data/jobs/all_jobs_shared.rb +19 -16
- data/jobs/copy_source_job.rb +11 -9
- data/jobs/csv_file_target_job.rb +10 -9
- data/jobs/json_job.rb +18 -14
- data/jobs/metadata_job.rb +33 -28
- data/jobs/parameters_job.rb +14 -11
- data/jobs/sample_job.rb +106 -77
- data/jobs/sftp_file_target_job.rb +14 -13
- data/jobs/sub_job_example_job.rb +86 -0
- data/jobs/sub_transform_example_job.rb +43 -0
- data/jobs/sub_transform_many_to_many_job.rb +46 -0
- data/jobs/transforms/concatenate_job.rb +16 -12
- data/jobs/transforms/data_frame_sieve_job.rb +24 -19
- data/jobs/transforms/date_diff_job.rb +15 -11
- data/jobs/transforms/nvl_job.rb +16 -12
- data/jobs/transforms/parse_date_job.rb +17 -14
- data/jobs/transforms/partitioner_job.rb +27 -19
- data/jobs/transforms/prefix_job.rb +13 -10
- data/jobs/transforms/truncate_job.rb +14 -10
- data/jobs/transforms/truthy_job.rb +11 -8
- data/lib/remi.rb +25 -11
- data/lib/remi/data_frame.rb +4 -4
- data/lib/remi/data_frame/daru.rb +1 -37
- data/lib/remi/data_subject.rb +234 -48
- data/lib/remi/data_subjects/csv_file.rb +171 -0
- data/lib/remi/data_subjects/data_frame.rb +106 -0
- data/lib/remi/data_subjects/file_system.rb +115 -0
- data/lib/remi/data_subjects/local_file.rb +109 -0
- data/lib/remi/data_subjects/none.rb +31 -0
- data/lib/remi/data_subjects/postgres.rb +186 -0
- data/lib/remi/data_subjects/s3_file.rb +84 -0
- data/lib/remi/data_subjects/salesforce.rb +211 -0
- data/lib/remi/data_subjects/sftp_file.rb +196 -0
- data/lib/remi/data_subjects/sub_job.rb +50 -0
- data/lib/remi/dsl.rb +74 -0
- data/lib/remi/encoder.rb +45 -0
- data/lib/remi/extractor.rb +21 -0
- data/lib/remi/field_symbolizers.rb +1 -0
- data/lib/remi/job.rb +279 -113
- data/lib/remi/job/parameters.rb +90 -0
- data/lib/remi/job/sub_job.rb +35 -0
- data/lib/remi/job/transform.rb +165 -0
- data/lib/remi/loader.rb +22 -0
- data/lib/remi/monkeys/daru.rb +4 -0
- data/lib/remi/parser.rb +44 -0
- data/lib/remi/testing/business_rules.rb +17 -23
- data/lib/remi/testing/data_stub.rb +2 -2
- data/lib/remi/version.rb +1 -1
- data/remi.gemspec +3 -0
- data/spec/data_subject_spec.rb +475 -11
- data/spec/data_subjects/csv_file_spec.rb +69 -0
- data/spec/data_subjects/data_frame_spec.rb +52 -0
- data/spec/{extractor → data_subjects}/file_system_spec.rb +0 -0
- data/spec/{extractor → data_subjects}/local_file_spec.rb +0 -0
- data/spec/data_subjects/none_spec.rb +41 -0
- data/spec/data_subjects/postgres_spec.rb +80 -0
- data/spec/{extractor → data_subjects}/s3_file_spec.rb +0 -0
- data/spec/data_subjects/salesforce_spec.rb +117 -0
- data/spec/{extractor → data_subjects}/sftp_file_spec.rb +16 -0
- data/spec/data_subjects/sub_job_spec.rb +33 -0
- data/spec/encoder_spec.rb +38 -0
- data/spec/extractor_spec.rb +11 -0
- data/spec/fixtures/sf_bulk_helper_stubs.rb +443 -0
- data/spec/job/transform_spec.rb +257 -0
- data/spec/job_spec.rb +507 -0
- data/spec/loader_spec.rb +11 -0
- data/spec/parser_spec.rb +38 -0
- data/spec/sf_bulk_helper_spec.rb +117 -0
- data/spec/testing/data_stub_spec.rb +5 -3
- metadata +109 -27
- data/features/aggregate.feature +0 -42
- data/jobs/aggregate_job.rb +0 -31
- data/jobs/transforms/transform_jobs.rb +0 -4
- data/lib/remi/data_subject/csv_file.rb +0 -162
- data/lib/remi/data_subject/data_frame.rb +0 -52
- data/lib/remi/data_subject/postgres.rb +0 -134
- data/lib/remi/data_subject/salesforce.rb +0 -136
- data/lib/remi/data_subject/sftp_file.rb +0 -65
- data/lib/remi/extractor/file_system.rb +0 -92
- data/lib/remi/extractor/local_file.rb +0 -43
- data/lib/remi/extractor/s3_file.rb +0 -57
- data/lib/remi/extractor/sftp_file.rb +0 -83
- data/spec/data_subject/csv_file_spec.rb +0 -79
- data/spec/data_subject/data_frame.rb +0 -27
@@ -0,0 +1,257 @@
|
|
1
|
+
require_relative '../remi_spec'
|
2
|
+
|
3
|
+
describe Job do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Object.send(:remove_const, :MyJob) if Object.constants.include?(:MyJob)
|
7
|
+
class MyJob < Job
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:job) { MyJob.new }
|
12
|
+
|
13
|
+
describe Job::Transform do
|
14
|
+
before do
|
15
|
+
class MyJob
|
16
|
+
def job_data_subject
|
17
|
+
@job_data_subject ||= Remi::DataSubject.new(name: 'job_data_subject')
|
18
|
+
end
|
19
|
+
|
20
|
+
def job_data_subject2
|
21
|
+
@job_data_subject2 ||= Remi::DataSubject.new(name: 'job_data_subject2')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
let(:my_transform) do
|
27
|
+
Job::Transform.new(job, name: 'my_transform name') do
|
28
|
+
job_data_subject
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'has a name' do
|
33
|
+
expect(my_transform.name).to eq 'my_transform name'
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#execute' do
|
37
|
+
it 'executes the transform in the context of the job' do
|
38
|
+
expect(job).to receive :job_data_subject
|
39
|
+
my_transform.execute
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'logs a message indicating that the transform is running' do
|
43
|
+
expect(job.logger).to receive(:info)
|
44
|
+
my_transform.execute
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '#map_source_fields' do
|
49
|
+
it 'creates a new method in the transform' do
|
50
|
+
expect {
|
51
|
+
my_transform.map_source_fields(:job_data_subject, :t_source, {})
|
52
|
+
}.to change { my_transform.methods.include? :t_source }.from(false).to(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'creates a new method representing a data subject' do
|
56
|
+
my_transform.map_source_fields(:job_data_subject, :t_source, {})
|
57
|
+
expect(my_transform.t_source).to be_a Remi::DataSubject
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'adds the linked method to the list of mapped sources' do
|
61
|
+
expect {
|
62
|
+
my_transform.map_source_fields(:job_data_subject, :t_source, {})
|
63
|
+
}.to change { my_transform.sources }.from([]).to([:t_source])
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'can add multiple linked methods' do
|
67
|
+
my_transform.map_source_fields(:job_data_subject, :t_source, {})
|
68
|
+
expect {
|
69
|
+
my_transform.map_source_fields(:job_data_subject2, :t_source2, {})
|
70
|
+
}.to change { my_transform.sources }.from([:t_source]).to([:t_source, :t_source2])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
describe '#source' do
|
76
|
+
context 'without mapping source data' do
|
77
|
+
it 'raises a NoMethodError' do
|
78
|
+
expect { my_transform.source :t_source, [] }.to raise_error NoMethodError
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'without mapping source fields' do
|
83
|
+
before { my_transform.map_source_fields(:job_data_subject, :t_source, {}) }
|
84
|
+
it 'raises an ArgumentError' do
|
85
|
+
expect { my_transform.source :t_source, [:st_field] }.to raise_error ArgumentError
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with mapping source data and fields fields' do
|
90
|
+
before { my_transform.map_source_fields(:job_data_subject, :t_source, { :job_field => :st_field }) }
|
91
|
+
it 'does not raise an error' do
|
92
|
+
expect { my_transform.source :t_source, [:st_field] }.not_to raise_error
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#map_target_fields' do
|
98
|
+
it 'creates a new method in the transform' do
|
99
|
+
expect {
|
100
|
+
my_transform.map_target_fields(:t_target, :job_data_subject, {})
|
101
|
+
}.to change { my_transform.methods.include? :t_target }.from(false).to(true)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'creates a new method representing a data subject' do
|
105
|
+
my_transform.map_source_fields(:job_data_subject, :t_target, {})
|
106
|
+
expect(my_transform.t_target).to be_a Remi::DataSubject
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'adds the linked method to the list of mapped targets' do
|
110
|
+
expect {
|
111
|
+
my_transform.map_target_fields(:t_target, :job_data_subject, {})
|
112
|
+
}.to change { my_transform.targets }.from([]).to([:t_target])
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'can add multiple linked methods' do
|
116
|
+
my_transform.map_target_fields(:t_target, :job_data_subject, {})
|
117
|
+
expect {
|
118
|
+
my_transform.map_target_fields(:t_target2, :job_data_subject2, {})
|
119
|
+
}.to change { my_transform.targets }.from([:t_target]).to([:t_target, :t_target2])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe '#target' do
|
124
|
+
context 'without mapping source data' do
|
125
|
+
it 'raises a NoMethodError' do
|
126
|
+
expect { my_transform.target :t_target, [] }.to raise_error NoMethodError
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'without mapping target fields' do
|
131
|
+
before { my_transform.map_target_fields(:t_target, :job_data_subject, {}) }
|
132
|
+
it 'raises an ArgumentError' do
|
133
|
+
expect { my_transform.target :t_target, [:st_field] }.to raise_error ArgumentError
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'with mapping target data and fields fields' do
|
138
|
+
before { my_transform.map_target_fields(:t_target, :job_data_subject, { :st_field => :job_field }) }
|
139
|
+
it 'does not raise an error' do
|
140
|
+
expect { my_transform.target :t_target, [:st_field] }.not_to raise_error
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe '#params' do
|
146
|
+
let(:my_transform) do
|
147
|
+
Job::Transform.new(job, t_param: 'my transform parameter') do
|
148
|
+
job_data_subject params[:t_param]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'defines parameters in the constructor' do
|
153
|
+
expect(my_transform.params[:t_param]).to eq 'my transform parameter'
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'allows parameters to be accessed during execution' do
|
157
|
+
expect(job).to receive(:job_data_subject) .with('my transform parameter')
|
158
|
+
my_transform.execute
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'returns an error if the parameter is not defined' do
|
162
|
+
expect { my_transform.params[:unk] }.to raise_error ArgumentError
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
# This needs some work. It's basically a way-too-complicated integration test.
|
169
|
+
describe '#import' do
|
170
|
+
before do
|
171
|
+
class MyJob
|
172
|
+
source :job_source do
|
173
|
+
fields({ :job_source_field_1 => { type: :date }, :job_source_field_2 => {}})
|
174
|
+
end
|
175
|
+
target :job_target do
|
176
|
+
fields({ :job_target_field_1 => {}, :job_target_field_2 => { from_job: true }})
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
job.job_source.df = Remi::DataFrame::Daru.new({
|
181
|
+
job_source_field_1: ['something'],
|
182
|
+
job_source_field_2: ['else']
|
183
|
+
})
|
184
|
+
end
|
185
|
+
|
186
|
+
let(:sub_transform) do
|
187
|
+
st = Job::Transform.new('arbitrary', name: 'sub_transform', st_param: 'my subtransform parameter') do
|
188
|
+
source :st_source, [:st_source_field_1, :st_source_field_2]
|
189
|
+
target :st_target, [:st_target_field_1, :st_target_field_2]
|
190
|
+
|
191
|
+
value_of_st_param(params[:st_param])
|
192
|
+
Remi::SourceToTargetMap.apply(st_source.df, st_target.df) do
|
193
|
+
map source(:st_source_field_1) .target(:st_target_field_1)
|
194
|
+
map source(:st_source_field_2) .target(:st_target_field_2)
|
195
|
+
end
|
196
|
+
|
197
|
+
st_target.fields[:st_target_field_2] = { from_sub_trans: 'cool' }
|
198
|
+
end
|
199
|
+
|
200
|
+
st.define_singleton_method(:value_of_st_param) { |arg| }
|
201
|
+
st
|
202
|
+
end
|
203
|
+
|
204
|
+
let(:my_transform) do
|
205
|
+
scoped_sub_transform = sub_transform
|
206
|
+
Job::Transform.new(job, name: 'my_transform') do
|
207
|
+
import scoped_sub_transform do
|
208
|
+
map_source_fields :job_source, :st_source, {
|
209
|
+
:job_source_field_1 => :st_source_field_1,
|
210
|
+
:job_source_field_2 => :st_source_field_2
|
211
|
+
}
|
212
|
+
map_target_fields :st_target, :job_target, {
|
213
|
+
:st_target_field_1 => :job_target_field_1,
|
214
|
+
:st_target_field_2 => :job_target_field_2
|
215
|
+
|
216
|
+
}
|
217
|
+
params[:st_param] = 'modified in parent transform'
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'maps source data fields on input' do
|
223
|
+
my_transform.execute
|
224
|
+
expect(sub_transform.st_source.fields.keys).to eq [:st_source_field_1, :st_source_field_2]
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'maps source data vectors on input' do
|
228
|
+
my_transform.execute
|
229
|
+
expect(sub_transform.st_source.df.vectors.to_a).to eq [:st_source_field_1, :st_source_field_2]
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'maps target data fields on output' do
|
233
|
+
my_transform.execute
|
234
|
+
expect(job.job_target.fields[:job_target_field_2]).to eq({ :from_job => true, :from_sub_trans => "cool" })
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'maps target data vectors on output' do
|
238
|
+
my_transform.execute
|
239
|
+
expect(job.job_target.df.vectors.to_a).to eq [:job_target_field_1, :job_target_field_2]
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'executes the sub transform' do
|
243
|
+
my_transform.execute
|
244
|
+
expect(job.job_target.df.to_a).to eq Remi::DataFrame::Daru.new({
|
245
|
+
job_target_field_1: ['something'],
|
246
|
+
job_target_field_2: ['else']
|
247
|
+
}).to_a
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'sets parameters used in the subtransform' do
|
252
|
+
expect(sub_transform).to receive(:value_of_st_param) .with('modified in parent transform')
|
253
|
+
my_transform.execute
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|
data/spec/job_spec.rb
ADDED
@@ -0,0 +1,507 @@
|
|
1
|
+
require_relative 'remi_spec'
|
2
|
+
|
3
|
+
describe Job do
|
4
|
+
|
5
|
+
before :each do
|
6
|
+
Object.send(:remove_const, :MyJob) if Object.constants.include?(:MyJob)
|
7
|
+
class MyJob < Job
|
8
|
+
end
|
9
|
+
|
10
|
+
Object.send(:remove_const, :MySubJob) if Object.constants.include?(:MySubJob)
|
11
|
+
class MySubJob < Job
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:job) { MyJob.new }
|
16
|
+
|
17
|
+
context 'DSL' do
|
18
|
+
describe '.param' do
|
19
|
+
before do
|
20
|
+
class MyJob
|
21
|
+
param(:my_param) { 'I am my_param' }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'adds a parameter to the parameter hash' do
|
26
|
+
expect(job.params.to_h.keys).to include :my_param
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'can be accessed at the class level' do
|
30
|
+
expect(MyJob.params[:my_param]).to eq 'I am my_param'
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'can be accessed at the job level' do
|
34
|
+
expect(MyJob.new.params[:my_param]).to eq 'I am my_param'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe '.sub_job' do
|
39
|
+
before do
|
40
|
+
class MyJob
|
41
|
+
sub_job(:my_sub_job) { MySubJob.new }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'adds the sub-job to the list of sub-jobs' do
|
46
|
+
expect(job.sub_jobs).to eq [:my_sub_job]
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'gives the sub-job a name' do
|
50
|
+
expect(job.my_sub_job.name).to eq :my_sub_job
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'appends a newly defined sub-job to the list of sub-jobs' do
|
54
|
+
expect {
|
55
|
+
class MyJob
|
56
|
+
sub_job(:my_sub_job2) { MySubJob.new }
|
57
|
+
end
|
58
|
+
}.to change { job.sub_jobs.size }.from(1).to(2)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'does not add the same sub-job to the list' do
|
62
|
+
expect {
|
63
|
+
class MyJob
|
64
|
+
sub_job(:my_sub_job) { MySubJob.new }
|
65
|
+
end
|
66
|
+
}.not_to change { job.sub_jobs.size }
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'raises an error if the return value is not a Remi job' do
|
70
|
+
class MyJob
|
71
|
+
sub_job(:my_sub_job) { 'something' }
|
72
|
+
end
|
73
|
+
expect { job.my_sub_job.job }.to raise_error ArgumentError
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns a Remi job' do
|
77
|
+
expect(job.my_sub_job.job).to be_a Remi::Job
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
describe '.transform' do
|
83
|
+
before do
|
84
|
+
class MyJob
|
85
|
+
transform :my_transform do
|
86
|
+
'I am a transform'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'adds a transform to the list of transforms' do
|
92
|
+
expect(job.transforms).to eq [:my_transform]
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'gives the transform a name' do
|
96
|
+
expect(job.my_transform.name).to eq :my_transform
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'appends a newly defined transform to the list of transforms' do
|
100
|
+
expect {
|
101
|
+
class MyJob
|
102
|
+
transform :my_transform2 do
|
103
|
+
'I am another transform'
|
104
|
+
end
|
105
|
+
end
|
106
|
+
}.to change { job.transforms.size }.from(1).to(2)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'does not add the same transform to the list' do
|
110
|
+
expect {
|
111
|
+
class MyJob
|
112
|
+
transform :my_transform do
|
113
|
+
'I am a modified transform'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
}.not_to change { job.transforms.size }
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'returns a transform' do
|
120
|
+
expect(job.my_transform).to be_a Job::Transform
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'returns a transform with the context of the job' do
|
124
|
+
expect(job.my_transform.context).to eq job
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
describe '.sub_transform' do
|
130
|
+
before do
|
131
|
+
class MyJob
|
132
|
+
sub_transform :my_sub_transform do
|
133
|
+
'I am a sub_transform'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'does not add a transform to the list of transforms' do
|
139
|
+
expect(job.transforms.size).to eq 0
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'returns a transform' do
|
143
|
+
expect(job.my_sub_transform).to be_a Job::Transform
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'returns a transform with the context of the job' do
|
147
|
+
expect(job.my_sub_transform.context).to eq job
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '.source' do
|
152
|
+
before do
|
153
|
+
class MyJob
|
154
|
+
source :my_source do
|
155
|
+
'I am a source'
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'adds a data source to the list of data sources' do
|
161
|
+
expect(job.sources).to eq [:my_source]
|
162
|
+
end
|
163
|
+
|
164
|
+
it 'gives the data source a name' do
|
165
|
+
expect(job.my_source.name).to eq :my_source
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'appends a newly defined data source to the list of data sources' do
|
169
|
+
expect {
|
170
|
+
class MyJob
|
171
|
+
source :my_source2 do
|
172
|
+
'I am another source'
|
173
|
+
end
|
174
|
+
end
|
175
|
+
}.to change { job.sources.size }.from(1).to(2)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'does not add the same data source to the list' do
|
179
|
+
expect {
|
180
|
+
class MyJob
|
181
|
+
source :my_source do
|
182
|
+
'I am a modified source'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
}.not_to change { job.sources.size }
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'returns a data source' do
|
189
|
+
expect(job.my_source).to be_a DataSource
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'returns a data soruce with the context of the job' do
|
193
|
+
expect(job.my_source.context).to eq job
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'does not require a block' do
|
197
|
+
class MyJob
|
198
|
+
source :another_source
|
199
|
+
end
|
200
|
+
expect(job.sources).to include :another_source
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe '.target' do
|
205
|
+
before do
|
206
|
+
class MyJob
|
207
|
+
target :my_target do
|
208
|
+
'I am a target'
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'adds a data target to the list of data targets' do
|
214
|
+
expect(job.targets).to eq [:my_target]
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'gives the data target a name' do
|
218
|
+
expect(job.my_target.name).to eq :my_target
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'appends a newly defined data target to the list of data targets' do
|
222
|
+
expect {
|
223
|
+
class MyJob
|
224
|
+
target :my_target2 do
|
225
|
+
'I am another target'
|
226
|
+
end
|
227
|
+
end
|
228
|
+
}.to change { job.targets.size }.from(1).to(2)
|
229
|
+
end
|
230
|
+
|
231
|
+
it 'does not add the same data target to the list' do
|
232
|
+
expect {
|
233
|
+
class MyJob
|
234
|
+
target :my_target do
|
235
|
+
'I am a modified target'
|
236
|
+
end
|
237
|
+
end
|
238
|
+
}.not_to change { job.targets.size }
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'returns a data target' do
|
242
|
+
expect(job.my_target).to be_a DataTarget
|
243
|
+
end
|
244
|
+
|
245
|
+
it 'returns a data soruce with the context of the job' do
|
246
|
+
expect(job.my_target.context).to eq job
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'does not require a block' do
|
250
|
+
class MyJob
|
251
|
+
target :another_target
|
252
|
+
end
|
253
|
+
expect(job.targets).to include :another_target
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
context '#params' do
|
260
|
+
before do
|
261
|
+
class MyJob
|
262
|
+
param(:my_param) { 'I am my_param' }
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'defined as part of class definition' do
|
267
|
+
it 'raises an error when the parameter is not defined' do
|
268
|
+
expect { job.params[:other_param] }.to raise_error ArgumentError
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'defined at instantiation' do
|
273
|
+
let(:job) { MyJob.new(my_param: 'instantiated') }
|
274
|
+
|
275
|
+
it 'has a value that can be overwritten' do
|
276
|
+
expect(job.params[:my_param]).to eq 'instantiated'
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'does not affect the values of other instances' do
|
280
|
+
job
|
281
|
+
other_job = MyJob.new
|
282
|
+
expect(other_job.params[:my_param]).to eq 'I am my_param'
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
context '#work_dir', skip: 'TODO' do
|
288
|
+
it 'does something awesome'
|
289
|
+
end
|
290
|
+
|
291
|
+
context '#logger', skip: 'TODO' do
|
292
|
+
it 'does something awesome'
|
293
|
+
end
|
294
|
+
|
295
|
+
context '#execute' do
|
296
|
+
before do
|
297
|
+
class MyJob
|
298
|
+
transform :transform_one do
|
299
|
+
end
|
300
|
+
|
301
|
+
transform :transform_two do
|
302
|
+
end
|
303
|
+
|
304
|
+
target :target_one do
|
305
|
+
end
|
306
|
+
|
307
|
+
target :target_two do
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
it 'executes all transforms' do
|
313
|
+
expect(job).to receive(:execute_transforms)
|
314
|
+
job.execute
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'executes load all targets' do
|
318
|
+
expect(job).to receive(:execute_load_targets)
|
319
|
+
job.execute
|
320
|
+
end
|
321
|
+
|
322
|
+
context '#execute(:transforms)' do
|
323
|
+
it 'executes all transforms' do
|
324
|
+
[:transform_one, :transform_two].each do |tform_name|
|
325
|
+
tform = instance_double(Job::Transform)
|
326
|
+
expect(tform).to receive(:execute)
|
327
|
+
expect(job).to receive(tform_name) .and_return(tform)
|
328
|
+
end
|
329
|
+
|
330
|
+
job.execute(:transforms)
|
331
|
+
end
|
332
|
+
|
333
|
+
it 'does not load all targets' do
|
334
|
+
expect(job).not_to receive(:execute_load_targets)
|
335
|
+
job.execute(:transforms)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
context '#execute(:load_targets)' do
|
340
|
+
it 'loads all targets' do
|
341
|
+
[:target_one, :target_two].each do |target_name|
|
342
|
+
target = instance_double(DataTarget)
|
343
|
+
expect(target).to receive(:load)
|
344
|
+
expect(job).to receive(target_name) .and_return(target)
|
345
|
+
end
|
346
|
+
|
347
|
+
job.execute(:load_targets)
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'does not execute all transforms' do
|
351
|
+
expect(job).not_to receive(:execute_transforms)
|
352
|
+
job.execute(:load_targets)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context 'inheritance' do
|
358
|
+
before do
|
359
|
+
Object.send(:remove_const, :MyInheritedJob) if Object.constants.include?(:MyInheritedJob)
|
360
|
+
class MyJob
|
361
|
+
param(:my_param) { 'I am my_param' }
|
362
|
+
source :my_source
|
363
|
+
target :my_target
|
364
|
+
transform(:my_transform) { }
|
365
|
+
sub_job(:my_sub_job) { }
|
366
|
+
end
|
367
|
+
|
368
|
+
class MyInheritedJob < MyJob; end
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'inherits a copy of the job parameters' do
|
372
|
+
expect(MyInheritedJob.params.to_h.keys).to eq [:my_param]
|
373
|
+
expect(MyInheritedJob.params.object_id).not_to eq MyJob.params.object_id
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'inherits a copy of the sources' do
|
377
|
+
expect(MyInheritedJob.sources).to eq [:my_source]
|
378
|
+
expect(MyInheritedJob.sources.object_id).not_to eq MyJob.sources.object_id
|
379
|
+
end
|
380
|
+
|
381
|
+
it 'inherits a copy of the targets' do
|
382
|
+
expect(MyInheritedJob.targets).to eq [:my_target]
|
383
|
+
expect(MyInheritedJob.targets.object_id).not_to eq MyJob.targets.object_id
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'inherits a copy of the transforms' do
|
387
|
+
expect(MyInheritedJob.transforms).to eq [:my_transform]
|
388
|
+
expect(MyInheritedJob.transforms.object_id).not_to eq MyJob.transforms.object_id
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'inherits a copy of the sub_jobs' do
|
392
|
+
expect(MyInheritedJob.sub_jobs).to eq [:my_sub_job]
|
393
|
+
expect(MyInheritedJob.sub_jobs.object_id).not_to eq MyJob.sub_jobs.object_id
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
|
399
|
+
describe Job::Parameters do
|
400
|
+
let(:params) { Job::Parameters.new }
|
401
|
+
|
402
|
+
context '#[]' do
|
403
|
+
let(:some_method) { double('some_method') }
|
404
|
+
|
405
|
+
before do
|
406
|
+
allow(some_method).to receive(:poke) { 'poked' }
|
407
|
+
scoped_some_method = some_method
|
408
|
+
params.__define__(:my_param) { scoped_some_method.poke }
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'fails if the parameter has not been defined' do
|
412
|
+
expect { params[:not_defined] }.to raise_error ArgumentError
|
413
|
+
end
|
414
|
+
|
415
|
+
it 'returns the evaluated value of the parameter' do
|
416
|
+
expect(params[:my_param]).to eq 'poked'
|
417
|
+
end
|
418
|
+
|
419
|
+
it 'does not evaluate the parameter block on subsequent calls' do
|
420
|
+
expect(some_method).to receive(:poke).once
|
421
|
+
params[:my_param]
|
422
|
+
params[:my_param]
|
423
|
+
end
|
424
|
+
|
425
|
+
it 'evaluates parameters in the context defined' do
|
426
|
+
params.__define__(:poke_context) { poke }
|
427
|
+
|
428
|
+
some_context = double('some_context')
|
429
|
+
allow(some_context).to receive(:poke) { 'poked some_context' }
|
430
|
+
params.context = some_context
|
431
|
+
|
432
|
+
expect(params[:poke_context]).to eq 'poked some_context'
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
context '#[]=' do
|
437
|
+
it 'set the value of the parameter' do
|
438
|
+
params[:my_param] = 'my boring parameter'
|
439
|
+
expect(params[:my_param]).to eq 'my boring parameter'
|
440
|
+
end
|
441
|
+
|
442
|
+
it 'overwrites an existing parameter' do
|
443
|
+
params[:my_param] = 'my boring parameter'
|
444
|
+
params[:my_param] = 'my fun parameter'
|
445
|
+
expect(params[:my_param]).to eq 'my fun parameter'
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
context '#to_h' do
|
450
|
+
it 'returns the parameters hash' do
|
451
|
+
expect(params.to_h).to be_a Hash
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
context '#clone' do
|
456
|
+
it 'creates a new parameter hash' do
|
457
|
+
new_params = params.clone
|
458
|
+
expect(new_params.to_h).not_to be params.to_h
|
459
|
+
end
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
|
465
|
+
describe Job::SubJob do
|
466
|
+
let(:sub_job) { MySubJob.new }
|
467
|
+
let(:job_sub_job) do
|
468
|
+
scoped_sub_job = sub_job
|
469
|
+
Job::SubJob.new { scoped_sub_job }
|
470
|
+
end
|
471
|
+
|
472
|
+
context '#job' do
|
473
|
+
it 'returns the job instance for the sub job' do
|
474
|
+
expect(job_sub_job.job).to eq sub_job
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
context '#fields' do
|
479
|
+
before do
|
480
|
+
class MySubJob
|
481
|
+
source :some_source do
|
482
|
+
fields({ :some_field => {} })
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'gets the fields from the specified data subject' do
|
488
|
+
expect(job_sub_job.fields(:some_source)).to eq({ :some_field => {} })
|
489
|
+
end
|
490
|
+
end
|
491
|
+
|
492
|
+
context '#execute' do
|
493
|
+
it 'executes the sub job' do
|
494
|
+
expect(sub_job).to receive(:execute)
|
495
|
+
job_sub_job.execute
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context '#execute_transforms' do
|
500
|
+
it 'executes the sub job transforms' do
|
501
|
+
expect(sub_job).to receive(:execute) .with(:transforms)
|
502
|
+
job_sub_job.execute_transforms
|
503
|
+
end
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
end
|