activewarehouse-etl 1.0.0.rc1 → 1.0.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.
- data/.gitignore +2 -1
- data/CHANGELOG +6 -1
- data/Guardfile +6 -0
- data/README.textile +6 -1
- data/activewarehouse-etl.gemspec +2 -0
- data/lib/etl/control/control.rb +11 -1
- data/lib/etl/control/source/database_source.rb +10 -7
- data/lib/etl/control/source/mysql_streamer.rb +67 -25
- data/lib/etl/engine.rb +18 -9
- data/lib/etl/execution/batch.rb +1 -0
- data/lib/etl/execution/job.rb +2 -1
- data/lib/etl/version.rb +1 -1
- data/test/config/database.yml +1 -0
- data/test/config/gemfiles/Gemfile.rails-3.2.x +1 -1
- data/test/config/gemfiles/common.rb +1 -1
- data/test/engine_test.rb +13 -0
- data/test/source_test.rb +79 -0
- data/test/test_helper.rb +4 -0
- metadata +42 -19
data/.gitignore
CHANGED
data/CHANGELOG
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
2
|
+
* Fixes for Rails 3.2 (pgericson)
|
3
|
+
* Allow .ctl.rb and .ebf.rb extensions for easier syntax coloring (chrisgogreen)
|
4
|
+
* Support for limit/offset in database source (kennym)
|
5
|
+
* Support for error handlers in etl: can be declared with on_error { |error| } (thbar)
|
6
|
+
* Support for mysql streaming for better performance on large data sets - use :mysqlstream => true on database source (main code by pdodds, tests and fixes by sgrgic and thbar)
|
2
7
|
* EnsureFieldsPresenceProcessor treat symbols and strings equally (thbar)
|
3
8
|
* BREAKING CHANGE: you must require 'iconv' if you use the :encode_processor from now on (thbar)
|
4
9
|
* A non-zero exit code should be returned on fatal screen or exceeded error threshold (thbar)
|
data/Guardfile
ADDED
data/README.textile
CHANGED
@@ -55,7 +55,7 @@ ActiveWarehouse-ETL is the work of many people since late 2006 - here is a list,
|
|
55
55
|
* Jeremy Lecour
|
56
56
|
* Steve Meyfroidt
|
57
57
|
* Seth Ladd
|
58
|
-
* Thibaut Barrère
|
58
|
+
* "Thibaut Barrère":https://github.com/thbar
|
59
59
|
* Stephen Touset
|
60
60
|
* sasikumargn
|
61
61
|
* Andrew Kuklewicz
|
@@ -64,6 +64,11 @@ ActiveWarehouse-ETL is the work of many people since late 2006 - here is a list,
|
|
64
64
|
* Tyler Kiley
|
65
65
|
* Colman Nady
|
66
66
|
* Scott Gonyea
|
67
|
+
* "Philip Dodds":https://github.com/pdodds
|
68
|
+
* "Sinisa Grgic":https://github.com/sgrgic
|
69
|
+
* "Kenny Meyer":https://github.com/kennym
|
70
|
+
* "Chris":https://github.com/chrisgogreen
|
71
|
+
* "Peter Glerup Ericson":https://github.com/pgericson
|
67
72
|
|
68
73
|
If your name should be on the list but isn't, please leave a comment!
|
69
74
|
|
data/activewarehouse-etl.gemspec
CHANGED
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
|
|
25
25
|
s.add_development_dependency('shoulda', '~>2.11.3')
|
26
26
|
s.add_development_dependency('flexmock', '~>0.9.0')
|
27
27
|
s.add_development_dependency('cartesian')
|
28
|
+
s.add_development_dependency('guard')
|
29
|
+
s.add_development_dependency('guard-shell')
|
28
30
|
|
29
31
|
s.files = `git ls-files`.split("\n")
|
30
32
|
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
data/lib/etl/control/control.rb
CHANGED
@@ -41,7 +41,12 @@ module ETL #:nodoc:
|
|
41
41
|
def dependencies
|
42
42
|
control.dependencies
|
43
43
|
end
|
44
|
-
|
44
|
+
|
45
|
+
# Register an error handler
|
46
|
+
def on_error(&block)
|
47
|
+
control.error_handlers << block
|
48
|
+
end
|
49
|
+
|
45
50
|
# Define a source.
|
46
51
|
def source(name, configuration={}, definition={})
|
47
52
|
if configuration[:type]
|
@@ -376,6 +381,11 @@ module ETL #:nodoc:
|
|
376
381
|
:warn => []
|
377
382
|
}
|
378
383
|
end
|
384
|
+
|
385
|
+
# A array of Procs to be invoked for errors notifications
|
386
|
+
def error_handlers
|
387
|
+
@error_handlers ||= []
|
388
|
+
end
|
379
389
|
|
380
390
|
# Get the error threshold. Defaults to 100.
|
381
391
|
def error_threshold
|
@@ -1,6 +1,8 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
|
3
3
|
module ETL #:nodoc:
|
4
|
+
class NoLimitSpecifiedError < StandardError; end
|
5
|
+
|
4
6
|
class Source < ::ActiveRecord::Base #:nodoc:
|
5
7
|
# Connection for database sources
|
6
8
|
end
|
@@ -189,12 +191,13 @@ module ETL #:nodoc:
|
|
189
191
|
|
190
192
|
q << " GROUP BY #{group}" if group
|
191
193
|
q << " ORDER BY #{order}" if order
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
194
|
+
|
195
|
+
limit = ETL::Engine.limit
|
196
|
+
offset = ETL::Engine.offset
|
197
|
+
if limit || offset
|
198
|
+
raise NoLimitSpecifiedError, "Specifying offset without limit is not allowed" if offset and limit.nil?
|
199
|
+
q << " LIMIT #{limit}"
|
200
|
+
q << " OFFSET #{offset}" if offset
|
198
201
|
end
|
199
202
|
|
200
203
|
q = q.gsub(/\n/,' ')
|
@@ -205,7 +208,7 @@ module ETL #:nodoc:
|
|
205
208
|
def query_rows
|
206
209
|
return @query_rows if @query_rows
|
207
210
|
if (configuration[:mysqlstream] == true)
|
208
|
-
MySqlStreamer.new(query,@target)
|
211
|
+
MySqlStreamer.new(query,@target,connection)
|
209
212
|
else
|
210
213
|
connection.select_all(query)
|
211
214
|
end
|
@@ -1,31 +1,73 @@
|
|
1
1
|
require 'open3'
|
2
2
|
|
3
|
+
# Internal: The MySQL streamer is a helper with works with the database_source
|
4
|
+
# in order to allow you to use the --quick option (which stops MySQL)
|
5
|
+
# from building a full result set, also we don't build a full resultset
|
6
|
+
# in Ruby - instead we yield a row at a time
|
7
|
+
#
|
3
8
|
class MySqlStreamer
|
9
|
+
# Internal: Creates a MySQL Streamer
|
10
|
+
#
|
11
|
+
# query - the SQL query
|
12
|
+
# target - the name of the ETL configuration (ie. development/production)
|
13
|
+
# connection - the ActiveRecord connection
|
14
|
+
#
|
15
|
+
# Examples
|
16
|
+
#
|
17
|
+
# MySqlStreamer.new("select * from bob", "development", my_connection)
|
18
|
+
#
|
19
|
+
def initialize(query, target, connection)
|
20
|
+
# Lets just be safe and also make sure there aren't new lines
|
21
|
+
# in the SQL - its bound to cause trouble
|
22
|
+
@query = query.split.join(' ')
|
23
|
+
@name = target
|
24
|
+
@first_row = connection.select_all("#{query} LIMIT 1")
|
25
|
+
end
|
4
26
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
27
|
+
# We implement some bits of a hash so that database_source
|
28
|
+
# can use them
|
29
|
+
def any?
|
30
|
+
@first_row.any?
|
31
|
+
end
|
9
32
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
33
|
+
def first
|
34
|
+
@first_row.first
|
35
|
+
end
|
36
|
+
|
37
|
+
def mandatory_option!(hash, key)
|
38
|
+
value = hash[key]
|
39
|
+
raise "Missing key #{key} in connection configuration #{@name}" if value.blank?
|
40
|
+
value
|
41
|
+
end
|
42
|
+
|
43
|
+
def each
|
44
|
+
keys = nil
|
45
|
+
|
46
|
+
config = ETL::Base.configurations[@name.to_s]
|
47
|
+
host = mandatory_option!(config, 'host')
|
48
|
+
username = mandatory_option!(config, 'username')
|
49
|
+
database = mandatory_option!(config, 'database')
|
50
|
+
password = config['password'] # this one can omitted in some cases
|
51
|
+
|
52
|
+
mysql_command = """mysql --quick -h #{host} -u #{username} -e \"#{@query.gsub("\n","")}\" -D #{database} --password=#{password} -B"""
|
53
|
+
Open3.popen3(mysql_command) do |stdin, out, err, external|
|
54
|
+
until (line = out.gets).nil? do
|
55
|
+
line = line.gsub("\n","")
|
56
|
+
if keys.nil?
|
57
|
+
keys = line.split("\t")
|
58
|
+
else
|
59
|
+
hash = Hash[keys.zip(line.split("\t"))]
|
60
|
+
# map out NULL to nil
|
61
|
+
hash.each do |k, v|
|
62
|
+
hash[k] = nil if v == 'NULL'
|
63
|
+
end
|
64
|
+
yield hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
error = err.gets
|
68
|
+
if (!error.nil? && error.strip.length > 0)
|
69
|
+
throw error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
31
73
|
end
|
data/lib/etl/engine.rb
CHANGED
@@ -49,8 +49,8 @@ module ETL #:nodoc:
|
|
49
49
|
# * ETL::Control::Control instance
|
50
50
|
# * ETL::Batch::Batch instance
|
51
51
|
#
|
52
|
-
# The process command will accept either a .ctl Control file or a .ebf
|
53
|
-
# ETL Batch File.
|
52
|
+
# The process command will accept either a .ctl or .ctl.rb for a Control file or a .ebf
|
53
|
+
# or .ebf.rb for an ETL Batch File.
|
54
54
|
def process(file)
|
55
55
|
new().process(file)
|
56
56
|
end
|
@@ -235,6 +235,14 @@ module ETL #:nodoc:
|
|
235
235
|
def errors
|
236
236
|
@errors ||= []
|
237
237
|
end
|
238
|
+
|
239
|
+
# First attempt at centralizing error notifications
|
240
|
+
def track_error(control, msg)
|
241
|
+
errors << msg
|
242
|
+
control.error_handlers.each do |handler|
|
243
|
+
handler.call(msg)
|
244
|
+
end
|
245
|
+
end
|
238
246
|
|
239
247
|
# Get a Hash of benchmark values where each value represents the total
|
240
248
|
# amount of time in seconds spent processing in that portion of the ETL
|
@@ -264,8 +272,8 @@ module ETL #:nodoc:
|
|
264
272
|
process(File.new(file))
|
265
273
|
when File
|
266
274
|
case file.path
|
267
|
-
when
|
268
|
-
when
|
275
|
+
when /\.ctl(\.rb)?$/; process_control(file)
|
276
|
+
when /\.ebf(\.rb)?$/; process_batch(file)
|
269
277
|
else raise RuntimeError, "Unsupported file type - #{file.path}"
|
270
278
|
end
|
271
279
|
when ETL::Control::Control
|
@@ -352,7 +360,8 @@ module ETL #:nodoc:
|
|
352
360
|
end
|
353
361
|
rescue => e
|
354
362
|
msg = "Error processing rows after read from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
|
355
|
-
|
363
|
+
# TODO - track more information: row if possible, full exception...
|
364
|
+
track_error(control, msg)
|
356
365
|
Engine.logger.error(msg)
|
357
366
|
e.backtrace.each { |line| Engine.logger.error(line) }
|
358
367
|
exceeded_error_threshold?(control) ? break : next
|
@@ -374,10 +383,10 @@ module ETL #:nodoc:
|
|
374
383
|
end
|
375
384
|
rescue ResolverError => e
|
376
385
|
Engine.logger.error(e.message)
|
377
|
-
|
386
|
+
track_error(control, e.message)
|
378
387
|
rescue => e
|
379
388
|
msg = "Error transforming from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
|
380
|
-
|
389
|
+
track_error(control, msg)
|
381
390
|
Engine.logger.error(msg)
|
382
391
|
e.backtrace.each { |line| Engine.logger.error(line) }
|
383
392
|
ensure
|
@@ -403,7 +412,7 @@ module ETL #:nodoc:
|
|
403
412
|
end
|
404
413
|
rescue => e
|
405
414
|
msg = "Error processing rows before write from #{Engine.current_source} on line #{Engine.current_source_row}: #{e}"
|
406
|
-
|
415
|
+
track_error(control, msg)
|
407
416
|
Engine.logger.error(msg)
|
408
417
|
e.backtrace.each { |line| Engine.logger.error(line) }
|
409
418
|
exceeded_error_threshold?(control) ? break : next
|
@@ -423,7 +432,7 @@ module ETL #:nodoc:
|
|
423
432
|
end
|
424
433
|
rescue => e
|
425
434
|
msg = "Error writing to #{Engine.current_destination}: #{e}"
|
426
|
-
|
435
|
+
track_error(control, msg)
|
427
436
|
Engine.logger.error msg
|
428
437
|
e.backtrace.each { |line| Engine.logger.error(line) }
|
429
438
|
exceeded_error_threshold?(control) ? break : next
|
data/lib/etl/execution/batch.rb
CHANGED
data/lib/etl/execution/job.rb
CHANGED
data/lib/etl/version.rb
CHANGED
data/test/config/database.yml
CHANGED
@@ -2,7 +2,7 @@ def declare_gems(activerecord_version)
|
|
2
2
|
source :rubygems
|
3
3
|
|
4
4
|
gem 'activerecord', activerecord_version
|
5
|
-
gem 'adapter_extensions', :git => 'https://github.com/activewarehouse/adapter_extensions.git'
|
5
|
+
gem 'adapter_extensions', :git => 'https://github.com/activewarehouse/adapter_extensions.git'
|
6
6
|
|
7
7
|
if activerecord_version < '3.1'
|
8
8
|
gem 'mysql2', '< 0.3'
|
data/test/engine_test.rb
CHANGED
@@ -33,6 +33,19 @@ CTL
|
|
33
33
|
|
34
34
|
assert_equal 1, engine.errors.size
|
35
35
|
end
|
36
|
+
|
37
|
+
should 'call error callbacks' do
|
38
|
+
engine = ETL::Engine.new
|
39
|
+
|
40
|
+
$our_errors = []
|
41
|
+
engine.process ETL::Control::Control.parse_text <<CTL
|
42
|
+
source :in, { :type => :enumerable, :enumerable => (1..100) }
|
43
|
+
on_error { |error| $our_errors << error }
|
44
|
+
after_read { |row| raise "Failure" }
|
45
|
+
CTL
|
46
|
+
assert_equal 100, $our_errors.size
|
47
|
+
assert_match /on line 100: Failure$/, $our_errors.last
|
48
|
+
end
|
36
49
|
|
37
50
|
end
|
38
51
|
|
data/test/source_test.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/test_helper'
|
2
2
|
|
3
|
+
# TODO - use FactoryGirl or similar
|
4
|
+
def build_source(options = {})
|
5
|
+
ETL::Control::DatabaseSource.new(nil, {
|
6
|
+
:target => 'operational_database',
|
7
|
+
:table => 'people',
|
8
|
+
:mysqlstream => true
|
9
|
+
}.merge(options), nil)
|
10
|
+
end
|
11
|
+
|
3
12
|
class SourceTest < Test::Unit::TestCase
|
4
13
|
|
5
14
|
context "source" do
|
@@ -84,6 +93,8 @@ class SourceTest < Test::Unit::TestCase
|
|
84
93
|
|
85
94
|
context "a database source" do
|
86
95
|
setup do
|
96
|
+
@offset = 2
|
97
|
+
@limit = 5
|
87
98
|
control = ETL::Control::Control.parse(File.dirname(__FILE__) + '/delimited.ctl')
|
88
99
|
configuration = {
|
89
100
|
:database => 'etl_unittest',
|
@@ -97,6 +108,44 @@ class SourceTest < Test::Unit::TestCase
|
|
97
108
|
]
|
98
109
|
@source = ETL::Control::DatabaseSource.new(control, configuration, definition)
|
99
110
|
end
|
111
|
+
|
112
|
+
context "with a specified LIMIT `n`" do
|
113
|
+
setup do
|
114
|
+
ETL::Engine.limit = @limit
|
115
|
+
10.times { |i| Person.create!( :first_name => 'Bob',
|
116
|
+
:last_name => 'Smith',
|
117
|
+
:ssn => i ) }
|
118
|
+
end
|
119
|
+
|
120
|
+
should "only return N rows" do
|
121
|
+
size = build_source(:store_locally => true, :mysqlstream => false).to_a.size
|
122
|
+
assert_equal 5, size
|
123
|
+
end
|
124
|
+
|
125
|
+
teardown do
|
126
|
+
Person.delete_all
|
127
|
+
ETL::Engine.limit = nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
context "with a specified OFFSET `offset`" do
|
132
|
+
setup do
|
133
|
+
ETL::Engine.limit = @limit
|
134
|
+
ETL::Engine.offset = @offset
|
135
|
+
end
|
136
|
+
|
137
|
+
should "raise an exception without LIMIT specified" do
|
138
|
+
ETL::Engine.limit = nil
|
139
|
+
assert_raise (NoLimitSpecifiedError) { build_source(:store_locally => true, :mysqlstream => false).to_a.size }
|
140
|
+
end
|
141
|
+
|
142
|
+
teardown do
|
143
|
+
Person.delete_all
|
144
|
+
ETL::Engine.limit = nil
|
145
|
+
ETL::Engine.offset = nil
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
100
149
|
should "set the local file for extraction storage" do
|
101
150
|
assert_match %r{source_data/localhost/activewarehouse_etl_test/people/\d+.csv}, @source.local_file.to_s
|
102
151
|
end
|
@@ -108,6 +157,36 @@ class SourceTest < Test::Unit::TestCase
|
|
108
157
|
rows = @source.collect { |row| row }
|
109
158
|
assert_equal 1, rows.length
|
110
159
|
end
|
160
|
+
if current_adapter =~ /mysql/
|
161
|
+
context 'with mysqlstream enabled' do
|
162
|
+
|
163
|
+
setup do
|
164
|
+
Person.delete_all
|
165
|
+
Person.create!(:first_name => 'Bob', :last_name => 'Smith', :ssn => '123456789')
|
166
|
+
Person.create!(:first_name => 'John', :last_name => 'Barry', :ssn => '123456790')
|
167
|
+
end
|
168
|
+
|
169
|
+
should 'support store_locally' do
|
170
|
+
assert_equal 2, build_source(:store_locally => true).to_a.size
|
171
|
+
end
|
172
|
+
|
173
|
+
context 'with a NULL value' do
|
174
|
+
|
175
|
+
should 'return nil in row attribute' do
|
176
|
+
Person.create!(:first_name => nil)
|
177
|
+
assert_equal nil, build_source.to_a.last[:first_name]
|
178
|
+
end
|
179
|
+
|
180
|
+
# does not work yet - we probably need a switch on --quick for this
|
181
|
+
should_eventually 'return NULL for string containing NULL' do
|
182
|
+
Person.create!(:first_name => 'NULL', :last_name => 'NULL2')
|
183
|
+
assert_equal 'NULL', build_source.to_a.last[:first_name]
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
end
|
111
190
|
end
|
112
191
|
|
113
192
|
context "a file source with an xml parser" do
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activewarehouse-etl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Anthony Eden
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-06-20 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
17
|
-
requirement: &
|
17
|
+
requirement: &70217989126480 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 0.8.3
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70217989126480
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activesupport
|
28
|
-
requirement: &
|
28
|
+
requirement: &70217989126020 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 3.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70217989126020
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: activerecord
|
39
|
-
requirement: &
|
39
|
+
requirement: &70217989125560 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: 3.0.0
|
45
45
|
type: :runtime
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70217989125560
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: fastercsv
|
50
|
-
requirement: &
|
50
|
+
requirement: &70217984932020 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,10 +55,10 @@ dependencies:
|
|
55
55
|
version: 1.2.0
|
56
56
|
type: :runtime
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70217984932020
|
59
59
|
- !ruby/object:Gem::Dependency
|
60
60
|
name: adapter_extensions
|
61
|
-
requirement: &
|
61
|
+
requirement: &70217984927180 !ruby/object:Gem::Requirement
|
62
62
|
none: false
|
63
63
|
requirements:
|
64
64
|
- - ! '>='
|
@@ -66,10 +66,10 @@ dependencies:
|
|
66
66
|
version: 0.9.5.rc1
|
67
67
|
type: :runtime
|
68
68
|
prerelease: false
|
69
|
-
version_requirements: *
|
69
|
+
version_requirements: *70217984927180
|
70
70
|
- !ruby/object:Gem::Dependency
|
71
71
|
name: shoulda
|
72
|
-
requirement: &
|
72
|
+
requirement: &70217984925860 !ruby/object:Gem::Requirement
|
73
73
|
none: false
|
74
74
|
requirements:
|
75
75
|
- - ~>
|
@@ -77,10 +77,10 @@ dependencies:
|
|
77
77
|
version: 2.11.3
|
78
78
|
type: :development
|
79
79
|
prerelease: false
|
80
|
-
version_requirements: *
|
80
|
+
version_requirements: *70217984925860
|
81
81
|
- !ruby/object:Gem::Dependency
|
82
82
|
name: flexmock
|
83
|
-
requirement: &
|
83
|
+
requirement: &70217989152940 !ruby/object:Gem::Requirement
|
84
84
|
none: false
|
85
85
|
requirements:
|
86
86
|
- - ~>
|
@@ -88,10 +88,10 @@ dependencies:
|
|
88
88
|
version: 0.9.0
|
89
89
|
type: :development
|
90
90
|
prerelease: false
|
91
|
-
version_requirements: *
|
91
|
+
version_requirements: *70217989152940
|
92
92
|
- !ruby/object:Gem::Dependency
|
93
93
|
name: cartesian
|
94
|
-
requirement: &
|
94
|
+
requirement: &70217989152560 !ruby/object:Gem::Requirement
|
95
95
|
none: false
|
96
96
|
requirements:
|
97
97
|
- - ! '>='
|
@@ -99,7 +99,29 @@ dependencies:
|
|
99
99
|
version: '0'
|
100
100
|
type: :development
|
101
101
|
prerelease: false
|
102
|
-
version_requirements: *
|
102
|
+
version_requirements: *70217989152560
|
103
|
+
- !ruby/object:Gem::Dependency
|
104
|
+
name: guard
|
105
|
+
requirement: &70217989152100 !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
type: :development
|
112
|
+
prerelease: false
|
113
|
+
version_requirements: *70217989152100
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: guard-shell
|
116
|
+
requirement: &70217989151680 !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: *70217989151680
|
103
125
|
description: ActiveWarehouse ETL is a pure Ruby Extract-Transform-Load application
|
104
126
|
for loading data into a database.
|
105
127
|
email:
|
@@ -115,6 +137,7 @@ files:
|
|
115
137
|
- 0.9-UPGRADE
|
116
138
|
- CHANGELOG
|
117
139
|
- Gemfile
|
140
|
+
- Guardfile
|
118
141
|
- HOW_TO_RELEASE
|
119
142
|
- LICENSE
|
120
143
|
- README.textile
|