cbaclig-ar-extensions 0.9.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/ChangeLog +148 -0
  2. data/Manifest +111 -0
  3. data/README +167 -0
  4. data/Rakefile +65 -0
  5. data/ar-extensions.gemspec +19 -0
  6. data/benchmarks/README +32 -0
  7. data/benchmarks/benchmark.rb +61 -0
  8. data/benchmarks/boot.rb +21 -0
  9. data/benchmarks/lib/base.rb +121 -0
  10. data/benchmarks/lib/cli_parser.rb +103 -0
  11. data/benchmarks/lib/float.rb +15 -0
  12. data/benchmarks/lib/mysql_benchmark.rb +27 -0
  13. data/benchmarks/lib/output_to_csv.rb +18 -0
  14. data/benchmarks/lib/output_to_html.rb +69 -0
  15. data/cbaclig-ar-extensions.gemspec +30 -0
  16. data/config/database.yml +7 -0
  17. data/config/database.yml.template +7 -0
  18. data/config/mysql.schema +72 -0
  19. data/config/postgresql.schema +39 -0
  20. data/db/migrate/generic_schema.rb +97 -0
  21. data/db/migrate/mysql_schema.rb +32 -0
  22. data/db/migrate/oracle_schema.rb +5 -0
  23. data/db/migrate/version.rb +4 -0
  24. data/init.rb +31 -0
  25. data/lib/ar-extensions.rb +5 -0
  26. data/lib/ar-extensions/adapters/abstract_adapter.rb +139 -0
  27. data/lib/ar-extensions/adapters/mysql.rb +10 -0
  28. data/lib/ar-extensions/adapters/oracle.rb +14 -0
  29. data/lib/ar-extensions/adapters/postgresql.rb +9 -0
  30. data/lib/ar-extensions/adapters/sqlite.rb +7 -0
  31. data/lib/ar-extensions/create_and_update.rb +509 -0
  32. data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
  33. data/lib/ar-extensions/csv.rb +309 -0
  34. data/lib/ar-extensions/delete.rb +143 -0
  35. data/lib/ar-extensions/delete/mysql.rb +3 -0
  36. data/lib/ar-extensions/extensions.rb +506 -0
  37. data/lib/ar-extensions/finder_options.rb +275 -0
  38. data/lib/ar-extensions/finder_options/mysql.rb +6 -0
  39. data/lib/ar-extensions/finders.rb +94 -0
  40. data/lib/ar-extensions/foreign_keys.rb +70 -0
  41. data/lib/ar-extensions/fulltext.rb +62 -0
  42. data/lib/ar-extensions/fulltext/mysql.rb +44 -0
  43. data/lib/ar-extensions/import.rb +362 -0
  44. data/lib/ar-extensions/import/mysql.rb +50 -0
  45. data/lib/ar-extensions/import/postgresql.rb +7 -0
  46. data/lib/ar-extensions/import/sqlite.rb +22 -0
  47. data/lib/ar-extensions/insert_select.rb +178 -0
  48. data/lib/ar-extensions/insert_select/mysql.rb +7 -0
  49. data/lib/ar-extensions/synchronize.rb +30 -0
  50. data/lib/ar-extensions/temporary_table.rb +131 -0
  51. data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
  52. data/lib/ar-extensions/union.rb +204 -0
  53. data/lib/ar-extensions/union/mysql.rb +6 -0
  54. data/lib/ar-extensions/util/sql_generation.rb +27 -0
  55. data/lib/ar-extensions/util/support_methods.rb +32 -0
  56. data/lib/ar-extensions/version.rb +9 -0
  57. data/tests/README +68 -0
  58. data/tests/boot.rb +23 -0
  59. data/tests/database.yml +31 -0
  60. data/tests/database.yml.sample +28 -0
  61. data/tests/fixtures/addresses.yml +25 -0
  62. data/tests/fixtures/books.yml +46 -0
  63. data/tests/fixtures/developers.yml +20 -0
  64. data/tests/fixtures/unit/active_record_base_finders/addresses.yml +25 -0
  65. data/tests/fixtures/unit/active_record_base_finders/books.yml +64 -0
  66. data/tests/fixtures/unit/active_record_base_finders/developers.yml +20 -0
  67. data/tests/fixtures/unit/synchronize/books.yml +16 -0
  68. data/tests/fixtures/unit/to_csv_headers/addresses.yml +8 -0
  69. data/tests/fixtures/unit/to_csv_headers/developers.yml +6 -0
  70. data/tests/fixtures/unit/to_csv_with_common_options/addresses.yml +40 -0
  71. data/tests/fixtures/unit/to_csv_with_common_options/developers.yml +13 -0
  72. data/tests/fixtures/unit/to_csv_with_common_options/languages.yml +29 -0
  73. data/tests/fixtures/unit/to_csv_with_common_options/teams.yml +3 -0
  74. data/tests/fixtures/unit/to_csv_with_default_options/developers.yml +7 -0
  75. data/tests/models/address.rb +4 -0
  76. data/tests/models/animal.rb +2 -0
  77. data/tests/models/book.rb +3 -0
  78. data/tests/models/cart_item.rb +4 -0
  79. data/tests/models/developer.rb +8 -0
  80. data/tests/models/group.rb +3 -0
  81. data/tests/models/language.rb +5 -0
  82. data/tests/models/mysql/book.rb +3 -0
  83. data/tests/models/mysql/test_innodb.rb +3 -0
  84. data/tests/models/mysql/test_memory.rb +3 -0
  85. data/tests/models/mysql/test_myisam.rb +3 -0
  86. data/tests/models/project.rb +2 -0
  87. data/tests/models/shopping_cart.rb +4 -0
  88. data/tests/models/team.rb +4 -0
  89. data/tests/models/topic.rb +13 -0
  90. data/tests/mysql/test_create_and_update.rb +290 -0
  91. data/tests/mysql/test_delete.rb +142 -0
  92. data/tests/mysql/test_finder_options.rb +121 -0
  93. data/tests/mysql/test_finders.rb +29 -0
  94. data/tests/mysql/test_import.rb +354 -0
  95. data/tests/mysql/test_insert_select.rb +173 -0
  96. data/tests/mysql/test_mysql_adapter.rb +45 -0
  97. data/tests/mysql/test_union.rb +81 -0
  98. data/tests/oracle/test_adapter.rb +14 -0
  99. data/tests/postgresql/test_adapter.rb +14 -0
  100. data/tests/prepare.rb +9 -0
  101. data/tests/run.rb +13 -0
  102. data/tests/run_from_gem.rb +17 -0
  103. data/tests/test_activerecord_compatability.rb +71 -0
  104. data/tests/test_finders.rb +543 -0
  105. data/tests/test_helper.rb +70 -0
  106. data/tests/test_import.rb +339 -0
  107. data/tests/test_synchronize.rb +31 -0
  108. data/tests/test_temporary_tables.rb +93 -0
  109. data/tests/test_to_csv_headers.rb +204 -0
  110. data/tests/test_to_csv_with_common_options.rb +670 -0
  111. data/tests/test_to_csv_with_default_options.rb +34 -0
  112. metadata +211 -0
