drudgery 0.0.1

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.
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+
4
+ describe Drudgery::Extractors::ActiveRecordExtractor do
5
+ class Record < ActiveRecord::Base; end
6
+
7
+ describe '#initialize' do
8
+ it 'sets model to provided argument' do
9
+ model = mock
10
+
11
+ extractor = Drudgery::Extractors::ActiveRecordExtractor.new(model)
12
+ extractor.instance_variable_get('@model').must_equal model
13
+ end
14
+ end
15
+
16
+ describe '#extract' do
17
+ it 'finds records using model' do
18
+ model = mock
19
+ model.expects(:find_each)
20
+
21
+ extractor = Drudgery::Extractors::ActiveRecordExtractor.new(model)
22
+ extractor.extract
23
+ end
24
+
25
+ it 'yields each record as a hash' do
26
+ record1 = mock
27
+ record1.expects(:attributes).returns({ :a => 1 })
28
+
29
+ record2 = mock
30
+ record2.expects(:attributes).returns({ :b => 2 })
31
+
32
+ model = mock
33
+ model.stubs(:find_each).multiple_yields([record1], [record2])
34
+
35
+ extractor = Drudgery::Extractors::ActiveRecordExtractor.new(model)
36
+
37
+ records = []
38
+ extractor.extract do |record|
39
+ records << record
40
+ end
41
+
42
+ records[0].must_equal({ :a => 1 })
43
+ records[1].must_equal({ :b => 2 })
44
+ end
45
+
46
+ describe 'without stubs' do
47
+ before(:each) do
48
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
49
+ ActiveRecord::Base.connection.create_table(:records) do |t|
50
+ t.integer :a
51
+ t.integer :b
52
+ end
53
+
54
+ Record.create!({ :a => 1, :b => 2 })
55
+ Record.create!({ :a => 3, :b => 4 })
56
+ Record.create!({ :a => 5, :b => 6 })
57
+ end
58
+
59
+ after(:each) do
60
+ ActiveRecord::Base.clear_active_connections!
61
+ end
62
+
63
+ it 'yields each record as a hash' do
64
+ extractor = Drudgery::Extractors::ActiveRecordExtractor.new(Record)
65
+
66
+ records = []
67
+ extractor.extract do |record|
68
+ records << record
69
+ end
70
+
71
+ records.must_equal([
72
+ { 'id' => 1, 'a' => 1, 'b' => 2 },
73
+ { 'id' => 2, 'a' => 3, 'b' => 4 },
74
+ { 'id' => 3, 'a' => 5, 'b' => 6 }
75
+ ])
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,83 @@
1
+ require 'spec_helper'
2
+
3
+ describe Drudgery::Extractors::CSVExtractor do
4
+ describe '#initialize' do
5
+ it 'sets filepath to provided filepath' do
6
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv')
7
+ extractor.instance_variable_get('@filepath').must_equal 'file.csv'
8
+ end
9
+
10
+ it 'initializes options hash' do
11
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv')
12
+ extractor.instance_variable_get('@options').must_equal({ :headers => true })
13
+ end
14
+
15
+ it 'merges provided options with default options' do
16
+ options = { :col_sep => '|', :headers => %w[id name email] }
17
+
18
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv', options)
19
+ extractor.instance_variable_get('@options').must_equal({ :col_sep => '|', :headers => %w[id name email] })
20
+ end
21
+ end
22
+
23
+ describe '#extract' do
24
+ it 'parses records from file' do
25
+ CSV.expects(:foreach).with('file.csv', :headers => true)
26
+
27
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv')
28
+ extractor.extract
29
+ end
30
+
31
+ it 'yields each record as a hash' do
32
+ record1 = mock
33
+ record1.expects(:to_hash).returns({ :a => 1 })
34
+
35
+ record2 = mock
36
+ record2.expects(:to_hash).returns({ :b => 2 })
37
+
38
+ CSV.stubs(:foreach).multiple_yields([record1], [record2])
39
+
40
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv')
41
+
42
+ records = []
43
+ extractor.extract do |record|
44
+ records << record
45
+ end
46
+
47
+ records[0].must_equal({ :a => 1 })
48
+ records[1].must_equal({ :b => 2 })
49
+ end
50
+
51
+ describe 'without stubs' do
52
+ before(:each) do
53
+ File.delete('file.csv') if File.exists?('file.csv')
54
+ end
55
+
56
+ after(:each) do
57
+ File.delete('file.csv') if File.exists?('file.csv')
58
+ end
59
+
60
+ it 'writes hash keys as header and records as rows' do
61
+ File.open('file.csv', 'w') do |f|
62
+ f.puts 'a,b'
63
+ f.puts '1,2'
64
+ f.puts '3,4'
65
+ f.puts '5,6'
66
+ end
67
+
68
+ extractor = Drudgery::Extractors::CSVExtractor.new('file.csv')
69
+
70
+ records = []
71
+ extractor.extract do |record|
72
+ records << record
73
+ end
74
+
75
+ records.must_equal([
76
+ { 'a' => '1', 'b' => '2' },
77
+ { 'a' => '3', 'b' => '4' },
78
+ { 'a' => '5', 'b' => '6' }
79
+ ])
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,161 @@
1
+ require 'spec_helper'
2
+ require 'sqlite3'
3
+
4
+ describe Drudgery::Extractors::SQLite3Extractor do
5
+ describe '#initialize' do
6
+ it 'sets db and table to provided arguments' do
7
+ db = mock
8
+ db.expects(:results_as_hash=).with(true)
9
+ db.expects(:type_translation=).with(true)
10
+
11
+ extractor = Drudgery::Extractors::SQLite3Extractor.new(db, 'tablename')
12
+ extractor.instance_variable_get('@db').must_equal db
13
+ extractor.instance_variable_get('@table').must_equal 'tablename'
14
+ end
15
+
16
+ it 'initializes clauses hash' do
17
+ db = mock
18
+ db.stubs(:results_as_hash=)
19
+ db.stubs(:type_translation=)
20
+
21
+ extractor = Drudgery::Extractors::SQLite3Extractor.new(db, 'tablename')
22
+ extractor.instance_variable_get('@clauses').must_equal({})
23
+ end
24
+ end
25
+
26
+ describe 'query building' do
27
+ before(:each) do
28
+ db = mock
29
+ db.stubs(:results_as_hash=)
30
+ db.stubs(:type_translation=)
31
+
32
+ @extractor = Drudgery::Extractors::SQLite3Extractor.new(db, 'tablename')
33
+ end
34
+
35
+ describe '#select' do
36
+ it 'sets select clause with provided expressions' do
37
+ @extractor.select('id', "(first_name || ' ' || last_name) AS name", 'email')
38
+ @extractor.instance_variable_get('@clauses').must_equal({ :select => "id, (first_name || ' ' || last_name) AS name, email" })
39
+ end
40
+ end
41
+
42
+ describe '#from' do
43
+ it 'sets from clause with provided expression' do
44
+ @extractor.from('table AS t')
45
+ @extractor.instance_variable_get('@clauses').must_equal({ :from => 'table AS t' })
46
+ end
47
+ end
48
+
49
+ describe '#joins' do
50
+ it 'sets join clauses with provided clauses' do
51
+ joins = [
52
+ 'JOIN table2 t2 ON t2.my_id = t1.id',
53
+ 'LEFT OUTER JOIN table3 t3 ON t3.my_id = t2.id'
54
+ ]
55
+
56
+ @extractor.joins(joins[0], joins[1])
57
+ @extractor.instance_variable_get('@clauses').must_equal({ :joins => joins })
58
+ end
59
+ end
60
+
61
+ describe '#group' do
62
+ it 'sets group clause with provided expressions' do
63
+ @extractor.group('id', 'email')
64
+ @extractor.instance_variable_get('@clauses').must_equal({ :group => 'id, email' })
65
+ end
66
+ end
67
+
68
+ describe '#where' do
69
+ it 'sets where clause with provided condition' do
70
+ @extractor.where('age >= 18 AND age < 50')
71
+ @extractor.instance_variable_get('@clauses').must_equal({ :where => 'age >= 18 AND age < 50' })
72
+ end
73
+ end
74
+
75
+ describe '#having' do
76
+ it 'sets having clause with provided condition' do
77
+ @extractor.having('count(*) > 1')
78
+ @extractor.instance_variable_get('@clauses').must_equal({ :having => 'count(*) > 1' })
79
+ end
80
+ end
81
+
82
+ describe '#order' do
83
+ it 'sets order clause with provided expressions' do
84
+ @extractor.order('id', 'email DESC')
85
+ @extractor.instance_variable_get('@clauses').must_equal({ :order => 'id, email DESC' })
86
+ end
87
+ end
88
+ end
89
+
90
+ describe '#extract' do
91
+ it 'selects records from db using defined query' do
92
+ db = mock
93
+ db.stubs(:results_as_hash=)
94
+ db.stubs(:type_translation=)
95
+ db.expects(:execute).with('SELECT * FROM tablename')
96
+ db.expects(:execute).with('SELECT age, count(*) AS nr_ages FROM tablename t table2 t2 ON t2.my_id = t.id WHERE age > 10 GROUP BY age HAVING count(*) > 1 ORDER BY nr_ages')
97
+
98
+ extractor = Drudgery::Extractors::SQLite3Extractor.new(db, 'tablename')
99
+ extractor.extract
100
+
101
+ extractor.select('age', 'count(*) AS nr_ages')
102
+ extractor.from('tablename t')
103
+ extractor.joins('table2 t2 ON t2.my_id = t.id')
104
+ extractor.where('age > 10')
105
+ extractor.group('age')
106
+ extractor.having('count(*) > 1')
107
+ extractor.order('nr_ages')
108
+
109
+ extractor.extract
110
+ end
111
+
112
+ it 'yields each record as a hash' do
113
+ record1 = { :a => 1 }
114
+ record2 = { :b => 2 }
115
+
116
+ db = mock
117
+ db.stubs(:results_as_hash=)
118
+ db.stubs(:type_translation=)
119
+ db.stubs(:execute).multiple_yields([record1], [record2])
120
+
121
+ extractor = Drudgery::Extractors::SQLite3Extractor.new(db, 'tablename')
122
+
123
+ records = []
124
+ extractor.extract do |record|
125
+ records << record
126
+ end
127
+
128
+ records[0].must_equal({ :a => 1 })
129
+ records[1].must_equal({ :b => 2 })
130
+ end
131
+
132
+ describe 'without stubs' do
133
+ before(:each) do
134
+ @db = SQLite3::Database.new(':memory:')
135
+ @db.execute('CREATE TABLE records (a INTEGER, b INTEGER)')
136
+ @db.execute('INSERT INTO records (a, b) VALUES (1, 2)');
137
+ @db.execute('INSERT INTO records (a, b) VALUES (3, 4)');
138
+ @db.execute('INSERT INTO records (a, b) VALUES (5, 6)');
139
+ end
140
+
141
+ after(:each) do
142
+ @db.close
143
+ end
144
+
145
+ it 'yields each record as a hash' do
146
+ extractor = Drudgery::Extractors::SQLite3Extractor.new(@db, 'records')
147
+
148
+ records = []
149
+ extractor.extract do |record|
150
+ records << record
151
+ end
152
+
153
+ records.must_equal([
154
+ { 'a' => 1, 'b' => 2 },
155
+ { 'a' => 3, 'b' => 4 },
156
+ { 'a' => 5, 'b' => 6 }
157
+ ])
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,144 @@
1
+ require 'spec_helper'
2
+
3
+ describe Drudgery::Job do
4
+ describe '#initialize' do
5
+ before(:each) do
6
+ @extractor = mock
7
+ @transformer = mock
8
+ @loader = mock
9
+
10
+ @job = Drudgery::Job.new(:extractor => @extractor, :transformer => @transformer, :loader => @loader)
11
+ end
12
+
13
+ it 'sets extractor, transformer, and loader with provided arguments' do
14
+ @job.instance_variable_get('@extractor').must_equal @extractor
15
+ @job.instance_variable_get('@transformer').must_equal @transformer
16
+ @job.instance_variable_get('@loader').must_equal @loader
17
+ end
18
+
19
+ it 'initializes extractor, transformer, and loader if none provided' do
20
+ job = Drudgery::Job.new
21
+ job.instance_variable_get('@extractor').must_be_nil
22
+ job.instance_variable_get('@transformer').must_be_instance_of(Drudgery::Transformer)
23
+ job.instance_variable_get('@loader').must_be_nil
24
+ end
25
+
26
+ it 'initializes records as array' do
27
+ @job.instance_variable_get('@records').must_equal []
28
+ end
29
+
30
+ it 'initializes batch_size as 1000' do
31
+ @job.instance_variable_get('@batch_size').must_equal 1000
32
+ end
33
+ end
34
+
35
+ describe '#batch_size' do
36
+ it 'sets batch_size to provided value' do
37
+ job = Drudgery::Job.new
38
+ job.batch_size 2
39
+ job.instance_variable_get('@batch_size').must_equal 2
40
+ end
41
+ end
42
+
43
+ describe '#extract' do
44
+ it 'instantiates extractor with type and args' do
45
+ Drudgery::Extractors.expects(:instantiate).with(:csv, 'filename.csv', :col_sep => '|')
46
+
47
+ job = Drudgery::Job.new
48
+ job.extract(:csv, 'filename.csv', :col_sep => '|')
49
+ end
50
+
51
+ it 'sets extractor' do
52
+ extractor = mock
53
+
54
+ Drudgery::Extractors.stubs(:instantiate).returns(extractor)
55
+
56
+ job = Drudgery::Job.new
57
+ job.extract(:csv, 'filename.csv', :col_sep => '|')
58
+
59
+ job.instance_variable_get('@extractor').must_equal extractor
60
+ end
61
+ end
62
+
63
+ describe '#transform' do
64
+ it 'registers provided proc with transformer' do
65
+ block = Proc.new { |data, cache| data }
66
+
67
+ transformer = mock
68
+ transformer.expects(:register).with(block)
69
+
70
+ job = Drudgery::Job.new(:transformer => transformer)
71
+ job.transform(&block)
72
+ end
73
+
74
+ it 'registers provided block with transformer' do
75
+ transformer = mock
76
+ transformer.expects(:register).with { |data, cache| data }
77
+
78
+ job = Drudgery::Job.new(:transformer => transformer)
79
+ job.transform { |data, cache| data }
80
+ end
81
+ end
82
+
83
+ describe '#load' do
84
+ it 'instantiates loader with type with args' do
85
+ Drudgery::Loaders.expects(:instantiate).with(:sqlite3, 'db.sqlite3', 'tablename')
86
+
87
+ job = Drudgery::Job.new
88
+ job.load(:sqlite3, 'db.sqlite3', 'tablename')
89
+ end
90
+
91
+ it 'sets extractor' do
92
+ loader = mock
93
+
94
+ Drudgery::Loaders.expects(:instantiate).with(:sqlite3, 'db.sqlite3', 'tablename').returns(loader)
95
+
96
+ job = Drudgery::Job.new
97
+ job.load(:sqlite3, 'db.sqlite3', 'tablename')
98
+ job.instance_variable_get('@loader').must_equal loader
99
+ end
100
+ end
101
+
102
+ describe '#perform' do
103
+ it 'extracts records from extractor' do
104
+ extractor = mock
105
+ extractor.expects(:extract).yields({ 'a' => 1 })
106
+
107
+ loader = mock
108
+ loader.stubs(:load)
109
+
110
+ job = Drudgery::Job.new(:extractor => extractor, :loader => loader)
111
+
112
+ job.perform
113
+ end
114
+
115
+ it 'transforms records with transformer' do
116
+ extractor = mock
117
+ extractor.stubs(:extract).yields({ 'a' => 1 })
118
+
119
+ transformer = mock
120
+ transformer.expects(:transform).with({ 'a' => 1 }).returns({ :a => 1 })
121
+
122
+ loader = mock
123
+ loader.stubs(:load)
124
+
125
+ job = Drudgery::Job.new(:extractor => extractor, :transformer => transformer, :loader => loader)
126
+
127
+ job.perform
128
+ end
129
+
130
+ it 'loads records with loader in batches' do
131
+ extractor = mock
132
+ extractor.stubs(:extract).multiple_yields([{ 'a' => 1 }], [{ 'b' => 2 }], [{ 'c' => 3 }])
133
+
134
+ loader = mock
135
+ loader.expects(:load).with([{ :a => 1 }, { :b => 2 }])
136
+ loader.expects(:load).with([{ :c => 3 }])
137
+
138
+ job = Drudgery::Job.new(:extractor => extractor, :loader => loader)
139
+ job.batch_size 2
140
+
141
+ job.perform
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,57 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+ require 'activerecord-import'
4
+
5
+ describe Drudgery::Loaders::ActiveRecordImportLoader do
6
+ class Record < ActiveRecord::Base; end
7
+
8
+ describe '#initialize' do
9
+ it 'sets model to provided argument' do
10
+ model = mock
11
+
12
+ loader = Drudgery::Loaders::ActiveRecordImportLoader.new(model)
13
+ loader.instance_variable_get('@model').must_equal model
14
+ end
15
+ end
16
+
17
+ describe '#load' do
18
+ it 'write records using model.import' do
19
+ record1 = { :a => 1, :b => 2 }
20
+ record2 = { :a => 3, :b => 4 }
21
+
22
+ model = mock
23
+ model.expects(:import).with([:a, :b], [[1, 2], [3, 4]], :validate => false)
24
+
25
+ loader = Drudgery::Loaders::ActiveRecordImportLoader.new(model)
26
+ loader.load([record1, record2])
27
+ end
28
+
29
+ describe 'without stubs' do
30
+ before(:each) do
31
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
32
+ ActiveRecord::Base.connection.create_table(:records) do |t|
33
+ t.integer :a
34
+ t.integer :b
35
+ end
36
+ end
37
+
38
+ after(:each) do
39
+ ActiveRecord::Base.clear_active_connections!
40
+ end
41
+
42
+ it 'yields each record as a hash' do
43
+ record1 = { :a => 1, :b => 2 }
44
+ record2 = { :a => 3, :b => 4 }
45
+
46
+ loader = Drudgery::Loaders::ActiveRecordImportLoader.new(Record)
47
+ loader.load([record1, record2])
48
+
49
+ records = Record.all.map(&:attributes)
50
+ records.must_equal([
51
+ { 'id' => 1, 'a' => 1, 'b' => 2 },
52
+ { 'id' => 2, 'a' => 3, 'b' => 4 }
53
+ ])
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,63 @@
1
+ require 'spec_helper'
2
+ require 'active_record'
3
+
4
+ describe Drudgery::Loaders::ActiveRecordLoader do
5
+ class Record < ActiveRecord::Base; end
6
+
7
+ describe '#initialize' do
8
+ it 'sets model to provided argument' do
9
+ model = mock
10
+
11
+ loader = Drudgery::Loaders::ActiveRecordLoader.new(model)
12
+ loader.instance_variable_get('@model').must_equal model
13
+ end
14
+ end
15
+
16
+ describe '#load' do
17
+ it 'write records using model.create' do
18
+ record1 = { :a => 1 }
19
+ record2 = { :a => 2 }
20
+
21
+ obj1 = mock
22
+ obj2 = mock
23
+
24
+ model = mock
25
+ model.expects(:new).with(record1).returns(obj1)
26
+ model.expects(:new).with(record2).returns(obj2)
27
+
28
+ obj1.expects(:save).with(:validate => false)
29
+ obj2.expects(:save).with(:validate => false)
30
+
31
+ loader = Drudgery::Loaders::ActiveRecordLoader.new(model)
32
+ loader.load([record1, record2])
33
+ end
34
+
35
+ describe 'without stubs' do
36
+ before(:each) do
37
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
38
+ ActiveRecord::Base.connection.create_table(:records) do |t|
39
+ t.integer :a
40
+ t.integer :b
41
+ end
42
+ end
43
+
44
+ after(:each) do
45
+ ActiveRecord::Base.clear_active_connections!
46
+ end
47
+
48
+ it 'yields each record as a hash' do
49
+ record1 = { :a => 1, :b => 2 }
50
+ record2 = { :a => 3, :b => 4 }
51
+
52
+ loader = Drudgery::Loaders::ActiveRecordLoader.new(Record)
53
+ loader.load([record1, record2])
54
+
55
+ records = Record.all.map(&:attributes)
56
+ records.must_equal([
57
+ { 'id' => 1, 'a' => 1, 'b' => 2 },
58
+ { 'id' => 2, 'a' => 3, 'b' => 4 }
59
+ ])
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,72 @@
1
+ require 'spec_helper'
2
+
3
+ describe Drudgery::Loaders::CSVLoader do
4
+ describe '#initialize' do
5
+ it 'sets filepath to provided filepath' do
6
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv')
7
+ loader.instance_variable_get('@filepath').must_equal 'file.csv'
8
+ end
9
+
10
+ it 'initializes write_headers boolean' do
11
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv')
12
+ loader.instance_variable_get('@write_headers').must_equal true
13
+ end
14
+
15
+ it 'sets options to provided options' do
16
+ options = { :col_sep => '|' }
17
+
18
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv', options)
19
+ loader.instance_variable_get('@options').must_equal({ :col_sep => '|' })
20
+ end
21
+ end
22
+
23
+ describe '#load' do
24
+ it 'opens CSV file to append records' do
25
+ CSV.expects(:open).with('file.csv', 'a', :col_sep => '|')
26
+
27
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv', :col_sep => '|')
28
+ loader.load([{}])
29
+ end
30
+
31
+ it 'writes hash keys as header and records as rows' do
32
+ record1 = { :a => 1, :b => 2 }
33
+ record2 = { :a => 3, :b => 4 }
34
+ record3 = { :a => 5, :b => 6 }
35
+
36
+ csv = mock
37
+ csv.expects(:<<).with([:a, :b])
38
+ csv.expects(:<<).with([1, 2])
39
+ csv.expects(:<<).with([3, 4])
40
+ csv.expects(:<<).with([5, 6])
41
+
42
+ CSV.expects(:open).with('file.csv', 'a', {}).yields(csv).times(2)
43
+
44
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv')
45
+ loader.load([record1, record2])
46
+ loader.load([record3])
47
+ end
48
+
49
+ describe 'without stubs' do
50
+ before(:each) do
51
+ File.delete('file.csv') if File.exists?('file.csv')
52
+ end
53
+
54
+ after(:each) do
55
+ File.delete('file.csv') if File.exists?('file.csv')
56
+ end
57
+
58
+ it 'writes hash keys as header and records as rows' do
59
+ record1 = { :a => 1, :b => 2 }
60
+ record2 = { :a => 3, :b => 4 }
61
+ record3 = { :a => 5, :b => 6 }
62
+
63
+ loader = Drudgery::Loaders::CSVLoader.new('file.csv')
64
+ loader.load([record1, record2])
65
+ loader.load([record3])
66
+
67
+ records = File.readlines('file.csv').map { |line| line.strip.split(',') }
68
+ records.must_equal [%w[a b], %w[1 2], %w[3 4], %w[5 6]]
69
+ end
70
+ end
71
+ end
72
+ end