ruport 0.6.0 → 0.6.1

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/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