@@ -0,0 +1,19 @@
1
+ require 'rake'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{ar-extensions}
5
+ s.version = "0.9.2"
6
+ s.date = %q{2009-04-20}
7
+ s.summary = %q{Extends ActiveRecord functionality.}
8
+ s.email = %q{zach.dennis@gmail.com}
9
+ s.homepage = %q{http://www.continuousthinking.com/tags/arext}
10
+ s.rubyforge_project = %q{arext}
11
+ s.description = %q{Extends ActiveRecord functionality by adding better finder/query support, as well as supporting mass data import, foreign key, CSV and temporary tables}
12
+ s.require_path = 'lib'
13
+ s.has_rdoc = true
14
+ s.authors = ["Zach Dennis", "Mark Van Holstyn", "Blythe Dunham"]
15
+ s.files = FileList[ 'init.rb', 'db/**/*', 'Rakefile', 'ChangeLog', 'README', 'config/**/*', 'lib/**/*.rb', 'test/**/*' ]
16
+ s.rdoc_options = ["--main", "README"]
17
+ s.extra_rdoc_files = ["README"]
18
+ s.add_dependency(%q<activerecord>, [">= 2.1.2"])
19
+ end
data/benchmarks/README ADDED
@@ -0,0 +1,32 @@
1
+ To run the benchmarks, from within the benchmarks run:
2
+ ruby benchmark.rb [options]
3
+
4
+ The following options are supported:
5
+ --adapter [String] The database adapter to use. IE: mysql, postgresql, oracle
6
+
7
+ --do-not-delete By default all records in the benchmark tables will be deleted at the end of the benchmark. This flag indicates not to delete the benchmark data.
8
+ --num [Integer] The number of objects to benchmark. (Required!)
9
+ --table-type [String] The table type to test. This can be used multiple times. By default it is all table types.
10
+ --to-csv [String] Print results in a CSV file format
11
+ --to-html [String] Print results in HTML format (String filename must be supplied)
12
+
13
+ See "ruby benchmark.rb -h" for the complete listing of options.
14
+
15
+ EXAMPLES
16
+ --------
17
+ To output to html format:
18
+ ruby benchmark.rb --adapter=mysql --to-html=results.html
19
+
20
+ To output to csv format:
21
+ ruby benchmark.rb --adapter=mysql --to-csv=results.csv
22
+
23
+ LIMITATIONS
24
+ -----------
25
+ Currently MySQL is the only supported adapter to benchmark.
26
+
27
+ AUTHOR
28
+ ------
29
+ Zach Dennis
30
+ zach.dennis@gmail.com
31
+ http://www.continuousthinking.com
32
+
@@ -0,0 +1,61 @@
1
+ require 'boot'
2
+
3
+ # Parse the options passed in via the command line
4
+ options = BenchmarkOptionParser.parse( ARGV )
5
+
6
+ # The lib directory that houses the library files we need to benchmark
7
+ LIB_DIR = File.expand_path(
8
+ File.join( File.dirname( __FILE__ ), '..', 'lib' ) )
9
+
10
+ # The support directory where we use to load our connections and models for the
11
+ # benchmarks.
12
+ SUPPORT_DIR = File.expand_path(
13
+ File.join( File.dirname( __FILE__ ), '..', 'tests' ) )
14
+
15
+ # Load our library files
16
+ Dir[ File.join( LIB_DIR, '**/*.rb' ) ].each{ |f| require f }
17
+
18
+ # Load the database adapter
19
+ db_adapter = options.adapter
20
+ require File.join( SUPPORT_DIR, 'connections', "native_#{db_adapter}", 'connection' )
21
+
22
+ # Load all generic models
23
+ Dir[ File.join( SUPPORT_DIR, 'models/*.rb' ) ].each{ |f| require f }
24
+
25
+ # Load database adapter specific models
26
+ Dir[ File.join( SUPPORT_DIR, 'models', "#{db_adapter}/*.rb" ) ].each{|f| require f }
27
+
28
+ # Load databse specific benchmarks
29
+ require File.join( File.dirname( __FILE__ ), 'lib', "#{db_adapter}_benchmark" )
30
+
31
+ # TODO implement method/table-type selection
32
+ table_types = nil
33
+ if options.benchmark_all_types
34
+ table_types = [ "all" ]
35
+ else
36
+ table_types = options.table_types.keys
37
+ end
38
+ puts
39
+
40
+ letter = options.adapter[0].chr
41
+ clazz_str = letter.upcase + options.adapter[1..-1].downcase
42
+ clazz = Object.const_get( clazz_str + "Benchmark" )
43
+
44
+ benchmarks = []
45
+ options.number_of_objects.each do |num|
46
+ benchmarks << (benchmark = clazz.new)
47
+ benchmark.send( "benchmark", table_types, num )
48
+ end
49
+
50
+ options.outputs.each do |output|
51
+ format = output.format.downcase
52
+ require File.join( File.dirname( __FILE__ ), 'lib', "output_to_#{format}" )
53
+ output_module = Object.const_get( "OutputTo#{format.upcase}" )
54
+ benchmarks.each do |benchmark|
55
+ output_module.output_results( output.filename, benchmark.results )
56
+ end
57
+ end
58
+
59
+ puts
60
+ puts "Done with benchmark!"
61
+
@@ -0,0 +1,21 @@
1
+ begin ; require 'rubygems' ; rescue LoadError ; end
2
+ require 'active_record' # ActiveRecord loads the Benchmark library automatically
3
+ require 'active_record/version'
4
+ require 'fastercsv'
5
+ require 'fileutils'
6
+ require 'logger'
7
+
8
+ # Files are loaded alphabetically. If this is a problem then manually specify the files
9
+ # that need to be loaded here.
10
+ Dir[ File.join( File.dirname( __FILE__ ), 'lib', '*.rb' ) ].sort.each{ |f| require f }
11
+
12
+ Dir[ File.join( File.dirname( __FILE__ ), 'lib/active_record_base/*.rb' ) ].each{ |f| require f }
13
+ Dir[ File.join( File.dirname( __FILE__ ), 'lib/adapters/*.rb' ) ].each{ |f| require f }
14
+
15
+ ActiveRecord::Base.logger = Logger.new STDOUT
16
+
17
+
18
+
19
+
20
+
21
+
@@ -0,0 +1,121 @@
1
+ class BenchmarkBase
2
+
3
+ attr_reader :results
4
+
5
+ # The main benchmark method dispatcher. This dispatches the benchmarks
6
+ # to actual benchmark_xxxx methods.
7
+ #
8
+ # == PARAMETERS
9
+ # * table_types - an array of table types to benchmark
10
+ # * num - the number of record insertions to test
11
+ def benchmark( table_types, num )
12
+ array_of_cols_and_vals = build_array_of_cols_and_vals( num )
13
+ table_types.each do |table_type|
14
+ self.send( "benchmark_#{table_type}", array_of_cols_and_vals )
15
+ end
16
+ end
17
+
18
+ # Returns an OpenStruct which contains two attritues, +description+ and +tms+ after performing an
19
+ # actual benchmark.
20
+ #
21
+ # == PARAMETERS
22
+ # * description - the description of the block that is getting benchmarked
23
+ # * blk - the block of code to benchmark
24
+ #
25
+ # == RETURNS
26
+ # An OpenStruct object with the following attributes:
27
+ # * description - the description of the benchmark ran
28
+ # * tms - a Benchmark::Tms containing the results of the benchmark
29
+ def bm( description, &blk )
30
+ tms = nil
31
+ puts "Benchmarking #{description}"
32
+
33
+ begin
34
+ Benchmark.bm { |x| tms = x.report { blk.call } }
35
+ failed = false
36
+ rescue Exception => ex
37
+ failed = true
38
+ end
39
+ OpenStruct.new :description=>description, :tms=>tms, :failed=>failed
40
+ end
41
+
42
+ # Given a model class (ie: Topic), and an array of columns and value sets
43
+ # this will perform all of the benchmarks necessary for this library.
44
+ #
45
+ # == PARAMETERS
46
+ # * model_clazz - the model class to benchmark (ie: Topic)
47
+ # * array_of_cols_and_vals - an array of column identifiers and value sets
48
+ #
49
+ # == RETURNS
50
+ # returns true
51
+ def bm_model( model_clazz, array_of_cols_and_vals )
52
+ cols,vals = array_of_cols_and_vals
53
+ num_inserts = vals.size
54
+
55
+ # add a new result group for this particular benchmark
56
+ group = []
57
+ @results << group
58
+
59
+ description = "#{model_clazz.name}.create (#{num_inserts})"
60
+ group << bm( description ) {
61
+ vals.each do |values|
62
+ model_clazz.create create_hash_for_cols_and_vals( cols, values )
63
+ end }
64
+
65
+ description = "#{model_clazz.name}.import (#{num_inserts}) With Validations"
66
+ group << bm( description ) { model_clazz.import cols, vals, :validate=>true }
67
+
68
+ description = "#{model_clazz.name}.import (#{num_inserts}) Without Validations"
69
+ group << bm( description ) { model_clazz.import cols, vals, :validate=>false }
70
+
71
+ true
72
+ end
73
+
74
+ # Returns a two element array composing of an array of columns and an array of
75
+ # value sets given the passed +num+.
76
+ #
77
+ # === What is a value set?
78
+ # A value set is an array of arrays. Each child array represents an array of value sets
79
+ # for a given row of data.
80
+ #
81
+ # For example, say we wanted to represent an insertion of two records:
82
+ # column_names = [ 'id', 'name', 'description' ]
83
+ # record1 = [ 1, 'John Doe', 'A plumber' ]
84
+ # record2 = [ 2, 'John Smith', 'A painter' ]
85
+ # value_set [ record1, record2 ]
86
+ #
87
+ # == PARAMETER
88
+ # * num - the number of records to create
89
+ def build_array_of_cols_and_vals( num )
90
+ cols = [ :my_name, :description ]
91
+ value_sets = []
92
+ num.times { |i| value_sets << [ "My Name #{i}", "My Description #{i}" ] }
93
+ [ cols, value_sets ]
94
+ end
95
+
96
+ # Returns a hash of column identifier to value mappings giving the passed in
97
+ # value array.
98
+ #
99
+ # Example:
100
+ # cols = [ 'id', 'name', 'description' ]
101
+ # values = [ 1, 'John Doe', 'A plumber' ]
102
+ # hsh = create_hash_for_cols_and_vals( cols, values )
103
+ # # hsh => { 'id'=>1, 'name'=>'John Doe', 'description'=>'A plumber' }
104
+ def create_hash_for_cols_and_vals( cols, vals )
105
+ h = {}
106
+ cols.zip( vals ){ |col,val| h[col] = val }
107
+ h
108
+ end
109
+
110
+ # Deletes all records from all ActiveRecord subclasses
111
+ def delete_all
112
+ ActiveRecord::Base.send( :subclasses ).each do |subclass|
113
+ subclass.delete_all if subclass.respond_to? :delete_all
114
+ end
115
+ end
116
+
117
+ def initialize # :nodoc:
118
+ @results = []
119
+ end
120
+
121
+ end
@@ -0,0 +1,103 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ #
5
+ # == PARAMETERS
6
+ # * a - database adapter. ie: mysql, postgresql, oracle, etc.
7
+ # * n - number of objects to test with. ie: 1, 100, 1000, etc.
8
+ # * t - the table types to test. ie: myisam, innodb, memory, temporary, etc.
9
+ #
10
+ module BenchmarkOptionParser
11
+ BANNER = "Usage: ruby #{$0} [options]\nSee ruby #{$0} -h for more options."
12
+
13
+ def self.print_banner
14
+ puts BANNER
15
+ end
16
+
17
+ def self.print_banner!
18
+ print_banner
19
+ exit
20
+ end
21
+
22
+ def self.print_options( options )
23
+ puts "Benchmarking the following options:"
24
+ puts "\tDatabase adapter: #{options.adapter}"
25
+ puts "\tNumber of objects: #{options.number_of_objects}"
26
+ puts "\tTable types:"
27
+ print_valid_table_types( options, :prefix=>"\t\t" )
28
+ end
29
+
30
+ # TODO IMPLEMENT THIS
31
+ def self.print_valid_table_types( options, hsh={:prefix=>''} )
32
+ if options.table_types.keys.size > 0
33
+ options.table_types.keys.sort.each{ |type| puts hsh[:prefix].to_s + type.to_s }
34
+ else
35
+ puts 'No table types defined.'
36
+ end
37
+ end
38
+
39
+ def self.parse( args )
40
+ options = OpenStruct.new(
41
+ :table_types => {},
42
+ :delete_on_finish => true,
43
+ :number_of_objects => [],
44
+ :outputs => [] )
45
+
46
+ opts = OptionParser.new do |opts|
47
+ opts.banner = BANNER
48
+
49
+ # parse the database adapter
50
+ opts.on( "a", "--adapter [String]",
51
+ "The database adapter to use. IE: mysql, postgresql, oracle" ) do |arg|
52
+ options.adapter = arg
53
+ end
54
+
55
+ # parse do_not_delete flag
56
+ opts.on( "d", "--do-not-delete",
57
+ "By default all records in the benchmark tables will be deleted at the end of the benchmark. " +
58
+ "This flag indicates not to delete the benchmark data." ) do |arg|
59
+ options.delete_on_finish = false
60
+ end
61
+
62
+ # parse the number of row objects to test
63
+ opts.on( "n", "--num [Integer]",
64
+ "The number of objects to benchmark." ) do |arg|
65
+ options.number_of_objects << arg.to_i
66
+ end
67
+
68
+ # parse the table types to test
69
+ opts.on( "t", "--table-type [String]",
70
+ "The table type to test. This can be used multiple times." ) do |arg|
71
+ if arg =~ /^all$/
72
+ options.table_types['all'] = options.benchmark_all_types = true
73
+ else
74
+ options.table_types[arg] = true
75
+ end
76
+ end
77
+
78
+ # print results in CSV format
79
+ opts.on( "--to-csv [String]", "Print results in a CSV file format" ) do |filename|
80
+ options.outputs << OpenStruct.new( :format=>'csv', :filename=>filename)
81
+ end
82
+
83
+ # print results in HTML format
84
+ opts.on( "--to-html [String]", "Print results in HTML format" ) do |filename|
85
+ options.outputs << OpenStruct.new( :format=>'html', :filename=>filename )
86
+ end
87
+ end #end opt.parse!
88
+
89
+ begin
90
+ opts.parse!( args )
91
+ if options.table_types.size == 0
92
+ options.table_types['all'] = options.benchmark_all_types = true
93
+ end
94
+ rescue Exception => ex
95
+ print_banner!
96
+ end
97
+
98
+ print_options( options )
99
+
100
+ options
101
+ end
102
+
103
+ end
@@ -0,0 +1,15 @@
1
+ # Taken from http://www.programmingishard.com/posts/show/128
2
+ # Posted by rbates
3
+ class Float
4
+ def round_to(x)
5
+ (self * 10**x).round.to_f / 10**x
6
+ end
7
+
8
+ def ceil_to(x)
9
+ (self * 10**x).ceil.to_f / 10**x
10
+ end
11
+
12
+ def floor_to(x)
13
+ (self * 10**x).floor.to_f / 10**x
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ class MysqlBenchmark < BenchmarkBase
2
+
3
+ def benchmark_all( array_of_cols_and_vals )
4
+ methods = self.methods.find_all { |m| m =~ /benchmark_/ }
5
+ methods.delete_if{ |m| m =~ /benchmark_(all|model)/ }
6
+ methods.each { |method| self.send( method, array_of_cols_and_vals ) }
7
+ end
8
+
9
+ def benchmark_myisam( array_of_cols_and_vals )
10
+ bm_model( TestMyISAM, array_of_cols_and_vals )
11
+ end
12
+
13
+ def benchmark_innodb( array_of_cols_and_vals )
14
+ bm_model( TestInnoDb, array_of_cols_and_vals )
15
+ end
16
+
17
+ def benchmark_memory( array_of_cols_and_vals )
18
+ bm_model( TestMemory, array_of_cols_and_vals )
19
+ end
20
+
21
+ # TODO utilize this in the benchmark
22
+ def b1enchmark_temporary( array_of_cols_and_vals )
23
+ bm_model( TestTemporary, array_of_cols_and_vals )
24
+ end
25
+
26
+ end
27
+
@@ -0,0 +1,18 @@
1
+ require 'fastercsv'
2
+
3
+ module OutputToCSV
4
+ def self.output_results( filename, results )
5
+ FasterCSV.open( filename, 'w' ) do |csv|
6
+ # Iterate over each result set, which contains many results
7
+ results.each do |result_set|
8
+ columns, times = [], []
9
+ result_set.each do |result|
10
+ columns << result.description
11
+ times << result.tms.real
12
+ end
13
+ csv << columns
14
+ csv << times
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ require 'erb'
2
+
3
+ module OutputToHTML
4
+
5
+ TEMPLATE_HEADER =<<"EOT"
6
+ <div>
7
+ All times are rounded to the nearest thousandth for display purposes. Speedups next to each time are computed
8
+ before any rounding occurs. Also, all speedup calculations are computed by comparing a given time against
9
+ the very first column (which is always the default ActiveRecord::Base.create method.
10
+ </div>
11
+ EOT
12
+
13
+ TEMPLATE =<<"EOT"
14
+ <style>
15
+ td#benchmarkTitle {
16
+ border: 1px solid black;
17
+ padding: 2px;
18
+ font-size: 0.8em;
19
+ background-color: black;
20
+ color: white;
21
+ }
22
+ td#benchmarkCell {
23
+ border: 1px solid black;
24
+ padding: 2px;
25
+ font-size: 0.8em;
26
+ }
27
+ </style>
28
+ <table>
29
+ <tr>
30
+ <% columns.each do |col| %>
31
+ <td id="benchmarkTitle"><%= col %></td>
32
+ <% end %>
33
+ </tr>
34
+ <tr>
35
+ <% times.each do |time| %>
36
+ <td id="benchmarkCell"><%= time %></td>
37
+ <% end %>
38
+ </tr>
39
+ <tr><td>&nbsp;</td></tr>
40
+ </table>
41
+ EOT
42
+
43
+ def self.output_results( filename, results )
44
+ html = ''
45
+ results.each do |result_set|
46
+ columns, times = [], []
47
+ result_set.each do |result|
48
+ columns << result.description
49
+ if result.failed
50
+ times << "failed"
51
+ else
52
+ time = result.tms.real.round_to( 3 )
53
+ speedup = ( result_set.first.tms.real / result.tms.real ).round
54
+
55
+ if result == result_set.first
56
+ times << "#{time}"
57
+ else
58
+ times << "#{time} (#{speedup}x speedup)"
59
+ end
60
+ end
61
+ end
62
+
63
+ template = ERB.new( TEMPLATE, 0, "%<>")
64
+ html << template.result( binding )
65
+ end
66
+
67
+ File.open( filename, 'w' ){ |file| file.write( TEMPLATE_HEADER + html ) }
68
+ end
69
+ end