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
@@ -2,13 +2,13 @@ module Remi
2
2
  module Testing
3
3
  module DataStub
4
4
  def stub_row_array
5
- @fields.values.map do |attribs|
5
+ fields.values.map do |attribs|
6
6
  stub_values(attribs)
7
7
  end
8
8
  end
9
9
 
10
10
  def empty_stub_df
11
- self.df = Daru::DataFrame.new([], order: @fields.keys)
11
+ self.df = Daru::DataFrame.new([], order: fields.keys)
12
12
  end
13
13
 
14
14
  def stub_df
@@ -1,3 +1,3 @@
1
1
  module Remi
2
- VERSION = '0.2.42'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -34,6 +34,9 @@ Gem::Specification.new do |s|
34
34
  # s.add_runtime_dependency 'salesforce_bulk_api', ['0.0.12']
35
35
 
36
36
  s.add_development_dependency 'iruby', ['0.2.7']
37
+ s.add_development_dependency 'yard', ['~> 0.9']
38
+ s.add_development_dependency 'redcarpet', ['~> 3.3']
39
+ s.add_development_dependency 'github-markup', ['~> 1.4']
37
40
 
38
41
  s.files = `git ls-files`.split("\n")
39
42
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -1,23 +1,96 @@
1
1
  require_relative 'remi_spec'
2
2
 
3
- # VERY SPARSE TESTING! DO MORE!
4
-
5
3
  describe DataSubject do
4
+ let(:data_subject) { DataSubject.new(name: :awesome_subject) }
5
+
6
+ context 'DSL' do
7
+ let(:dsl_data_subject) do
8
+ DataSubject.new(name: :awesome_dsl_subject) do
9
+ fields :id => {}
10
+ field_symbolizer :salesforce
11
+ end
12
+ end
13
+
14
+ it 'defines the fields' do
15
+ expect(dsl_data_subject.dsl_eval.fields).to eq({ :id => {} })
16
+ end
17
+
18
+ it 'sets the field symbolizer' do
19
+ expect(dsl_data_subject.dsl_eval.field_symbolizer).to eq(Remi::FieldSymbolizers[:salesforce])
20
+ end
21
+ end
22
+
23
+ it 'has a name' do
24
+ expect(data_subject.name).to eq :awesome_subject
25
+ end
26
+
27
+ context '#df_type' do
28
+ it 'returns the dataframe type' do
29
+ expect(data_subject.df_type).to eq :daru
30
+ end
31
+
32
+ it 'sets the dataframe type' do
33
+ expect { data_subject.df_type(:spark) }.to change {
34
+ data_subject.df_type
35
+ }.from(:daru).to(:spark)
36
+ end
37
+ end
38
+
39
+ context '#fields' do
40
+ it 'returns the field metadata' do
41
+ expect(data_subject.fields).to be_a Remi::Fields
42
+ end
43
+
44
+ it 'sets the field metadata' do
45
+ expect { data_subject.fields({ :id => {} }) }.to change {
46
+ data_subject.fields
47
+ }.from({}).to({ :id => {} })
48
+ end
49
+ end
50
+
51
+ context '#field_symbolizer' do
52
+ it 'returns the field symbolizer defined for this source' do
53
+ expect(data_subject.field_symbolizer).to eq Remi::FieldSymbolizers[:standard]
54
+ end
55
+
56
+ it 'sets the field symbolizer' do
57
+ data_subject.field_symbolizer :salesforce
58
+ expect(data_subject.field_symbolizer).to eq Remi::FieldSymbolizers[:salesforce]
59
+ end
60
+ end
6
61
 
7
- describe 'enforcing types' do
8
- let(:dataframe) do
9
- Remi::DataFrame::Daru.new({ my_date: ['10/21/2015'] })
62
+ context '#df' do
63
+ it 'returns the dataframe associated with this subject' do
64
+ expect(data_subject.df).to be_a Remi::DataFrame::Daru
10
65
  end
