ruport 0.8.14 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/README +42 -107
  2. data/Rakefile +29 -32
  3. data/examples/centered_pdf_text_box.rb +13 -19
  4. data/examples/example.csv +3 -0
  5. data/examples/line_plotter.rb +15 -15
  6. data/examples/pdf_complex_report.rb +10 -23
  7. data/examples/pdf_table_with_title.rb +12 -12
  8. data/examples/rope_examples/itunes/Rakefile +22 -1
  9. data/examples/rope_examples/itunes/config/environment.rb +4 -0
  10. data/examples/rope_examples/itunes/lib/init.rb +32 -2
  11. data/examples/rope_examples/itunes/util/build +50 -16
  12. data/examples/rope_examples/sales_report/README +1 -1
  13. data/examples/rope_examples/sales_report/Rakefile +22 -1
  14. data/examples/rope_examples/sales_report/config/environment.rb +4 -0
  15. data/examples/rope_examples/sales_report/lib/init.rb +32 -2
  16. data/examples/rope_examples/sales_report/lib/reports/sales.rb +10 -16
  17. data/examples/rope_examples/sales_report/util/build +50 -16
  18. data/examples/row_renderer.rb +39 -0
  19. data/examples/ruport_list/png_embed.rb +61 -0
  20. data/examples/ruport_list/roadmap.png +0 -0
  21. data/examples/sample.rb +16 -0
  22. data/examples/simple_pdf_lines.rb +24 -0
  23. data/lib/ruport.rb +143 -57
  24. data/lib/ruport/acts_as_reportable.rb +246 -0
  25. data/lib/ruport/data.rb +1 -2
  26. data/lib/ruport/data/grouping.rb +311 -0
  27. data/lib/ruport/data/record.rb +113 -84
  28. data/lib/ruport/data/table.rb +275 -174
  29. data/lib/ruport/formatter.rb +149 -0
  30. data/lib/ruport/formatter/csv.rb +87 -0
  31. data/lib/ruport/formatter/html.rb +89 -0
  32. data/lib/ruport/formatter/pdf.rb +357 -0
  33. data/lib/ruport/formatter/text.rb +151 -0
  34. data/lib/ruport/generator.rb +127 -30
  35. data/lib/ruport/query.rb +46 -99
  36. data/lib/ruport/renderer.rb +238 -194
  37. data/lib/ruport/renderer/grouping.rb +67 -0
  38. data/lib/ruport/renderer/table.rb +25 -98
  39. data/lib/ruport/report.rb +45 -96
  40. data/test/acts_as_reportable_test.rb +229 -0
  41. data/test/csv_formatter_test.rb +97 -0
  42. data/test/{_test_database.rb → database_test_.rb} +0 -0
  43. data/test/grouping_test.rb +305 -0
  44. data/test/html_formatter_test.rb +104 -0
  45. data/test/pdf_formatter_test.rb +25 -0
  46. data/test/{test_query.rb → query_test.rb} +32 -121
  47. data/test/{test_record.rb → record_test.rb} +40 -23
  48. data/test/renderer_test.rb +344 -0
  49. data/test/{test_report.rb → report_test.rb} +74 -44
  50. data/test/samples/ticket_count.csv +124 -0
  51. data/test/{test_sql_split.rb → sql_split_test.rb} +0 -0
  52. data/test/{test_table.rb → table_test.rb} +255 -44
  53. data/test/text_formatter_test.rb +144 -0
  54. data/util/bench/data/record/bench_as_vs_to.rb +17 -0
  55. data/util/bench/data/record/bench_constructor.rb +46 -0
  56. data/util/bench/data/record/bench_indexing.rb +65 -0
  57. data/util/bench/data/record/bench_reorder.rb +35 -0
  58. data/util/bench/data/record/bench_to_a.rb +19 -0
  59. data/util/bench/data/table/bench_column_manip.rb +103 -0
  60. data/util/bench/data/table/bench_dup.rb +24 -0
  61. data/util/bench/data/table/bench_init.rb +67 -0
  62. data/util/bench/data/table/bench_manip.rb +125 -0
  63. data/util/bench/formatter/bench_csv.rb +14 -0
  64. data/util/bench/formatter/bench_html.rb +14 -0
  65. data/util/bench/formatter/bench_pdf.rb +14 -0
  66. data/util/bench/formatter/bench_text.rb +14 -0
  67. data/util/bench/samples/tattle.csv +1237 -0
  68. metadata +121 -143
  69. data/TODO +0 -21
  70. data/examples/invoice.rb +0 -142
  71. data/examples/invoice_report.rb +0 -29
  72. data/examples/line_graph.rb +0 -38
  73. data/examples/rope_examples/itunes/config/ruport_config.rb +0 -8
  74. data/examples/rope_examples/sales_report/config/ruport_config.rb +0 -8
  75. data/lib/ruport/attempt.rb +0 -63
  76. data/lib/ruport/config.rb +0 -204
  77. data/lib/ruport/data/groupable.rb +0 -93
  78. data/lib/ruport/data/taggable.rb +0 -80
  79. data/lib/ruport/format.rb +0 -1
  80. data/lib/ruport/format/csv.rb +0 -29
  81. data/lib/ruport/format/html.rb +0 -42
  82. data/lib/ruport/format/latex.rb +0 -47
  83. data/lib/ruport/format/pdf.rb +0 -233
  84. data/lib/ruport/format/plugin.rb +0 -31
  85. data/lib/ruport/format/svg.rb +0 -60
  86. data/lib/ruport/format/text.rb +0 -103
  87. data/lib/ruport/format/xml.rb +0 -32
  88. data/lib/ruport/layout.rb +0 -1
  89. data/lib/ruport/layout/component.rb +0 -7
  90. data/lib/ruport/mailer.rb +0 -99
  91. data/lib/ruport/renderer/graph.rb +0 -46
  92. data/lib/ruport/report/graph.rb +0 -14
  93. data/lib/ruport/system_extensions.rb +0 -71
  94. data/test/test_config.rb +0 -88
  95. data/test/test_format_text.rb +0 -63
  96. data/test/test_graph_renderer.rb +0 -97
  97. data/test/test_groupable.rb +0 -56
  98. data/test/test_mailer.rb +0 -170
  99. data/test/test_renderer.rb +0 -151
  100. data/test/test_ruport.rb +0 -58
  101. data/test/test_table_renderer.rb +0 -141
  102. data/test/test_taggable.rb +0 -52
