remi 0.2.42 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/jobs/json_job.rb
CHANGED
@@ -1,21 +1,25 @@
|
|
1
1
|
require_relative 'all_jobs_shared'
|
2
2
|
|
3
|
-
class JsonJob
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
class JsonJob < Remi::Job
|
4
|
+
source :source_data do
|
5
|
+
fields(
|
6
|
+
{
|
7
|
+
:json_array => { type: :json },
|
8
|
+
:json_hash => { type: :json }
|
9
|
+
}
|
10
|
+
)
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
fields
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
target :target_data do
|
14
|
+
fields(
|
15
|
+
{
|
16
|
+
:second_element => {},
|
17
|
+
:name_field => {}
|
18
|
+
}
|
19
|
+
)
|
20
|
+
end
|
17
21
|
|
18
|
-
|
22
|
+
transform :main do
|
19
23
|
Remi::SourceToTargetMap.apply(source_data.df, target_data.df, source_metadata: source_data.fields) do
|
20
24
|
map source(:json_array) .target(:second_element)
|
21
25
|
.transform(->(values) { values[1] })
|
data/jobs/metadata_job.rb
CHANGED
@@ -1,37 +1,42 @@
|
|
1
1
|
require_relative 'all_jobs_shared'
|
2
2
|
ENV['TZ'] = 'UTC'
|
3
3
|
|
4
|
-
class MetadataJob
|
5
|
-
|
4
|
+
class MetadataJob < Remi::Job
|
5
|
+
source :source_data do
|
6
|
+
fields(
|
7
|
+
{
|
8
|
+
:activity_id => { from: 'in', in: true, cdc_type: 2 },
|
9
|
+
:student_id => { from: 'in', in: true, type: :string, cdc_type: 2 },
|
10
|
+
:student_dob => { from: 'in', in: true, type: :date, in_format: '%m/%d/%Y', out_format: '%Y-%m-%d', cdc_type: 2 },
|
11
|
+
:activity_type => { from: 'in', in: true, type: :string, valid_values: ['A', 'B', 'C'], cdc_type: 2 },
|
12
|
+
:activity_counter => { from: 'in', in: true, type: :integer, cdc_type: 2 },
|
13
|
+
:activity_score => { from: 'in', in: true, type: :float, cdc_type: 2 },
|
14
|
+
:activity_cost => { from: 'in', in: true, type: :decimal, precision: 8, scale: 2, cdc_type: 2 },
|
15
|
+
:activity_date => { from: 'in', in: true, type: :datetime, in_format: '%m/%d/%Y %H:%M:%S', out_format: '%Y-%m-%dT%H:%M:%S', cdc_type: 2 },
|
16
|
+
:source_filename => { from: 'in', in: true, type: :string, cdc_type: 1 }
|
17
|
+
}
|
18
|
+
)
|
19
|
+
end
|
6
20
|
|
7
|
-
|
8
|
-
|
9
|
-
:activity_id => { from: 'in', in: true, cdc_type: 2 },
|
10
|
-
:student_id => { from: 'in', in: true, type: :string, cdc_type: 2 },
|
11
|
-
:student_dob => { from: 'in', in: true, type: :date, in_format: '%m/%d/%Y', out_format: '%Y-%m-%d', cdc_type: 2 },
|
12
|
-
:activity_type => { from: 'in', in: true, type: :string, valid_values: ['A', 'B', 'C'], cdc_type: 2 },
|
13
|
-
:activity_counter => { from: 'in', in: true, type: :integer, cdc_type: 2 },
|
14
|
-
:activity_score => { from: 'in', in: true, type: :float, cdc_type: 2 },
|
15
|
-
:activity_cost => { from: 'in', in: true, type: :decimal, precision: 8, scale: 2, cdc_type: 2 },
|
16
|
-
:activity_date => { from: 'in', in: true, type: :datetime, in_format: '%m/%d/%Y %H:%M:%S', out_format: '%Y-%m-%dT%H:%M:%S', cdc_type: 2 },
|
17
|
-
:source_filename => { from: 'in', in: true, type: :string, cdc_type: 1 }
|
18
|
-
}
|
21
|
+
target :target_data do
|
22
|
+
encoder Remi::Encoder::CsvFile.new path: "#{Remi::Settings.work_dir}/target_data.csv"
|
19
23
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
24
|
+
fields(
|
25
|
+
{
|
26
|
+
:activity_id => { from: 'out', out: true },
|
27
|
+
:student_id => { from: 'out', out: true, type: :string },
|
28
|
+
:student_dob => { from: 'out', out: true, type: :date, in_format: '%m/%d/%Y', out_format: '%Y-%m-%d' },
|
29
|
+
:activity_type => { from: 'out', out: true, type: :string, valid_values: ['A', 'B', 'C'] },
|
30
|
+
:activity_counter => { from: 'out', out: true, type: :integer },
|
31
|
+
:activity_score => { from: 'out', out: true, type: :float },
|
32
|
+
:activity_cost => { from: 'out', out: true, type: :decimal, precision: 8, scale: 2 },
|
33
|
+
:activity_date => { from: 'out', out: true, type: :datetime, in_format: '%m/%d/%Y %H:%M:%S', out_format: '%Y-%m-%dT%H:%M:%S' },
|
34
|
+
:source_filename => { from: 'out', out: true, type: :string, cdc_type: 1 }
|
35
|
+
}
|
36
|
+
)
|
37
|
+
end
|
33
38
|
|
34
|
-
|
39
|
+
transform :main do
|
35
40
|
source_data.enforce_types
|
36
41
|
|
37
42
|
Remi::SourceToTargetMap.apply(source_data.df, target_data.df, source_metadata: source_data.fields, target_metadata: target_data.fields) do
|
data/jobs/parameters_job.rb
CHANGED
@@ -1,22 +1,25 @@
|
|
1
1
|
require_relative 'all_jobs_shared'
|
2
2
|
|
3
|
-
class ParametersJob
|
4
|
-
|
3
|
+
class ParametersJob < Remi::Job
|
4
|
+
param(:myparam) {}
|
5
|
+
param(:test_parameter) { "my test parameter value" }
|
5
6
|
|
6
|
-
|
7
|
+
source :source_data do
|
8
|
+
fields(
|
9
|
+
{
|
10
|
+
:parameter_name => {}
|
11
|
+
}
|
12
|
+
)
|
13
|
+
end
|
7
14
|
|
8
|
-
|
9
|
-
fields: {
|
10
|
-
:parameter_name => {}
|
11
|
-
}
|
12
|
-
define_target :target_data, Remi::DataTarget::DataFrame
|
15
|
+
target :target_data
|
13
16
|
|
14
|
-
|
17
|
+
transform :main do
|
15
18
|
Remi::SourceToTargetMap.apply(source_data.df, target_data.df) do
|
16
19
|
map target(:myparam)
|
17
|
-
.transform(Remi::Transform::Constant.new(params[:myparam]))
|
20
|
+
.transform(Remi::Transform::Constant.new(job.params[:myparam]))
|
18
21
|
map source(:parameter_name) .target(:parameter_name)
|
19
|
-
.transform(->(v) { params[v.to_sym] })
|
22
|
+
.transform(->(v) { job.params[v.to_sym] })
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
data/jobs/sample_job.rb
CHANGED
@@ -1,88 +1,118 @@
|
|
1
1
|
# This is an example Remi job that was auto-generated by Remi.
|
2
2
|
require_relative 'all_jobs_shared'
|
3
|
-
require 'remi/
|
4
|
-
|
5
|
-
class SampleJob
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
3
|
+
require 'remi/data_subjects/salesforce'
|
4
|
+
|
5
|
+
class SampleJob < AllJobsShared
|
6
|
+
|
7
|
+
param :program_name_lookup do
|
8
|
+
RegexSieve.new(
|
9
|
+
{
|
10
|
+
/^BIO$/ => "Biology",
|
11
|
+
/^Fake Biology$/ => nil,
|
12
|
+
/(?:B|Microb)iology/ => "Biology",
|
13
|
+
/^CHEM$/ => "Chemistry",
|
14
|
+
/Chemistry/ => "Chemistry",
|
15
|
+
/Physics/ => "Physics"
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
source :existing_contacts do
|
21
|
+
extractor Remi::Extractor::Salesforce.new(
|
22
|
+
object: :Contact,
|
23
|
+
credentials: params[:salesforce_credentials],
|
24
|
+
api: :bulk,
|
25
|
+
query: <<-EOQ
|
26
|
+
SELECT
|
27
|
+
Id,
|
28
|
+
External_ID__c,
|
29
|
+
IsActive,
|
30
|
+
CreatedDate
|
31
|
+
FROM
|
32
|
+
Contact
|
33
|
+
EOQ
|
34
|
+
)
|
35
|
+
parser Remi::Parser::Salesforce.new
|
36
|
+
|
37
|
+
field_symbolizer :salesforce
|
38
|
+
fields(
|
39
|
+
{
|
40
|
+
:Id => {},
|
41
|
+
:External_ID__c => {},
|
42
|
+
:IsActive => { type: :boolean },
|
43
|
+
:CreatedDate => { type: :date, in_format: '%Y-%m-%d %H:%M:%S' }
|
44
|
+
}
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
source :sample_file do
|
49
|
+
extractor Remi::Extractor::SftpFile.new(
|
29
50
|
credentials: params[:sftp],
|
30
51
|
remote_path: '/',
|
31
52
|
pattern: /^SampleFile_(\d+)\.txt/,
|
32
53
|
most_recent_only: true
|
33
|
-
)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
credentials: params[:salesforce_credentials],
|
66
|
-
object: :Contact,
|
67
|
-
operation: :create,
|
68
|
-
api: :bulk
|
69
|
-
|
70
|
-
define_param :program_name_lookup, RegexSieve.new({
|
71
|
-
/^BIO$/ => "Biology",
|
72
|
-
/^Fake Biology$/ => nil,
|
73
|
-
/(?:B|Microb)iology/ => "Biology",
|
74
|
-
/^CHEM$/ => "Chemistry",
|
75
|
-
/Chemistry/ => "Chemistry",
|
76
|
-
/Physics/ => "Physics"
|
77
|
-
})
|
78
|
-
|
79
|
-
define_transform :map_common_fields, sources: [:sample_file, :existing_contacts], targets: :all_contacts do
|
54
|
+
)
|
55
|
+
|
56
|
+
parser Remi::Parser::CsvFile.new(
|
57
|
+
csv_options: {
|
58
|
+
headers: true,
|
59
|
+
col_sep: ",",
|
60
|
+
encoding: "ISO-8859-1:UTF-8"
|
61
|
+
}
|
62
|
+
)
|
63
|
+
|
64
|
+
fields(
|
65
|
+
{
|
66
|
+
:student_id => {},
|
67
|
+
:school_id => {},
|
68
|
+
:school_name => {},
|
69
|
+
:program => {},
|
70
|
+
:last_name => {},
|
71
|
+
:first_name => {},
|
72
|
+
:current_email => {},
|
73
|
+
:mailing_address_line_1 => {},
|
74
|
+
:mailing_address_line_2 => {},
|
75
|
+
:mailing_city => {},
|
76
|
+
:mailing_state => {},
|
77
|
+
:mailing_postal_code => {},
|
78
|
+
:birthdate => { type: :date, in_format: '%m/%d/%Y'},
|
79
|
+
:applied_date => { type: :date, in_format: '%m/%d/%Y'}
|
80
|
+
}
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
target :all_contacts
|
80
86
|
|
87
|
+
target :contact_updates do
|
88
|
+
encoder Remi::Encoder::Salesforce.new
|
89
|
+
loader Remi::Loader::Salesforce.new(
|
90
|
+
credentials: params[:salesforce_credentials],
|
91
|
+
object: :Contact,
|
92
|
+
operation: :update,
|
93
|
+
api: :bulk
|
94
|
+
)
|
95
|
+
field_symbolizer :salesforce
|
96
|
+
end
|
97
|
+
|
98
|
+
target :contact_creates do
|
99
|
+
encoder Remi::Encoder::Salesforce.new
|
100
|
+
loader Remi::Loader::Salesforce.new(
|
101
|
+
credentials: params[:salesforce_credentials],
|
102
|
+
object: :Contact,
|
103
|
+
operation: :create,
|
104
|
+
api: :bulk
|
105
|
+
)
|
106
|
+
field_symbolizer :salesforce
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
transform :map_common_fields do
|
81
111
|
# Exclude all source records with an invalid program name
|
82
112
|
all_contacts.df = sample_file.df.dup
|
83
113
|
Remi::SourceToTargetMap.apply(all_contacts.df) do
|
84
114
|
map source(:program) .target(:Major__c)
|
85
|
-
.transform(Remi::Transform::Lookup.new(params[:program_name_lookup]))
|
115
|
+
.transform(Remi::Transform::Lookup.new(job.params[:program_name_lookup]))
|
86
116
|
end
|
87
117
|
all_contacts.df = all_contacts.df.where(all_contacts.df[:Major__c].not_eq(nil))
|
88
118
|
|
@@ -102,8 +132,7 @@ class SampleJob
|
|
102
132
|
end
|
103
133
|
|
104
134
|
|
105
|
-
|
106
|
-
|
135
|
+
transform :map_creates do
|
107
136
|
work_contact_creates = all_contacts.df.where(all_contacts.df[:Id].eq(nil))
|
108
137
|
|
109
138
|
Remi::SourceToTargetMap.apply(work_contact_creates) do
|
@@ -166,7 +195,7 @@ class SampleJob
|
|
166
195
|
]
|
167
196
|
end
|
168
197
|
|
169
|
-
|
198
|
+
transform :map_updates do
|
170
199
|
contact_updates.df = all_contacts.df[
|
171
200
|
:Id,
|
172
201
|
:Major__c
|
@@ -1,18 +1,19 @@
|
|
1
1
|
require_relative 'all_jobs_shared'
|
2
2
|
|
3
|
-
class SftpFileTargetJob
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
class SftpFileTargetJob < Remi::Job
|
4
|
+
target :some_file do
|
5
|
+
encoder Remi::Encoder::CsvFile.new
|
6
|
+
loader Remi::Loader::SftpFile.new(
|
7
|
+
credentials: {
|
8
|
+
host: 'example.com',
|
9
|
+
username: 'user',
|
10
|
+
password: 'secret'
|
11
|
+
},
|
12
|
+
local_path: "#{Remi::Settings.work_dir}/some_file.csv",
|
13
|
+
remote_path: "some_file_#{DateTime.current.strftime('%Y%m%d')}.csv"
|
14
|
+
)
|
15
|
+
end
|
15
16
|
|
16
|
-
|
17
|
+
transform :main do
|
17
18
|
end
|
18
19
|
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require_relative 'all_jobs_shared'
|
2
|
+
|
3
|
+
class BeersJob < Remi::Job
|
4
|
+
source :beers do
|
5
|
+
extractor Remi::Extractor::DataFrame.new(
|
6
|
+
data: [
|
7
|
+
[ 'Baerlic', 'IPA' ],
|
8
|
+
[ 'Ex Novo', 'Red' ]
|
9
|
+
]
|
10
|
+
)
|
11
|
+
parser Remi::Parser::DataFrame.new
|
12
|
+
fields(
|
13
|
+
{
|
14
|
+
brewer: {},
|
15
|
+
style: {}
|
16
|
+
}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
transform :main do
|
21
|
+
# In the real world, add lots of complex stuff here, possibly grabbing
|
22
|
+
# from multiple sources.
|
23
|
+
beers.df
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ZombifyJob < Remi::Job
|
28
|
+
source :beers
|
29
|
+
target :zombie_beers
|
30
|
+
|
31
|
+
transform :main do
|
32
|
+
Remi::SourceToTargetMap.apply(beers.df, zombie_beers.df) do
|
33
|
+
map source(:brewer) .target(:brewer)
|
34
|
+
.transform(Remi::Transform::Prefix.new('Zombie '))
|
35
|
+
map source(:style) .target(:style)
|
36
|
+
.transform(Remi::Transform::Prefix.new('Zombie '))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class SubJobExampleJob < Remi::Job
|
42
|
+
sub_job(:beers_job) { BeersJob.new }
|
43
|
+
sub_job(:zombify_job) { ZombifyJob.new }
|
44
|
+
|
45
|
+
# This originates from a source in the sub job
|
46
|
+
source :beer_fridge do
|
47
|
+
extractor Remi::Extractor::SubJob.new(
|
48
|
+
sub_job: beers_job,
|
49
|
+
data_subject: :beers
|
50
|
+
)
|
51
|
+
fields beers_job.fields :beers
|
52
|
+
end
|
53
|
+
|
54
|
+
# This target is used as a source in the sub job
|
55
|
+
target :beers_to_zombify do
|
56
|
+
loader Remi::Loader::SubJob.new(
|
57
|
+
sub_job: zombify_job,
|
58
|
+
data_subject: :beers
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
# This source is obtained from the target of the sub job
|
63
|
+
source :zombie_fridge do
|
64
|
+
extractor Remi::Extractor::SubJob.new(
|
65
|
+
sub_job: zombify_job,
|
66
|
+
data_subject: :zombie_beers
|
67
|
+
)
|
68
|
+
fields zombify_job.fields :zombie_beers
|
69
|
+
end
|
70
|
+
|
71
|
+
# These are the ultimate targets of this job
|
72
|
+
target :just_beers
|
73
|
+
target :zombified_beers
|
74
|
+
|
75
|
+
transform :zombification do
|
76
|
+
# Sub jobs must be executed before their sources are available
|
77
|
+
beers_job.execute
|
78
|
+
just_beers.df = beer_fridge.df
|
79
|
+
|
80
|
+
# Sub job targets must be loaded before they are available to subjobs
|
81
|
+
beers_to_zombify.df = just_beers.df
|
82
|
+
beers_to_zombify.load
|
83
|
+
zombify_job.execute
|
84
|
+
zombified_beers.df = zombie_fridge.df
|
85
|
+
end
|
86
|
+
end
|