ruport-util 0.10.0 → 0.12.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/Rakefile CHANGED
@@ -10,7 +10,7 @@ require "rake/gempackagetask"
10
10
 
11
11
  require 'spec/rake/spectask'
12
12
 
13
- task :default => [:wraptest]
13
+ task :default => [:test]
14
14
 
15
15
  desc "Run all tests"
16
16
  Spec::Rake::SpecTask.new('test') do |t|
@@ -33,7 +33,7 @@ end
33
33
 
34
34
  spec = Gem::Specification.new do |spec|
35
35
  spec.name = "ruport-util"
36
- spec.version = "0.10.0"
36
+ spec.version = "0.12.0"
37
37
  spec.platform = Gem::Platform::RUBY
38
38
  spec.summary = "A set of tools and helper libs for Ruby Reports"
39
39
  spec.files = Dir.glob("{example,lib,test,bin}/**/**/*") +
Binary file
data/lib/ruport/util.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Ruport
2
2
  module Util
3
- VERSION = "0.10.0"
3
+ VERSION = "0.12.0"
4
4
 
5
5
  file = __FILE__
6
6
  file = File.readlink(file) if File.symlink?(file)
@@ -18,4 +18,6 @@ require "ruport/util/mailer"
18
18
  require "ruport/util/bench"
19
19
  require "ruport/util/generator"
20
20
  require "ruport/util/pdf/form"
21
- require "ruport/util/ods"
21
+ require "ruport/util/ods"
22
+ require "ruport/util/query"
23
+ require "ruport/util/xls"
@@ -39,11 +39,11 @@ class Ruport::Formatter
39
39
 
40
40
  class PDF
41
41
  def draw_graph(graph, opts={})
42
- x = opts[:x]
43
- y = opts[:y]
44
- width = opts[:width]
45
- height = opts[:height]
46
- g = graph.as(:jpg)
42
+ x = opts.delete(:x)
43
+ y = opts.delete(:y)
44
+ width = opts.delete(:width)
45
+ height = opts.delete(:height)
46
+ g = graph.as(:jpg, opts)
47
47
  info = ::PDF::Writer::Graphics::ImageInfo.new(g)
48
48
 
49
49
  # reduce the size of the image until it fits into the requested box
@@ -4,8 +4,6 @@ module Ruport
4
4
 
5
5
  required_option :customer_info,:company_info,:order_info,:comments
6
6
 
7
- option :title
8
-
9
7
  stage :invoice_headers,:invoice_body,:invoice_footer
10
8
 
11
9
  finalize :invoice
