ruport 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +4 -0
- data/CHANGELOG +10 -1
- data/Rakefile +1 -1
- data/examples/simple_table_interface.rb +20 -0
- data/lib/ruport.rb +87 -69
- data/lib/ruport/attempt.rb +1 -1
- data/lib/ruport/config.rb +198 -166
- data/lib/ruport/data.rb +6 -1
- data/lib/ruport/data/collection.rb +15 -8
- data/lib/ruport/data/groupable.rb +68 -6
- data/lib/ruport/data/record.rb +73 -34
- data/lib/ruport/data/record.rb~ +236 -0
- data/lib/ruport/data/set.rb +48 -13
- data/lib/ruport/data/table.rb +164 -74
- data/lib/ruport/data/table.rb.rej +67 -0
- data/lib/ruport/data/table.rb~ +153 -68
- data/lib/ruport/data/taggable.rb +37 -9
- data/lib/ruport/format.rb +1 -1
- data/lib/ruport/mailer.rb +41 -27
- data/lib/ruport/meta_tools.rb +26 -11
- data/lib/ruport/query.rb +102 -68
- data/lib/ruport/query/sql_split.rb +1 -1
- data/lib/ruport/report.rb +84 -58
- data/lib/ruport/report/graph.rb +1 -1
- data/lib/ruport/report/invoice.rb +1 -1
- data/test/test_query.rb +305 -48
- data/test/test_query.rb.rej +161 -0
- data/test/test_query.rb~ +337 -0
- data/test/test_record.rb +6 -0
- data/test/test_table.rb +18 -0
- data/test/test_table.rb~ +336 -0
- data/test/unit.log +180 -6
- metadata +8 -2
data/lib/ruport/format.rb
CHANGED
data/lib/ruport/mailer.rb
CHANGED
@@ -6,37 +6,47 @@
|
|
6
6
|
# See LICENSE for details
|
7
7
|
require "net/smtp"
|
8
8
|
require "forwardable"
|
9
|
-
module Ruport
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
10
|
+
module Ruport
|
11
|
+
|
12
|
+
#
|
13
|
+
# === Overview
|
14
|
+
#
|
15
|
+
# This class uses SMTP to provide a simple mail sending mechanism.
|
16
|
+
# It also uses MailFactory to provide attachment and HTML email support.
|
17
|
+
#
|
18
|
+
# === Example
|
19
|
+
#
|
20
|
+
# Here is a simple example of a message which attaches a README file:
|
21
|
+
#
|
22
|
+
# require "ruport"
|
23
|
+
#
|
24
|
+
# Ruport.configure do |config|
|
25
|
+
# config.mailer :default,
|
26
|
+
# :host => "mail.adelphia.net",
|
27
|
+
# :address => "gregory.t.brown@gmail.com"
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# mailer = Ruport::Mailer.new
|
31
|
+
#
|
32
|
+
# mailer.attach "README"
|
33
|
+
#
|
34
|
+
# mailer.deliver :to => "gregory.t.brown@gmail.com",
|
35
|
+
# :from => "gregory.t.brown@gmail.com",
|
36
|
+
# :subject => "Hey there",
|
37
|
+
# :text => "This is what you asked for"
|
38
|
+
#
|
31
39
|
class Mailer
|
32
40
|
extend Forwardable
|
33
41
|
|
34
|
-
|
35
|
-
# Creates a new Mailer object. Optionally, can select a mailer specified
|
36
|
-
# by Ruport::Config.
|
37
42
|
#
|
38
|
-
#
|
39
|
-
#
|
43
|
+
# Creates a new Mailer object. Optionally, you can select a mailer
|
44
|
+
# specified by Ruport::Config.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
#
|
48
|
+
# a = Mailer.new # uses the :default mailer
|
49
|
+
# a = Mailer.new :foo # uses :foo mail config from Ruport::Config
|
40
50
|
#
|
41
51
|
def initialize( mailer_label=:default )
|
42
52
|
select_mailer(mailer_label);
|
@@ -49,10 +59,14 @@ module Ruport
|
|
49
59
|
:subject, :subject=, :attach,
|
50
60
|
:text, :text=, :html, :html= )
|
51
61
|
|
52
|
-
#
|
62
|
+
#
|
63
|
+
# Sends the message.
|
64
|
+
#
|
65
|
+
# Example:
|
53
66
|
#
|
54
67
|
# mailer.deliver :from => "gregory.t.brown@gmail.com",
|
55
68
|
# :to => "greg7224@gmail.com"
|
69
|
+
#
|
56
70
|
def deliver(options={})
|
57
71
|
options.each { |k,v| send("#{k}=",v) if respond_to? "#{k}=" }
|
58
72
|
|
data/lib/ruport/meta_tools.rb
CHANGED
@@ -1,11 +1,18 @@
|
|
1
|
+
# Gets raised if you try to add the same action to an object more than once.
|
1
2
|
class ActionAlreadyDefinedError < RuntimeError; end
|
3
|
+
|
2
4
|
module Ruport
|
5
|
+
|
6
|
+
#
|
7
|
+
# === Overview
|
8
|
+
#
|
3
9
|
# This module provides a few tools for doing some manipulations of the
|
4
|
-
#
|
5
|
-
# formatting system, and might be
|
10
|
+
# singleton class of an object. These are used in the implementation of
|
11
|
+
# Ruport's formatting system, and might be useful for other things.
|
6
12
|
#
|
7
13
|
module MetaTools
|
8
|
-
#
|
14
|
+
#
|
15
|
+
# Allows you to define an attribute accessor on the singleton class.
|
9
16
|
#
|
10
17
|
# Example:
|
11
18
|
#
|
@@ -14,21 +21,27 @@ module Ruport
|
|
14
21
|
# attribute :foo
|
15
22
|
# end
|
16
23
|
#
|
17
|
-
# A.foo
|
18
|
-
# A.foo = 7
|
24
|
+
# A.foo #=> nil
|
25
|
+
# A.foo = 7 #=> 7
|
26
|
+
#
|
19
27
|
def attribute(sym,value = nil)
|
20
28
|
singleton_class.send(:attr_accessor, sym )
|
21
29
|
self.send("#{sym}=",value)
|
22
30
|
end
|
23
31
|
|
24
|
-
# Same as attribute, but takes an array of attributes
|
25
32
|
#
|
26
|
-
#
|
33
|
+
# Same as <tt>attribute</tt>, but takes an Array of attributes.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
#
|
37
|
+
# attributes [:foo,:bar,:baz]
|
38
|
+
#
|
27
39
|
def attributes(syms)
|
28
40
|
syms.each { |s| attribute s }
|
29
41
|
end
|
30
42
|
|
31
|
-
#
|
43
|
+
#
|
44
|
+
# Allows you to define a method on the singleton class.
|
32
45
|
#
|
33
46
|
# Example:
|
34
47
|
#
|
@@ -37,15 +50,17 @@ module Ruport
|
|
37
50
|
# action(:bar) { |x| x + 1 }
|
38
51
|
# end
|
39
52
|
#
|
40
|
-
# A.bar(3)
|
53
|
+
# A.bar(3) #=> 4
|
54
|
+
#
|
41
55
|
def action(name,&block)
|
42
56
|
raise ActionAlreadyDefinedError if respond_to? name
|
43
57
|
singleton_class.send(:define_method, name, &block)
|
44
58
|
end
|
59
|
+
|
45
60
|
end
|
46
61
|
end
|
47
62
|
|
48
63
|
class Module
|
49
|
-
|
50
|
-
|
64
|
+
# Provides the singleton_class object.
|
65
|
+
def singleton_class; (class << self; self; end); end
|
51
66
|
end
|
data/lib/ruport/query.rb
CHANGED
@@ -3,49 +3,59 @@ require "ruport/query/sql_split"
|
|
3
3
|
|
4
4
|
module Ruport
|
5
5
|
|
6
|
-
# Query offers a way to interact with databases via DBI. It supports
|
7
|
-
# returning result sets in either Ruport's native Data::Table, or in their raw
|
8
|
-
# form as DBI::Rows.
|
9
6
|
#
|
10
|
-
#
|
11
|
-
#
|
7
|
+
# === Overview
|
8
|
+
#
|
9
|
+
# Query offers a way to interact with databases via DBI. It supports
|
10
|
+
# returning result sets in either Ruport's native Data::Table, or in their
|
11
|
+
# raw form as DBI::Rows.
|
12
|
+
#
|
13
|
+
# It offers basic caching support, the ability to instantiate a generator
|
14
|
+
# for a result set, and the ability to quickly and easily swap between data
|
12
15
|
# sources.
|
16
|
+
#
|
13
17
|
class Query
|
14
18
|
|
15
19
|
|
16
20
|
include Enumerable
|
17
21
|
|
18
|
-
# Queries are initialized with some SQL and a number of options that effect
|
19
|
-
# their operation. They are NOT executed at initialization.
|
20
22
|
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
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.
|
24
28
|
#
|
25
29
|
# This kind of laziness is supposed to be A Good Thing, and
|
26
30
|
# as long as you keep it in mind, it should not cause any problems.
|
27
31
|
#
|
28
|
-
# The SQL can be single or multistatement, but the resulting Data::Table
|
29
|
-
# consist only of the result of the last statement which returns
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
# <tt
|
41
|
-
#
|
42
|
-
# <tt>:
|
43
|
-
#
|
44
|
-
# <tt>:
|
45
|
-
#
|
46
|
-
# <tt>:
|
47
|
-
#
|
48
|
-
#
|
32
|
+
# The SQL can be single or multistatement, but the resulting Data::Table
|
33
|
+
# will consist only of the result of the last statement which returns
|
34
|
+
# something.
|
35
|
+
#
|
36
|
+
# Available options:
|
37
|
+
#
|
38
|
+
# <b><tt>:source</tt></b>:: A source specified in
|
39
|
+
# Ruport::Config.sources, defaults to
|
40
|
+
# <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
|
+
# <b><tt>:dsn</tt></b>:: If specifed, the Query object will
|
47
|
+
# manually override Ruport::Config.
|
48
|
+
# <b><tt>:user</tt></b>:: If a DSN is specified, the user can
|
49
|
+
# be set with this option.
|
50
|
+
# <b><tt>:password</tt></b>:: If a DSN is specified, the password
|
51
|
+
# can be set with this option.
|
52
|
+
# <b><tt>:raw_data</tt></b>:: When set to true, DBI::Rows will be
|
53
|
+
# returned instead of a Data::Table.
|
54
|
+
# <b><tt>:cache_enabled</tt></b>:: When set to true, Query will download
|
55
|
+
# results only once, and then return
|
56
|
+
# cached values until the cache has been
|
57
|
+
# cleared.
|
58
|
+
#
|
49
59
|
# Examples:
|
50
60
|
#
|
51
61
|
# # uses Ruport::Config's default source
|
@@ -63,6 +73,7 @@ module Ruport
|
|
63
73
|
#
|
64
74
|
# # explicitly use a file, even if it doesn't end in .sql
|
65
75
|
# Ruport::Query.new("foo",:origin => :file)
|
76
|
+
#
|
66
77
|
def initialize(sql, options={})
|
67
78
|
options = { :source => :default, :origin => :string }.merge(options)
|
68
79
|
options[:binding] ||= binding
|
@@ -87,103 +98,124 @@ module Ruport
|
|
87
98
|
@cached_data = nil
|
88
99
|
end
|
89
100
|
|
90
|
-
#
|
101
|
+
#
|
102
|
+
# Set this to <tt>true</tt> to get DBI:Rows, <tt>false</tt> to get Ruport
|
103
|
+
# constructs.
|
104
|
+
#
|
91
105
|
attr_accessor :raw_data
|
92
106
|
|
93
|
-
#
|
94
|
-
# ruport when caching
|
107
|
+
# The data stored by Ruport when caching.
|
95
108
|
attr_accessor :cached_data
|
96
109
|
|
97
|
-
#
|
110
|
+
# The original SQL for the Query object
|
98
111
|
attr_reader :sql
|
99
112
|
|
100
|
-
#
|
101
|
-
#
|
113
|
+
#
|
114
|
+
# This will set the <tt>dsn</tt>, <tt>username</tt>, and <tt>password</tt>
|
115
|
+
# to one specified by a source in Ruport::Config.
|
116
|
+
#
|
102
117
|
def select_source(label)
|
103
118
|
@dsn = Ruport::Config.sources[label].dsn
|
104
119
|
@user = Ruport::Config.sources[label].user
|
105
120
|
@password = Ruport::Config.sources[label].password
|
106
121
|
end
|
107
122
|
|
108
|
-
#
|
123
|
+
#
|
124
|
+
# Standard <tt>each</tt> iterator, iterates through the result set row by
|
125
|
+
# row.
|
126
|
+
#
|
109
127
|
def each(&action)
|
110
128
|
Ruport.log(
|
111
129
|
"no block given!", :status => :fatal,
|
112
130
|
:level => :log_only, :exception => LocalJumpError
|
113
131
|
) unless action
|
114
132
|
fetch(&action)
|
133
|
+
self
|
115
134
|
end
|
116
135
|
|
117
|
-
#
|
118
|
-
# DBI::Row objects
|
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
|
+
#
|
119
140
|
def result; fetch; end
|
120
141
|
|
121
142
|
# Runs the query without returning its results.
|
122
143
|
def execute; fetch; nil; end
|
123
144
|
|
124
|
-
#
|
145
|
+
# Clears the contents of the cache.
|
125
146
|
def clear_cache
|
126
147
|
@cached_data = nil
|
127
148
|
end
|
128
149
|
|
129
|
-
#
|
130
|
-
# cache
|
150
|
+
#
|
151
|
+
# Clears the contents of the cache, then runs the query, filling the
|
152
|
+
# cache with the new result.
|
153
|
+
#
|
131
154
|
def update_cache
|
132
155
|
return unless @cache_enabled
|
133
156
|
clear_cache; fetch
|
134
157
|
end
|
135
158
|
|
136
|
-
#
|
137
|
-
# caching is
|
159
|
+
#
|
160
|
+
# Turns on caching. New data will not be loaded until the cache is clear
|
161
|
+
# or caching is disabled.
|
162
|
+
#
|
138
163
|
def enable_caching
|
139
164
|
@cache_enabled = true
|
140
165
|
end
|
141
166
|
|
142
|
-
# Turns off caching and flushes the cached data
|
167
|
+
# Turns off caching and flushes the cached data.
|
143
168
|
def disable_caching
|
144
169
|
clear_cache
|
145
170
|
@cache_enabled = false
|
146
171
|
end
|
147
172
|
|
148
|
-
#
|
149
|
-
#
|
173
|
+
#
|
174
|
+
# Returns a Data::Table, even if in <tt>raw_data</tt> mode.
|
175
|
+
# This doesn't work with raw data if the cache is enabled and filled.
|
176
|
+
#
|
150
177
|
def to_table
|
151
178
|
data_flag, @raw_data = @raw_data, false
|
152
179
|
data = fetch; @raw_data = data_flag; return data
|
153
180
|
end
|
154
181
|
|
155
|
-
# Returns a csv dump of the query
|
182
|
+
# Returns a csv dump of the query.
|
156
183
|
def to_csv
|
157
184
|
Format.table :plugin => :csv, :data => fetch
|
158
185
|
end
|
159
186
|
|
160
|
-
# Returns a Generator object of the result set
|
187
|
+
# Returns a Generator object of the result set.
|
161
188
|
def generator
|
162
189
|
Generator.new(fetch)
|
163
190
|
end
|
164
191
|
|
165
192
|
private
|
166
193
|
|
167
|
-
def query_data(
|
168
|
-
|
194
|
+
def query_data(query_text, params=@params)
|
169
195
|
require "dbi"
|
170
196
|
|
171
197
|
data = @raw_data ? [] : Data::Table.new
|
172
198
|
DBI.connect(@dsn, @user, @password) do |dbh|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
199
|
+
dbh.execute(query_text, *(params || [])) do |sth|
|
200
|
+
# Work-around for inconsistent DBD behavior w/ resultless queries
|
201
|
+
names = sth.column_names rescue []
|
202
|
+
if names.empty?
|
203
|
+
# Work-around for SQLite3 DBD bug
|
204
|
+
sth.cancel rescue nil
|
205
|
+
return nil
|
206
|
+
end
|
207
|
+
|
208
|
+
data.column_names = names unless @raw_data
|
209
|
+
sth.each do |row|
|
210
|
+
row = row.to_a
|
211
|
+
row = Data::Record.new(row, :attributes => names) unless @raw_data
|
212
|
+
yield row if block_given?
|
213
|
+
data << row if !block_given? || @cache_enabled
|
214
|
+
end
|
177
215
|
end
|
178
|
-
return unless sth.fetchable?
|
179
|
-
results = sth.fetch_all
|
180
|
-
data.column_names = sth.column_names unless @raw_data
|
181
|
-
results.each { |row| data << row.to_a }
|
182
|
-
sth.finish
|
183
216
|
end
|
184
217
|
data
|
185
|
-
|
186
|
-
end
|
218
|
+
end
|
187
219
|
|
188
220
|
def get_query(type,query)
|
189
221
|
type.eql?(:file) ? load_file( query ) : query
|
@@ -196,17 +228,19 @@ module Ruport
|
|
196
228
|
end
|
197
229
|
end
|
198
230
|
|
199
|
-
def fetch
|
231
|
+
def fetch(&block)
|
200
232
|
data = nil
|
201
233
|
if @cache_enabled and @cached_data
|
202
234
|
data = @cached_data
|
235
|
+
data.each { |r| yield(r) } if block_given?
|
203
236
|
else
|
204
|
-
@statements.
|
237
|
+
final = @statements.size - 1
|
238
|
+
@statements.each_with_index do |query_text, index|
|
239
|
+
data = query_data(query_text, &(index == final ? block : nil))
|
240
|
+
end
|
241
|
+
@cached_data = data if @cache_enabled
|
205
242
|
end
|
206
|
-
data.each { |r| yield(r) } if block_given?
|
207
|
-
@cached_data = data if @cache_enabled
|
208
243
|
return data
|
209
244
|
end
|
210
|
-
|
211
245
|
end
|
212
246
|
end
|