remi 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.bundle/config +1 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +45 -5
- data/README.md +245 -0
- data/features/step_definitions/remi_step.rb +16 -0
- data/jobs/sub_job_example_job.rb +5 -5
- data/lib/remi.rb +4 -1
- data/lib/remi/data_subject.rb +10 -1
- data/lib/remi/data_subjects/file_system.rb +31 -1
- data/lib/remi/data_subjects/gsheet.rb +140 -0
- data/lib/remi/data_subjects/sftp_file.rb +1 -0
- data/lib/remi/data_subjects/sub_job.rb +13 -4
- data/lib/remi/encoder.rb +1 -1
- data/lib/remi/job.rb +9 -1
- data/lib/remi/job/parameters.rb +8 -3
- data/lib/remi/job/sub_job.rb +14 -8
- data/lib/remi/loader.rb +14 -2
- data/lib/remi/testing/business_rules.rb +12 -9
- data/lib/remi/transform.rb +9 -0
- data/lib/remi/version.rb +1 -1
- data/spec/data_subject_spec.rb +23 -5
- data/spec/data_subjects/file_system_spec.rb +43 -9
- data/spec/data_subjects/gsheet_spec.rb +133 -0
- data/spec/data_subjects/sub_job_spec.rb +40 -8
- data/spec/job_spec.rb +58 -15
- metadata +5 -2
data/lib/remi/loader.rb
CHANGED
@@ -4,11 +4,12 @@ module Remi
|
|
4
4
|
# define specific ways to load data.
|
5
5
|
class Loader
|
6
6
|
|
7
|
-
def initialize(*args, logger: Remi::Settings.logger, **kargs, &block)
|
7
|
+
def initialize(*args, context: nil, logger: Remi::Settings.logger, **kargs, &block)
|
8
|
+
@context = context
|
8
9
|
@logger = logger
|
9
10
|
end
|
10
11
|
|
11
|
-
attr_accessor :logger
|
12
|
+
attr_accessor :logger, :context
|
12
13
|
|
13
14
|
# Any child classes need to define a load method that loads data from
|
14
15
|
# the given dataframe into the target system.
|
@@ -18,5 +19,16 @@ module Remi
|
|
18
19
|
raise NoMethodError, "#{__method__} not defined for #{self.class.name}"
|
19
20
|
end
|
20
21
|
|
22
|
+
# If autoload is set to true, then any loaders are called at the moment
|
23
|
+
# a dataframe is assigned to a target (e.g., `my_target.df = some_df` will
|
24
|
+
# call `#load` on any loaders associated with `my_target`).
|
25
|
+
def autoload
|
26
|
+
false
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Remi::Fields] The fields defined in the context
|
30
|
+
def fields
|
31
|
+
context && context.respond_to?(:fields) ? context.fields : Remi::Fields.new({})
|
32
|
+
end
|
21
33
|
end
|
22
34
|
end
|
@@ -521,19 +521,22 @@ module Remi::Testing::BusinessRules
|
|
521
521
|
@table = table
|
522
522
|
end
|
523
523
|
|
524
|
+
def parse_formula(value)
|
525
|
+
parsed_value = ParseFormula.parse(value)
|
526
|
+
case parsed_value
|
527
|
+
when '\nil'
|
528
|
+
nil
|
529
|
+
else
|
530
|
+
parsed_value
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
524
534
|
def to_df(seed_hash, field_symbolizer:)
|
525
535
|
table_headers = @table.headers.map { |h| h.symbolize(field_symbolizer) }
|
526
536
|
df = Daru::DataFrame.new([], order: seed_hash.keys | table_headers)
|
527
537
|
@table.hashes.each do |example_row|
|
528
538
|
example_row_sym = example_row.reduce({}) do |h, (k,v)|
|
529
|
-
|
530
|
-
value = case formula_value
|
531
|
-
when '\nil'
|
532
|
-
nil
|
533
|
-
else
|
534
|
-
formula_value
|
535
|
-
end
|
536
|
-
h[k.symbolize(field_symbolizer)] = value
|
539
|
+
h[k.symbolize(field_symbolizer)] = parse_formula(v)
|
537
540
|
h
|
538
541
|
end
|
539
542
|
df.add_row(seed_hash.merge(example_row_sym))
|
@@ -546,7 +549,7 @@ module Remi::Testing::BusinessRules
|
|
546
549
|
def column_hash
|
547
550
|
@table.hashes.reduce({}) do |h, row|
|
548
551
|
row.each do |k,v|
|
549
|
-
(h[k.symbolize] ||= []) << v
|
552
|
+
(h[k.symbolize] ||= []) << parse_formula(v)
|
550
553
|
end
|
551
554
|
h
|
552
555
|
end
|
data/lib/remi/transform.rb
CHANGED
@@ -514,6 +514,11 @@ module Remi
|
|
514
514
|
type == :string ? '' : nil
|
515
515
|
end
|
516
516
|
|
517
|
+
def truthy(value)
|
518
|
+
@truthy ||= Truthy.new(allow_nils: false).to_proc
|
519
|
+
@truthy.call(value)
|
520
|
+
end
|
521
|
+
|
517
522
|
def transform(value)
|
518
523
|
if value.blank? && type != :json
|
519
524
|
blank_handler(value)
|
@@ -537,6 +542,10 @@ module Remi
|
|
537
542
|
else
|
538
543
|
value.is_a?(Hash) || value.is_a?(Array) ? value : JSON.parse(value)
|
539
544
|
end
|
545
|
+
when :boolean
|
546
|
+
# Ugh, there is a bug with Daru 0.1.4 that converts false values to nil when joining
|
547
|
+
# For now, we'll just standardize boolean values (#to_s)
|
548
|
+
truthy(value).to_s
|
540
549
|
else
|
541
550
|
raise ArgumentError, "Unknown type enforcement: #{type}"
|
542
551
|
end
|
data/lib/remi/version.rb
CHANGED
data/spec/data_subject_spec.rb
CHANGED
@@ -339,7 +339,9 @@ describe DataTarget do
|
|
339
339
|
|
340
340
|
before do
|
341
341
|
allow(my_loader).to receive(:load)
|
342
|
+
allow(my_loader).to receive(:context=)
|
342
343
|
allow(my_loader2).to receive(:load)
|
344
|
+
allow(my_loader2).to receive(:context=)
|
343
345
|
allow(my_encoder).to receive(:encode) .and_return 'encoded data'
|
344
346
|
end
|
345
347
|
|
@@ -378,7 +380,6 @@ describe DataTarget do
|
|
378
380
|
end
|
379
381
|
end
|
380
382
|
|
381
|
-
|
382
383
|
context '#field_symbolizer' do
|
383
384
|
context 'field_symbolizer called before encoder' do
|
384
385
|
let(:before_encoder) do
|
@@ -448,15 +449,15 @@ describe DataTarget do
|
|
448
449
|
end
|
449
450
|
|
450
451
|
context '#loader' do
|
451
|
-
before { data_target.loader
|
452
|
+
before { data_target.loader my_loader }
|
452
453
|
|
453
454
|
it 'adds a loader to the list of loaders' do
|
454
|
-
expect(data_target.loaders).to eq [
|
455
|
+
expect(data_target.loaders).to eq [my_loader]
|
455
456
|
end
|
456
457
|
|
457
458
|
it 'allows for multiple loaders to be defined' do
|
458
|
-
data_target.loader
|
459
|
-
expect(data_target.loaders).to eq [
|
459
|
+
data_target.loader my_loader2
|
460
|
+
expect(data_target.loaders).to eq [my_loader, my_loader2]
|
460
461
|
end
|
461
462
|
end
|
462
463
|
|
@@ -505,4 +506,21 @@ describe DataTarget do
|
|
505
506
|
end
|
506
507
|
end
|
507
508
|
end
|
509
|
+
|
510
|
+
context '#df=' do
|
511
|
+
before do
|
512
|
+
data_target.encoder my_encoder
|
513
|
+
data_target.loader my_loader
|
514
|
+
data_target.loader my_loader2
|
515
|
+
|
516
|
+
allow(my_loader).to receive(:autoload) { false }
|
517
|
+
allow(my_loader2).to receive(:autoload) { true }
|
518
|
+
end
|
519
|
+
|
520
|
+
it 'loads any loaders set to autoload' do
|
521
|
+
expect(my_loader).not_to receive :load
|
522
|
+
expect(my_loader2).to receive :load
|
523
|
+
data_target.df = Remi::DataFrame::Daru.new([])
|
524
|
+
end
|
525
|
+
end
|
508
526
|
end
|
@@ -5,15 +5,15 @@ describe Extractor::FileSystem do
|
|
5
5
|
now = Time.new
|
6
6
|
|
7
7
|
example_files = [
|
8
|
-
{ pathname: "pdir/ApplicantsA-9.csv",
|
9
|
-
{ pathname: "pdir/ApplicantsA-3.csv",
|
10
|
-
{ pathname: "pdir/ApplicantsA-5.csv",
|
11
|
-
{ pathname: "pdir/ApplicantsB-7.csv",
|
12
|
-
{ pathname: "pdir/ApplicantsB-6.csv",
|
13
|
-
{ pathname: "pdir/ApplicantsB-2.csv",
|
14
|
-
{ pathname: "pdir/ApplicantsB-2.txt",
|
15
|
-
{ pathname: "pdir/Apples.csv",
|
16
|
-
{ pathname: "otherdir/ApplicantsA-11.csv",
|
8
|
+
{ pathname: "pdir/ApplicantsA-9.csv", create_time: now - 10.minutes },
|
9
|
+
{ pathname: "pdir/ApplicantsA-3.csv", create_time: now - 5.minutes },
|
10
|
+
{ pathname: "pdir/ApplicantsA-5.csv", create_time: now - 1.minutes },
|
11
|
+
{ pathname: "pdir/ApplicantsB-7.csv", create_time: now - 10.minutes },
|
12
|
+
{ pathname: "pdir/ApplicantsB-6.csv", create_time: now - 5.minutes },
|
13
|
+
{ pathname: "pdir/ApplicantsB-2.csv", create_time: now - 1.minutes },
|
14
|
+
{ pathname: "pdir/ApplicantsB-2.txt", create_time: now - 0.minutes },
|
15
|
+
{ pathname: "pdir/Apples.csv", create_time: now - 1.minutes },
|
16
|
+
{ pathname: "otherdir/ApplicantsA-11.csv", create_time: now - 1.minutes },
|
17
17
|
]
|
18
18
|
|
19
19
|
remote_path = 'pdir'
|
@@ -89,6 +89,40 @@ describe Extractor::FileSystem do
|
|
89
89
|
end
|
90
90
|
|
91
91
|
|
92
|
+
context 'extracting the most recent file by create time' do
|
93
|
+
before do
|
94
|
+
@params.merge!({
|
95
|
+
most_recent_within_n: 1.hour,
|
96
|
+
most_recent_only: true
|
97
|
+
})
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'extracts the files within n hours of creation' do
|
101
|
+
expect(file_system.entries.map(&:name)).to match_array([
|
102
|
+
"ApplicantsB-2.txt"
|
103
|
+
])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'extracting all recent files by create time' do
|
108
|
+
before do
|
109
|
+
@params.merge!({
|
110
|
+
created_within: 0.02.hours,
|
111
|
+
most_recent_only: false
|
112
|
+
})
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'extracts the files within n hours of creation' do
|
116
|
+
puts @params
|
117
|
+
expect(file_system.entries.map(&:name)).to match_array([
|
118
|
+
"Apples.csv",
|
119
|
+
"ApplicantsA-5.csv",
|
120
|
+
"ApplicantsB-2.csv",
|
121
|
+
"ApplicantsB-2.txt"
|
122
|
+
])
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
92
126
|
context 'extracting files matching a pattern with a by group' do
|
93
127
|
before do
|
94
128
|
@params.merge!({
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'remi_spec'
|
2
|
+
require 'remi/data_subjects/gsheet'
|
3
|
+
|
4
|
+
describe Extractor::Gsheet do
|
5
|
+
|
6
|
+
let(:remote_path) { '' }
|
7
|
+
let(:credentials) {
|
8
|
+
{
|
9
|
+
:client_id => 'some_client_id',
|
10
|
+
:access_token => 'some_access_token',
|
11
|
+
:refresh_token => 'some_refresh_token',
|
12
|
+
:client_secret => 'some_client_secret',
|
13
|
+
:application_name => 'some_application_name',
|
14
|
+
:project_id => 'some_project_id',
|
15
|
+
:expiration_time => '123456789'
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
|
20
|
+
let(:params) {
|
21
|
+
{
|
22
|
+
credentials: credentials,
|
23
|
+
folder_id: 'some_google_folder_id',
|
24
|
+
remote_path: remote_path
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:gsheet_file) {
|
29
|
+
Extractor::Gsheet.new(params)
|
30
|
+
}
|
31
|
+
|
32
|
+
let(:response) { double('response') }
|
33
|
+
let(:remote_filenames) {["test_file_1","test_file_2"]}
|
34
|
+
let(:remote_files) do
|
35
|
+
[{name: "test_file_1", create_time:Date.current, id: "1234"},
|
36
|
+
{name: "test_file_2", create_time:Date.current, id: "5678"}]
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context '.new' do
|
41
|
+
it 'creates an instance with valid parameters' do
|
42
|
+
gsheet_file
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'requires a client_id' do
|
46
|
+
credentials.delete(:client_id)
|
47
|
+
expect { gsheet_file }.to raise_error KeyError
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'requires an access_token' do
|
51
|
+
credentials.delete(:access_token)
|
52
|
+
expect { gsheet_file }.to raise_error KeyError
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'requires a client_secret' do
|
56
|
+
credentials.delete(:client_secret)
|
57
|
+
expect { gsheet_file }.to raise_error KeyError
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'requires a refresh_token' do
|
61
|
+
credentials.delete(:refresh_token)
|
62
|
+
expect { gsheet_file }.to raise_error KeyError
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'requires a folder id' do
|
66
|
+
params.delete(:credentials)
|
67
|
+
expect { gsheet_file }.to raise_error ArgumentError
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'requires an application name' do
|
71
|
+
credentials.delete(:application_name)
|
72
|
+
expect { gsheet_file }.to raise_error KeyError
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'requires a project id' do
|
76
|
+
credentials.delete(:project_id)
|
77
|
+
expect { gsheet_file }.to raise_error KeyError
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
context '#all_entires' do
|
83
|
+
it 'returns all entries' do
|
84
|
+
|
85
|
+
allow(response).to receive(:files) { remote_files }
|
86
|
+
allow(gsheet_file).to receive(:service_list_files) { response }
|
87
|
+
|
88
|
+
expect(gsheet_file.all_entries.map(&:name)).to eq remote_filenames
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
context '#extract' do
|
94
|
+
it 'downloads files from google' do
|
95
|
+
|
96
|
+
allow(response).to receive(:files) { remote_files }
|
97
|
+
allow(gsheet_file).to receive(:service_list_files) { response }
|
98
|
+
expect(gsheet_file).to receive(:get_spreadsheet_vals).exactly(remote_filenames.size).times
|
99
|
+
gsheet_file.extract
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe Parser::Gsheet do
|
107
|
+
|
108
|
+
let(:parser) { Parser::Gsheet.new }
|
109
|
+
let(:gs_extract) { double('gs_extract') }
|
110
|
+
let(:example_data) do
|
111
|
+
[{"headers" => ["header_1", "header_2", "header_3"],
|
112
|
+
"row 1" => ["value 1", "value 2", "value 3"]
|
113
|
+
}]
|
114
|
+
end
|
115
|
+
|
116
|
+
before do
|
117
|
+
allow(gs_extract).to receive(:data) { example_data }
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'converts Google Sheets response data into a dataframe' do
|
121
|
+
expect(parser.parse gs_extract).to be_a Remi::DataFrame::Daru
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'converted data into the correct dataframe' do
|
125
|
+
expected_df = Daru::DataFrame.new(
|
126
|
+
:header_1 => ['value 1'],
|
127
|
+
:header_2 => ['value 2'],
|
128
|
+
:header_3 => ['value 3'],
|
129
|
+
)
|
130
|
+
expect(parser.parse(gs_extract).to_a).to eq expected_df.to_a
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
@@ -4,7 +4,10 @@ describe 'sub jobs' do
|
|
4
4
|
before :each do
|
5
5
|
Object.send(:remove_const, :MySubJob) if Object.constants.include?(:MySubJob)
|
6
6
|
class MySubJob < Job
|
7
|
-
source
|
7
|
+
source :sub_source do
|
8
|
+
extractor Extractor::None.new
|
9
|
+
fields({ a: { from_sub_job: true, to_overwrite: 'from_sub_job' } })
|
10
|
+
end
|
8
11
|
target(:sub_target) {}
|
9
12
|
end
|
10
13
|
end
|
@@ -13,21 +16,50 @@ describe 'sub jobs' do
|
|
13
16
|
|
14
17
|
|
15
18
|
describe Extractor::SubJob do
|
16
|
-
let(:
|
19
|
+
let(:target_extractor) { Extractor::SubJob.new(sub_job: sub_job, data_subject: :sub_target) }
|
20
|
+
let(:source_extractor) { Extractor::SubJob.new(sub_job: sub_job, data_subject: :sub_source) }
|
17
21
|
|
18
22
|
it 'returns the data from the sub-job' do
|
19
|
-
allow(sub_job.
|
20
|
-
expect(
|
23
|
+
allow(sub_job.sub_job.sub_target).to receive(:df) { 'sub target df' }
|
24
|
+
expect(target_extractor.extract).to eq 'sub target df'
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'executes the sub job when target data is requested' do
|
28
|
+
expect(sub_job).to receive(:execute).once
|
29
|
+
target_extractor.extract
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'does not execute the sub job when source data is requested' do
|
33
|
+
expect(sub_job).not_to receive(:execute)
|
34
|
+
source_extractor.extract
|
21
35
|
end
|
22
36
|
end
|
23
37
|
|
24
38
|
describe Loader::SubJob do
|
25
|
-
let(:
|
39
|
+
let(:data_target) { DataTarget.new }
|
40
|
+
let(:loader) { Loader::SubJob.new(context: data_target, sub_job: sub_job, data_subject: :sub_source) }
|
41
|
+
let(:my_data_frame) { Daru::DataFrame.new({ a: [1,2,3] }) }
|
26
42
|
|
27
43
|
it 'populates the sub-job data frame' do
|
28
|
-
|
29
|
-
|
30
|
-
|
44
|
+
loader.load(my_data_frame)
|
45
|
+
expect(sub_job.sub_job.sub_source.df).to eq my_data_frame
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'merges fields from the parent source when requested' do
|
49
|
+
data_target.fields({ a: { from_parent: :true, to_overwrite: 'from_parent' } })
|
50
|
+
loader.load(my_data_frame)
|
51
|
+
expect(sub_job.sub_job.sub_source.fields).to eq MySubJob.new.sub_source.fields.merge data_target.fields
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not merge fields from the parent source when requested' do
|
55
|
+
loader.merge_fields = false
|
56
|
+
data_target.fields({ a: { from_parent: :true, to_overwrite: 'from_parent' } })
|
57
|
+
loader.load(my_data_frame)
|
58
|
+
expect(sub_job.sub_job.sub_source.fields).to eq MySubJob.new.sub_source.fields
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'autoloads the target' do
|
62
|
+
expect(loader.autoload).to be true
|
31
63
|
end
|
32
64
|
end
|
33
65
|
end
|
data/spec/job_spec.rb
CHANGED
@@ -70,11 +70,11 @@ describe Job do
|
|
70
70
|
class MyJob
|
71
71
|
sub_job(:my_sub_job) { 'something' }
|
72
72
|
end
|
73
|
-
expect { job.my_sub_job.
|
73
|
+
expect { job.my_sub_job.sub_job }.to raise_error ArgumentError
|
74
74
|
end
|
75
75
|
|
76
76
|
it 'returns a Remi job' do
|
77
|
-
expect(job.my_sub_job.
|
77
|
+
expect(job.my_sub_job.sub_job).to be_a Remi::Job
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
@@ -276,6 +276,11 @@ describe Job do
|
|
276
276
|
expect(job.params[:my_param]).to eq 'instantiated'
|
277
277
|
end
|
278
278
|
|
279
|
+
it 'works with booleans too' do
|
280
|
+
other_job = MyJob.new(my_param: false)
|
281
|
+
expect(other_job.params[:my_param]).to eq false
|
282
|
+
end
|
283
|
+
|
279
284
|
it 'does not affect the values of other instances' do
|
280
285
|
job
|
281
286
|
other_job = MyJob.new
|
@@ -295,17 +300,12 @@ describe Job do
|
|
295
300
|
context '#execute' do
|
296
301
|
before do
|
297
302
|
class MyJob
|
298
|
-
transform
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
target :target_one do
|
305
|
-
end
|
306
|
-
|
307
|
-
target :target_two do
|
308
|
-
end
|
303
|
+
transform(:transform_one) {}
|
304
|
+
transform(:transform_two) {}
|
305
|
+
sub_job(:sub_job_one) { Remi::Job.new }
|
306
|
+
sub_job(:sub_job_two) { Remi::Job.new }
|
307
|
+
target(:target_one) {}
|
308
|
+
target(:target_two) {}
|
309
309
|
end
|
310
310
|
end
|
311
311
|
|
@@ -314,6 +314,11 @@ describe Job do
|
|
314
314
|
job.execute
|
315
315
|
end
|
316
316
|
|
317
|
+
it 'executes all subjobs' do
|
318
|
+
expect(job).to receive(:execute_sub_jobs)
|
319
|
+
job.execute
|
320
|
+
end
|
321
|
+
|
317
322
|
it 'executes load all targets' do
|
318
323
|
expect(job).to receive(:execute_load_targets)
|
319
324
|
job.execute
|
@@ -330,12 +335,39 @@ describe Job do
|
|
330
335
|
job.execute(:transforms)
|
331
336
|
end
|
332
337
|
|
338
|
+
it 'does not execute all sub jobs' do
|
339
|
+
expect(job).not_to receive(:execute_sub_jobs)
|
340
|
+
job.execute(:transforms)
|
341
|
+
end
|
342
|
+
|
333
343
|
it 'does not load all targets' do
|
334
344
|
expect(job).not_to receive(:execute_load_targets)
|
335
345
|
job.execute(:transforms)
|
336
346
|
end
|
337
347
|
end
|
338
348
|
|
349
|
+
context '#execute(:sub_jobs)' do
|
350
|
+
it 'executes all sub_jobs' do
|
351
|
+
[:sub_job_one, :sub_job_two].each do |sub_job_name|
|
352
|
+
sub_job = instance_double(Job::SubJob)
|
353
|
+
expect(sub_job).to receive(:execute)
|
354
|
+
expect(job).to receive(sub_job_name) .and_return(sub_job)
|
355
|
+
end
|
356
|
+
|
357
|
+
job.execute(:sub_jobs)
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'does not execute all transforms' do
|
361
|
+
expect(job).not_to receive(:execute_transforms)
|
362
|
+
job.execute(:sub_jobs)
|
363
|
+
end
|
364
|
+
|
365
|
+
it 'does not load all targets' do
|
366
|
+
expect(job).not_to receive(:execute_load_targets)
|
367
|
+
job.execute(:sub_jobs)
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
339
371
|
context '#execute(:load_targets)' do
|
340
372
|
it 'loads all targets' do
|
341
373
|
[:target_one, :target_two].each do |target_name|
|
@@ -351,6 +383,11 @@ describe Job do
|
|
351
383
|
expect(job).not_to receive(:execute_transforms)
|
352
384
|
job.execute(:load_targets)
|
353
385
|
end
|
386
|
+
|
387
|
+
it 'does not execute all sub jobs' do
|
388
|
+
expect(job).not_to receive(:execute_sub_jobs)
|
389
|
+
job.execute(:load_targets)
|
390
|
+
end
|
354
391
|
end
|
355
392
|
end
|
356
393
|
|
@@ -469,9 +506,9 @@ describe Job do
|
|
469
506
|
Job::SubJob.new { scoped_sub_job }
|
470
507
|
end
|
471
508
|
|
472
|
-
context '#
|
509
|
+
context '#sub_job' do
|
473
510
|
it 'returns the job instance for the sub job' do
|
474
|
-
expect(job_sub_job.
|
511
|
+
expect(job_sub_job.sub_job).to eq sub_job
|
475
512
|
end
|
476
513
|
end
|
477
514
|
|
@@ -494,6 +531,12 @@ describe Job do
|
|
494
531
|
expect(sub_job).to receive(:execute)
|
495
532
|
job_sub_job.execute
|
496
533
|
end
|
534
|
+
|
535
|
+
it 'only executes the sub job once' do
|
536
|
+
expect(sub_job).to receive(:execute).once
|
537
|
+
job_sub_job.execute
|
538
|
+
job_sub_job.execute
|
539
|
+
end
|
497
540
|
end
|
498
541
|
|
499
542
|
context '#execute_transforms' do
|