@@ -0,0 +1,39 @@
1
+ require "ruport"
2
+
3
+ class CSV2Something < Ruport::Renderer
4
+ required_option :file
5
+ stage :table_body
6
+
7
+ module Helpers
8
+ def table_feeder
9
+ Table(options.file,:has_names => false) { |t,r| yield(r) }
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+ class HTML < Ruport::Formatter::HTML
16
+
17
+ renders :html, :for => CSV2Something
18
+
19
+ def layout
20
+ output << "<html><div id='ruport'><table>\n"
21
+ yield
22
+ output << "</table></div></html>\n"
23
+ end
24
+
25
+ def build_table_body
26
+ table_feeder { |r| render_row(r) }
27
+ end
28
+ end
29
+
30
+ class Text < Ruport::Formatter::Text
31
+ renders :text, :for => CSV2Something
32
+
33
+ def build_table_body
34
+ table_feeder { |r| output << r.to_a.join("$") }
35
+ end
36
+
37
+ end
38
+
39
+ CSV2Something.render_html(:file => "example.csv",:io => STDOUT)
@@ -0,0 +1,61 @@
1
+ require "rubygems"
2
+ require "ruport"
3
+
4
+ class RoadmapRenderer < Ruport::Renderer
5
+
6
+ option :image_file
7
+
8
+ stage :roadmap_image, :roadmap_text_body
9
+ finalize :roadmap
10
+
11
+ end
12
+
13
+ class HTMLRoadmap < Ruport::Formatter
14
+
15
+ renders :html, :for => RoadmapRenderer
16
+ opt_reader :image_file
17
+
18
+ def layout
19
+ output << "<html><body>\n"
20
+ yield
21
+ output << "</body></html>\n"
22
+ end
23
+
24
+ def build_roadmap_image
25
+ output << "<img src='#{image_file}'/>"
26
+ end
27
+
28
+ def build_roadmap_text_body
29
+ output << "<h2>This is a sample HTML report w. PDF</h2>"
30
+ end
31
+
32
+ end
33
+
34
+ class PDFRoadmap < Ruport::Formatter::PDF
35
+
36
+ renders :pdf, :for => RoadmapRenderer
37
+ opt_reader :image_file
38
+
39
+ def build_roadmap_image
40
+ center_image_in_box image_file, :x => 0, :y => 200,
41
+ :width => 624, :height => 432
42
+ move_cursor_to 80
43
+ end
44
+
45
+ def build_roadmap_text_body
46
+ draw_text "This is a sample PDF with embedded PNG", :font_size => 16,
47
+ :x1 => 150
48
+ end
49
+
50
+ def finalize_roadmap
51
+ render_pdf
52
+ end
53
+
54
+ end
55
+
56
+ formats = [:html, :pdf]
57
+ formats.each do |format|
58
+ File.open("roadmap.#{format}","w") { |f|
59
+ RoadmapRenderer.render format, :io => f, :image_file => "roadmap.png"
60
+ }
61
+ end
Binary file
@@ -0,0 +1,16 @@
1
+ require "ruport"
2
+
3
+ class Sample < Ruport::Report
4
+
5
+ renders_with Ruport::Renderer::Table
6
+
7
+ def generate
8
+ Table(%w[a b c]) << [1,2,3] << [4,5,6]
9
+ end
10
+
11
+ end
12
+
13
+ if __FILE__ == $0
14
+ report = Sample.new(:csv)
15
+ puts report.run
16
+ end
@@ -0,0 +1,24 @@
1
+ require 'rubygems'
2
+ require "ruport"
3
+
4
+ class SimpleLines < Ruport::Renderer
5
+ stage :horizontal_lines
6
+ end
7
+
8
+ class PDFLines < Ruport::Formatter::PDF
9
+ renders :pdf, :for => SimpleLines
10
+
11
+ def build_horizontal_lines
12
+ data.each do |points|
13
+ pad(10) { horizontal_line(*points) }
14
+ end
15
+ render_pdf
16
+ end
17
+ end
18
+
19
+ # generate 35 random lines
20
+ data = (0..34).inject([]) { |s,r|
21
+ s << [rand(100),100+rand(400)]
22
+ }
23
+
24
+ puts SimpleLines.render_pdf(:data => data)
data/lib/ruport.rb CHANGED
@@ -12,73 +12,159 @@
12
12
 
