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.
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