ruport 1.0.2 → 1.2.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/AUTHORS CHANGED
@@ -43,3 +43,6 @@ Stefan Mahlitz:
43
43
 
44
44
  Chris Carter
45
45
  - Table#remove_columns (r440)
46
+
47
+ Dave Nelson
48
+ - Grouping#sigma (r1131)
data/README CHANGED
@@ -1,4 +1,4 @@
1
- # ------------------------------------------------------------------------
1
+ # -----------------------------------------------------------------
2
2
  # Contents:
3
3
  #
4
4
  # + What Ruport Is
@@ -27,6 +27,11 @@
27
27
  # functions that will let you build complex reports while maintaining a DRY and
28
28
  # consistent interface.
29
29
  #
30
+ # To get a quick feel for what you can accomplish with Ruport, take a look at
31
+ # a few simple examples provided on our web site.
32
+ #
33
+ # http://rubyreports.org/examples.html
34
+ #
30
35
  # Since Ruport's core support is intentionally minimalistic, you may be looking
31
36
  # for some higher level support for specific needs such as graphing, invoices,
32
37
  # report mailing support, etc. For this, you may wish to take a look at the
@@ -50,12 +55,13 @@
50
55
  # -- formatting
51
56
  #
52
57
  # Ruport relies on PDF::Writer and FasterCSV for its formatting support.
53
- # If you want to make use of textile helpers, you'll also need RedCloth
58
+ # If you want to make use of textile helpers, you'll also need RedCloth.
54
59
  #
55
60
  # -- database interaction
56
61
  #
57
62
  # If you wish to use Ruport to report against a rails project,
58
- # a camping project, or do standalone AAR reports, you'll need ActiveRecord.
63
+ # a camping project, or do standalone acts_as_reportable reports, you'll need
64
+ # ActiveRecord.
59
65
  #
60
66
  # If you want to use Ruport::Query for raw SQL support, you'll need to
61
67
  # install RubyDBI and whatever database drivers you might need.
@@ -72,8 +78,8 @@
72
78
  # - The latest stable API documentation is available at:
73
79
  # http://api.rubyreports.org
74
80
  #
75
- # - Our Trac is at: http://code.stonecode.org
76
- # You may use the username ruport and password blinky to file tickets
81
+ # - Our Trac is at: http://code.rubyreports.org/ruport
82
+ # You may use the username ruport and password blinky to file tickets.
77
83
  #
78
84
  # = Hacking
79
85
  #
data/Rakefile CHANGED
@@ -1,9 +1,9 @@
1
1
  require "rake/rdoctask"
2
2
  require "rake/testtask"
3
3
  require "rake/gempackagetask"
4
+ #
4
5
 
5
-
6
- RUPORT_VERSION = "1.0.2"
6
+ RUPORT_VERSION = "1.2.0"
7
7
 
8
8
  begin
9
9
  require "rubygems"
@@ -62,7 +62,7 @@ module Commaleon::Helpers
62
62
  option :key, :mcsv, :ccsv
63
63
 
64
64
  # This setup() idiom has become the default way of doing some
65
- # manipulations on the data and optionsbefore handing off the
65
+ # manipulations on the data and options before handing off the
66
66
  # rendering task to the formatters.
67
67
  #
68
68
  # We're using grouping mainly for the renderer support,
@@ -0,0 +1,34 @@
1
+ require "ruport"
2
+
3
+ Ruport::Formatter::Template.create(:simple) do |t|
4
+ t.page_format = {
5
+ :size => "LETTER",
6
+ :layout => :landscape
7
+ }
8
+ t.text_format = {
9
+ :font_size => 16
10
+ }
11
+ t.table_format = {
12
+ :font_size => 16,
13
+ :show_headings => false
14
+ }
15
+ t.column_format = {
16
+ :alignment => :center,
17
+ }
18
+ t.heading_format = {
19
+ :alignment => :right
20
+ }
21
+ t.grouping_format = {
22
+ :style => :separated
23
+ }
24
+ end
25
+
26
+ Ruport::Formatter::Template.create(:derived, :base => :simple) do |t|
27
+ t.table_format[:show_headings] = true
28
+ end
29
+
30
+ t = Table(%w[a b c]) << [1,2,3] << [1,"hello",6] << [2,3,4]
31
+ g = Grouping(t, :by => "a")
32
+
33
+ puts g.to_pdf(:template => :simple)
34
+ #puts g.to_pdf(:template => :derived)
@@ -1,8 +1,10 @@
1
1
  # A dump of the database for this example can be found in ./data/tattle.dump