13
13
  module Ruport
14
14
 
15
- VERSION = "0.8.14"
15
+ VERSION = "0.10.0"
16
16
 
17
- # This method is Ruport's logging and error interface. It can generate
18
- # warnings or raise fatal errors, logging +message+ to the file defined by
19
- # <tt>Config::log_file</tt>.
20
- #
21
- # You can configure the logging preferences with the +options+ hash.
22
- # Available options are:
17
+ # SystemExtensions lovingly ganked from HighLine 1.2.1
23
18
  #
24
- # <b><tt>:status</tt></b>:: Sets the severity level. This defaults to
25
- # <tt>:warn</tt>, which will invoke
26
- # <tt>Logger#warn</tt>. A status of
27
- # <tt>:fatal</tt> will invoke
28
- # <tt>Logger#fatal</tt> and raise an exception.
29
- # <b><tt>:output</tt></b>:: Optional 2nd output. By default, <tt>log()</tt>
30
- # will print warnings to <tt>$stderr</tt> in
31
- # addition to <tt>Config::log_file</tt>. You
32
- # can redirect this to any I/O object with this
33
- # option.
34
- # <b><tt>:level</tt></b>:: Set this to <tt>:log_only</tt> to disable
35
- # secondary output. If you want to globally
36
- # override this setting for all calls to
37
- # <tt>log()</tt> (which can be useful for
38
- # debugging), you can set
39
- # <tt>Config.debug_mode</tt>.
40
- # <b><tt>:raises</tt></b>:: The +Exception+ to throw on failure. This
41
- # defaults to +RuntimeError+.
42
- #
43
- def self.log(message, options={})
44
- options = {:status => :warn, :output => $stderr}.merge(options)
45
- options[:output].puts "[!!] #{message}" unless
46
- options[:level].eql?(:log_only) and not Ruport::Config.debug_mode?
47
- Ruport::Config::logger.send(options[:status],message) if Config.logger
48
- if options[:status].eql? :fatal
49
- raise(options[:raises] || RuntimeError, message)
50
- end
51
- end
52
-
53
- # This method yields a <tt>Ruport::Config</tt> object, allowing you to
54
- # set the configuration options for your application.
19
+ # The following modifications have been made by Gregory Brown on 2006.06.25
55
20
  #
