itiel 0.1.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 (98) hide show
  1. checksums.yaml +7 -0
  2. data/.autotest +17 -0
  3. data/.gitignore +13 -0
  4. data/.gitlab-ci.yml +36 -0
  5. data/.rspec +2 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +9 -0
  8. data/Gemfile +4 -0
  9. data/Gemfile.rails.4.0 +7 -0
  10. data/Gemfile.rails.4.1 +7 -0
  11. data/Gemfile.rails.4.2 +7 -0
  12. data/README.markdown +106 -0
  13. data/Rakefile +13 -0
  14. data/build.sh +10 -0
  15. data/features/extract/database_table.feature +16 -0
  16. data/features/extract/sql_script.feature +17 -0
  17. data/features/load/database_table_loader.feature +21 -0
  18. data/features/lookup/csv_file.feature +41 -0
  19. data/features/lookup/database_table.feature +43 -0
  20. data/features/script/ruby_script.feature +19 -0
  21. data/features/step_definitions/csv_steps.rb +15 -0
  22. data/features/step_definitions/extractor/csv_file_steps.rb +3 -0
  23. data/features/step_definitions/extractor/custom_sql_steps.rb +6 -0
  24. data/features/step_definitions/extractor/database_steps.rb +27 -0
  25. data/features/step_definitions/extractor/database_table_steps.rb +8 -0
  26. data/features/step_definitions/extractor/extraction_steps.rb +3 -0
  27. data/features/step_definitions/flow_steps.rb +9 -0
  28. data/features/step_definitions/loader/csv_file_steps.rb +4 -0
  29. data/features/step_definitions/loader/database_table_steps.rb +14 -0
  30. data/features/step_definitions/lookup/lookup_steps.rb +35 -0
  31. data/features/step_definitions/scripting/ruby_script_steps.rb +5 -0
  32. data/features/step_definitions/stream_steps.rb +8 -0
  33. data/features/step_definitions/transformation/calculated_column_steps.rb +5 -0
  34. data/features/step_definitions/transformation/calculated_columns_steps.rb +7 -0
  35. data/features/step_definitions/transformation/constant_column_steps.rb +3 -0
  36. data/features/step_definitions/transformation/map_values_step.rb +4 -0
  37. data/features/step_definitions/transformation/rename_column_steps.rb +3 -0
  38. data/features/step_definitions/transformation/select_column_steps.rb +3 -0
  39. data/features/step_definitions/transformation/single_column_sort_steps.rb +3 -0
  40. data/features/support/database.yml +1 -0
  41. data/features/support/env.rb +13 -0
  42. data/features/transform/transformations.feature +123 -0
  43. data/itiel.gemspec +34 -0
  44. data/lib/itiel.rb +45 -0
  45. data/lib/itiel/db/connection.rb +24 -0
  46. data/lib/itiel/db/sql_connectable.rb +33 -0
  47. data/lib/itiel/db/truncator.rb +30 -0
  48. data/lib/itiel/extract/chained_step.rb +22 -0
  49. data/lib/itiel/extract/csv_file.rb +31 -0
  50. data/lib/itiel/extract/custom_sql.rb +38 -0
  51. data/lib/itiel/extract/database_table.rb +23 -0
  52. data/lib/itiel/job.rb +116 -0
  53. data/lib/itiel/load/chained_step.rb +37 -0
  54. data/lib/itiel/load/csv_file.rb +45 -0
  55. data/lib/itiel/load/database_table.rb +34 -0
  56. data/lib/itiel/load/input_output_behavior.rb +36 -0
  57. data/lib/itiel/logger.rb +47 -0
  58. data/lib/itiel/lookup/chained_step.rb +35 -0
  59. data/lib/itiel/lookup/csv_file.rb +16 -0
  60. data/lib/itiel/lookup/database_table.rb +36 -0
  61. data/lib/itiel/lookup/hash_lookup.rb +35 -0
  62. data/lib/itiel/nameable.rb +6 -0
  63. data/lib/itiel/script/chained_step.rb +18 -0
  64. data/lib/itiel/script/ruby_script.rb +31 -0
  65. data/lib/itiel/script/sql_script.rb +29 -0
  66. data/lib/itiel/transform/calculated_columns.rb +47 -0
  67. data/lib/itiel/transform/chained_step.rb +27 -0
  68. data/lib/itiel/transform/constant_column.rb +35 -0
  69. data/lib/itiel/transform/input_output_behavior.rb +44 -0
  70. data/lib/itiel/transform/map_values.rb +43 -0
  71. data/lib/itiel/transform/remove_column.rb +33 -0
  72. data/lib/itiel/transform/rename_column.rb +43 -0
  73. data/lib/itiel/transform/select_column.rb +37 -0
  74. data/lib/itiel/version.rb +3 -0
  75. data/spec/db/sql_connectable_spec.rb +20 -0
  76. data/spec/extract/chained_step_spec.rb +31 -0
  77. data/spec/extract/csv_file_spec.rb +22 -0
  78. data/spec/extract/custom_sql_spec.rb +19 -0
  79. data/spec/extract/database_table_spec.rb +22 -0
  80. data/spec/job_spec.rb +80 -0
  81. data/spec/loader/chained_step_spec.rb +39 -0
  82. data/spec/loader/csv_file_spec.rb +69 -0
  83. data/spec/loader/database_table_spec.rb +29 -0
  84. data/spec/lookup/hash_lookup_spec.rb +108 -0
  85. data/spec/nameable_spec.rb +17 -0
  86. data/spec/script/chained_step_spec.rb +24 -0
  87. data/spec/script/ruby_script_spec.rb +18 -0
  88. data/spec/script/sql_script_spec.rb +41 -0
  89. data/spec/spec_helper.rb +24 -0
  90. data/spec/support/config/database.yml +1 -0
  91. data/spec/support/config/sources.yml +9 -0
  92. data/spec/transform/calculated_columns_spec.rb +36 -0
  93. data/spec/transform/chained_step_spec.rb +36 -0
  94. data/spec/transform/constant_column_spec.rb +22 -0
  95. data/spec/transform/map_values_spec.rb +26 -0
  96. data/spec/transform/rename_column_spec.rb +25 -0
  97. data/spec/transform/select_column_spec.rb +21 -0
  98. metadata +344 -0