2
2
 
3
+
3
4
  require "active_record"
4
5
  require "ruport"
5
6
 
7
+
6
8
  # Update with your connection parameters
7
9
  ActiveRecord::Base.establish_connection(
8
10
  :adapter => 'mysql',
@@ -31,9 +33,8 @@ grouping.each do |name,group|
31
33
  end
32
34
  end
33
35
 
34
- sorted_table = rubygems_versions.sort_rows_by { |r| -r.count }
35
- g = Grouping(sorted_table, :by => "platform")
36
+ sorted_table = rubygems_versions.sort_rows_by("count", :order => :descending)
37
+ sorted_table.reduce { |r| r["platform"] !~ /darwin/i }
38
+ g = Grouping(sorted_table, :by => "platform", :order => "name")
39
+ puts g.to_pdf
36
40
 
37
- File.open("platforms_gems.html", "w") do |f|
38
- f.write g.to_html(:style => :justified)
39
- end
@@ -41,7 +41,7 @@ class TracSummaryReport
41
41
  Grouping(table,:by => :date)
42
42
  end
43
43
 
44
- def renderable_data
44
+ def renderable_data(format)
45
45
  summary = feed_data.summary :date,
46
46
  :opened => lambda { |g| g.sigma { |r| r.opened } },
47
47
  :closed => lambda { |g| g.sigma { |r| r.closed } },
@@ -54,6 +54,6 @@ end
54
54
 
55
55
  timeline = "http://stonecode.svnrepository.com/ruport/trac.cgi/timeline"
56
56
 
57
- report = TracSummaryReport.new(:timeline_uri => timeline, :days => 14)
57
+ report = TracSummaryReport.new(:timeline_uri => timeline, :days => 30)
58
58
  puts report.as(:text)
59
59
 
data/lib/ruport.rb CHANGED
@@ -12,7 +12,7 @@
12
12
 
13
13
  module Ruport #:nodoc:#
14
14
 
15
- VERSION = "1.0.2"
15
+ VERSION = "1.2.0"
16
16
 
17
17
  class FormatterError < RuntimeError #:nodoc:
18
18
  end
@@ -79,6 +79,7 @@ module Ruport #:nodoc:#
79
79
  end
80
80
  return $? == 0 ? size : [80,24]
81
81
  end
82
+
82
83
  end
83
84
 
84
85
  def terminal_width
@@ -89,16 +90,16 @@ module Ruport #:nodoc:#
89
90
 
90
91
  # quiets warnings for block
91
92
  def quiet #:nodoc:
92
- warnings = $VERBOSE
93
+ warns = $VERBOSE
93
94
  $VERBOSE = nil
94
95
  result = yield
95
- $VERBOSE = warnings
96
+ $VERBOSE = warns
96
97
  return result
97
98
  end
98
99
 
99
100
  module_function :quiet
100
101
 
101
- end
102
+ end
102
103
 
103
104
  require "enumerator"
104
105
  require "ruport/renderer"
@@ -93,23 +93,32 @@ module Ruport
93
93
  #
94
94
  # Additional options include:
95
95
  #
96
- # <b><tt>:only</tt></b>:: an attribute name or array of attribute
96
+ # <b><tt>:only</tt></b>:: An attribute name or array of attribute
97
97
  # names to include in the results, other
98
98
  # attributes will be excuded.
99
- # <b><tt>:except</tt></b>:: an attribute name or array of attribute
99
+ # <b><tt>:except</tt></b>:: An attribute name or array of attribute
100
100
  # names to exclude from the results.
101
- # <b><tt>:methods</tt></b>:: a method name or array of method names
101
+ # <b><tt>:methods</tt></b>:: A method name or array of method names
102
102
  # whose result(s) will be included in the
103
103
  # table.
104
- # <b><tt>:include</tt></b>:: an associated model or array of associated
104
+ # <b><tt>:include</tt></b>:: An associated model or array of associated
105
105
  # models to include in the results.
106
- # <b><tt>:record_class</tt></b>:: specify the class of the table's
106
+ # <b><tt>:filters</tt></b>:: A proc or array of procs that set up
107
+ # conditions to filter the data being added
108
+ # to the table.
109
+ # <b><tt>:transforms</tt></b>:: A proc or array of procs that perform
110
+ # transformations on the data being added
111
+ # to the table.
112
+ # <b><tt>:record_class</tt></b>:: Specify the class of the table's
107
113
  # records.
114
+ # <b><tt>:eager_loading</tt></b>:: Set to false if you don't want to
115
+ # eager load included associations.
108
116
  #
109
- # The same set of options may be passed to the :include option in order to
110
- # specify the output for any associated models. In this case, the
111
- # :include option must be a hash, where the keys are the names of the
112
- # associations and the values are hashes of options.
117
+ # The :only, :except, :methods, and :include options may also be passed
118
+ # to the :include option in order to specify the output for any
119
+ # associated models. In this case, the :include option must be a hash,
120
+ # where the keys are the names of the associations and the values
121
+ # are hashes of options.
113
122
  #
114
123
  # Any options passed to report_table will disable the options set by
115
124
  # the acts_as_reportable class method.
@@ -138,23 +147,21 @@ module Ruport
138
147
  # an html version of the table with all columns from books and authors.
139
148
  #
140
149
  # Note: column names for attributes of included models will be qualified
141
- # with the model's underscored class name, e.g. 'author.name'
142
- # By default, this will not preserve the entire namespace, but you
143
- # can get the fully qualified namespace by using the
144
- # :preserve_namespace => true option to report_table. So if the
145
- # Author model was enclosed in a module called MyModule, you'd
146
- # get 'my_module/author.name' as the column name.
150
+ # with the name of the association.
147
151
  #
148
152
  def report_table(number = :all, options = {})
149
153
  only = options.delete(:only)
150
154
  except = options.delete(:except)
151
155
  methods = options.delete(:methods)
152
156
  includes = options.delete(:include)
153
- preserve_namespace = options.delete(:preserve_namespace)
157
+ filters = options.delete(:filters)
158
+ transforms = options.delete(:transforms)
154
159
  record_class = options.delete(:record_class) || Ruport::Data::Record
155
160
  self.aar_columns = []
156
161
 
157
- options[:include] = get_include_for_find(includes)
162
+ unless options.delete(:eager_loading) == false
163
+ options[:include] = get_include_for_find(includes)
164
+ end
158
165
 
159
166
  data = [find(number, options)].flatten
160
167
  data = data.map {|r| r.reportable_data(:include => includes,
@@ -162,18 +169,25 @@ module Ruport
162
169
  :except => except,
163
170
  :methods => methods) }.flatten
164
171
 
172
+
165
173
  table = Ruport::Data::Table.new(:data => data,
166
174
  :column_names => aar_columns,
167
- :record_class => record_class)
168
- normalize_column_names(table) unless preserve_namespace
169
- table
175
+ :record_class => record_class,
176
+ :filters => filters,
177
+ :transforms => transforms )
170
178
  end