56
- # Example:
21
+ # - Outer Module is changed from HighLine to Ruport
22
+ # - terminal_width / terminal_height added
57
23
  #
58
- # Ruport.configure do |c|
24
+ # The following modifications have been made by Gregory Brown on 2006.08.13
25
+ # - removed most methods, preserving only terminal geometry features
59
26
  #
60
- # c.source :default,
61
- # :dsn => "dbi:mysql:foo",
62
- # :user => "clyde",
63
- # :password => "pacman"
27
+ # All modifications are under the distributions terms of Ruport.
28
+ # Copyright 2006, Gregory Brown. All Rights Reserved
64
29
  #
65
- # c.mailer :default,
66
- # :host => "mail.example.com",
67
- # :address => "inky@example.com"
30
+ # Original copyright notice preserved below.
31
+ # --------------------------------------------------------------------------
68
32
  #
69
- # end
33
+ # Created by James Edward Gray II on 2006-06-14.
34
+ # Copyright 2006 Gray Productions. All rights reserved.
70
35
  #
71
- def self.configure(&block)
72
- block.call(Ruport::Config)
36
+ # This is Free Software. See LICENSE and COPYING for details.
37
+
38
+ module SystemExtensions #:nodoc:
39
+ module_function
40
+
41
+ # This section builds character reading and terminal size functions
42
+ # to suit the proper platform we're running on. Be warned: Here be
43
+ # dragons!
44
+ #
45
+ begin
46
+ require "Win32API" # See if we're on Windows.
47
+
48
+ # A Windows savvy method to fetch the console columns, and rows.
49
+ def terminal_size
50
+ m_GetStdHandle = Win32API.new( 'kernel32',
51
+ 'GetStdHandle',
52
+ ['L'],
53
+ 'L' )
54
+ m_GetConsoleScreenBufferInfo = Win32API.new(
55
+ 'kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L'
56
+ )
57
+
58
+ format = 'SSSSSssssSS'
59
+ buf = ([0] * format.size).pack(format)
60
+ stdout_handle = m_GetStdHandle.call(0xFFFFFFF5)
61
+
62
+ m_GetConsoleScreenBufferInfo.call(stdout_handle, buf)
63
+ bufx, bufy, curx, cury, wattr,
64
+ left, top, right, bottom, maxx, maxy = buf.unpack(format)
65
+ return right - left + 1, bottom - top + 1
66
+ end
67
+ rescue LoadError # If we're not on Windows try...
68
+ # A Unix savvy method to fetch the console columns, and rows.
69
+ def terminal_size
70
+ size = `stty size 2>/dev/null`.split.map { |x| x.to_i }.reverse
71
+ return $? == 0 ? size : [80,24]
72
+ end
73
+
74
+ end
75
+
76
+ def terminal_width
77
+ terminal_size.first
78
+ end
79
+
73
80
  end
