remi 0.2.42 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +7 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +13 -26
  5. data/README.md +1 -1
  6. data/features/step_definitions/remi_step.rb +33 -13
  7. data/features/sub_job_example.feature +24 -0
  8. data/features/sub_transform_example.feature +35 -0
  9. data/features/sub_transform_many_to_many.feature +49 -0
  10. data/features/support/env_app.rb +1 -1
  11. data/jobs/all_jobs_shared.rb +19 -16
  12. data/jobs/copy_source_job.rb +11 -9
  13. data/jobs/csv_file_target_job.rb +10 -9
  14. data/jobs/json_job.rb +18 -14
  15. data/jobs/metadata_job.rb +33 -28
  16. data/jobs/parameters_job.rb +14 -11
  17. data/jobs/sample_job.rb +106 -77
  18. data/jobs/sftp_file_target_job.rb +14 -13
  19. data/jobs/sub_job_example_job.rb +86 -0
  20. data/jobs/sub_transform_example_job.rb +43 -0
  21. data/jobs/sub_transform_many_to_many_job.rb +46 -0
  22. data/jobs/transforms/concatenate_job.rb +16 -12
  23. data/jobs/transforms/data_frame_sieve_job.rb +24 -19
  24. data/jobs/transforms/date_diff_job.rb +15 -11
  25. data/jobs/transforms/nvl_job.rb +16 -12
  26. data/jobs/transforms/parse_date_job.rb +17 -14
  27. data/jobs/transforms/partitioner_job.rb +27 -19
  28. data/jobs/transforms/prefix_job.rb +13 -10
  29. data/jobs/transforms/truncate_job.rb +14 -10
  30. data/jobs/transforms/truthy_job.rb +11 -8
  31. data/lib/remi.rb +25 -11
  32. data/lib/remi/data_frame.rb +4 -4
  33. data/lib/remi/data_frame/daru.rb +1 -37
  34. data/lib/remi/data_subject.rb +234 -48
  35. data/lib/remi/data_subjects/csv_file.rb +171 -0
  36. data/lib/remi/data_subjects/data_frame.rb +106 -0
  37. data/lib/remi/data_subjects/file_system.rb +115 -0
  38. data/lib/remi/data_subjects/local_file.rb +109 -0
  39. data/lib/remi/data_subjects/none.rb +31 -0
  40. data/lib/remi/data_subjects/postgres.rb +186 -0
  41. data/lib/remi/data_subjects/s3_file.rb +84 -0
  42. data/lib/remi/data_subjects/salesforce.rb +211 -0
  43. data/lib/remi/data_subjects/sftp_file.rb +196 -0
  44. data/lib/remi/data_subjects/sub_job.rb +50 -0
  45. data/lib/remi/dsl.rb +74 -0
  46. data/lib/remi/encoder.rb +45 -0
  47. data/lib/remi/extractor.rb +21 -0
  48. data/lib/remi/field_symbolizers.rb +1 -0
  49. data/lib/remi/job.rb +279 -113
  50. data/lib/remi/job/parameters.rb +90 -0
  51. data/lib/remi/job/sub_job.rb +35 -0
  52. data/lib/remi/job/transform.rb +165 -0
  53. data/lib/remi/loader.rb +22 -0
  54. data/lib/remi/monkeys/daru.rb +4 -0
  55. data/lib/remi/parser.rb +44 -0
  56. data/lib/remi/testing/business_rules.rb +17 -23
  57. data/lib/remi/testing/data_stub.rb +2 -2
  58. data/lib/remi/version.rb +1 -1
  59. data/remi.gemspec +3 -0
  60. data/spec/data_subject_spec.rb +475 -11
  61. data/spec/data_subjects/csv_file_spec.rb +69 -0
  62. data/spec/data_subjects/data_frame_spec.rb +52 -0
  63. data/spec/{extractor → data_subjects}/file_system_spec.rb +0 -0
  64. data/spec/{extractor → data_subjects}/local_file_spec.rb +0 -0
  65. data/spec/data_subjects/none_spec.rb +41 -0
  66. data/spec/data_subjects/postgres_spec.rb +80 -0
  67. data/spec/{extractor → data_subjects}/s3_file_spec.rb +0 -0
  68. data/spec/data_subjects/salesforce_spec.rb +117 -0
  69. data/spec/{extractor → data_subjects}/sftp_file_spec.rb +16 -0
  70. data/spec/data_subjects/sub_job_spec.rb +33 -0
  71. data/spec/encoder_spec.rb +38 -0
  72. data/spec/extractor_spec.rb +11 -0
  73. data/spec/fixtures/sf_bulk_helper_stubs.rb +443 -0
  74. data/spec/job/transform_spec.rb +257 -0
  75. data/spec/job_spec.rb +507 -0
  76. data/spec/loader_spec.rb +11 -0
  77. data/spec/parser_spec.rb +38 -0
  78. data/spec/sf_bulk_helper_spec.rb +117 -0
  79. data/spec/testing/data_stub_spec.rb +5 -3
  80. metadata +109 -27
  81. data/features/aggregate.feature +0 -42
  82. data/jobs/aggregate_job.rb +0 -31
  83. data/jobs/transforms/transform_jobs.rb +0 -4
  84. data/lib/remi/data_subject/csv_file.rb +0 -162
  85. data/lib/remi/data_subject/data_frame.rb +0 -52
  86. data/lib/remi/data_subject/postgres.rb +0 -134
  87. data/lib/remi/data_subject/salesforce.rb +0 -136
  88. data/lib/remi/data_subject/sftp_file.rb +0 -65
  89. data/lib/remi/extractor/file_system.rb +0 -92
  90. data/lib/remi/extractor/local_file.rb +0 -43
  91. data/lib/remi/extractor/s3_file.rb +0 -57
  92. data/lib/remi/extractor/sftp_file.rb +0 -83
  93. data/spec/data_subject/csv_file_spec.rb +0 -79
  94. 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
@@ -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