activewarehouse-etl 0.9.1 → 0.9.5.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +7 -0
  2. data/0.9-UPGRADE +6 -0
  3. data/CHANGELOG +182 -150
  4. data/Gemfile +4 -0
  5. data/HOW_TO_RELEASE +9 -0
  6. data/README +18 -2
  7. data/Rakefile +35 -91
  8. data/active_support_logger.patch +78 -0
  9. data/activewarehouse-etl.gemspec +30 -0
  10. data/lib/etl.rb +10 -2
  11. data/lib/etl/batch/directives.rb +11 -1
  12. data/lib/etl/control/control.rb +2 -2
  13. data/lib/etl/control/destination.rb +27 -7
  14. data/lib/etl/control/destination/database_destination.rb +8 -6
  15. data/lib/etl/control/destination/excel_destination.rb +91 -0
  16. data/lib/etl/control/destination/file_destination.rb +6 -4
  17. data/lib/etl/control/destination/insert_update_database_destination.rb +133 -0
  18. data/lib/etl/control/destination/update_database_destination.rb +109 -0
  19. data/lib/etl/control/source.rb +3 -2
  20. data/lib/etl/control/source/database_source.rb +14 -10
  21. data/lib/etl/control/source/file_source.rb +2 -2
  22. data/lib/etl/engine.rb +17 -15
  23. data/lib/etl/execution.rb +0 -1
  24. data/lib/etl/execution/batch.rb +3 -1
  25. data/lib/etl/execution/migration.rb +5 -0
  26. data/lib/etl/parser/delimited_parser.rb +20 -1
  27. data/lib/etl/parser/excel_parser.rb +112 -0
  28. data/lib/etl/processor/bulk_import_processor.rb +4 -2
  29. data/lib/etl/processor/database_join_processor.rb +68 -0
  30. data/lib/etl/processor/escape_csv_processor.rb +77 -0
  31. data/lib/etl/processor/filter_row_processor.rb +51 -0
  32. data/lib/etl/processor/ftp_downloader_processor.rb +68 -0
  33. data/lib/etl/processor/ftp_uploader_processor.rb +65 -0
  34. data/lib/etl/processor/imapattachment_downloader_processor.rb +91 -0
  35. data/lib/etl/processor/pop3attachment_downloader_processor.rb +90 -0
  36. data/lib/etl/processor/sftp_downloader_processor.rb +63 -0
  37. data/lib/etl/processor/sftp_uploader_processor.rb +63 -0
  38. data/lib/etl/processor/zip_file_processor.rb +27 -0
  39. data/lib/etl/transform/calculation_transform.rb +71 -0
  40. data/lib/etl/transform/foreign_key_lookup_transform.rb +25 -7
  41. data/lib/etl/transform/ordinalize_transform.rb +3 -1
  42. data/lib/etl/transform/split_fields_transform.rb +27 -0
  43. data/lib/etl/version.rb +1 -7
  44. data/test-matrix.yml +10 -0
  45. data/test/.gitignore +1 -0
  46. data/test/.ignore +2 -0
  47. data/test/all.ebf +6 -0
  48. data/test/apache_combined_log.ctl +11 -0
  49. data/test/batch_test.rb +41 -0
  50. data/test/batch_with_error.ebf +6 -0
  51. data/test/batched1.ctl +0 -0
  52. data/test/batched2.ctl +0 -0
  53. data/test/block_processor.ctl +6 -0
  54. data/test/block_processor_error.ctl +1 -0
  55. data/test/block_processor_pre_post_process.ctl +4 -0
  56. data/test/block_processor_remove_rows.ctl +5 -0
  57. data/test/block_processor_test.rb +38 -0
  58. data/test/config/Gemfile.rails-2.3.x +3 -0
  59. data/test/config/Gemfile.rails-2.3.x.lock +38 -0
  60. data/test/config/Gemfile.rails-3.0.x +3 -0
  61. data/test/config/Gemfile.rails-3.0.x.lock +49 -0
  62. data/test/config/common.rb +21 -0
  63. data/test/connection/mysql/connection.rb +9 -0
  64. data/test/connection/mysql/schema.sql +36 -0
  65. data/test/connection/postgresql/connection.rb +13 -0
  66. data/test/connection/postgresql/schema.sql +39 -0
  67. data/test/control_test.rb +43 -0
  68. data/test/data/apache_combined_log.txt +3 -0
  69. data/test/data/bulk_import.txt +3 -0
  70. data/test/data/bulk_import_with_empties.txt +3 -0
  71. data/test/data/decode.txt +3 -0
  72. data/test/data/delimited.txt +3 -0
  73. data/test/data/encode_source_latin1.txt +2 -0
  74. data/test/data/excel.xls +0 -0
  75. data/test/data/excel2.xls +0 -0
  76. data/test/data/fixed_width.txt +3 -0
  77. data/test/data/multiple_delimited_1.txt +3 -0
  78. data/test/data/multiple_delimited_2.txt +3 -0
  79. data/test/data/people.txt +3 -0
  80. data/test/data/sax.xml +14 -0
  81. data/test/data/xml.xml +16 -0
  82. data/test/date_dimension_builder_test.rb +96 -0
  83. data/test/delimited.ctl +30 -0
  84. data/test/delimited_absolute.ctl +33 -0
  85. data/test/delimited_destination_db.ctl +25 -0
  86. data/test/delimited_excel.ctl +31 -0
  87. data/test/delimited_insert_update.ctl +34 -0
  88. data/test/delimited_update.ctl +34 -0
  89. data/test/delimited_with_bulk_load.ctl +34 -0
  90. data/test/destination_test.rb +275 -0
  91. data/test/directive_test.rb +23 -0
  92. data/test/encode_processor_test.rb +32 -0
  93. data/test/engine_test.rb +32 -0
  94. data/test/errors.ctl +24 -0
  95. data/test/etl_test.rb +42 -0
  96. data/test/excel.ctl +24 -0
  97. data/test/excel2.ctl +25 -0
  98. data/test/fixed_width.ctl +35 -0
  99. data/test/generator_test.rb +14 -0
  100. data/test/inline_parser.ctl +17 -0
  101. data/test/mocks/mock_destination.rb +26 -0
  102. data/test/mocks/mock_source.rb +25 -0
  103. data/test/model_source.ctl +14 -0
  104. data/test/multiple_delimited.ctl +22 -0
  105. data/test/multiple_source_delimited.ctl +39 -0
  106. data/test/parser_test.rb +224 -0
  107. data/test/performance/delimited.ctl +30 -0
  108. data/test/processor_test.rb +44 -0
  109. data/test/row_processor_test.rb +17 -0
  110. data/test/sax.ctl +26 -0
  111. data/test/scd/1.txt +1 -0
  112. data/test/scd/2.txt +1 -0
  113. data/test/scd/3.txt +1 -0
  114. data/test/scd_test.rb +257 -0
  115. data/test/scd_test_type_1.ctl +43 -0
  116. data/test/scd_test_type_2.ctl +34 -0
  117. data/test/screen_test.rb +9 -0
  118. data/test/screen_test_error.ctl +3 -0
  119. data/test/screen_test_fatal.ctl +3 -0
  120. data/test/source_test.rb +139 -0
  121. data/test/test_helper.rb +34 -0
  122. data/test/transform_test.rb +101 -0
  123. data/test/vendor/adapter_extensions-0.5.0/CHANGELOG +26 -0
  124. data/test/vendor/adapter_extensions-0.5.0/LICENSE +16 -0
  125. data/test/vendor/adapter_extensions-0.5.0/README +7 -0
  126. data/test/vendor/adapter_extensions-0.5.0/Rakefile +158 -0
  127. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions.rb +12 -0
  128. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/abstract_adapter.rb +44 -0
  129. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/mysql_adapter.rb +63 -0
  130. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/postgresql_adapter.rb +52 -0
  131. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/connection_adapters/sqlserver_adapter.rb +44 -0
  132. data/test/vendor/adapter_extensions-0.5.0/lib/adapter_extensions/version.rb +10 -0
  133. data/test/xml.ctl +31 -0
  134. metadata +229 -70
  135. data/lib/etl/execution/record.rb +0 -18
