ruport 0.8.14 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +42 -107
- data/Rakefile +29 -32
- data/examples/centered_pdf_text_box.rb +13 -19
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +15 -15
- data/examples/pdf_complex_report.rb +10 -23
- data/examples/pdf_table_with_title.rb +12 -12
- data/examples/rope_examples/itunes/Rakefile +22 -1
- data/examples/rope_examples/itunes/config/environment.rb +4 -0
- data/examples/rope_examples/itunes/lib/init.rb +32 -2
- data/examples/rope_examples/itunes/util/build +50 -16
- data/examples/rope_examples/sales_report/README +1 -1
- data/examples/rope_examples/sales_report/Rakefile +22 -1
- data/examples/rope_examples/sales_report/config/environment.rb +4 -0
- data/examples/rope_examples/sales_report/lib/init.rb +32 -2
- data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
- data/examples/rope_examples/sales_report/util/build +50 -16
- data/examples/row_renderer.rb +39 -0
- data/examples/ruport_list/png_embed.rb +61 -0
- data/examples/ruport_list/roadmap.png +0 -0
- data/examples/sample.rb +16 -0
- data/examples/simple_pdf_lines.rb +24 -0
- data/lib/ruport.rb +143 -57
- data/lib/ruport/acts_as_reportable.rb +246 -0
- data/lib/ruport/data.rb +1 -2
- data/lib/ruport/data/grouping.rb +311 -0
- data/lib/ruport/data/record.rb +113 -84
- data/lib/ruport/data/table.rb +275 -174
- data/lib/ruport/formatter.rb +149 -0
- data/lib/ruport/formatter/csv.rb +87 -0
- data/lib/ruport/formatter/html.rb +89 -0
- data/lib/ruport/formatter/pdf.rb +357 -0
- data/lib/ruport/formatter/text.rb +151 -0
- data/lib/ruport/generator.rb +127 -30
- data/lib/ruport/query.rb +46 -99
- data/lib/ruport/renderer.rb +238 -194
- data/lib/ruport/renderer/grouping.rb +67 -0
- data/lib/ruport/renderer/table.rb +25 -98
- data/lib/ruport/report.rb +45 -96
- data/test/acts_as_reportable_test.rb +229 -0
- data/test/csv_formatter_test.rb +97 -0
- data/test/{_test_database.rb → database_test_.rb} +0 -0
- data/test/grouping_test.rb +305 -0
- data/test/html_formatter_test.rb +104 -0
- data/test/pdf_formatter_test.rb +25 -0
- data/test/{test_query.rb → query_test.rb} +32 -121
- data/test/{test_record.rb → record_test.rb} +40 -23
- data/test/renderer_test.rb +344 -0
- data/test/{test_report.rb → report_test.rb} +74 -44
- data/test/samples/ticket_count.csv +124 -0
- data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
- data/test/{test_table.rb → table_test.rb} +255 -44
- data/test/text_formatter_test.rb +144 -0
- data/util/bench/data/record/bench_as_vs_to.rb +17 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +121 -143
- data/TODO +0 -21
- data/examples/invoice.rb +0 -142
- data/examples/invoice_report.rb +0 -29
- data/examples/line_graph.rb +0 -38
- data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
- data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
- data/lib/ruport/attempt.rb +0 -63
- data/lib/ruport/config.rb +0 -204
- data/lib/ruport/data/groupable.rb +0 -93
- data/lib/ruport/data/taggable.rb +0 -80
- data/lib/ruport/format.rb +0 -1
- data/lib/ruport/format/csv.rb +0 -29
- data/lib/ruport/format/html.rb +0 -42
- data/lib/ruport/format/latex.rb +0 -47
- data/lib/ruport/format/pdf.rb +0 -233
- data/lib/ruport/format/plugin.rb +0 -31
- data/lib/ruport/format/svg.rb +0 -60
- data/lib/ruport/format/text.rb +0 -103
- data/lib/ruport/format/xml.rb +0 -32
- data/lib/ruport/layout.rb +0 -1
- data/lib/ruport/layout/component.rb +0 -7
- data/lib/ruport/mailer.rb +0 -99
- data/lib/ruport/renderer/graph.rb +0 -46
- data/lib/ruport/report/graph.rb +0 -14
- data/lib/ruport/system_extensions.rb +0 -71
- data/test/test_config.rb +0 -88
- data/test/test_format_text.rb +0 -63
- data/test/test_graph_renderer.rb +0 -97
- data/test/test_groupable.rb +0 -56
- data/test/test_mailer.rb +0 -170
- data/test/test_renderer.rb +0 -151
- data/test/test_ruport.rb +0 -58
- data/test/test_table_renderer.rb +0 -141
- data/test/test_taggable.rb +0 -52
@@ -0,0 +1,151 @@
|
|
1
|
+
module Ruport
|
2
|
+
class Formatter::Text < Formatter
|
3
|
+
|
4
|
+
renders :text, :for => [ Renderer::Row, Renderer::Table,
|
5
|
+
Renderer::Group, Renderer::Grouping ]
|
6
|
+
|
7
|
+
opt_reader :max_col_width, :alignment, :table_width,
|
8
|
+
:show_table_headers, :show_group_headers
|
9
|
+
|
10
|
+
# Checks to ensure the table is not empty and then calls
|
11
|
+
# calculate_max_col_widths
|
12
|
+
#
|
13
|
+
def prepare_table
|
14
|
+
raise "Can't output empty table" if data.empty?
|
15
|
+
calculate_max_col_widths
|
16
|
+
end
|
17
|
+
|
18
|
+
# Uses the column names from the given Data::Table to generate a table
|
19
|
+
# header.
|
20
|
+
#
|
21
|
+
# Calls fit_to_width to truncate table heading if necessary.
|
22
|
+
#
|
23
|
+
def build_table_header
|
24
|
+
return unless should_render_column_names?
|
25
|
+
|
26
|
+
c = data.column_names.enum_for(:each_with_index).map { |f,i|
|
27
|
+
f.to_s.center(max_col_width[i])
|
28
|
+
}
|
29
|
+
|
30
|
+
output << fit_to_width("#{hr}| #{c.join(' | ')} |\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
# Generates the body of the text table.
|
34
|
+
#
|
35
|
+
# Defaults to numeric values being right justified, and other values being
|
36
|
+
# left justified. Can be changed to support centering of output by
|
37
|
+
# setting options.alignment to :center
|
38
|
+
#
|
39
|
+
# Uses fit_to_width to truncate table if necessary.
|
40
|
+
#
|
41
|
+
def build_table_body
|
42
|
+
output << fit_to_width(hr)
|
43
|
+
|
44
|
+
calculate_max_col_widths unless max_col_width
|
45
|
+
|
46
|
+
render_data_by_row do |rend|
|
47
|
+
rend.options do |o|
|
48
|
+
o.max_col_width = max_col_width
|
49
|
+
o.alignment = alignment
|
50
|
+
o.table_width = table_width
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
output << fit_to_width(hr)
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_row
|
58
|
+
max_col_widths_for_row(data) unless max_col_width
|
59
|
+
|
60
|
+
data.enum_for(:each_with_index).inject(line=[]) { |s,e|
|
61
|
+
field,index = e
|
62
|
+
if alignment.eql? :center
|
63
|
+
line << field.to_s.center(max_col_width[index])
|
64
|
+
else
|
65
|
+
align = field.is_a?(Numeric) ? :rjust : :ljust
|
66
|
+
line << field.to_s.send(align, max_col_width[index])
|
67
|
+
end
|
68
|
+
}
|
69
|
+
output << fit_to_width("| #{line.join(' | ')} |\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
# Renders the header for a group using the group name.
|
73
|
+
#
|
74
|
+
def build_group_header
|
75
|
+
output << "#{data.name}:\n\n"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Creates the group body. Since group data is a table, just uses the
|
79
|
+
# Table renderer.
|
80
|
+
#
|
81
|
+
def build_group_body
|
82
|
+
render_table data, options
|
83
|
+
end
|
84
|
+
|
85
|
+
# Generates the body for a grouping. Iterates through the groups and
|
86
|
+
# renders them using the group renderer.
|
87
|
+
#
|
88
|
+
def build_grouping_body
|
89
|
+
render_inline_grouping(options)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns false if column_names are empty or options.show_table_headers
|
93
|
+
# is false/nil. Returns true otherwise.
|
94
|
+
#
|
95
|
+
def should_render_column_names?
|
96
|
+
not data.column_names.empty? || !show_table_headers
|
97
|
+
end
|
98
|
+
|
99
|
+
# Generates the horizontal rule by calculating the total table width and
|
100
|
+
# then generating a bar that looks like this:
|
101
|
+
#
|
102
|
+
# "+------------------+"
|
103
|
+
def hr
|
104
|
+
len = max_col_width.inject(data[0].to_a.length * 3) {|s,e|s+e}+1
|
105
|
+
"+" + "-"*(len-2) + "+\n"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Returns options.table_width if specified.
|
109
|
+
#
|
110
|
+
# Otherwise, uses SystemExtensions to determine terminal width.
|
111
|
+
def width
|
112
|
+
table_width || SystemExtensions.terminal_width
|
113
|
+
end
|
114
|
+
|
115
|
+
# Truncates a string so that it does not exceed Text#width
|
116
|
+
def fit_to_width(s)
|
117
|
+
# workaround for Rails setting terminal_width to 1
|
118
|
+
max_width = width < 2 ? 80 : width
|
119
|
+
|
120
|
+
s.split("\n").each { |r|
|
121
|
+
r.gsub!(/\A.{#{max_width+1},}/) { |m| m[0,max_width-2] + ">>" }
|
122
|
+
}.join("\n") + "\n"
|
123
|
+
end
|
124
|
+
|
125
|
+
# determines the text widths for each column.
|
126
|
+
def calculate_max_col_widths
|
127
|
+
# allow override
|
128
|
+
return if max_col_width
|
129
|
+
|
130
|
+
options.max_col_width = []
|
131
|
+
|
132
|
+
unless data.column_names.empty?
|
133
|
+
data.column_names.each_index do |i|
|
134
|
+
max_col_width[i] = data.column_names[i].to_s.length
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
data.each { |r| max_col_widths_for_row(r) }
|
139
|
+
end
|
140
|
+
|
141
|
+
def max_col_widths_for_row(row)
|
142
|
+
options.max_col_width ||= []
|
143
|
+
row.each_with_index do |f,i|
|
144
|
+
if !max_col_width[i] || f.to_s.length > max_col_width[i]
|
145
|
+
max_col_width[i] = f.to_s.length
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
data/lib/ruport/generator.rb
CHANGED
@@ -2,6 +2,22 @@ module Ruport
|
|
2
2
|
class Generator
|
3
3
|
extend FileUtils
|
4
4
|
|
5
|
+
module Helpers
|
6
|
+
def format_class_name(string)
|
7
|
+
string.downcase.split("_").map { |s| s.capitalize }.join
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_for_files
|
11
|
+
if File.exist? "lib/reports/#{ARGV[1]}.rb"
|
12
|
+
raise "Report #{ARGV[1]} exists!"
|
13
|
+
end
|
14
|
+
|
15
|
+
if File.exist? "lib/renderers/#{ARGV[1]}.rb"
|
16
|
+
raise "Renderer #{ARGV[1]} exists!"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
5
21
|
begin
|
6
22
|
require "rubygems"
|
7
23
|
rescue LoadError
|
@@ -19,6 +35,7 @@ module Ruport
|
|
19
35
|
puts "\nSuccessfully generated project: #{proj}"
|
20
36
|
end
|
21
37
|
|
38
|
+
|
22
39
|
def self.build_init
|
23
40
|
m = "#{project}/lib/init.rb"
|
24
41
|
puts " #{m}"
|
@@ -32,7 +49,7 @@ module Ruport
|
|
32
49
|
File.open(m,"w") { |f| f << RAKEFILE }
|
33
50
|
end
|
34
51
|
|
35
|
-
# Generates the build.rb
|
52
|
+
# Generates the build.rb and sql_exec.rb utilities
|
36
53
|
def self.build_utils
|
37
54
|
|
38
55
|
m = "#{project}/util/build"
|
@@ -51,24 +68,22 @@ module Ruport
|
|
51
68
|
mkdir project
|
52
69
|
puts "creating directories.."
|
53
70
|
%w[ test config output data lib lib/reports
|
54
|
-
templates sql log util].each do |d|
|
71
|
+
lib/renderers templates sql log util].each do |d|
|
55
72
|
m="#{project}/#{d}"
|
56
73
|
puts " #{m}"
|
57
74
|
mkdir(m)
|
58
75
|
end
|
59
76
|
|
60
77
|
puts "creating files.."
|
61
|
-
%w[reports helpers].each { |f|
|
78
|
+
%w[reports helpers renderers].each { |f|
|
62
79
|
m = "#{project}/lib/#{f}.rb"
|
63
80
|
puts " #{m}"
|
64
81
|
touch(m)
|
65
82
|
}
|
66
83
|
end
|
67
84
|
|
68
|
-
# Builds a file called config/ruport_config.rb which stores a Ruport::Config
|
69
|
-
# skeleton
|
70
85
|
def self.build_config
|
71
|
-
m = "#{project}/config/
|
86
|
+
m = "#{project}/config/environment.rb"
|
72
87
|
puts " #{m}"
|
73
88
|
File.open(m,"w") { |f| f << CONFIG }
|
74
89
|
end
|
@@ -89,34 +104,50 @@ Rake::TestTask.new do |test|
|
|
89
104
|
end
|
90
105
|
|
91
106
|
task :build do
|
92
|
-
|
107
|
+
if ENV['report']
|
108
|
+
sh "ruby util/build report #{ENV['report']}"
|
109
|
+
elsif ENV['renderer']
|
110
|
+
sh "ruby util/build renderer #{ENV['renderer']}"
|
111
|
+
end
|
93
112
|
end
|
94
113
|
|
95
114
|
task :run do
|
96
115
|
sh "ruby lib/reports/#{ENV['report']}.rb"
|
97
116
|
end
|
117
|
+
|
118
|
+
task :use_utils do
|
119
|
+
raise "File lib/init_utils.rb exists!" if File.exist?('lib/init_utils.rb')
|
120
|
+
File.open('lib/init_utils.rb','w') { |f|
|
121
|
+
if ENV['only']
|
122
|
+
ENV['only'].split(',').each { |e|
|
123
|
+
f.puts "require 'ruport/util/#{e}'"
|
124
|
+
}
|
125
|
+
else
|
126
|
+
f.puts "require 'ruport/util'"
|
127
|
+
end
|
128
|
+
}
|
129
|
+
File.open('lib/init.rb','a') { |f|
|
130
|
+
f.puts '#comment out the line below to disable auto-loading of utils'
|
131
|
+
f.puts 'require "lib/init_utils.rb"'
|
132
|
+
}
|
133
|
+
end
|
98
134
|
END_RAKEFILE
|
99
135
|
|
100
136
|
CONFIG = <<END_CONFIG
|
101
137
|
require "ruport"
|
102
138
|
|
103
|
-
|
104
|
-
|
105
|
-
c.source :default, :user => "root",
|
106
|
-
:dsn => "dbi:mysql:mydb"
|
107
|
-
c.log_file "log/ruport.log"
|
108
|
-
}
|
139
|
+
Ruport::Query.add_source :default, :user => "root",
|
140
|
+
:dsn => "dbi:mysql:mydb"
|
109
141
|
END_CONFIG
|
110
142
|
|
111
143
|
BUILD = <<'END_BUILD'
|
112
144
|
#!/usr/bin/env ruby
|
113
145
|
|
114
146
|
require 'fileutils'
|
147
|
+
require 'lib/init.rb'
|
148
|
+
require "ruport/generator"
|
115
149
|
include FileUtils
|
116
|
-
|
117
|
-
def format_class_name(string)
|
118
|
-
string.downcase.split("_").map { |s| s.capitalize }.join
|
119
|
-
end
|
150
|
+
include Ruport::Generator::Helpers
|
120
151
|
|
121
152
|
unless ARGV.length > 1
|
122
153
|
puts "usage: build [command] [options]"
|
@@ -125,31 +156,23 @@ end
|
|
125
156
|
|
126
157
|
class_name = format_class_name(ARGV[1])
|
127
158
|
|
128
|
-
exit if File.exist? "lib/reports/#{ARGV[1]}.rb"
|
129
159
|
if ARGV[0].eql? "report"
|
160
|
+
check_for_files
|
130
161
|
File.open("lib/reports.rb", "a") { |f|
|
131
162
|
f.puts("require \"lib/reports/#{ARGV[1]}\"")
|
132
163
|
}
|
133
164
|
REP = <<EOR
|
134
165
|
require "lib/init"
|
135
166
|
class #{class_name} < Ruport::Report
|
136
|
-
|
137
|
-
def prepare
|
138
167
|
|
139
|
-
end
|
140
|
-
|
141
168
|
def generate
|
142
169
|
|
143
170
|
end
|
144
|
-
|
145
|
-
def cleanup
|
146
|
-
|
147
|
-
end
|
148
|
-
|
171
|
+
|
149
172
|
end
|
150
173
|
|
151
174
|
if __FILE__ == $0
|
152
|
-
|
175
|
+
puts #{class_name}.run
|
153
176
|
end
|
154
177
|
EOR
|
155
178
|
|
@@ -163,11 +186,54 @@ class Test#{class_name} < Test::Unit::TestCase
|
|
163
186
|
end
|
164
187
|
end
|
165
188
|
EOR
|
166
|
-
|
189
|
+
|
167
190
|
File.open("lib/reports/#{ARGV[1]}.rb", "w") { |f| f << REP }
|
191
|
+
puts "reports file: lib/reports/#{ARGV[1]}.rb"
|
168
192
|
puts "test file: test/test_#{ARGV[1]}.rb"
|
169
193
|
puts "class name: #{class_name}"
|
170
194
|
File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
|
195
|
+
|
196
|
+
elsif ARGV[0].eql? "renderer"
|
197
|
+
|
198
|
+
check_for_files
|
199
|
+
File.open("lib/renderers.rb","a") { |f|
|
200
|
+
f.puts("require \"lib/renderers/#{ARGV[1]}\"")
|
201
|
+
}
|
202
|
+
REP = <<EOR
|
203
|
+
require "lib/init"
|
204
|
+
|
205
|
+
class #{class_name} < Ruport::Renderer
|
206
|
+
stage :#{class_name.downcase}
|
207
|
+
end
|
208
|
+
|
209
|
+
class #{class_name}Formatter < Ruport::Formatter
|
210
|
+
|
211
|
+
# change to your format name, or add additional formats
|
212
|
+
renders :my_format, :for => #{class_name}
|
213
|
+
|
214
|
+
def build_#{class_name.downcase}
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
219
|
+
EOR
|
220
|
+
|
221
|
+
TEST = <<EOR
|
222
|
+
require "test/unit"
|
223
|
+
require "lib/renderers/#{ARGV[1]}"
|
224
|
+
|
225
|
+
class Test#{class_name} < Test::Unit::TestCase
|
226
|
+
def test_flunk
|
227
|
+
flunk "Write your real tests here or in any test/test_* file"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
EOR
|
231
|
+
puts "renderer file: lib/renderers/#{ARGV[1]}.rb"
|
232
|
+
File.open("lib/renderers/#{ARGV[1]}.rb", "w") { |f| f << REP }
|
233
|
+
puts "test file: test/test_#{ARGV[1]}.rb"
|
234
|
+
|
235
|
+
puts "class name: #{class_name}"
|
236
|
+
File.open("test/test_#{ARGV[1]}.rb","w") { |f| f << TEST }
|
171
237
|
else
|
172
238
|
puts "Incorrect usage."
|
173
239
|
end
|
@@ -190,8 +256,39 @@ rescue LoadError
|
|
190
256
|
end
|
191
257
|
require "ruport"
|
192
258
|
require "lib/helpers"
|
193
|
-
require "config/
|
259
|
+
require "config/environment"
|
260
|
+
|
261
|
+
class String
|
262
|
+
def /(other)
|
263
|
+
self + "/" + other
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class Ruport::Report
|
268
|
+
|
269
|
+
def output_dir
|
270
|
+
config.output_dir or dir('output')
|
271
|
+
end
|
272
|
+
|
273
|
+
def data_dir
|
274
|
+
config.data_dir or dir('data')
|
275
|
+
end
|
276
|
+
|
277
|
+
def query_dir
|
278
|
+
config.query_dir or dir('sql')
|
279
|
+
end
|
280
|
+
|
281
|
+
def template_dir
|
282
|
+
config.template_dir or dir('templates')
|
283
|
+
end
|
284
|
+
|
285
|
+
private
|
286
|
+
def dir(name)
|
287
|
+
"#{FileUtils.pwd}/#{ARGV[0]}/\#{name}"
|
288
|
+
end
|
289
|
+
end
|
194
290
|
END_INIT
|
195
291
|
|
292
|
+
|
196
293
|
end
|
197
294
|
end
|
data/lib/ruport/query.rb
CHANGED
@@ -3,7 +3,6 @@ require "ruport/query/sql_split"
|
|
3
3
|
|
4
4
|
module Ruport
|
5
5
|
|
6
|
-
#
|
7
6
|
# === Overview
|
8
7
|
#
|
9
8
|
# Query offers a way to interact with databases via DBI. It supports
|
@@ -19,32 +18,17 @@ module Ruport
|
|
19
18
|
|
20
19
|
include Enumerable
|
21
20
|
|
22
|
-
#
|
23
|
-
# Queries are initialized with some SQL and a number of options that
|
24
|
-
# affect their operation. They are NOT executed at initialization. This
|
25
|
-
# is important to note as they will not query the database until either
|
26
|
-
# Query#result, Query#execute, Query#generator, or an Enumerable method
|
27
|
-
# is called on them.
|
28
|
-
#
|
29
|
-
# This kind of laziness is supposed to be A Good Thing, and
|
30
|
-
# as long as you keep it in mind, it should not cause any problems.
|
31
|
-
#
|
21
|
+
# Ruport::Query provides an interface for dealing with raw SQL queries.
|
32
22
|
# The SQL can be single or multistatement, but the resulting Data::Table
|
33
|
-
# will consist only of the result of the last statement
|
34
|
-
# something.
|
23
|
+
# will consist only of the result of the last statement.
|
35
24
|
#
|
36
25
|
# Available options:
|
37
26
|
#
|
38
27
|
# <b><tt>:source</tt></b>:: A source specified in
|
39
|
-
# Ruport::
|
28
|
+
# Ruport::Query.sources, defaults to
|
40
29
|
# <tt>:default</tt>.
|
41
|
-
# <b><tt>:origin</tt></b>:: Query origin, defaults to
|
42
|
-
# <tt>:string</tt>, but it can be set to
|
43
|
-
# <tt>:file</tt>, loading the path
|
44
|
-
# specified by the <tt>sql</tt>
|
45
|
-
# parameter.
|
46
30
|
# <b><tt>:dsn</tt></b>:: If specifed, the Query object will
|
47
|
-
# manually override Ruport::
|
31
|
+
# manually override Ruport::Query.
|
48
32
|
# <b><tt>:user</tt></b>:: If a DSN is specified, the user can
|
49
33
|
# be set with this option.
|
50
34
|
# <b><tt>:password</tt></b>:: If a DSN is specified, the password
|
@@ -58,10 +42,10 @@ module Ruport
|
|
58
42
|
#
|
59
43
|
# Examples:
|
60
44
|
#
|
61
|
-
# # uses Ruport::
|
45
|
+
# # uses Ruport::Query's default source
|
62
46
|
# Ruport::Query.new("select * from fo")
|
63
47
|
#
|
64
|
-
# # uses the Ruport::
|
48
|
+
# # uses the Ruport::Query's source labeled :my_source
|
65
49
|
# Ruport::Query.new("select * from fo", :source => :my_source)
|
66
50
|
#
|
67
51
|
# # uses a manually entered source
|
@@ -76,101 +60,69 @@ module Ruport
|
|
76
60
|
#
|
77
61
|
def initialize(sql, options={})
|
78
62
|
options = { :source => :default, :origin => :string }.merge(options)
|
79
|
-
options[:binding] ||= binding
|
80
63
|
options[:origin] = :file if sql =~ /.sql$/
|
81
|
-
|
82
|
-
q = ERB.new(get_query(options[:origin],sql)).result(options[:binding])
|
83
|
-
@statements = SqlSplit.new(q)
|
64
|
+
@statements = SqlSplit.new(get_query(options[:origin],sql))
|
84
65
|
@sql = @statements.join
|
85
66
|
|
86
67
|
if options[:dsn]
|
87
|
-
Ruport::
|
88
|
-
|
68
|
+
Ruport::Query.add_source :temp, :dsn => options[:dsn],
|
69
|
+
:user => options[:user],
|
89
70
|
:password => options[:password]
|
90
71
|
options[:source] = :temp
|
91
72
|
end
|
92
73
|
|
93
74
|
select_source(options[:source])
|
94
75
|
|
95
|
-
@raw_data = options[:
|
96
|
-
@cache_enabled = options[:cache_enabled]
|
76
|
+
@raw_data = options[:row_type].eql?(:raw)
|
97
77
|
@params = options[:params]
|
98
|
-
@cached_data = nil
|
99
78
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
79
|
+
|
80
|
+
def self.default_source
|
81
|
+
sources[:default]
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.sources
|
85
|
+
@sources ||= {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.add_source(name,options={})
|
89
|
+
sources[name] = OpenStruct.new(options)
|
90
|
+
check_source(sources[name],name)
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def self.check_source(settings,label) # :nodoc:
|
96
|
+
raise ArgumentError unless settings.dsn
|
97
|
+
end
|
98
|
+
|
99
|
+
public
|
100
|
+
|
105
101
|
attr_accessor :raw_data
|
106
102
|
|
107
|
-
# The data stored by Ruport when caching.
|
108
|
-
attr_accessor :cached_data
|
109
|
-
|
110
103
|
# The original SQL for the Query object
|
111
104
|
attr_reader :sql
|
112
105
|
|
113
|
-
#
|
114
106
|
# This will set the <tt>dsn</tt>, <tt>username</tt>, and <tt>password</tt>
|
115
|
-
# to one specified by a source in Ruport::
|
107
|
+
# to one specified by a source in Ruport::Query.
|
116
108
|
#
|
117
109
|
def select_source(label)
|
118
|
-
@dsn = Ruport::
|
119
|
-
@user = Ruport::
|
120
|
-
@password = Ruport::
|
110
|
+
@dsn = Ruport::Query.sources[label].dsn
|
111
|
+
@user = Ruport::Query.sources[label].user
|
112
|
+
@password = Ruport::Query.sources[label].password
|
121
113
|
end
|
122
114
|
|
123
|
-
|
124
|
-
|
125
|
-
# row.
|
126
|
-
#
|
127
|
-
def each(&action)
|
128
|
-
Ruport.log(
|
129
|
-
"no block given!", :status => :fatal,
|
130
|
-
:level => :log_only, :raises => LocalJumpError
|
131
|
-
) unless action
|
115
|
+
def each(&action)
|
116
|
+
raise(LocalJumpError, "No block given!") unless action
|
132
117
|
fetch(&action)
|
133
118
|
self
|
134
119
|
end
|
135
120
|
|
136
|
-
#
|
137
|
-
# Grabs the result set as a Data::Table or an Array of DBI::Row objects
|
138
|
-
# if in <tt>raw_data</tt> mode.
|
139
|
-
#
|
140
121
|
def result; fetch; end
|
141
122
|
|
142
123
|
# Runs the query without returning its results.
|
143
124
|
def execute; fetch; nil; end
|
144
125
|
|
145
|
-
# Clears the contents of the cache.
|
146
|
-
def clear_cache
|
147
|
-
@cached_data = nil
|
148
|
-
end
|
149
|
-
|
150
|
-
#
|
151
|
-
# Clears the contents of the cache, then runs the query, filling the
|
152
|
-
# cache with the new result.
|
153
|
-
#
|
154
|
-
def update_cache
|
155
|
-
return unless @cache_enabled
|
156
|
-
clear_cache; fetch
|
157
|
-
end
|
158
|
-
|
159
|
-
#
|
160
|
-
# Turns on caching. New data will not be loaded until the cache is clear
|
161
|
-
# or caching is disabled.
|
162
|
-
#
|
163
|
-
def enable_caching
|
164
|
-
@cache_enabled = true
|
165
|
-
end
|
166
|
-
|
167
|
-
# Turns off caching and flushes the cached data.
|
168
|
-
def disable_caching
|
169
|
-
clear_cache
|
170
|
-
@cache_enabled = false
|
171
|
-
end
|
172
|
-
|
173
|
-
#
|
174
126
|
# Returns a Data::Table, even if in <tt>raw_data</tt> mode.
|
175
127
|
# This doesn't work with raw data if the cache is enabled and filled.
|
176
128
|
#
|
@@ -224,24 +176,19 @@ module Ruport
|
|
224
176
|
type.eql?(:file) ? load_file( query ) : query
|
225
177
|
end
|
226
178
|
|
227
|
-
def load_file(
|
228
|
-
begin
|
229
|
-
|
230
|
-
|
179
|
+
def load_file(query_file)
|
180
|
+
begin
|
181
|
+
File.read( query_file ).strip
|
182
|
+
rescue
|
183
|
+
raise LoadError, "Could not open #{query_file}"
|
231
184
|
end
|
232
185
|
end
|
233
186
|
|
234
187
|
def fetch(&block)
|
235
188
|
data = nil
|
236
|
-
|
237
|
-
|
238
|
-
data
|
239
|
-
else
|
240
|
-
final = @statements.size - 1
|
241
|
-
@statements.each_with_index do |query_text, index|
|
242
|
-
data = query_data(query_text, &(index == final ? block : nil))
|
243
|
-
end
|
244
|
-
@cached_data = data if @cache_enabled
|
189
|
+
final = @statements.size - 1
|
190
|
+
@statements.each_with_index do |query_text, index|
|
191
|
+
data = query_data(query_text, &(index == final ? block : nil))
|
245
192
|
end
|
246
193
|
return data
|
247
194
|
end
|