171
179
 
172
180
  # Creates a Ruport::Data::Table from an ActiveRecord find_by_sql.
173
181
  #
174
182
  # Additional options include:
175
183
  #
176
- # <b><tt>:record_class</tt></b>:: specify the class of the table's
184
+ # <b><tt>:filters</tt></b>:: A proc or array of procs that set up
185
+ # conditions to filter the data being added
186
+ # to the table.
187
+ # <b><tt>:transforms</tt></b>:: A proc or array of procs that perform
188
+ # transformations on the data being added
189
+ # to the table.
190
+ # <b><tt>:record_class</tt></b>:: Specify the class of the table's
177
191
  # records.
178
192
  #
179
193
  # Example:
@@ -187,6 +201,8 @@ module Ruport
187
201
  #
188
202
  def report_table_by_sql(sql, options = {})
189
203
  record_class = options.delete(:record_class) || Ruport::Data::Record
204
+ filters = options.delete(:filters)
205
+ transforms = options.delete(:transforms)
190
206
  self.aar_columns = []
191
207
 
192
208
  data = find_by_sql(sql)
@@ -194,22 +210,33 @@ module Ruport
194
210
 
195
211
  table = Ruport::Data::Table.new(:data => data,
196
212
  :column_names => aar_columns,
197
- :record_class => record_class)
213
+ :record_class => record_class,
214
+ :filters => filters,
215
+ :transforms => transforms)
198
216
  end