81
+
82
+ require 'timeout'
83
+
84
+ class Attempt # :nodoc:
85
+ VERSION = '0.1.0'
86
+
87
+ # Number of attempts to make before failing. The default is 3.
88
+ attr_accessor :tries
89
+
90
+ # Number of seconds to wait between attempts. The default is 60.
91
+ attr_accessor :interval
92
+
93
+ # a level which ruport understands.
94
+ attr_accessor :log_level
95
+
96
+ # If set, this increments the interval with each failed attempt by that
97
+ # number of seconds.
98
+ attr_accessor :increment
99
+
100
+ # If set, the code block is further wrapped in a timeout block.
101
+ attr_accessor :timeout
102
+
103
+ # Determines which exception level to check when looking for errors to
104
+ # retry. The default is 'Exception' (i.e. all errors).
105
+ attr_accessor :level
106
+
107
+ # :call-seq:
108
+ # Attempt.new{ |a| ... }
109
+ #
110
+ # Creates and returns a new +Attempt+ object. Use a block to set the
111
+ # accessors.
112
+ #
113
+ def initialize
114
+ @tries = 3 # Reasonable default
115
+ @interval = 60 # Reasonable default
116
+ @increment = nil # Should be an int, if provided
117
+ @timeout = nil # Wrap the code in a timeout block if provided
118
+ @level = Exception # Level of exception to be caught
119
+
120
+ yield self if block_given?
121
+ end
122
+
123
+ def attempt
124
+ count = 1
125
+ begin
126
+ if @timeout
127
+ Timeout.timeout(@timeout){ yield }
128
+ else
129
+ yield
130
+ end
131
+ rescue @level => error
132
+ @tries -= 1
133
+ if @tries > 0
134
+ msg = "Error on attempt # #{count}: #{error}; retrying"
135
+ count += 1
136
+ @interval += @increment if @increment
137
+ sleep @interval
138
+ retry
139
+ end
140
+ raise
141
+ end
142
+ end
143
+ end
144
+
145
+
74
146
  end
75
147
 
76
- require "ruport/attempt"
77
- require "ruport/config"
148
+ module Kernel
149
+
150
+ # quiets warnings for block
151
+ def quiet #:nodoc:
152
+ warnings = $VERBOSE
153
+ $VERBOSE = nil
154
+ result = yield
155
+ $VERBOSE = warnings
156
+ return result
157
+ end
158
+
159
+ end
160
+
161
+ require "enumerator"
162
+ require "ruport/renderer"
78
163
  require "ruport/data"
79
- require "ruport/report"
80
- require "ruport/format"
164
+ require "ruport/report"
165
+ require "ruport/formatter"
81
166
  require "ruport/query"