@@ -0,0 +1,43 @@
1
+ module Itiel
2
+ module Transform
3
+ #
4
+ # Renames a column in the data stream
5
+ #
6
+ # Usage:
7
+ #
8
+ # @transformer = Itiel::Transform::RenameColumn.new("order_id" => "id")
9
+ #
10
+ # This would rename the order_id column in the input stream to id
11
+ #
12
+ class RenameColumn
13
+ include ChainedStep
14
+ include Itiel::Nameable
15
+
16
+ attr_accessor :mappings
17
+
18
+ def initialize(*args)
19
+ self.mappings = args
20
+ end
21
+
22
+ def transform!(input_stream)
23
+ old_keys = self.mappings.first.keys
24
+ all_keys = input_stream.first.keys
25
+
26
+ transformed_output = []
27
+ input_stream.each do |object|
28
+ element = {}
29
+ all_keys.each do |k|
30
+ if old_keys.include?(k)
31
+ element[self.mappings.first[k]] = object[k]
32
+ else
33
+ element[k] = object[k]
34
+ end
35
+ end
36
+ transformed_output << element
37
+ end
38
+
39
+ transformed_output
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,37 @@
1
+ module Itiel
2
+ module Transform
3
+ #
4
+ # This transformation only selects specific columns on the data stream
5
+ #
6
+ # Usage:
7
+ #
8
+ # @transformer = Itiel::Transform::SelectColumn.new("order_id", "name")
9
+ #
10
+ # In the example, the output stream would only have the order_id and the name column
11
+ # All other columns will be ignored
12
+ #
13
+ class SelectColumn
14
+ include ChainedStep
15
+ include Itiel::Nameable
16
+
17
+ attr_accessor :mappings
18
+
19
+ def input=(stream)
20
+ next_step.input = transform!(stream)
21
+ end
22
+
23
+ def initialize(*args)
24
+ self.mappings = args
25
+ end
26
+
27
+ def transform!(input_stream)
28
+ selected_output = []
29
+ input_stream.each do |object|
30
+ selected_output << object.select {|key, value| self.mappings.include? key }
31
+ end
32
+
33
+ selected_output
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module Itiel
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::DB::SQLConnectable do
4
+ before :each do
5
+ class Step
6
+ include Itiel::DB::SQLConnectable
7
+ end
8
+ end
9
+
10
+ it "sets and gets connection_file_path" do
11
+ expect(Step).to respond_to :connection_file_path
12
+ expect(Step).to respond_to :connection_file_path=
13
+ end
14
+
15
+ it "returns a sequel_connection object based on the connection file" do
16
+ Step.connection_file_path = File.dirname(__FILE__) + '/../support/config/database.yml'
17
+ expect(Step.sequel_connection(:test)).to_not be_nil
18
+ end
19
+ end
20
+
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Extract::ChainedStep do
4
+ before :each do
5
+ klass = Class.new
6
+ klass.send(:include, Itiel::Extract::ChainedStep)
7
+ @step = klass.new
8
+
9
+ @stream = double
10
+ end
11
+
12
+ describe "#start" do
13
+ before :each do
14
+ @next_step = double
15
+ @step.next_step = @next_step
16
+ end
17
+
18
+ it "extracts and sends the stream to the next step" do
19
+ allow(@step).to receive(:extract).and_return(@stream)
20
+ allow(@next_step).to receive(:input=).and_return(@stream)
21
+
22
+ @step.start
23
+ end
24
+ end
25
+
26
+ describe "#extract" do
27
+ it "raises an exception" do
28
+ expect { @step.extract }.to raise_error Itiel::MethodNotImplementedException
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Extract::CSVFile do
4
+ before :each do
5
+ @step = Itiel::Extract::CSVFile.new 'FILENAME'
6
+ end
7
+
8
+ describe "#extract" do
9
+ it "reads a csv file and returns it as a hash to the stream" do
10
+ row = double
11
+ allow(row).to receive(:to_hash).and_return({data: true})
12
+
13
+ @stream = [ row ]
14
+ allow(CSV).to receive(:read).
15
+ with('FILENAME', headers: true).
16
+ and_return(@stream)
17
+
18
+ expect(@step.extract).to eq [{ data: true }]
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Extract::CustomSQL do
4
+ describe "#extract" do
5
+ before :each do
6
+ @step = Itiel::Extract::CustomSQL.new 'SCRIPT'
7
+ @step.connection = :test
8
+ end
9
+
10
+ it "Runs a script on the database and returns its results to the stream" do
11
+ result = double
12
+ allow(result).to receive(:all).and_return double
13
+ db = { 'SCRIPT' => result }
14
+ allow(Itiel::Extract::CustomSQL).to receive(:sequel_connection).with(:test).and_return db
15
+
16
+ expect(@step.extract).to eq result.all
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Extract::DatabaseTable do
4
+ before :each do
5
+ @step = Itiel::Extract::DatabaseTable.new
6
+ @step.connection = :test
7
+ @step.table_name = 'table_name'
8
+ end
9
+
10
+ describe "#extract" do
11
+ it "returns all the rows in the specified database table" do
12
+ result = double
13
+ db = { table_name: result }
14
+ allow(result).to receive(:all).and_return double
15
+ expect(Itiel::Extract::DatabaseTable).
16
+ to receive(:sequel_connection).
17
+ with(:test).and_return db
18
+
19
+ expect(@step.extract).to eq result.all
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,80 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Job do
4
+ describe "#run" do
5
+ it "yields a new instance of Itiel::Job" do
6
+ Itiel::Job.run { |object| expect(object).to be_instance_of(Itiel::Job) }
7
+ end
8
+ end
9
+
10
+ describe "#define" do
11
+ it "returns an instance of Itiel::Job" do
12
+ expect(Itiel::Job.define).to be_instance_of(Itiel::Job)
13
+ end
14
+
15
+ it "yields a new instance of Itiel::Job" do
16
+ Itiel::Job.define {|object| assert_instance_of(Itiel::Job, object)}
17
+ end
18
+
19
+ it "sets given block as the block attribute of the instance it returns" do
20
+ block = Proc.new {}
21
+ job = Itiel::Job.define(&block)
22
+ expect(block).to eq job.block
23
+ end
24
+ end
25
+
26
+ describe "#run!" do
27
+ it "calls the block on self.block" do
28
+ block = Proc.new {}
29
+ expect(block).to receive(:call)
30
+ job = Itiel::Job.define(&block)
31
+ job.run!
32
+ end
33
+
34
+ it "responds to step inside the block" do
35
+ @object = double
36
+ instanced_job = Itiel::Job.define do |job|
37
+ job.step @object
38
+ end
39
+
40
+ expect(instanced_job).to receive(:step).with(@object)
41
+ instanced_job.run!
42
+ end
43
+ end
44
+
45
+ describe "#step" do
46
+ before :each do
47
+ @job = Itiel::Job.new
48
+ @source = double
49
+ @destination = double
50
+ @stream = double
51
+
52
+ allow(@source).to receive(:output).and_return @stream
53
+ expect(@destination).to receive(:input=).and_return @stream
54
+ end
55
+
56
+ describe "@source => @destination" do
57
+ it "hardwires the @destination's input with the @source output" do
58
+ @job.step @source => @destination
59
+ end
60
+ end
61
+
62
+ describe "step @source; step @destination" do
63
+ it "hardwires the @destination's input with the @source output" do
64
+ expect(@destination).to receive(:output)
65
+ @job.step @source
66
+ @job.step @destination
67
+ end
68
+ end
69
+
70
+ describe "step @source => [ @destination, @second_destination ]" do
71
+ it "hardwires both destination's inputs with the @source output" do
72
+ @second_destination = double
73
+ expect(@second_destination).to receive(:input=).and_return @stream
74
+
75
+ @job.step @source => [@destination, @second_destination]
76
+ end
77
+ end
78
+ end
79
+ end
80
+
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Load::ChainedStep do
4
+ before :each do
5
+ klass = Class.new
6
+ klass.send :include, Itiel::Load::ChainedStep
7
+
8
+ @step = klass.new
9
+ end
10
+
11
+ it "defines next_step" do
12
+ expect(@step).to respond_to(:next_step)
13
+ expect(@step).to respond_to(:next_step=)
14
+ end
15
+
16
+ describe :persist do
17
+ it "raises an error if undefined" do
18
+ expect { @step.persist([]) }.to raise_error Itiel::MethodNotImplementedException
19
+ end
20
+ end
21
+
22
+ describe :input= do
23
+ before :each do
24
+ @next_step = double
25
+ @input = [ double ]
26
+ @step.next_step = @next_step
27
+
28
+ expect(Itiel::Logger).to receive(:log_received).with(@step, @input.size)
29
+ expect(Itiel::Logger).to receive(:log_processed).with(@step, @input.size)
30
+ end
31
+
32
+ it "calls persist with the stream received through input= and sends it to next_step" do
33
+ expect(@step).to receive(:persist).with @input
34
+ expect(@next_step).to receive(:input=).with @input
35
+ @step.input = @input
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,69 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Load::CSVFile do
4
+ before :each do
5
+ @filename = File.expand_path("#{File.dirname(__FILE__)}/../../tmp/output.csv")
6
+ File.unlink(@filename) if File.exist?(@filename)
7
+
8
+ @input = [
9
+ {
10
+ "id" => 1,
11
+ "name" => "Subject Name"
12
+ },
13
+ {
14
+ "id" => 2,
15
+ "name" => "Subject Name"
16
+ }
17
+ ]
18
+
19
+ @expected_result = [
20
+ [ "id" , "name" ] ,
21
+ [ "1" , "Subject Name" ] ,
22
+ [ "2" , "Subject Name" ]
23
+ ]
24
+
25
+ @csv_output = Itiel::Load::CSVFile.new(@filename)
26
+ end
27
+
28
+ describe "the file does not exist" do
29
+ before :each do
30
+ @csv_output.persist(@input)
31
+ @result = CSV.read(@filename, :headers => true)
32
+ end
33
+
34
+ it "generates a CSV file with the input data" do
35
+ expect(@expected_result).to eq @result.to_a
36
+ end
37
+ end
38
+
39
+ describe "the file already exists" do
40
+ before :each do
41
+ FileUtils.touch(@filename)
42
+ @csv_output.persist(@input)
43
+ @result = CSV.read(@filename)
44
+ end
45
+
46
+ it "appends to the existing data" do
47
+ expect(@expected_result - [[ "id", "name" ]]).to eq @result
48
+ end
49
+ end
50
+
51
+ describe "use append = false" do
52
+ before do
53
+ @csv_output = Itiel::Load::CSVFile.new(@filename, false)
54
+ end
55
+
56
+ describe "the file already exists" do
57
+ before :each do
58
+ FileUtils.touch(@filename)
59
+ @csv_output.persist(@input)
60
+ @result = CSV.read(@filename)
61
+ end
62
+
63
+ it "replaces the existing file with the input data" do
64
+ expect(@expected_result).to eq @result
65
+ end
66
+ end
67
+ end
68
+ end
69
+
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Load::DatabaseTable do
4
+ before(:each) do
5
+ @output = Itiel::Load::DatabaseTable.new :test, "users"
6
+ @input = [
7
+ {
8
+ "id" => "1",
9
+ "name" => "Some Name"
10
+ },
11
+ {
12
+ "id" => "2",
13
+ "name" => "Some Other Name"
14
+ },
15
+ ]
16
+ end
17
+
18
+ it "inserts a record for each row" do
19
+ table = double
20
+ allow(@output).to receive(:table).and_return table
21
+
22
+ @input.each do |row|
23
+ expect(table).to receive(:insert).and_return row
24
+ end
25
+
26
+ @output.persist(@input)
27
+ end
28
+ end
29
+
@@ -0,0 +1,108 @@
1
+ require 'spec_helper'
2
+
3
+ describe Itiel::Lookup::HashLookup do
4
+ before(:each) do
5
+ @input = [
6
+ {
7
+ :id => 1,
8
+ :author => "Isaac"
9
+ },
10
+ {
11
+ :id => 2,
12
+ :author => "Carver"
13
+ }
14
+ ]
15
+
16
+ @lookup_source = [
17
+ {
18
+ :id => 1,
19
+ :name => "Isaac",
20
+ :active => "t"
21
+ },
22
+ {
23
+ :id => 2,
24
+ :name => "Carver",
25
+ :active => "f"
26
+ }
27
+ ]
28
+
29
+ @lookup_stream = {
30
+ "Isaac" => {
31
+ :author_id => 1,
32
+ :active => "t"
33
+ },
34
+
35
+ "Carver" => {
36
+ :author_id => 2,
37
+ :active => "f"
38
+ }
39
+ }
40
+
41
+ @expected_output = [
42
+ {
43
+ :id => 1,
44
+ :author => "Isaac",
45
+ :author_id => 1,
46
+ :active => "t"
47
+ },
48
+ {
49
+ :id => 2,
50
+ :author => "Carver",
51
+ :author_id => 2,
52
+ :active => "f"
53
+ }
54
+ ]
55
+
56
+ class FooLookup
57
+ include Itiel::Lookup::HashLookup
58
+ end
59
+
60
+ @lookup = FooLookup.new
61
+ @lookup.lookup_columns = { "author" => "name" }
62
+ @lookup.joined_columns = { "id" => "author_id", "active" => "active" }
63
+ end
64
+
65
+ describe "#lookup!" do
66
+ it "joins the data using the specified column" do
67
+ allow(@lookup).to receive(:lookup_stream).and_return @lookup_stream
68
+ expect(@expected_output).to eq @lookup.lookup!(@input)
69
+ end
70
+ end
71
+
72
+ describe "#lookup_stream" do
73
+ it do
74
+ allow(@lookup).to receive(:lookup_source).and_return @lookup_source
75
+ expect(@lookup_stream).to eq @lookup.lookup_stream
76
+ end
77
+ end
78
+
79
+ describe 'with nil values in the lookup column' do
80
+ before do
81
+ @input.append({ id: 3, author: nil })
82
+ @expected_output.append({ id: 3, author: nil, author_id: nil, active: nil })
83
+ end
84
+
85
+ describe "#lookup!" do
86
+ it 'joins the data setting the value to nil' do
87
+ allow(@lookup).to receive(:lookup_stream).and_return @lookup_stream
88
+ expect(@expected_output).to eq @lookup.lookup!(@input)
89
+ end
90
+ end
91
+ end
92
+
93
+ describe "When the lookup column doesn't exist in the input_stream" do
94
+ before do
95
+ @input.append({ id: 3 })
96
+ @expected_output.append({ id: 3, author_id: nil, active: nil })
97
+ end
98
+
99
+ describe "#lookup!" do
100
+ it 'joins the data setting the value to nil' do
101
+ allow(@lookup).to receive(:lookup_stream).and_return @lookup_stream
102
+
103
+ expect(@expected_output).to eq @lookup.lookup!(@input)
104
+ end
105
+ end
106
+ end
107
+ end
108
+