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/lib/ruport/format.rb CHANGED
@@ -79,7 +79,7 @@ module Ruport
79
79
  require "ruport/format/#{lib}"
80
80
  }
81
81
 
82
- @@filters = Hash.new
82
+ @@filters ||= Hash.new
83
83
 
84
84
  # To hook up a Format object to your current class, you need to pass it a
85
85
  # binding. This way, when filters are being processed, they will be
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
- # This class uses SMTP to provide a simple mail sending mechanism.
12
- # It also uses MailFactory to provide attachment and HTML email support.
13
- #
14
- # Here is a simple example of a message which attaches a readme file:
15
- #
16
- # require "ruport"
17
- #
18
- # Ruport.configure do |conf|
19
- # conf.mailer :default,
20
- # :host => "mail.adelphia.net", :address => "gregory.t.brown@gmail.com"
21
- # end
22
- #
23
- # mailer = Ruport::Mailer.new
24
- #
25
- # mailer.attach "README"
26
- #
27
- # mailer.deliver :to => "gregory.t.brown@gmail.com",
28
- # :from => "gregory.t.brown@gmail.com",
29
- # :subject => "Hey there",
30
- # :text => "This is what you asked for"
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
- # a = Mailer.new # uses the :default mailer
39
- # a = Mailer.new :foo # uses :foo mail config from Ruport::Config
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
- # sends the message
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
 
@@ -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
- # eigenclass on an object. These are used in the implementation of Ruport's
5
- # formatting system, and might be helpful for other things.
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
- # Allows you to define an attribute accessor on the singleton_class.
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 #=> nil
18
- # A.foo = 7 #=> 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
- # e.g. attributes [:foo,:bar,:baz]
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
- # Allows you to define a method on the singleton_class
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) #=> 4
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
- # provides the singleton_class object
50
- def singleton_class; (class << self; self; end); end
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
- # It offers basic caching support, the ability to instantiate a generator for
11
- # a result set, and the ability to quickly and easily swap between data
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
- # This is important to note as they will not query the database until either
22
- # Query#result, Query#execute, Query#generator, or an enumerable method is
23
- # called on them.
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 will
29
- # consist only of the result of the last statement which returns something.
30
- #
31
- # Options:
32
- #
33
- # <tt>:source</tt>
34
- # A source specified in Ruport::Config.sources, defaults to :default
35
- # <tt>:origin</tt>
36
- # query origin, default to :string, but can be
37
- # set to :file, loading the path specified by the sql parameter
38
- # <tt>:dsn</tt>
39
- # If specifed, the query object will manually override Ruport::Config
40
- # <tt>:user</tt>
41
- # If a DSN is specified, a user can be set by this option
42
- # <tt>:password</tt>
43
- # If a DSN is specified, a password can be set by this option
44
- # <tt>:raw_data</tt>
45
- # When set to true, DBI::Rows will be returned
46
- # <tt>:cache_enabled</tt>
47
- # When set to true, Query will download results only once, and then return
48
- # cached values until cache has been cleared.
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
- # set to true to get DBI:Rows, false to get Ruport constructs
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
- # modifying this might be useful for testing, this is the data stored by
94
- # ruport when caching
107
+ # The data stored by Ruport when caching.
95
108
  attr_accessor :cached_data
96
109
 
97
- # this is the original SQL for the Query object
110
+ # The original SQL for the Query object
98
111
  attr_reader :sql
99
112
 
100
- # This will set the dsn, username, and password to one specified by a label
101
- # that corresponds to a source in Ruport::Config
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
- # Standard each iterator, iterates through result set row by row.
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
- # Grabs the result set as a Data::Table or if in raw_data mode, an array of
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
- # clears the contents of the cache
145
+ # Clears the contents of the cache.
125
146
  def clear_cache
126
147
  @cached_data = nil
127
148
  end
128
149
 
129
- # clears the contents of the cache and then runs the query, filling the
130
- # cache with the new result
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
- # Turns on caching. New data will not be loaded until cache is clear or
137
- # caching is disabled.
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
- # Returns a Data::Table, even if in raw_data mode
149
- # Does not work with raw data if cache is enabled and filled
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( query_text, params=@params )
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
- if params
174
- sth = dbh.execute(query_text,*params)
175
- else
176
- sth = dbh.execute(query_text)
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
- rescue NoMethodError; nil
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.each { |query_text| data = query_data( query_text ) }
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