ruport 1.0.2 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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