66
+ end
11
67
 
12
- let(:data_subject) do
13
- DataSubject.new(fields: fields).tap { |ds| ds.df = dataframe }
68
+ context '#df=' do
69
+ let(:reassigned_df) { Daru::DataFrame.new({ a: [1955] }) }
70
+ it 'reassigns the dataframe associated with this subject' do
71
+ data_subject.df = reassigned_df
72
+ expect(data_subject.df).to eq reassigned_df
14
73
  end
15
74
 
75
+ it 'converts any non-remi dataframes to a remi dataframe' do
76
+ data_subject.df = reassigned_df
77
+ expect(data_subject.df).to be_a Remi::DataFrame::Daru
78
+ end
79
+ end
80
+
81
+ context '#enforce_types' do
82
+ let(:dataframe) { Remi::DataFrame::Daru.new({ my_date: ['10/21/2015'] }) }
83
+
16
84
  let(:fields) do
17
- Fields.new({
18
- my_date: { type: :date, in_format: '%m/%d/%Y' },
85
+ {
86
+ my_date: { type: :date, in_format: '%m/%d/%Y' },
19
87
  other_date: { type: :date, in_format: '%m/%d/%Y' }
20
- })
88
+ }
89
+ end
90
+
91
+ before do
92
+ data_subject.fields = fields
93
+ data_subject.df = dataframe
21
94
  end
22
95
 
23
96
  it 'converts a date string to a date using an in_format' do
@@ -25,12 +98,22 @@ describe DataSubject do
25
98
  expect(data_subject.df[:my_date].to_a).to eq [Date.new(2015, 10, 21)]
26
99
  end
27
100
 
28
- it 'does not do any conversion if the type is not specified' do
101
+ it 'converts types when explicitly specified' do
102
+ data_subject.enforce_types(:date)
103
+ expect(data_subject.df[:my_date].to_a).to eq [Date.new(2015, 10, 21)]
104
+ end
105
+
106
+ it 'does not do any conversion if the field has no type specified' do
29
107
  fields[:my_date].delete(:type)
30
108
  data_subject.enforce_types
31
109
  expect(data_subject.df[:my_date].to_a).to eq ['10/21/2015']
32
110
  end
33
111
 
112
+ it 'does not do any conversion if field metadata does not match the selected enforcement type' do
113
+ data_subject.enforce_types(:decimal)
114
+ expect(data_subject.df[:my_date].to_a).to eq ['10/21/2015']
115
+ end
116
+
34
117
  it 'throws an error if the data does not conform to its type' do
35
118
  dataframe[:my_date].recode! { |v| '2015-10-21' }
36
119
  expect { data_subject.enforce_types }.to raise_error ArgumentError
@@ -42,3 +125,384 @@ describe DataSubject do
42
125
  end
43
126
  end
44
127
  end