199
217
 
200
218
  private
201
219
 
202
220
  def get_include_for_find(report_option)
203
221
  includes = report_option.blank? ? aar_options[:include] : report_option
204
- includes.is_a?(Hash) ? includes.keys : includes
222
+ if includes.is_a?(Hash)
223
+ result = {}
224
+ includes.each do |k,v|
225
+ if v.empty? || !v[:include]
226
+ result.merge!(k => {})
227
+ else
228
+ result.merge!(k => get_include_for_find(v[:include]))
229
+ end
230
+ end
231
+ result
232
+ elsif includes.is_a?(Array)
233
+ result = {}
234
+ includes.each {|i| result.merge!(i => {}) }
235
+ result
236
+ else
237
+ includes
238
+ end
205
239
  end
206
-
207
- def normalize_column_names(table)
208
- renamed = table.column_names.inject({}) do |s,c|
209
- s.merge(c => c.sub(/.*\//,""))
210
- end
211
- table.rename_columns(renamed)
212
- end
213
240
  end
214
241
 
215
242
  # === Overview
@@ -290,10 +317,10 @@ module Ruport
290
317
  data_records = []
291
318
 
292
319
  if include_has_options
293
- assoc_options =
294
- includes[association].merge({ :qualify_attribute_names => true })
320
+ assoc_options = includes[association].merge({
321
+ :qualify_attribute_names => association })
295
322
  else
296
- assoc_options = { :qualify_attribute_names => true }
323
+ assoc_options = { :qualify_attribute_names => association }
297
324
  end
298
325
 
299
326
  association_objects = [send(association)].flatten.compact
@@ -327,8 +354,8 @@ module Ruport
327
354
  #
328
355
  # Use the :only or :except options to limit the attributes returned.
329
356
  #
330
- # Use the :qualify_attribute_names option to append the underscored
331
- # model name to the attribute name as model.attribute
357
+ # Use the :qualify_attribute_names option to append the association
358
+ # name to the attribute name as association.attribute
332
359
  #
333
360
  def get_attributes_with_options(options = {})
334
361
  only_or_except =
@@ -336,9 +363,8 @@ module Ruport
336
363
  { :only => options[:only], :except => options[:except] }
337
364
  end
338
365
  attrs = attributes(only_or_except)
339
- namespace = self.class.to_s.underscore
340
366
  attrs = attrs.inject({}) {|h,(k,v)|
341
- h["#{namespace}.#{k}"] = v; h
367
+ h["#{options[:qualify_attribute_names]}.#{k}"] = v; h
342
368
  } if options[:qualify_attribute_names]
343
369
  attrs
344
370
  end
data/lib/ruport/data.rb CHANGED
@@ -1,3 +1,4 @@
1
1
  require "ruport/data/record"
2
2
  require "ruport/data/table"
3
3
  require "ruport/data/grouping"
4
+ require "ruport/data/feeder"
@@ -0,0 +1,111 @@
1
+ # Ruport : Extensible Reporting System
2
+ #
3
+ # data/feeder.rb provides a data transformation and filtering proxy for ruport
4
+ #
5
+ # Copyright August 2007, Gregory Brown / Michael Milner. All Rights Reserved.
6
+ #
7
+ # This is free software. Please see the LICENSE and COPYING files for details.
8
+
9
+
10
+ # This class provides a simple way to apply transformations and filters that
11
+ # get run while you are aggregating data. This is used primarily to build
12
+ # constrained wrappers to Ruport::Data::Table, but can be used with abstract
13
+ # data structures as well.
14
+ #
15
+ # Table Example:
16
+ #
17
+ # t = Table(%w[a b c]) do |feeder|
18
+ # feeder.filter { |r| r.a < 5 }
19
+ # feeder.transform { |r| r.b = "B: #{r.b}"}
20
+ # feeder << [1,2,3]
21
+ # feeder << [7,1,2]
22
+ # feeder << { "a" => 3, "b" => 6, "c" => 7 }
23
+ # end
24
+ # t.length #=> 2
25
+ # t.column("b") #=> ["B: 2","B: 6"]
26
+ #
27
+ # Filters and transforms are added in a sequential order to a single list of
28
+ # constraints. You could add some constraints and then append some data, then
29
+ # add additional constraints, or even build up dynamic constraints if you'd
30
+ # like.
31
+ #
32
+ # Wrapping an arbitrary data object:
33
+ #
34
+ # In order to make Data::Feeder work with an object other than Data::Table, it
35
+ # must implement two things:
36
+ #
37
+ # * A method called feed_element that accepts a single argument.
38
+ # When Feeder#<< is called, the object to be appended is converted by this
39
+ # method, and then yielded to the filters / transforms.
40
+ #
41
+ # * A meaningful #<< method. Feeder#<< simply delegates this to the wrapped
42
+ # object once the filters and transforms have been applied, so be sure that
43
+ # the object returned by feed_element is one that can be used by your #<<
44
+ # method.
45
+ #
46
+ # Here is a sample implementation of wrapping a feeder around an Array.
47
+ #
48
+ # class Array
49
+ # def feed_element(element)
50
+ # element
51
+ # end
52
+ # end
53
+ #
54
+ # int_array = []
55
+ # feeder = Ruport::Data::Feeder.new(int_array)
56
+ # feeder.filter { |r| r.kind_of?(Integer) }
57
+ #
58
+ # feeder << 1 << "5" << 4.7 << "kitten" << 4
59
+ # int_array #=> [1, 4]
60
+ #
61
+ class Ruport::Data::Feeder
62
+
63
+ # Creates a new Data::Feeder, wrapping the data object provided.
64
+ def initialize(data)
65
+ @data = data
66
+ @constraints = []
67
+ end
68
+
69
+ # Accesses the underlying data object directly
70
+ attr_reader :data
71
+
72
+ # Constrained append operation.
73
+ #
74
+ # Before filters and transforms are run, the element to be appended is first
75
+ # converted by data.feed_element(some_element)
76
+ #
77
+ # Filters and transforms are then run sequentially, and if the constraints
78
+ # are met, it is appended using data << some_element.
79
+ #
80
+ def <<(element)
81
+ feed_element = data.feed_element(element)
82
+
83
+ @constraints.each do |type,block|
84
+ if type == :filter
85
+ return self unless block[feed_element]
86
+ else
87
+ block[feed_element]
88
+ end
89
+ end
90
+
91
+ data << feed_element
92
+ return self
93
+ end
94
+
95
+ # Creates a filter which must be satisfied for an object to be appended via
96
+ # the feeder.
97
+ #
98
+ # feeder.filter { |r| r.length < 4 }
99
+ #
100
+ def filter(&block)
101
+ @constraints << [:filter,block]
102
+ end
103
+
104
+ # Creates a transformation which may change the object as it is appended.
105
+ #
106
+ # feeder.transform { |r| r.a += 10 }
107
+ def transform(&block)
108
+ @constraints << [:transform,block]
109
+ end
110
+
111
+ end