@@ -0,0 +1,253 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # query.rb provides a basic wrapper around RubyDBI for SQL interaction
4
+ #
5
+ # Original work began by Gregory Brown based on ideas from James Edward Gray II
6
+ # in August, 2005.
7
+ #
8
+ # Copyright (C) 2005-2007, Gregory Brown
9
+ # All Rights Reserved.
10
+ #
11
+ # This is free software distributed under the same terms as Ruby 1.8
12
+ # See LICENSE and COPYING for details.
13
+ require "generator"
14
+
15
+ module Ruport
16
+
17
+ # === Overview
18
+ #
19
+ # Query offers a way to interact with databases via RubyDBI. It supports
20
+ # returning result sets in either Ruport's Data::Table, or in their
21
+ # raw form as DBI::Rows.
22
+ #
23
+ # Query allows you to treat your result sets as an Enumerable data structure
24
+ # that plays well with the rest of Ruport.
25
+ #
26
+ # If you are using ActiveRecord, you might prefer our acts_as_reportable
27
+ # extension.
28
+ #
29
+ class Query
30
+
31
+ include Enumerable
32
+
33
+ # Ruport::Query provides an interface for dealing with raw SQL queries.
34
+ # The SQL can be single or multistatement, but the resulting Data::Table
35
+ # will consist only of the result of the last statement.
36
+ #
37
+ # Available options:
38
+ #
39
+ # <b><tt>:source</tt></b>:: A source specified in
40
+ # Ruport::Query.sources, defaults to
41
+ # <tt>:default</tt>.
42
+ # <b><tt>:dsn</tt></b>:: If specifed, the Query object will
43
+ # manually override Ruport::Query.
44
+ # <b><tt>:user</tt></b>:: If a DSN is specified, the user can
45
+ # be set with this option.
46
+ # <b><tt>:password</tt></b>:: If a DSN is specified, the password
47
+ # can be set with this option.
48
+ # <b><tt>:row_type</tt></b>:: When set to :raw, DBI::Rows will be
49
+ # returned instead of a Data::Table
50
+ #
51
+ # Examples:
52
+ #
53
+ # # uses Ruport::Query's default source
54
+ # Ruport::Query.new("select * from fo")
55
+ #
56
+ # # uses the Ruport::Query's source labeled :my_source
57
+ # Ruport::Query.new("select * from fo", :source => :my_source)
58
+ #
59
+ # # uses a manually entered source
60
+ # Ruport::Query.new("select * from fo", :dsn => "dbi:mysql:my_db",
61
+ # :user => "greg", :password => "chunky_bacon" )
62
+ #
63
+ # # uses a SQL file stored on disk
64
+ # Ruport::Query.new("my_query.sql")
65
+ #
66
+ # # explicitly use a file, even if it doesn't end in .sql
67
+ # Ruport::Query.new(:file => "foo")
68
+ #
69
+ def initialize(sql, options={})
70
+ if sql.kind_of?(Hash)
71
+ options = { :source => :default }.merge(sql)
72
+ sql = options[:file] || options[:string]
73
+ else
74
+ options = { :source => :default, :string => sql }.merge(options)
75
+ options[:file] = sql if sql =~ /.sql$/
76
+ end
77
+ origin = options[:file] ? :file : :string
78
+
79
+ @statements = SqlSplit.new(get_query(origin,sql))
80
+ @sql = @statements.join
81
+
82
+ if options[:dsn]
83
+ Ruport::Query.add_source :temp, :dsn => options[:dsn],
84
+ :user => options[:user],
85
+ :password => options[:password]
86
+ options[:source] = :temp
87
+ end
88
+
89
+ select_source(options[:source])
90
+
91
+ @raw_data = options[:row_type].eql?(:raw)
92
+ @params = options[:params]
93
+ end
94
+
95
+ # Returns an OpenStruct with the configuration options for the default
96
+ # database source.
97
+ #
98
+ def self.default_source
99
+ sources[:default]
100
+ end
101
+
102
+ # Returns a hash of database sources, keyed by label.
103
+ def self.sources
104
+ @sources ||= {}
105
+ end
106
+
107
+ # Allows you to add a labeled DBI source configuration.
108
+ #
109
+ # Query objects will use the source labeled <tt>:default</tt>,
110
+ # unless another source is specified.
111
+ #
112
+ # Examples:
113
+ #
114
+ # # a connection to a MySQL database foo with user root, pass chunkybacon
115
+ # Query.add_source :default, :dsn => "dbi:mysql:foo",
116
+ # :user => "root",
117
+ # :password => "chunkybacon"
118
+ #
119
+ #
120
+ # # a second connection to a MySQL database bar
121
+ # Query.add_source :test, :dsn => "dbi:mysql:bar",
122
+ # :user => "tester",
123
+ # :password => "blinky"
124
+ #
125
+ #
126
+ def self.add_source(name,options={})
127
+ sources[name] = OpenStruct.new(options)
128
+ check_source(sources[name],name)
129
+ end
130
+
131
+ attr_accessor :raw_data
132
+
133
+ # The original SQL for the Query object
134
+ attr_reader :sql
135
+
136
+ # This will set the <tt>dsn</tt>, <tt>username</tt>, and <tt>password</tt>
137
+ # to one specified by a source in Ruport::Query.
138
+ #
139
+ def select_source(label)
140
+ @dsn = Ruport::Query.sources[label].dsn
141
+ @user = Ruport::Query.sources[label].user
142
+ @password = Ruport::Query.sources[label].password
143
+ end
144
+
145
+ # Yields result set by row.
146
+ def each(&action)
147
+ raise(LocalJumpError, "No block given!") unless action
148
+ fetch(&action)
149
+ self
150
+ end
151
+
152
+ # Runs the SQL query and returns the result set
153
+ def result; fetch; end
154
+
155
+ # Runs the query without returning its results.
156
+ def execute; fetch; nil; end
157
+
158
+ # Returns a Data::Table, even if in <tt>raw_data</tt> mode.
159
+ def to_table
160
+ data_flag, @raw_data = @raw_data, false
161
+ data = fetch; @raw_data = data_flag; return data
162
+ end
163
+
164
+ # Returns a csv dump of the query.
165
+ def to_csv
166
+ fetch.to_csv
167
+ end
168
+
169
+ # Returns a Generator object of the result set.
170
+ def generator
171
+ Generator.new(fetch)
172
+ end
173
+
174
+ private
175
+
176
+ def query_data(query_text, params=@params)
177
+
178
+ require "dbi"
179
+
180
+ data = @raw_data ? [] : Data::Table.new
181
+
182
+ DBI.connect(@dsn, @user, @password) do |dbh|
183
+ dbh.execute(query_text, *(params || [])) do |sth|
184
+ # Work-around for inconsistent DBD behavior w/ resultless queries
185
+ names = sth.column_names rescue []
186
+ if names.empty?
187
+ # Work-around for SQLite3 DBD bug
188
+ sth.cancel rescue nil
189
+ return nil
190
+ end
191
+
192
+ data.column_names = names unless @raw_data
193
+
194
+ sth.each do |row|
195
+ row = row.to_a
196
+ row = Data::Record.new(row, :attributes => names) unless @raw_data
197
+ yield row if block_given?
198
+ data << row if !block_given?
199
+ end
200
+ end
201
+ end
202
+ data
203
+ end
204
+
205
+ def get_query(type,query)
206
+ type.eql?(:file) ? load_file( query ) : query
207
+ end
208
+
209
+ def fetch(&block)
210
+ data = nil
211
+ final = @statements.size - 1
212
+ @statements.each_with_index do |query_text, index|
213
+ data = query_data(query_text, &(index == final ? block : nil))
214
+ end
215
+ return data
216
+ end
217
+
218
+ def load_file(query_file)
219
+ begin
220
+ File.read( query_file ).strip
221
+ rescue
222
+ raise LoadError, "Could not open #{query_file}"
223
+ end
224
+ end
225
+
226
+ def self.check_source(settings,label) # :nodoc:
227
+ raise ArgumentError unless settings.dsn
228
+ end
229
+
230
+ end
231
+
232
+ # Created by Francis Hwang, 2005.12.31
233
+ # Copyright (c) 2005, All Rights Reserved.
234
+ #++
235
+ class SqlSplit < Array #:nodoc:
236
+ def initialize( sql )
237
+ super()
238
+ next_sql = ''
239
+ sql.each do |line|
240
+ unless line =~ /^--/ or line =~ %r{^/\*.*\*/;} or line =~ /^\s*$/
241
+ next_sql << line
242
+ if line =~ /;$/
243
+ next_sql.gsub!( /;\s$/, '' )
244
+ self << next_sql
245
+ next_sql = ''
246
+ end
247
+ end
248
+ end
249
+ self << next_sql if next_sql != ''
250
+ end
251
+ end
252
+
253
+ end
@@ -0,0 +1,301 @@
1
+ require 'cgi'
2
+ require 'tmpdir'
3
+ require 'zip/zip'
4
+
5
+ module Ruport
6
+ # This class provides Excel output for Ruport's Table renderers.
7
+ # It can export to format :
8
+ # * Excel 2003 (use spreadsheet/excel gems)
9
+ # * Excel 2003 XML
10
+ # * Excel 2007 OpenXML
11
+ #
12
+ # === Rendering Options
13
+ # * worksheet_name : Name of the Worksheet
14
+ # * Renders
15
+ # * xls => Excel 2003 (If spreadsheet/excel no exist use xml format instead)
16
+ # * xlsx => Excel 2007 OpenXML
17
+ # * xlsxml => Excel 2003 XML
18
+ #
19
+ class Formatter::XLS < Formatter
20
+ renders :xls, :for => [ Renderer::Row, Renderer::Table]
21
+
22
+ def initialize
23
+ Ruport.quiet {
24
+ require 'spreadsheet/excel'
25
+ }
26
+ end
27
+
28
+
29
+ def prepare_table
30
+ @xls_row = 0
31
+ @tempfile = Tempfile.new('output.xls')
32
+ @workbook = Spreadsheet::Excel.new(@tempfile.path)
33
+ @worksheet = @workbook.add_worksheet(options.worksheet_name || 'Ruport')
34
+ @header_style = options.header_style || @workbook.add_format(:bold => 1, :size => 12)
35
+ end
36
+
37
+ def build_table_header
38
+ if options.show_table_headers
39
+ table_row { build_cells(data.column_names, @header_style) }
40
+ end
41
+ end
42
+
43
+ def build_table_body
44
+ data.each do |r|
45
+ table_row { build_cells(r) }
46
+ end
47
+ end
48
+
49
+ def build_row
50
+ table_row{ build_cells(data.to_a) }
51
+ end
52
+
53
+ def table_row
54
+ yield
55
+ @xls_row += 1
56
+ end
57
+
58
+ def build_cells(values, style = nil)
59
+ col = 0
60
+ values.each do |value|
61
+ if style
62
+ @worksheet.write(@xls_row, col, value, style)
63
+ else
64
+ @worksheet.write(@xls_row, col, value)
65
+ end
66
+ col += 1
67
+ end
68
+ end
69
+
70
+ def finalize_table
71
+ @workbook.close
72
+ options.io =
73
+ if options.tempfile
74
+ @tempfile
75
+ else
76
+ File.read(@tempfile.path)
77
+ end
78
+ end
79
+ end
80
+
81
+ # Excel 2007 OpenXML
82
+ class Formatter::XLSX < Formatter
83
+ BLANK_XLSX = File.join(Ruport::Util::BASEDIR, 'example', 'data', 'blank.xlsx')
84
+ renders :xlsx, :for => [ Renderer::Row, Renderer::Table]
85
+
86
+ def initialize
87
+ end
88
+
89
+
90
+
91
+ def prepare_table
92
+ @xls_row = 0
93
+ output << %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
94
+ <worksheet xml:space="preserve" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
95
+ <sheetPr codeName="#{options.worksheet_name || 'Ruport'}"/>
96
+
97
+ <sheetViews>
98
+ <sheetView tabSelected="1" workbookViewId="0">
99
+ <selection/>
100
+ </sheetView>
101
+ </sheetViews>
102
+ <sheetFormatPr defaultRowHeight="12.75"/>
103
+ <cols>
104
+ }
105
+ data.column_names.size.times {
106
+ output << %{ <col min="1" max="1" width="10" customWidth="true"/>}
107
+ }
108
+ output << %{</cols><sheetData>}
109
+ @strings = []
110
+ end
111
+
112
+ def build_table_header
113
+ if options.show_table_headers
114
+ table_row { build_cells(data.column_names, 'Heading') }
115
+ end
116
+ end
117
+
118
+ def build_table_body
119
+ data.each do |r|
120
+ table_row { build_cells(r) }
121
+ end
122
+ end
123
+
124
+ def build_row
125
+ table_row{ build_cells(data.to_a) }
126
+ end
127
+
128
+ def table_row
129
+ output << %{ <row r="#{@xls_row + 1}">\n}
130
+ yield
131
+ output << %{ </row>\n}
132
+ @xls_row += 1
133
+ end
134
+
135
+ def get_cell_name(row, col)
136
+ name = ((col % 26) + 65).chr + row.to_s
137
+ name = ((col / 26) + 65).chr + name if (col / 26 != 0)
138
+ name
139
+ end
140
+
141
+ def build_cells(values, style = '')
142
+ col = 0
143
+ values.each do |value|
144
+ value = CGI.escapeHTML(value.to_s)
145
+ id = @strings.length
146
+ @strings.push(value)
147
+ output << %{<c r="#{get_cell_name(@xls_row + 1, col)}" t="s">
148
+ <v>#{id}</v>
149
+ </c>}
150
+ col += 1
151
+ end
152
+ end
153
+
154
+ def build_strings_file
155
+ out = ''
156
+ out << %{<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" uniqueCount="#{@strings.length}">\n}
157
+ @strings.each {|val|
158
+ out << %{ <si><t>#{val}</t></si>\n}
159
+ }
160
+ out << %{</sst>\n}
161
+ out
162
+ end
163
+
164
+ def finalize_table
165
+ output << %{</sheetData>
166
+ <sheetProtection sheet="false" objects="false" scenarios="false" formatCells="false" formatColumns="false" formatRows="false" insertColumns="false" insertRows="false" insertHyperlinks="false" deleteColumns="false" deleteRows="false" selectLockedCells="false" sort="false" autoFilter="false" pivotTables="false" selectUnlockedCells="false"/>
167
+ <printOptions gridLines="false" gridLinesSet="true"/>
168
+ <pageMargins left="0.7" right="0.7" top="0.75" bottom="0.75" header="0.3" footer="0.3"/>
169
+
170
+ <pageSetup paperSize="1" orientation="default"/>
171
+ <headerFooter differentOddEven="false" differentFirst="false" scaleWithDoc="true" alignWithMargins="true">
172
+ <oddHeader></oddHeader>
173
+ <oddFooter></oddFooter>
174
+ <evenHeader></evenHeader>
175
+ <evenFooter></evenFooter>
176
+ <firstHeader></firstHeader>
177
+ <firstFooter></firstFooter>
178
+ </headerFooter>
179
+
180
+ </worksheet>}
181
+
182
+ @tempfile = Tempfile.new('output.xlsx')
183
+
184
+ File.open(BLANK_XLSX) { |bo|
185
+ @tempfile.print(bo.read(1024)) until bo.eof?
186
+ }
187
+ @tempfile.close
188
+ zip = Zip::ZipFile.open(@tempfile.path)
189
+ zip.get_output_stream('xl/worksheets/sheet1.xml') do |cxml|
190
+ cxml.write(output)
191
+ end
192
+ zip.get_output_stream('xl/sharedStrings.xml') do |cxml|
193
+ cxml.write(build_strings_file)
194
+ end
195
+ workbook = %{<?xml version="1.0" encoding="UTF-8" standalone="yes"?><workbook xml:space="preserve" xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
196
+ <fileVersion appName="xl" lastEdited="4" lowestEdited="4" rupBuild="4505"/>
197
+ <workbookPr codeName="ThisWorkbook"/>
198
+ <bookViews>
199
+ <workbookView activeTab="0" autoFilterDateGrouping="1" firstSheet="0" minimized="0" showHorizontalScroll="1" showSheetTabs="1" showVerticalScroll="1" tabRatio="600" visibility="visible"/>
200
+ </bookViews>
201
+ <sheets>
202
+ <sheet name="#{options.worksheet_name || 'Ruport'}" sheetId="1" r:id="rId4"/>
203
+ </sheets>
204
+ <definedNames/>
205
+ <calcPr calcId="124519" calcMode="auto" fullCalcOnLoad="1"/>
206
+ </workbook>}
207
+ zip.get_output_stream('xl/workbook.xml') do |cxml|
208
+ cxml.write(workbook)
209
+ end
210
+ zip.close
211
+ options.io =
212
+ if options.tempfile
213
+ @tempfile
214
+ else
215
+ File.read(@tempfile.path)
216
+ end
217
+ end
218
+ end
219
+
220
+ # Excel 2003 XML
221
+ class Formatter::XLSXML < Formatter
222
+ renders :xlsxml, :for => [ Renderer::Row, Renderer::Table]
223
+
224
+ def prepare_table
225
+ output << %{<?xml version="1.0" encoding="UTF-8"?><?mso-application progid="Excel.Sheet"?>
226
+ <Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
227
+ xmlns:o="urn:schemas-microsoft-com:office:office"
228
+ xmlns:x="urn:schemas-microsoft-com:office:excel"
229
+ xmlns:html="http://www.w3.org/TR/REC-html40"
230
+ xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet">
231
+ <Styles>
232
+ <Style ss:ID="Default" ss:Name="Default"/>
233
+
234
+ <Style ss:ID="Heading" ss:Name="Heading">#{options.header_style || '
235
+ <Alignment ss:Horizontal="Center"/>
236
+ <Font ss:Bold="1" ss:Italic="1" ss:Size="12"/>'}
237
+ </Style>
238
+ <Style ss:ID="co1"/>
239
+ <Style ss:ID="ta1"/>
240
+ </Styles>
241
+ <ss:Worksheet ss:Name="#{options.worksheet_name || 'Ruport'}">
242
+ <Table ss:StyleID="ta1">
243
+ }
244
+ data.column_names.size.times {
245
+ output << %{<Column ss:AutoFitWidth="1"/>}
246
+ }
247
+ end
248
+
249
+ def build_table_header
250
+ if options.show_table_headers
251
+ table_row { build_cells(data.column_names, 'Heading') }
252
+ end
253
+ end
254
+
255
+ def build_table_body
256
+ data.each do |r|
257
+ table_row { build_cells(r) }
258
+ end
259
+ end
260
+
261
+ def build_row
262
+ table_row{ build_cells(data.to_a) }
263
+ end
264
+
265
+ def table_row
266
+ output << %{ <Row>\n}
267
+ yield
268
+ output << %{ </Row>\n}
269
+ end
270
+
271
+ def build_cells(values, style = '')
272
+ values.each do |value|
273
+ value = CGI.escapeHTML(value.to_s)
274
+ if style.length > 0
275
+ output << %{ <Cell>\n}
276
+ else
277
+ output << %{ <Cell ss:StyleID="#{style}">\n}
278
+ end
279
+ output << %{ <Data ss:Type="String">#{value}</Data>\n}
280
+ output << %{ </Cell>\n}
281
+ end
282
+ end
283
+
284
+ def finalize_table
285
+ output << %{ </Table>
286
+ </ss:Worksheet>
287
+ </Workbook>}
288
+
289
+ @tempfile = Tempfile.new('output.xls')
290
+ @tempfile.print(output)
291
+ @tempfile.close();
292
+ options.io =
293
+ if options.tempfile
294
+ @tempfile
295
+ else
296
+ File.read(@tempfile.path)
297
+ end
298
+ end
299
+
300
+ end
301
+ end
data/test/helper.rb CHANGED
@@ -1,7 +1,14 @@
1
1
  begin
2
2
  require "rubygems"
3
+ gem "ruport", "=1.2.3"
3
4
  rescue LoadError
4
5
  nil
6
+ end
7
+
8
+ class Array
9
+ def to_table(columns)
10
+ Table(columns) { |t| each { |r| t << r }}
11
+ end
5
12
  end
6
13
 
7
14
  require "spec"
@@ -0,0 +1,35 @@
1
+ require 'test/helper'
2
+ require 'rexml/document'
3
+ testcase_requires 'hpricot'
4
+ testcase_requires 'spreadsheet/excel'
5
+
6
+ describe 'XLS Formatter' do
7
+ before :all do
8
+ @csv = "first col,second col,third col\n" +
9
+ "first row,cell 1,cell 2\n" +
10
+ "second row,cell 3,cell 4\n" +
11
+ "third row,special >,special <\n" +
12
+ "fourth row,,after empty\n" +
13
+ "\n" +
14
+ "seventh row,nothing,more\n"
15
+ @table = Table(:string => @csv)
16
+ @xlsxml = Hpricot(@table.to_xlsxml())
17
+ @xls = @table.to_xls()
18
+ @xlsx = @table.to_xlsx(:tempfile => true)
19
+ zip = Zip::ZipFile.open(@xlsx.path)
20
+ @xlsx_content = Hpricot(zip.read('xl/worksheets/sheet1.xml'))
21
+ zip.close
22
+ @xlsx_rows = @xlsx_content.search('//row')
23
+ end
24
+
25
+ it 'should have content' do
26
+ @xlsxml.should_not be_nil
27
+ @xls.should_not be_nil
28
+ @xlsx.should_not be_nil
29
+ end
30
+
31
+ it 'should have all rows' do
32
+ #@xlsxml.search('//row').should have(7).rows
33
+ @xlsx_rows.should have(7).rows
34
+ end
35
+ end
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env ruby -w
2
+ require 'test/helper'
3
+ testcase_requires 'dbi'
4
+
5
+ $VERBOSE = nil
6
+
7
+ describe "A Query" do
8
+
9
+ before :each do
10
+ @sources = {
11
+ :default => {
12
+ :dsn => 'ruport:test', :user => 'greg', :password => 'apple' },
13
+ :alternative => {
14
+ :dsn => "ruport:test2", :user => "sandal", :password => "harmonix" },
15
+ }
16
+ Ruport::Query.add_source :default, @sources[:default]
17
+ Ruport::Query.add_source :alternative, @sources[:alternative]
18
+
19
+ @columns = %w(a b c)
20
+ @data = [ [[1,2,3],[4,5,6],[7,8,9]],
21
+ [[9,8,7],[6,5,4],[3,2,1]],
22
+ [[7,8,9],[4,5,6],[1,2,3]], ]
23
+ @datasets = @data.dup
24
+
25
+ @sql = [ "select * from foo", "create table foo ..." ]
26
+ @sql << @sql.values_at(0, 0).join(";\n")
27
+ @sql << @sql.values_at(1, 0).join(";\n")
28
+ @query = {
29
+ :plain => Ruport::Query.new(@sql[0]),
30
+ :sourced => Ruport::Query.new(@sql[0], :source => :alternative),
31
+ :paramed => Ruport::Query.new(@sql[0], :params => [ 42 ]),
32
+ :raw => Ruport::Query.new(@sql[0], :row_type => :raw),
33
+ :resultless => Ruport::Query.new(@sql[1]),
34
+ :multi => Ruport::Query.new(@sql[2]),
35
+ :mixed => Ruport::Query.new(@sql[3]),
36
+ }
37
+ end
38
+
39
+ if Object.const_defined? :DBI
40
+
41
+ it "should have a nil result on execute" do
42
+ query = @query[:plain]
43
+ setup_mock_dbi(1)
44
+
45
+ query.execute.should == nil
46
+ end
47
+
48
+ it "should allow execute to work with sources" do
49
+ query = @query[:sourced]
50
+ setup_mock_dbi(1, :source => :alternative)
51
+
52
+ query.execute.should == nil
53
+ end
54
+
55
+ it "should allow excute to accept parameters" do
56
+ query = @query[:paramed]
57
+ setup_mock_dbi(1, :params => [ 42 ])
58
+
59
+ query.execute.should == nil
60
+ end
61
+
62
+ it "should return nil for empty results" do
63
+ query = @query[:resultless]
64
+ setup_mock_dbi(1, :resultless => true, :sql => @sql[1])
65
+
66
+ query.result.should be_nil
67
+ end
68
+
69
+ it "should return last query result for multiple statements" do
70
+ query = @query[:multi]
71
+ setup_mock_dbi(2)
72
+
73
+ get_raw(query.result).should == @data[1]
74
+ end
75
+
76
+ it "should allow raw mode" do
77
+ query = @query[:raw]
78
+ setup_mock_dbi(1)
79
+
80
+ query.result.should == @data[0]
81
+ end
82
+
83
+ it "should allow reading from file with .sql extension" do
84
+ File.should_receive(:read).
85
+ with("query_test.sql").
86
+ and_return("select * from foo\n")
87
+
88
+ query = Ruport::Query.new "query_test.sql"
89
+ query.sql.should == "select * from foo"
90
+ end
91
+
92
+ it "should allow reading from file with explicit :file argument" do
93
+ File.should_receive(:read).
94
+ with("query_test").
95
+ and_return("select * from foo\n")
96
+
97
+ query = Ruport::Query.new(:file => "query_test")
98
+ query.sql.should == "select * from foo"
99
+
100
+ query = Ruport::Query.new(:string => "query_test")
101
+ query.sql.should == "query_test"
102
+ end
103
+
104
+ it "should raise a LoadError if the file is not found" do
105
+ File.should_receive(:read).
106
+ with("query_test.sql").
107
+ and_raise(Errno::ENOENT)
108
+
109
+ lambda { query = Ruport::Query.new "query_test.sql" }.
110
+ should raise_error(LoadError)
111
+ end
112
+
113
+ it "should support an each() iterator" do
114
+ query = @query[:plain]
115
+ setup_mock_dbi(2)
116
+
117
+ result = []; query.each { |r| result << r.to_a }
118
+ result.should == @data[0]
119
+
120
+ result = []; query.each { |r| result << r.to_a }
121
+ result.should == @data[1]
122
+ end
123
+
124
+ it "should iterate the last data set if multiple queries" do
125
+ query = @query[:multi]
126
+ setup_mock_dbi(2)
127
+
128
+ result = []; query.each { |r| result << r.to_a }
129
+ result.should == @data[1]
130
+ end
131
+
132
+ it "should raise a LocalJumpError if a block is not given" do
133
+ lambda { @query[:plain].each }.should raise_error(LocalJumpError)
134
+ end
135
+
136
+ it "should allow selecting sources" do
137
+ query = @query[:plain]
138
+ query.select_source :alternative
139
+ get_query_source(query).should == @sources[:alternative]
140
+
141
+ query.select_source :default
142
+ get_query_source(query).should == @sources[:default]
143
+ end
144
+
145
+ it "should initialize a temporary source" do
146
+ query = Ruport::Query.new "<unused>", @sources[:alternative]
147
+ get_query_source(query).should == @sources[:alternative]
148
+ end
149
+
150
+ it "should initialize multiple temporary source" do
151
+ query1 = Ruport::Query.new "<unused>", @sources[:default]
152
+ query2 = Ruport::Query.new "<unused>", @sources[:alternative]
153
+
154
+ get_query_source(query1).should == @sources[:default]
155
+ get_query_source(query2).should == @sources[:alternative]
156
+ end
157
+
158
+ it "should allow conversion to table" do
159
+ query = @query[:raw]
160
+ setup_mock_dbi(3, :returns => [@data[0]])
161
+
162
+ query.result.should == @data[0]
163
+ query.to_table.should == @data[0].to_table(@columns)
164
+ query.result.should == @data[0]
165
+ end
166
+
167
+ it "should support conversion to csv" do
168
+ query = @query[:plain]
169
+ setup_mock_dbi(1)
170
+
171
+ csv = @data[0].to_table(@columns).as(:csv)
172
+ query.to_csv.should == csv
173
+ end
174
+
175
+ it "should raise error when DSN is missing" do
176
+ lambda {
177
+ Ruport::Query.add_source :foo, :user => "root", :password => "fff"
178
+ }.should raise_error(ArgumentError)
179
+ end
180
+
181
+
182
+ it "should be able to set new defaults" do
183
+ Ruport::Query.add_source :default, :dsn => "dbi:mysql:test",
184
+ :user => "root",
185
+ :password => ""
186
+ Ruport::Query.default_source.dsn.should == "dbi:mysql:test"
187
+ Ruport::Query.default_source.user.should == "root"
188
+ Ruport::Query.default_source.password.should == ""
189
+ end
190
+
191
+ it "should allow setting multiple sources" do
192
+ Ruport::Query.add_source :foo, :dsn => "dbi:mysql:test"
193
+ Ruport::Query.add_source :bar, :dsn => "dbi:mysql:test2"
194
+ Ruport::Query.sources[:foo].dsn.should == "dbi:mysql:test"
195
+ Ruport::Query.sources[:bar].dsn.should == "dbi:mysql:test2"
196
+ end
197
+
198
+ end
199
+
200
+ private
201
+ def setup_mock_dbi(count, options={})
202
+ sql = options[:sql] || @sql[0]
203
+ source = options[:source] || :default
204
+ resultless = options[:resultless]
205
+ params = options[:params] || []
206
+
207
+ @dbh = mock("database_handle")
208
+ @sth = mock("statement_handle")
209
+ def @dbh.execute(*a, &b); execute__(*a, &b); ensure; sth__.finish if b; end
210
+ def @sth.each; data__.each { |x| yield(x.dup) }; end
211
+ def @sth.fetch_all; data__; end
212
+
213
+ DBI.should_receive(:connect).exactly(count).times.
214
+ with(*@sources[source].values_at(:dsn, :user, :password)).
215
+ and_yield(@dbh)
216
+ c = @dbh.should_receive(:execute__).exactly(count).times.with(sql, *params)
217
+ c.and_yield(@sth)
218
+ c.and_return(@sth)
219
+ @dbh.stub!(:sth__).and_return(@sth)
220
+ @sth.should_receive(:finish).with().exactly(count).times
221
+ unless resultless
222
+ @sth.stub!(:fetchable?).and_return(true)
223
+ @sth.stub!(:column_names).and_return(@columns)
224
+ if options[:returns]
225
+ if Array == options[:returns]
226
+ @sth.should_receive(:data__).any_number_of_times.and_return(*options[:returns])
227
+ else
228
+ @sth.should_receive(:data__).any_number_of_times.and_return(*Array(options[:returns]))
229
+ end
230
+ else
231
+ @sth.should_receive(:data__).any_number_of_times.and_return(*@datasets)
232
+ end
233
+ else
234
+ @sth.stub!(:fetchable?).and_return(false)
235
+ @sth.stub!(:column_names).and_return([])
236
+ @sth.stub!(:cancel)
237
+ @sth.should_receive(:data__).exactly(0).times
238
+ end
239
+ end
240
+
241
+ def get_query_source(query)
242
+ [ :dsn, :user, :password ].inject({}) do |memo, var|
243
+ memo.update var => query.instance_variable_get("@#{var}")
244
+ end
245
+ end
246
+
247
+ def get_raw(table)
248
+ table.map { |row| row.to_a }
249
+ end
250
+ end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.4
2
+ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: ruport-util
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.10.0
7
- date: 2007-11-24 00:00:00 -05:00
6
+ version: 0.12.0
7
+ date: 2007-12-25 00:00:00 -05:00
8
8
  summary: A set of tools and helper libs for Ruby Reports
9
9
  require_paths:
10
10
  - lib
@@ -30,64 +30,71 @@ authors:
30
30
  - Gregory Brown
31
31
  files:
32
32
  - example/data
33
+ - example/data/amline_settings.xml
34
+ - example/data/blank.xlsx
35
+ - example/data/amline_graph.xml
36
+ - example/data/blank.ods
37
+ - example/invoice_report.rb
33
38
  - example/draw_graph.rb
34
39
  - example/form.rb
35
- - example/graph_report.rb
36
40
  - example/gruff_report.rb
37
- - example/invoice_report.rb
38
- - example/mailer.rb
39
- - example/managed_report.rb
40
41
  - example/ods.rb
41
- - example/data/amline_graph.xml
42
- - example/data/amline_settings.xml
43
- - example/data/blank.ods
44
- - lib/open_flash_chart.rb
42
+ - example/managed_report.rb
43
+ - example/mailer.rb
44
+ - example/graph_report.rb
45
45
  - lib/ruport
46
46
  - lib/ruport/util
47
- - lib/ruport/util.rb
48
- - lib/ruport/util/bench.rb
49
- - lib/ruport/util/generator.rb
50
47
  - lib/ruport/util/graph
51
- - lib/ruport/util/graph.rb
52
- - lib/ruport/util/invoice.rb
53
- - lib/ruport/util/mailer.rb
54
- - lib/ruport/util/ods.rb
55
- - lib/ruport/util/pdf
56
- - lib/ruport/util/report.rb
57
- - lib/ruport/util/report_manager.rb
48
+ - lib/ruport/util/graph/scruffy.rb
58
49
  - lib/ruport/util/graph/amline.rb
59
- - lib/ruport/util/graph/gruff.rb
60
50
  - lib/ruport/util/graph/o_f_c.rb
61
- - lib/ruport/util/graph/scruffy.rb
51
+ - lib/ruport/util/graph/gruff.rb
52
+ - lib/ruport/util/pdf
62
53
  - lib/ruport/util/pdf/form.rb
54
+ - lib/ruport/util/generator.rb
55
+ - lib/ruport/util/bench.rb
56
+ - lib/ruport/util/graph.rb
57
+ - lib/ruport/util/ods.rb
58
+ - lib/ruport/util/query.rb
59
+ - lib/ruport/util/report_manager.rb
60
+ - lib/ruport/util/xls.rb
61
+ - lib/ruport/util/mailer.rb
62
+ - lib/ruport/util/report.rb
63
+ - lib/ruport/util/invoice.rb
64
+ - lib/ruport/util.rb
65
+ - lib/open_flash_chart.rb
63
66
  - test/helper
64
- - test/helper.rb
65
- - test/samples
66
- - test/test_format_ods.rb
67
- - test/test_graph_ofc.rb
68
- - test/test_graph_renderer.rb
69
- - test/test_hpricot_traverser.rb
70
- - test/test_invoice.rb
71
- - test/test_mailer.rb
72
- - test/test_report.rb
73
- - test/test_report_manager.rb
74
67
  - test/helper/layout.rb
75
68
  - test/helper/wrap.rb
69
+ - test/samples
76
70
  - test/samples/data.csv
77
71
  - test/samples/foo.rtxt
72
+ - test/test_report_manager.rb
73
+ - test/test_format_xls.rb
74
+ - test/helper.rb
75
+ - test/test_query.rb
76
+ - test/test_hpricot_traverser.rb
77
+ - test/test_graph_renderer.rb
78
+ - test/test_graph_ofc.rb
79
+ - test/test_mailer.rb
80
+ - test/test_report.rb
81
+ - test/test_invoice.rb
82
+ - test/test_format_ods.rb
78
83
  - bin/csv2ods
79
84
  - bin/rope
80
85
  - Rakefile
81
86
  - INSTALL
82
87
  test_files:
83
- - test/test_format_ods.rb
84
- - test/test_graph_ofc.rb
85
- - test/test_graph_renderer.rb
88
+ - test/test_report_manager.rb
89
+ - test/test_format_xls.rb
90
+ - test/test_query.rb
86
91
  - test/test_hpricot_traverser.rb
87
- - test/test_invoice.rb
92
+ - test/test_graph_renderer.rb
93
+ - test/test_graph_ofc.rb
88
94
  - test/test_mailer.rb
89
95
  - test/test_report.rb
90
- - test/test_report_manager.rb
96
+ - test/test_invoice.rb
97
+ - test/test_format_ods.rb
91
98
  rdoc_options:
92
99
  - --title
93
100
  - ruport-util Documentation