128
+
129
+
130
+
131
+
132
+ describe DataSource do
133
+ let(:data_source) { DataSource.new }
134
+
135
+ let(:my_extractor) { double('my_extractor') }
136
+ let(:my_extractor2) { double('my_extractor2') }
137
+ let(:my_parser) { Remi::Parser.new }
138
+
139
+
140
+ before do
141
+ allow(my_extractor).to receive(:extract) .and_return 'result_1'
142
+ allow(my_extractor2).to receive(:extract) .and_return 'result_2'
143
+ allow(my_parser).to receive(:parse)
144
+ end
145
+
146
+
147
+ context 'DSL' do
148
+ let(:dsl_data_source) do
149
+ scoped_my_extractor = my_extractor
150
+ scoped_my_extractor2 = my_extractor2
151
+ scoped_my_parser = my_parser
152
+
153
+ DataSource.new(name: :awesome_dsl_source) do
154
+ extractor scoped_my_extractor
155
+ extractor scoped_my_extractor2
156
+ parser scoped_my_parser
157
+ end
158
+ end
159
+
160
+ it 'adds extractors to the list of extractors' do
161
+ expect(dsl_data_source.dsl_eval.extractors).to eq [my_extractor, my_extractor2]
162
+ end
163
+
164
+ it 'sets the parser' do
165
+ expect(dsl_data_source.dsl_eval.parser).to eq my_parser
166
+ end
167
+
168
+ context '#df' do
169
+ it 'executes the DSL commands that have been declared' do
170
+ expect(my_extractor).to receive :extract
171
+ expect(my_extractor2).to receive :extract
172
+ expect(my_parser).to receive :parse
173
+ dsl_data_source.df
174
+ end
175
+ end
176
+
177
+ context '#field_symbolizer' do
178
+ context 'field_symbolizer called before parser' do
179
+ let(:before_parser) do
180
+ scoped_my_parser = my_parser
181
+ DataSource.new do
182
+ field_symbolizer :salesforce
183
+ parser scoped_my_parser
184
+ end
185
+ end
186
+
187
+ it 'is used to set the field_symbolizer of the parser' do
188
+ expect {
189
+ before_parser.dsl_eval
190
+ }.to change {
191
+ my_parser.field_symbolizer
192
+ }.from(Remi::FieldSymbolizers[:standard]).to(Remi::FieldSymbolizers[:salesforce])
193
+ end
194
+ end
195
+
196
+ context 'field_symbolizer called after parser' do
197
+ let(:after_parser) do
198
+ scoped_my_parser = my_parser
199
+ DataSource.new do
200
+ parser scoped_my_parser
201
+ field_symbolizer :salesforce
202
+ end
203
+ end
204
+
205
+ it 'sets the field symbolizer of the parser for any parsers defined above' do
206
+ expect {
207
+ after_parser.dsl_eval
208
+ }.to change {
209
+ my_parser.field_symbolizer
210
+ }.from(Remi::FieldSymbolizers[:standard]).to(Remi::FieldSymbolizers[:salesforce])
211
+ end
212
+ end
213
+ end
214
+ end
215
+
216
+ context '#extractor' do
217
+ before { data_source.extractor 'my_extractor' }
218
+
219
+ it 'adds an extractor to the list of extractors' do
220
+ expect(data_source.extractors).to eq ['my_extractor']
221
+ end
222
+
223
+ it 'allows for multiple extractors to be defined' do
224
+ data_source.extractor 'my_extractor2'
225
+ expect(data_source.extractors).to eq ['my_extractor', 'my_extractor2']
226
+ end
227
+ end
228
+
229
+ context '#parser' do
230
+ let(:my_parser) { Remi::Parser.new }
231
+
232
+ context 'default parser' do
233
+ it 'uses the None parser' do
234
+ expect(data_source.parser).to be_a Parser::None
235
+ end
236
+ end
237
+
238
+ context 'defining a parser' do
239
+ before { data_source.parser my_parser }
240
+
241
+ it 'sets the parser' do
242
+ expect(data_source.parser).to eq my_parser
243
+ end
244
+
245
+ it 'only allows one parser to be defined' do
246
+ my_new_parser = my_parser.clone
247
+ data_source.parser my_new_parser
248
+ expect(data_source.parser).to eq my_new_parser
249
+ end
250
+
251
+ it 'sets the context of parser' do
252
+ data_source.parser my_parser
253
+ expect(my_parser.context).to eq data_source
254
+ end
255
+ end
256
+ end
257
+
258
+ context 'with parsers and extractors defined' do
259
+ before do
260
+ data_source.extractor my_extractor
261
+ data_source.extractor my_extractor2
262
+ data_source.parser my_parser
263
+ end
264
+
265
+ context '#extract' do
266
+ it 'extracts data from each extractor' do
267
+ expect(my_extractor).to receive :extract
268
+ expect(my_extractor2).to receive :extract
269
+ data_source.extract
270
+ end
271
+
272
+ it 'collects the results of each extractor' do
273
+ expect(data_source.extract).to eq ['result_1', 'result_2']
274
+ end
275
+ end
276
+
277
+ context '#parse' do
278
+ it 'uses the specified parser to parse the extracted data' do
279
+ expect(my_parser).to receive(:parse) .with('result_1', 'result_2')
280
+ data_source.parse
281
+ end
282
+ end
283
+
284
+ context '#df' do
285
+ context 'a dataframe has not already been defined' do
286
+
287
+ it 'extracts' do
288
+ expect(data_source).to receive :extract
289
+ data_source.df
290
+ end
291
+
292
+ it 'parses' do
293
+ expect(data_source).to receive :parse
294
+ data_source.df
295
+ end
296
+ end
297
+
298
+ context 'a dataframe has already been defined' do
299
+ let(:dataframe) do
300
+ df = double('df')
301
+ allow(df).to receive :df_type
302
+ df
303
+ end
304
+ before { data_source.df = dataframe }
305
+
306
+ it 'simply returns the defined dataframe' do
307
+ expect(data_source.df).to eq dataframe
308
+ end
309
+
310
+ it 'does not extract' do
311
+ expect(data_source).not_to receive :extract
312
+ data_source.df
313
+ end
314
+
315
+ it 'does not parse' do
316
+ expect(data_source).not_to receive :parse
317
+ data_source.df
318
+ end
319
+ end
320
+ end
321
+
322
+ context '#reset', skip: 'todo' do
323
+ it 'clears the current dataframe' do
324
+ end
325
+
326
+ it 'allows the source data to be extracted and parsed again' do
327
+ end
328
+ end
329
+ end
330
+ end
331
+
332
+
333
+ describe DataTarget do
334
+ let(:data_target) { DataTarget.new }
335
+
336
+ let(:my_encoder) { Remi::Encoder.new }
337
+ let(:my_loader) { double('my_loader') }
338
+ let(:my_loader2) { double('my_loader2') }
339
+
340
+ before do
341
+ allow(my_loader).to receive(:load)
342
+ allow(my_loader2).to receive(:load)
343
+ allow(my_encoder).to receive(:encode) .and_return 'encoded data'
344
+ end
345
+
346
+ context 'DSL' do
347
+ let(:dsl_data_target) do
348
+ scoped_my_encoder = my_encoder
349
+ scoped_my_loader = my_loader
350
+ scoped_my_loader2 = my_loader2
351
+
352
+ DataTarget.new do
353
+ encoder scoped_my_encoder
354
+ loader scoped_my_loader
355
+ loader scoped_my_loader2
356
+ end
357
+ end
358
+
359
+ it 'adds loaders to the list of loaders' do
360
+ expect(dsl_data_target.dsl_eval.loaders).to eq [my_loader, my_loader2]
361
+ end
362
+
363
+ it 'sets the encoder' do
364
+ expect(dsl_data_target.dsl_eval.encoder).to eq my_encoder
365
+ end
366
+
367
+ context '#load' do
368
+ it 'executes the DSL commands that have been declared' do
369
+ df_double = double('df')
370
+ allow(df_double).to receive(:size) .and_return(1)
371
+
372
+ allow(dsl_data_target).to receive(:df) .and_return(df_double)
373
+
374
+ expect(my_encoder).to receive :encode
375
+ expect(my_loader).to receive :load
376
+ expect(my_loader2).to receive :load
377
+ dsl_data_target.load
378
+ end
379
+ end
380
+
381
+
382
+ context '#field_symbolizer' do
383
+ context 'field_symbolizer called before encoder' do
384
+ let(:before_encoder) do
385
+ scoped_my_encoder = my_encoder
386
+ DataTarget.new do
387
+ field_symbolizer :salesforce
388
+ encoder scoped_my_encoder
389
+ end
390
+ end
391
+
392
+ it 'is used to set the field_symbolizer of the encoder' do
393
+ expect {
394
+ before_encoder.dsl_eval
395
+ }.to change {
396
+ my_encoder.field_symbolizer
397
+ }.from(Remi::FieldSymbolizers[:standard]).to(Remi::FieldSymbolizers[:salesforce])
398
+ end
399
+ end
400
+
401
+ context 'field_symbolizer called after encoder' do
402
+ let(:after_encoder) do
403
+ scoped_my_encoder = my_encoder
404
+ DataTarget.new do
405
+ encoder scoped_my_encoder
406
+ field_symbolizer :salesforce
407
+ end
408
+ end
409
+
410
+ it 'sets the field symbolizer of the encoder for any encoders defined above' do
411
+ expect {
412
+ after_encoder.dsl_eval
413
+ }.to change {
414
+ my_encoder.field_symbolizer
415
+ }.from(Remi::FieldSymbolizers[:standard]).to(Remi::FieldSymbolizers[:salesforce])
416
+ end
417
+ end
418
+ end
419
+ end
420
+
421
+ context '#encoder' do
422
+ let(:my_encoder) { Remi::Encoder.new }
423
+
424
+ context 'default encoder' do
425
+ it 'uses the None encoder' do
426
+ expect(data_target.encoder).to be_a Encoder::None
427
+ end
428
+ end
429
+
430
+ context 'defining an encoder' do
431
+ before { data_target.encoder my_encoder }
432
+
433
+ it 'sets the encoder' do
434
+ expect(data_target.encoder).to eq my_encoder
435
+ end
436
+
437
+ it 'only allows one encoder to be defined' do
438
+ my_new_encoder = my_encoder.clone
439
+ data_target.encoder my_new_encoder
440
+ expect(data_target.encoder).to eq my_new_encoder
441
+ end
442
+
443
+ it 'sets the context of encoder' do
444
+ data_target.encoder my_encoder
445
+ expect(my_encoder.context).to eq data_target
446
+ end
447
+ end
448
+ end
449
+
450
+ context '#loader' do
451
+ before { data_target.loader 'my_loader' }
452
+
453
+ it 'adds a loader to the list of loaders' do
454
+ expect(data_target.loaders).to eq ['my_loader']
455
+ end
456
+
457
+ it 'allows for multiple loaders to be defined' do
458
+ data_target.loader 'my_loader2'
459
+ expect(data_target.loaders).to eq ['my_loader', 'my_loader2']
460
+ end
461
+ end
462
+
463
+ context '#load' do
464
+ before do
465
+ data_target.encoder my_encoder
466
+ data_target.loader my_loader
467
+ data_target.loader my_loader2
468
+
469
+ df_double = double('df')
470
+ allow(df_double).to receive(:size) .and_return(1)
471
+
472
+ allow(data_target).to receive(:df) .and_return(df_double)
473
+ end
474
+
475
+ it 'encodes data represented in the dataframe' do
476
+ expect(my_encoder).to receive(:encode).once
477
+ data_target.load
478
+ end
479
+
480
+ it 'passes encoded data to each of the loaders' do
481
+ expect(my_loader).to receive(:load).with('encoded data')
482
+ expect(my_loader2).to receive(:load).with('encoded data')
483
+ data_target.load
484
+ end
485
+
486
+ it 'triggers a load for all of the loaders' do
487
+ expect(my_loader).to receive(:load).once
488
+ expect(my_loader2).to receive(:load).once
489
+ data_target.load
490
+ end
491
+
492
+ it 'does not trigger loads twice' do
493
+ expect(my_loader).to receive(:load).once
494
+ expect(my_loader2).to receive(:load).once
495
+ data_target.load
496
+ data_target.load
497
+ end
498
+
499
+ context '#load!' do
500
+ it 'triggers loads every time it is called' do
501
+ expect(my_loader).to receive(:load).twice
502
+ expect(my_loader2).to receive(:load).twice
503
+ data_target.load!
504
+ data_target.load!
505
+ end
506
+ end
507
+ end
508
+ end