82
- require "ruport/mailer"
83
- require "ruport/layout"
84
- require "ruport/renderer"
167
+
168
+ if Object.const_defined? :ActiveRecord
169
+ require "ruport/acts_as_reportable"
170
+ end
@@ -0,0 +1,246 @@
1
+ require "ruport"
2
+ quiet { require "active_record" }
3
+
4
+ module Ruport
5
+
6
+ # This module is designed to be mixed in with an ActiveRecord model
7
+ # to add easy conversion to Ruport's data structures.
8
+ module Reportable
9
+
10
+ def self.included(base) # :nodoc:
11
+ base.extend ClassMethods
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ # In the ActiveRecord model you wish to integrate with Ruport, add the
17
+ # following line just below the class definition:
18
+ #
19
+ # acts_as_reportable
20
+ #
21
+ # This will automatically make all the methods in this module available
22
+ # in the model.
23
+ #
24
+ # You may pass the acts_as_reportable method the :only, :except,
25
+ # :methods, and :include options. See report_table for the format
26
+ # of these options.
27
+ #
28
+ def acts_as_reportable(options = {})
29
+ cattr_accessor :aar_options, :aar_columns
30
+
31
+ self.aar_options = options
32
+
33
+ include Ruport::Reportable::InstanceMethods
34
+ extend Ruport::Reportable::SingletonMethods
35
+ end
36
+ end
37
+
38
+ module SingletonMethods
39
+
40
+ # Creates a Ruport::Data::Table from an ActiveRecord find. Takes
41
+ # parameters just like a regular find. If you use the :include
42
+ # option, it will return a table with all columns from the model and
43
+ # the included associations. If you use the :only option, it will
44
+ # return a table with only the specified columns. If you use the
45
+ # :except option, it will return a table with all columns except
46
+ # those specified.
47
+ #
48
+ # Options may be passed to the :include option in order to specify
49
+ # the output for any associated models. In this case, the :include
50
+ # option must be a hash, where the keys are the names of the
51
+ # associations and the values are hashes of options.
52
+ #
53
+ # Use the :methods option to include a column with the same name as
54
+ # the method and the value resulting from calling the method on the
55
+ # model object.
56
+ #
57
+ # Any options passed to report_table will disable the options set by
58
+ # the acts_as_reportable class method.
59
+ #
60
+ # Example:
61
+ #
62
+ # class Book < ActiveRecord::Base
63
+ # belongs_to :author
64
+ # acts_as_reportable
65
+ # end
66
+ #
67
+ # Book.report_table(:all, :only => ['title'],
68
+ # :include => { :author => { :only => 'name' } }).as(:html)
69
+ #
70
+ # Returns: an html version of a report with two columns, title from
71
+ # the book, and name from the associated author.
72
+ #
73
+ # Calling Book.report_table(:all, :include => [:author]).as(:html) will
74
+ # return a table with all columns from books and authors.
75
+ #
76
+ # Note: column names for attributes of included models will be qualified
77
+ # with the model's underscored class name, e.g. 'author.name'
78
+ # By default, this will not preserve the entire namespace, but you
79
+ # can get the fully qualified namespace by using the
80
+ # :preserve_namespace => true option to report_table. So if the
81
+ # Author model was enclosed in a module called MyModule, you'd
82
+ # get 'my_module/author.name' as the column name.
83
+ #
84
+ def report_table(number = :all, options = {})
85
+ only = options.delete(:only)
86
+ except = options.delete(:except)
87
+ methods = options.delete(:methods)
88
+ includes = options.delete(:include)
89
+ preserve_namespace = options.delete(:preserve_namespace)
90
+ self.aar_columns = []
91
+
92
+ options[:include] = get_include_for_find(includes)
93
+
94
+ data = [find(number, options)].flatten
95
+ data = data.map {|r| r.reportable_data(:include => includes,
96
+ :only => only,
97
+ :except => except,
98
+ :methods => methods) }.flatten
99
+
100
+ table =
101
+ Ruport::Data::Table.new(:data => data, :column_names => aar_columns)
102
+ normalize_column_names(table) unless preserve_namespace
103
+ table
104
+ end
105
+
106
+ private
107
+
108
+ def get_include_for_find(report_option) #:nodoc:
109
+ includes = report_option.blank? ? aar_options[:include] : report_option
110
+ includes.is_a?(Hash) ? includes.keys : includes
111
+ end
112
+
113
+ def normalize_column_names(table) #:nodoc:
114
+ renamed = table.column_names.inject({}) do |s,c|
115
+ s.merge(c => c.sub(/.*\//,""))
116
+ end
117
+ table.rename_columns(renamed)
118
+ end
119
+ end
120
+
121
+ module InstanceMethods
122
+
123
+ # Grabs all of the object's attributes and the attributes of the
124
+ # associated objects and returns them as an array of record hashes.
125
+ #
126
+ # Associated object attributes are stored in the record with
127
+ # "association.attribute" keys.
128
+ #
129
+ # Passing :only as an option will only get those attributes.
130
+ # Passing :except as an option will exclude those attributes.
131
+ # Must pass :include as an option to access associations. Options
132
+ # may be passed to the included associations by providing the :include
133
+ # option as a hash.
134
+ # Passing :methods as an option will include any methods on the object.
135
+ #
136
+ # Example:
137
+ #
138
+ # class Book < ActiveRecord::Base
139
+ # belongs_to :author
140
+ # acts_as_reportable
141
+ # end
142
+ #
143
+ # abook.reportable_data(:only => ['title'], :include => [:author])
144
+ #
145
+ # Returns: [{'title' => 'book title',
146
+ # 'author.id' => 'author id',
147
+ # 'author.name' => 'author name' }]
148
+ #
149
+ # NOTE: title will only be returned if the value exists in the table.
150
+ # If the books table does not have a title column, it will not be
151
+ # returned.
152
+ #
153
+ # Example:
154
+ #
155
+ # abook.reportable_data(:only => ['title'],
156
+ # :include => { :author => { :only => ['name'] } })
157
+ #
158
+ # Returns: [{'title' => 'book title',
159
+ # 'author.name' => 'author name' }]
160
+ #
161
+ def reportable_data(options = {})
162
+ options = options.merge(self.class.aar_options) unless
163
+ has_report_options?(options)
164
+
165
+ data_records = [get_attributes_with_options(options)]
166
+ Array(options[:methods]).each do |method|
167
+ data_records.first[method.to_s] = send(method)
168
+ end
169
+
170
+ self.class.aar_columns |= data_records.first.keys
171
+
172
+ data_records =
173
+ add_includes(data_records, options[:include]) if options[:include]
174
+ data_records
175
+ end
176
+
177
+ private
178
+
179
+ # Add data for all included associations
180
+ #
181
+ def add_includes(data_records, includes)
182
+ include_has_options = includes.is_a?(Hash)
183
+ associations = include_has_options ? includes.keys : Array(includes)
184
+
185
+ associations.each do |association|
186
+ existing_records = data_records.dup
187
+ data_records = []
188
+
189
+ if include_has_options
190
+ assoc_options =
191
+ includes[association].merge({ :qualify_attribute_names => true })
192
+ else
193
+ assoc_options = { :qualify_attribute_names => true }
194
+ end
195
+
196
+ association_objects = [send(association)].flatten.compact
197
+
198
+ existing_records.each do |existing_record|
199
+ if association_objects.empty?
200
+ data_records << existing_record
201
+ else
202
+ association_objects.each do |obj|
203
+ association_records = obj.reportable_data(assoc_options)
204
+ association_records.each do |assoc_record|
205
+ data_records << existing_record.merge(assoc_record)
206
+ end
207
+ self.class.aar_columns |= data_records.last.keys
208
+ end
209
+ end
210
+ end
211
+ end
212
+ data_records
213
+ end
214
+
215
+ # Check if the options hash has any report options
216
+ # (:only, :except, :methods, or :include).
217
+ #
218
+ def has_report_options?(options)
219
+ options[:only] || options[:except] || options[:methods] ||
220
+ options[:include]
221
+ end
222
+
223
+ # Get the object's attributes using the supplied options.
224
+ #
225
+ # Use the :only or :except options to limit the attributes returned.
226
+ #
227
+ # Use the :qualify_attribute_names option to append the underscored
228
+ # model name to the attribute name as model.attribute
229
+ #
230
+ def get_attributes_with_options(options = {})
231
+ only_or_except =
232
+ if options[:only] or options[:except]
233
+ { :only => options[:only], :except => options[:except] }
234
+ end
235
+ attrs = attributes(only_or_except)
236
+ namespace = self.class.to_s.underscore
237
+ attrs = attrs.inject({}) {|h,(k,v)|
238
+ h["#{namespace}.#{k}"] = v; h
239
+ } if options[:qualify_attribute_names]
240
+ attrs
241
+ end
242
+ end
243
+ end
244
+ end
245
+
246
+ ActiveRecord::Base.send :include, Ruport::Reportable