@@ -0,0 +1,5 @@
1
+ source :in, { :type => :mock, :name => :block_input }
2
+
3
+ before_write { |row| row[:obsolete] == true ? nil : row }
4
+
5
+ destination :out, { :type => :mock, :name => :block_output }
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+ include ETL
3
+ include ETL::Control
4
+
5
+ class TestWitness
6
+ end
7
+
8
+ class BlockProcessorTest < Test::Unit::TestCase
9
+
10
+ def test_block_processor_should_work_as_both_after_read_and_before_write_row_processor
11
+ MockSource[:block_processed_input] = [{ :first_name => 'John'},{:first_name => 'Gary'}]
12
+ process 'block_processor.ctl'
13
+ assert_equal 4, MockDestination[:block_processed_output].size
14
+ assert_equal({ :first_name => 'John', :added_by_after_read => 'after-John', :added_by_before_write => "Row 1" }, MockDestination[:block_processed_output][0])
15
+ assert_equal({ :new_row => 'added by post_processor' }, MockDestination[:block_processed_output][1])
16
+ assert_equal({ :first_name => 'Gary', :added_by_after_read => 'after-Gary', :added_by_before_write => "Row 2" }, MockDestination[:block_processed_output][2])
17
+ assert_equal({ :new_row => 'added by post_processor' }, MockDestination[:block_processed_output][3])
18
+ end
19
+
20
+ def test_block_processor_should_let_rows_be_removed_by_setting_it_to_nil
21
+ MockSource[:block_input] = [{ :obsolete => true, :name => 'John'},{ :obsolete => false, :name => 'Gary'}]
22
+ process 'block_processor_remove_rows.ctl'
23
+ assert_equal([{ :obsolete => false, :name => 'Gary' }], MockDestination[:block_output]) # only one record should be kept
24
+ end
25
+
26
+ def test_block_processor_should_work_as_pre_or_post_processor
27
+ flexmock(TestWitness).should_receive(:call).with("I'm called from pre_process")
28
+ flexmock(TestWitness).should_receive(:call).with("I'm called from post_process")
29
+ MockSource[:another_input] = [{ :obsolete => true, :name => 'John'},{ :obsolete => false, :name => 'Gary'}]
30
+ process 'block_processor_pre_post_process.ctl'
31
+ assert_equal(MockSource[:another_input], MockDestination[:another_output])
32
+ end
33
+
34
+ def test_block_error_should_be_propagated
35
+ assert_raise(ControlError) { process 'block_processor_error.ctl' }
36
+ end
37
+
38
+ end
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/common'
2
+
3
+ common_gemfile('2.3.11')
@@ -0,0 +1,38 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activerecord (2.3.11)
5
+ activesupport (= 2.3.11)
6
+ activesupport (2.3.11)
7
+ fastercsv (1.5.4)
8
+ flexmock (0.9.0)
9
+ mysql (2.8.1)
10
+ net-sftp (2.0.5)
11
+ net-ssh (>= 2.0.9)
12
+ net-ssh (2.1.4)
13
+ pg (0.11.0)
14
+ rdoc (3.6.1)
15
+ ruby-ole (1.2.11.1)
16
+ shoulda (2.11.3)
17
+ spreadsheet (0.6.5.4)
18
+ ruby-ole (>= 1.0)
19
+ tmail (1.2.7.1)
20
+ zip (2.0.2)
21
+
22
+ PLATFORMS
23
+ java
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ activerecord (= 2.3.11)
28
+ activesupport (= 2.3.11)
29
+ fastercsv (= 1.5.4)
30
+ flexmock (= 0.9.0)
31
+ mysql (= 2.8.1)
32
+ net-sftp (= 2.0.5)
33
+ pg (= 0.11.0)
34
+ rdoc
35
+ shoulda (= 2.11.3)
36
+ spreadsheet (= 0.6.5.4)
37
+ tmail (= 1.2.7.1)
38
+ zip (= 2.0.2)
@@ -0,0 +1,3 @@
1
+ require File.dirname(__FILE__) + '/common'
2
+
3
+ common_gemfile('3.0.7')
@@ -0,0 +1,49 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.7)
5
+ activesupport (= 3.0.7)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.5.0)
8
+ activerecord (3.0.7)
9
+ activemodel (= 3.0.7)
10
+ activesupport (= 3.0.7)
11
+ arel (~> 2.0.2)
12
+ tzinfo (~> 0.3.23)
13
+ activesupport (3.0.7)
14
+ arel (2.0.10)
15
+ builder (2.1.2)
16
+ fastercsv (1.5.4)
17
+ flexmock (0.9.0)
18
+ i18n (0.5.0)
19
+ mysql (2.8.1)
20
+ net-sftp (2.0.5)
21
+ net-ssh (>= 2.0.9)
22
+ net-ssh (2.1.4)
23
+ pg (0.11.0)
24
+ rdoc (3.6.1)
25
+ ruby-ole (1.2.11.1)
26
+ shoulda (2.11.3)
27
+ spreadsheet (0.6.5.4)
28
+ ruby-ole (>= 1.0)
29
+ tmail (1.2.7.1)
30
+ tzinfo (0.3.27)
31
+ zip (2.0.2)
32
+
33
+ PLATFORMS
34
+ java
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ activerecord (= 3.0.7)
39
+ activesupport (= 3.0.7)
40
+ fastercsv (= 1.5.4)
41
+ flexmock (= 0.9.0)
42
+ mysql (= 2.8.1)
43
+ net-sftp (= 2.0.5)
44
+ pg (= 0.11.0)
45
+ rdoc
46
+ shoulda (= 2.11.3)
47
+ spreadsheet (= 0.6.5.4)
48
+ tmail (= 1.2.7.1)
49
+ zip (= 2.0.2)
@@ -0,0 +1,21 @@
1
+ def common_gemfile(rails_version)
2
+ source :rubygems
3
+
4
+ # using explicit versions for the gems to avoid any weirdness later on
5
+ gem "activesupport", rails_version
6
+ gem "activerecord", rails_version
7
+
8
+ gem "fastercsv", "1.5.4"
9
+ gem "spreadsheet", "0.6.5.4"
10
+ gem "tmail", "1.2.7.1"
11
+ gem "net-sftp", "2.0.5"
12
+ gem "zip", "2.0.2"
13
+
14
+ gem "shoulda", "2.11.3"
15
+ gem "flexmock", "0.9.0"
16
+
17
+ gem "mysql", "2.8.1"
18
+ gem "pg", "0.11.0"
19
+
20
+ gem "rdoc"
21
+ end
@@ -0,0 +1,9 @@
1
+ print "Using native MySQL\n"
2
+
3
+ puts "Resetting database"
4
+ conn = ETL::Engine.connection(:data_warehouse)
5
+ conn.recreate_database(conn.current_database)
6
+ conn.reconnect!
7
+ lines = open(File.join(File.dirname(__FILE__), 'schema.sql')).readlines
8
+ lines.join.split(';').each { |line| conn.execute(line) }
9
+ conn.disconnect!
@@ -0,0 +1,36 @@
1
+ drop table if exists people;
2
+ create table people (
3
+ id int not null primary key,
4
+ first_name char(255) not null,
5
+ last_name char(255) not null,
6
+ ssn char(64) not null
7
+ );
8
+ drop table if exists places;
9
+ create table places (
10
+ address text,
11
+ city char(255),
12
+ state char(255),
13
+ country char(2)
14
+ );
15
+
16
+ drop table if exists person_dimension;
17
+ create table person_dimension (
18
+ id int not null primary key,
19
+ first_name char(50),
20
+ last_name char(50),
21
+ address char(100),
22
+ city char(50),
23
+ state char(50),
24
+ zip_code char(20),
25
+ effective_date datetime,
26
+ end_date datetime,
27
+ latest_version boolean not null
28
+ );
29
+
30
+ drop table if exists truncate_test;
31
+ create table truncate_test (
32
+ x char(4)
33
+ );
34
+ insert into truncate_test (x) values ('a');
35
+ insert into truncate_test (x) values ('b');
36
+ insert into truncate_test (x) values ('c');
@@ -0,0 +1,13 @@
1
+ print "Using PostgreSQL\n"
2
+
3
+ puts "Resetting database"
4
+ conn = ETL::Engine.connection(:data_warehouse)
5
+
6
+ lines = open(File.join(File.dirname(__FILE__), 'schema.sql')).readlines
7
+ lines.join.split(';').each_with_index do |line, index|
8
+ begin
9
+ conn.execute(line)
10
+ rescue => e
11
+ puts "failed to load line #{index}: #{e}"
12
+ end
13
+ end
@@ -0,0 +1,39 @@
1
+ drop table people;
2
+ create table people (
3
+ id SERIAL PRIMARY KEY,
4
+ first_name character varying(255) not null,
5
+ /* null below allowed for bulk_import_with_empties.txt test */
6
+ last_name character varying(255) null,
7
+ ssn character varying(64) not null
8
+ );
9
+
10
+ drop table places;
11
+ create table places (
12
+ id SERIAL PRIMARY KEY,
13
+ address text,
14
+ city character varying(255),
15
+ state character varying(255),
16
+ country character varying(2)
17
+ );
18
+
19
+ drop table person_dimension;
20
+ create table person_dimension (
21
+ id SERIAL PRIMARY KEY,
22
+ first_name character varying(50),
23
+ last_name character varying(50),
24
+ address character varying(100),
25
+ city character varying(50),
26
+ state character varying(50),
27
+ zip_code character varying(20),
28
+ effective_date timestamp without time zone,
29
+ end_date timestamp without time zone,
30
+ latest_version boolean not null
31
+ );
32
+
33
+ drop table truncate_test;
34
+ create table truncate_test (
35
+ x character varying(4)
36
+ );
37
+ insert into truncate_test (x) values ('a');
38
+ insert into truncate_test (x) values ('b');
39
+ insert into truncate_test (x) values ('c');
@@ -0,0 +1,43 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ControlTest < Test::Unit::TestCase
4
+ # Test the ability to parse control files.
5
+ def test_parse
6
+ assert_nothing_raised do
7
+ Dir.glob('*.ctl') do |f|
8
+ ETL::Control::Control.parse(File.join(File.dirname(__FILE__), f))
9
+ end
10
+ end
11
+ end
12
+
13
+ def test_bad_control_raises_error
14
+ assert_raise ETL::ControlError do
15
+ ETL::Control::Control.resolve(0)
16
+ end
17
+ end
18
+
19
+ def test_resolve_control_object
20
+ assert_nothing_raised do
21
+ ETL::Control::Control.resolve(ETL::Control::Control.parse(File.join(File.dirname(__FILE__), 'delimited.ctl')))
22
+ end
23
+ end
24
+
25
+ def test_set_error_threshold
26
+ assert_nothing_raised do
27
+ ETL::Engine.process(File.join(File.dirname(__FILE__), 'errors.ctl'))
28
+ end
29
+ end
30
+
31
+ def test_bad_processor_name
32
+ assert_raise ETL::ControlError do
33
+ s = "before_write :chunky_monkey"
34
+ ETL::Control::Control.parse_text(s)
35
+ end
36
+ end
37
+
38
+ def test_dependencies
39
+ s = "depends_on 'foo', 'bar'"
40
+ control = ETL::Control::Control.parse_text(s)
41
+ assert_equal control.dependencies, ['foo','bar']
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ 127.0.0.1 - frank [10/Oct/2000:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326 "http://www.example.com/start.html" "Mozilla/4.08 [en] (Win98; I ;Nav)"
2
+ 127.0.0.1 - bob [11/Oct/2000:05:22:02 -0700] "GET /apache_pb.gif HTTP/1.1" 200 2326 "http://www.foo.com/" "Mozilla/4.08 [en] (Win98; I ;Nav)"
3
+ 127.0.0.1 - bob [11/Oct/2000:05:52:31 -0700] "GET /apache_pb.gif HTTP/1.1" 200 2326 "-" "Mozilla/4.08 [en] (Win98; I ;Nav)"
@@ -0,0 +1,3 @@
1
+ 1,Chris,Smith,111223333
2
+ 2,Jim,Foxworthy,444332222
3
+ 3,Brian,Collingsworth,123443435
@@ -0,0 +1,3 @@
1
+ 1,Chris,Smith,111223333
2
+ 2,Jim,,444332222
3
+ 3,Brian,Collingsworth,123443435
@@ -0,0 +1,3 @@
1
+ M:Male
2
+ F:Female
3
+ :Unknown
@@ -0,0 +1,3 @@
1
+ Chris,Smith,111223333,24,M
2
+ Jim,Foxworthy,444332222,51,M
3
+ Brian,Collingsworth,123443435,10,M
@@ -0,0 +1,2 @@
1
+ �ph�m�re has accents.
2
+ let's encode them.
Binary file
Binary file
@@ -0,0 +1,3 @@
1
+ Bob Smith 12344555523
2
+ Jane Doe 98766211145
3
+ AbcdefghiJklmnopqrstu12345678920
@@ -0,0 +1,3 @@
1
+ Chris,Smith,111223333,24
2
+ Jim,Foxworthy,444332222,51
3
+ Brian,Collingsworth,123443435,10
@@ -0,0 +1,3 @@
1
+ Bob,Jones,444223333,28
2
+ Tom,Allen,324001232,33
3
+ Jesse,Baker,555443333,21
@@ -0,0 +1,3 @@
1
+ Bob,Smith
2
+ Jane,Doe
3
+ Chris,Cornell
@@ -0,0 +1,14 @@
1
+ <?xml version="1.0"?>
2
+
3
+ <people>
4
+ <person age="24">
5
+ <first_name>Bob</first_name>
6
+ <last_name>Smith</last_name>
7
+ <social_security_number>123456789</social_security_number>
8
+ </person>
9
+ <person age="31">
10
+ <first_name>John</first_name>
11
+ <last_name>Doe</last_name>
12
+ <social_security_number>222114545</social_security_number>
13
+ </person>
14
+ </people>
@@ -0,0 +1,16 @@
1
+ <?xml version="1.0"?>
2
+
3
+ <people>
4
+ <person>
5
+ <first_name>Bob</first_name>
6
+ <last_name>Smith</last_name>
7
+ <social_security_number>123456789</social_security_number>
8
+ <age>24</age>
9
+ </person>
10
+ <person>
11
+ <first_name>John</first_name>
12
+ <last_name>Doe</last_name>
13
+ <social_security_number>222114545</social_security_number>
14
+ <age>31</age>
15
+ </person>
16
+ </people>
@@ -0,0 +1,96 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class DateDimensionBuilderTest < Test::Unit::TestCase
4
+
5
+ context "the DateDimensionBuilder" do
6
+ context "when initialized with defaults" do
7
+ setup do
8
+ @builder = ETL::Builder::DateDimensionBuilder.new
9
+ end
10
+ should "have a start date of 5 years ago" do
11
+ assert_equal Time.now.years_ago(5).to_date, @builder.start_date.to_date
12
+ end
13
+ should "have an end date of now" do
14
+ assert_equal Time.now.to_date, @builder.end_date.to_date
15
+ end
16
+ should "have an empty of array of holiday indicators" do
17
+ assert_equal [], @builder.holiday_indicators
18
+ end
19
+ end
20
+ context "when initialized with arguments" do
21
+ setup do
22
+ @start_date = Time.now.years_ago(2)
23
+ @end_date = Time.now.years_ago(1)
24
+ @builder = ETL::Builder::DateDimensionBuilder.new(@start_date, @end_date)
25
+ end
26
+ should "respect a custom start date" do
27
+ assert_equal @start_date.to_date, @builder.start_date.to_date
28
+ end
29
+ should "respect a custom end date" do
30
+ assert_equal @end_date.to_date, @builder.end_date.to_date
31
+ end
32
+ end
33
+ context "when building a date dimension using the default settings" do
34
+ setup do
35
+ # specific dates required when testing, because leap years affect
36
+ # how many records are built
37
+ @start_date = Date.parse('2002-05-19').to_time
38
+ @end_date = Date.parse('2007-05-19').to_time
39
+ @builder = ETL::Builder::DateDimensionBuilder.new(@start_date, @end_date)
40
+ @records = @builder.build
41
+ end
42
+ should "build a dimension with the correct number of records" do
43
+ assert_equal 1827, @records.length
44
+ end
45
+ should "have the correct first date" do
46
+ assert_date_dimension_record_equal(@builder.start_date, @records.first)
47
+ end
48
+ end
49
+ context "when building a date dimension with a fiscal year offset month" do
50
+ should_eventually "respect the fiscal year offset month" do
51
+
52
+ end
53
+ end
54
+ end
55
+
56
+ def assert_date_dimension_record_equal(date, record)
57
+ real_date = date
58
+ date = date.to_time
59
+ assert_equal date.strftime("%m/%d/%Y"), record[:date]
60
+ assert_equal date.strftime("%B %d,%Y"), record[:full_date_description]
61
+ assert_equal date.strftime("%A"), record[:day_of_week]
62
+ assert_equal date.day, record[:day_number_in_calendar_month]
63
+ assert_equal date.yday, record[:day_number_in_calendar_year]
64
+ assert_equal date.day, record[:day_number_in_fiscal_month]
65
+ assert_equal date.fiscal_year_yday, record[:day_number_in_fiscal_year]
66
+ assert_equal "Week #{date.week}", record[:calendar_week]
67
+ assert_equal date.week, record[:calendar_week_number_in_year]
68
+ assert_equal date.strftime("%B"), record[:calendar_month_name]
69
+ assert_equal date.month, record[:calendar_month_number_in_year]
70
+ assert_equal date.strftime("%Y-%m"), record[:calendar_year_month]
71
+ assert_equal "Q#{date.quarter}", record[:calendar_quarter]
72
+ assert_equal date.quarter, record[:calendar_quarter_number_in_year]
73
+ assert_equal "#{date.strftime('%Y')}-#{record[:calendar_quarter]}", record[:calendar_year_quarter]
74
+ assert_equal "#{date.year}", record[:calendar_year]
75
+ assert_equal "FY Week #{date.fiscal_year_week}", record[:fiscal_week]
76
+ assert_equal date.fiscal_year_week, record[:fiscal_week_number_in_year]
77
+ assert_equal date.fiscal_year_month, record[:fiscal_month]
78
+ assert_equal date.fiscal_year_month, record[:fiscal_month_number_in_year]
79
+ assert_equal "FY#{date.fiscal_year}-" + date.fiscal_year_month.to_s.rjust(2, '0'), record[:fiscal_year_month]
80
+ assert_equal "FY Q#{date.fiscal_year_quarter}", record[:fiscal_quarter]
81
+ assert_equal "FY#{date.fiscal_year}-Q#{date.fiscal_year_quarter}", record[:fiscal_year_quarter]
82
+ assert_equal date.fiscal_year_quarter, record[:fiscal_year_quarter_number]
83
+ assert_equal "FY#{date.fiscal_year}", record[:fiscal_year]
84
+ assert_equal date.fiscal_year, record[:fiscal_year_number]
85
+ assert_equal 'Nonholiday', record[:holiday_indicator]
86
+ assert_equal weekday_indicators[date.wday], record[:weekday_indicator]
87
+ assert_equal 'None', record[:selling_season]
88
+ assert_equal 'None', record[:major_event]
89
+ assert_equal record[:sql_date_stamp], real_date
90
+ end
91
+
92
+ private
93
+ def weekday_indicators
94
+ ['Weekend','Weekday','Weekday','Weekday','Weekday','Weekday','Weekend']
95
+ end
96
+ end