dynamic_reports 0.0.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/HISTORY +2 -0
- data/README +146 -0
- data/gemspec.rb +24 -0
- data/lib/dynamic_reports.rb +47 -0
- data/lib/dynamic_reports/charts.rb +217 -0
- data/lib/dynamic_reports/reports.rb +252 -0
- data/lib/dynamic_reports/templates.rb +178 -0
- data/lib/dynamic_reports/vendor/google_chart.rb +11 -0
- data/lib/dynamic_reports/vendor/google_chart/bar_chart.rb +90 -0
- data/lib/dynamic_reports/vendor/google_chart/base.rb +539 -0
- data/lib/dynamic_reports/vendor/google_chart/financial_line_chart.rb +31 -0
- data/lib/dynamic_reports/vendor/google_chart/line_chart.rb +79 -0
- data/lib/dynamic_reports/vendor/google_chart/pie_chart.rb +33 -0
- data/lib/dynamic_reports/vendor/google_chart/scatter_chart.rb +38 -0
- data/lib/dynamic_reports/vendor/google_chart/venn_diagram.rb +36 -0
- data/lib/dynamic_reports/views.rb +30 -0
- data/lib/dynamic_reports/views/default_chart.html.erb +0 -0
- data/lib/dynamic_reports/views/default_layout.html.erb +1 -0
- data/lib/dynamic_reports/views/default_report.html.erb +73 -0
- data/lib/dynamic_reports/views/default_report.html.haml +62 -0
- metadata +74 -0
data/HISTORY
ADDED
data/README
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
= Dynamic Reports
|
2
|
+
|
3
|
+
A dynamic reporting engine for Ruby / Rails
|
4
|
+
|
5
|
+
== Reports
|
6
|
+
|
7
|
+
The dynamic reports gem was created to fill a HUGE hole that we felt existed in the
|
8
|
+
Ruby community - the ability to QUICKLY create stylized admin reports and charts for
|
9
|
+
people to use to view key metrics and data.
|
10
|
+
|
11
|
+
Sample uses include the ability to quickly display sales data if your an eShop, our
|
12
|
+
site metrics if you are recording your own site visits, or user feedback if you are storing
|
13
|
+
feedback in a model somewhere.
|
14
|
+
|
15
|
+
Basically, with DR you can create a stylized table of ANY information found in a model
|
16
|
+
(kind of like looking at the grid output from a GUI query analyzer) as well as add Google
|
17
|
+
Charts API powered line, pie, bar or column charts of any numeric data. All this can
|
18
|
+
be done by simply creating a report definition and feeding it your data.
|
19
|
+
|
20
|
+
While this library is usable in any Ruby application it was made mainly with Rails in mind.
|
21
|
+
Suppose we have an online store and we wish to add reporting to the admin area quickly and easily.
|
22
|
+
First we define a report in app/reports/orders_report.rb, something like:
|
23
|
+
|
24
|
+
class OrdersReport < DynamicReports::Report
|
25
|
+
columns :total, :created_at
|
26
|
+
end
|
27
|
+
|
28
|
+
Then in our admin/reports controller (this can be any controller) we define an action to deliver the report:
|
29
|
+
|
30
|
+
def orders
|
31
|
+
@orders = Order.find(:all, :limit => 25)
|
32
|
+
render :text => OrdersReport.on(@orders).to_html, :layout => "application"
|
33
|
+
end
|
34
|
+
|
35
|
+
This will render an html table containing some basic styling and containing the columns 'total' and 'created_at' from the order objects.
|
36
|
+
Note that the report Title will be "Orders Report" and it's name will be :orders_report
|
37
|
+
Report#on expects that it receives an object that responds to #each and
|
38
|
+
That each object that it iterates over is either a
|
39
|
+
* An object
|
40
|
+
* A Hash
|
41
|
+
that responds to a method / has keys for each column defined within the report.
|
42
|
+
|
43
|
+
|
44
|
+
Templating engines may also be specified, currently :erb and :haml are supported (we will soon be adding :csv and :pdf) like so:
|
45
|
+
|
46
|
+
render :text => OrdersReport.on(@orders).to_html(:engine => :haml), :layout => "application"
|
47
|
+
|
48
|
+
Note that erb is the default templating engine since it is available by default in Ruby.
|
49
|
+
|
50
|
+
One may also surpress the default rendered styles you may specify that as an option as well:
|
51
|
+
|
52
|
+
render :text => OrdersReport.on(@orders).to_html(:style => false), :layout => "application"
|
53
|
+
|
54
|
+
Now let us extend our report definition to specify a template to use!
|
55
|
+
|
56
|
+
class OrdersReport < DynamicReports::Report
|
57
|
+
columns :total, :created_at
|
58
|
+
template :orders_report
|
59
|
+
end
|
60
|
+
|
61
|
+
This will look in app/views/reports/ for a template named "orders_report.html.erb" by default.
|
62
|
+
If you specify :engine => :haml then it will look for "orders_report.html.haml"
|
63
|
+
|
64
|
+
If you happen to have your report templates in a different location you can specify this as follows:
|
65
|
+
|
66
|
+
class OrdersReport < DynamicReports::Report
|
67
|
+
columns :total, :created_at
|
68
|
+
template :orders_report
|
69
|
+
views "app/views/admin/reports/"
|
70
|
+
end
|
71
|
+
|
72
|
+
And DynamicReports will look for the specified template in app/views/reports as well as app/views/admin/reports.
|
73
|
+
|
74
|
+
== Charts
|
75
|
+
|
76
|
+
Charts can be defined on a report easily. Let's say we wish to chart the total versus the item quantity sold for our Orders Report exmaple:
|
77
|
+
|
78
|
+
class OrdersReport < DynamicReports::Report
|
79
|
+
columns :total, :created_at
|
80
|
+
|
81
|
+
chart :total_vs_quantity do
|
82
|
+
columns :total, :quantity
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
This will render a *line* chart by default displaying the columns total and quantity.
|
87
|
+
Chart types may be specified easily:
|
88
|
+
|
89
|
+
type :bar
|
90
|
+
|
91
|
+
Available chart types are:
|
92
|
+
|
93
|
+
* :line (default)
|
94
|
+
* :bar
|
95
|
+
* :pie
|
96
|
+
|
97
|
+
Other chart types are planned.
|
98
|
+
|
99
|
+
== Rails Usage
|
100
|
+
|
101
|
+
Inside the initializer block in config/environment.rb
|
102
|
+
|
103
|
+
config.gem "dynamic_reports"
|
104
|
+
|
105
|
+
Then define your reports (as exampled above) in app/reports/*_report.rb
|
106
|
+
If you would like to customize the default report simply create your report templates
|
107
|
+
within app/views/reports/*_report.<content-type>.<engine>.
|
108
|
+
|
109
|
+
Two Rails features that we are currently working on are:
|
110
|
+
|
111
|
+
* generator
|
112
|
+
* render extensions
|
113
|
+
|
114
|
+
== Optional Dependencies
|
115
|
+
|
116
|
+
We are currently examining solutions for csv, pdf and charting.
|
117
|
+
|
118
|
+
* Fastercsv # csv
|
119
|
+
* Prawn # pdf
|
120
|
+
* flying saucer # html => PDF - if jRuby available
|
121
|
+
* amcharts # Charting, note that default is built in google charts.
|
122
|
+
|
123
|
+
These will be defined/implemented using DynamicReports plugin API (not implemented yet)
|
124
|
+
Which allows for user defined plugins of arbitrary types beyond html,csv,pdf,xml
|
125
|
+
|
126
|
+
== Contact / Feedback
|
127
|
+
|
128
|
+
If you have any suggestions on improvement please send us an email.
|
129
|
+
|
130
|
+
== Authors (alphabetically)
|
131
|
+
|
132
|
+
Joshua Lippiner (jlippiner@gmail.com)
|
133
|
+
|
134
|
+
Wayne E. Seguin (wayneeseguin@gmail.com, irc: wayneeseguin)
|
135
|
+
|
136
|
+
== Thanks To
|
137
|
+
|
138
|
+
* Daniel Neighman
|
139
|
+
* Kenneth Kalmer (And his friend :))
|
140
|
+
* Yehuda Katz
|
141
|
+
|
142
|
+
For their encouragement, feedback and advise.
|
143
|
+
|
144
|
+
== Source
|
145
|
+
http://github.com/wayneeseguin/dynamic_reports
|
146
|
+
|
data/gemspec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
|
3
|
+
library="dynamic_reports"
|
4
|
+
version="0.0.0"
|
5
|
+
|
6
|
+
Gem::Specification::new do |spec|
|
7
|
+
$VERBOSE = nil
|
8
|
+
spec.name = library
|
9
|
+
spec.summary = library
|
10
|
+
spec.version = version
|
11
|
+
spec.description = "Dynamic Ruby Reporting Engine with support for Charts"
|
12
|
+
spec.platform = Gem::Platform::RUBY
|
13
|
+
spec.files = ["HISTORY", "README", "gemspec.rb", Dir::glob("lib/**/**")].flatten
|
14
|
+
spec.executables = Dir::glob("bin/*").map{ |script| File::basename script }
|
15
|
+
spec.require_path = "lib"
|
16
|
+
spec.has_rdoc = File::exist?("doc")
|
17
|
+
spec.author = "Wayne E. Seguin & Joshua Lippiner"
|
18
|
+
spec.email = "wayneeseguin@gmail.com, jlippiner@gmail.com"
|
19
|
+
spec.homepage = "http://github.com/wayneeseguin/direct_reports"
|
20
|
+
# spec.test_suite_file = "test/#{library}.rb" if File::directory?("test")
|
21
|
+
#spec.add_dependency "gchartrb", ">= 0.8"
|
22
|
+
spec.extensions << "extconf.rb" if File::exists?("extconf.rb")
|
23
|
+
spec.rubyforge_project = library
|
24
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# :include: README
|
2
|
+
|
3
|
+
# Library Files
|
4
|
+
module DynamicReports
|
5
|
+
@@default_view_paths = ["#{File::dirname(File::expand_path(__FILE__))}/dynamic_reports/views/"]
|
6
|
+
def self.default_view_paths
|
7
|
+
@@default_view_paths
|
8
|
+
end
|
9
|
+
def self.default_view_paths=(paths)
|
10
|
+
@@default_view_paths = paths
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
require "dynamic_reports/charts"
|
15
|
+
require "dynamic_reports/reports"
|
16
|
+
require "dynamic_reports/templates"
|
17
|
+
require "dynamic_reports/views"
|
18
|
+
require "dynamic_reports/vendor/google_chart"
|
19
|
+
|
20
|
+
|
21
|
+
# TODO: Figure out why this will not load:
|
22
|
+
# require "dynamic_reports/rails"
|
23
|
+
# For now placing the code right here:
|
24
|
+
if defined?(Rails)
|
25
|
+
# Load all defined reports.
|
26
|
+
# Question: How to get Rails to reload files other than ones matching the requested constant...
|
27
|
+
#Dir.glob("#{File.join(Rails.root, "app", "reports")}/*.rb").each { |file| require file }
|
28
|
+
ActiveSupport::Dependencies.load_paths << File.join(Rails.root, "app", "reports")
|
29
|
+
# Question: Should we allow for report directory nesting ?
|
30
|
+
|
31
|
+
# Set default views directory
|
32
|
+
# TODO: These should be added to Rails views directories?
|
33
|
+
DynamicReports.default_view_paths += [
|
34
|
+
File.join(Rails.root, "app", "views", "reports"),
|
35
|
+
File.join(Rails.root, "app", "views", "layouts")
|
36
|
+
]
|
37
|
+
|
38
|
+
# TODO: Render extension
|
39
|
+
|
40
|
+
# TODO: AR extensions:
|
41
|
+
# has_report :daily, :columns => [...], :options => {...}, :title => ...
|
42
|
+
# Which will generate a report object DailyReport with the given definition.
|
43
|
+
# Everything after the name corresponds to options available in the configuration block.
|
44
|
+
|
45
|
+
# TODO: Generator
|
46
|
+
end
|
47
|
+
|
@@ -0,0 +1,217 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module DynamicReports
|
4
|
+
class Chart
|
5
|
+
def self.configure(name, *chart_options, &block)
|
6
|
+
chart_options = chart_options.shift || {}
|
7
|
+
chart = new(chart_options)
|
8
|
+
chart.instance_eval(&block)
|
9
|
+
chart.name name
|
10
|
+
chart
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*chart_options)
|
14
|
+
chart_options = chart_options.shift || {}
|
15
|
+
options.merge!(chart_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def options
|
19
|
+
@options ||= {} ; @options
|
20
|
+
end
|
21
|
+
|
22
|
+
# (Optional) Accessor used to set the chart title:
|
23
|
+
#
|
24
|
+
# title "Pageviews versus Visits"
|
25
|
+
#
|
26
|
+
# This is displayed above the chart
|
27
|
+
def title(value = nil)
|
28
|
+
value ? options[:title] = value : options[:title]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Accessor used to set or get a specific report. Must be unique:
|
32
|
+
#
|
33
|
+
# name "PV_Visits"
|
34
|
+
#
|
35
|
+
def name(value = nil)
|
36
|
+
value ? options[:name] = value.to_sym : options[:name]
|
37
|
+
end
|
38
|
+
|
39
|
+
# (Optional) Accessor used by bar and pie charts to determine the
|
40
|
+
# independent varible chart labels. Due to size constraints
|
41
|
+
# this is NOT used by column or line charts, but you can add
|
42
|
+
# labels to the X-axis for those charts via chart_options and
|
43
|
+
# passing Google Chart API calls."
|
44
|
+
#
|
45
|
+
# label_column should be a SINGLE column name from the dataset.
|
46
|
+
#
|
47
|
+
# label_column "recorded_at"
|
48
|
+
#
|
49
|
+
def label_column(value = nil)
|
50
|
+
value ? options[:label_column] = value : options[:label_column]
|
51
|
+
end
|
52
|
+
|
53
|
+
# (Optional - Default = line). Accessor used to determine the
|
54
|
+
# type of chart to display. Valid options are line, column, bar
|
55
|
+
# or pie:
|
56
|
+
#
|
57
|
+
# type "bar"
|
58
|
+
#
|
59
|
+
def type(value = nil)
|
60
|
+
value ? options[:type] = value : (options[:type] || :line)
|
61
|
+
end
|
62
|
+
|
63
|
+
# (Optional - Default = 250). Accessor used to set the width, in pixels,
|
64
|
+
# of the chart.
|
65
|
+
#
|
66
|
+
# width "400"
|
67
|
+
#
|
68
|
+
def width(value = nil)
|
69
|
+
value ? options[:width] = value : (options[:width] || "250")
|
70
|
+
end
|
71
|
+
|
72
|
+
# (Optional - Default = 175). Accessor used to set the height, in pixels,
|
73
|
+
# of the chart.
|
74
|
+
#
|
75
|
+
# height "350"
|
76
|
+
#
|
77
|
+
def height(value = nil)
|
78
|
+
value ? options[:height] = value : (options[:height] || "175")
|
79
|
+
end
|
80
|
+
|
81
|
+
# (Optional - Default = false). Accessor used to determine if axis labels
|
82
|
+
# should be shown. By default y-axis labels are shown.
|
83
|
+
#
|
84
|
+
# no_labels true
|
85
|
+
#
|
86
|
+
def no_labels(value = nil)
|
87
|
+
value ? options[:no_labels] = value : options[:no_labels]
|
88
|
+
end
|
89
|
+
|
90
|
+
# (Optional) Accessor for columns
|
91
|
+
#
|
92
|
+
# Pass an array of symbols to define columns to chart. Columns MUST
|
93
|
+
# be numeric in value to chart.
|
94
|
+
#
|
95
|
+
# Example:
|
96
|
+
#
|
97
|
+
# columns :pageviews, :visits
|
98
|
+
#
|
99
|
+
# You may leave this accessor blank to default to the report columns
|
100
|
+
# specified that are numeric.
|
101
|
+
#
|
102
|
+
def columns(*array)
|
103
|
+
unless array.empty?
|
104
|
+
if (array.class == Array)
|
105
|
+
options[:columns] = array
|
106
|
+
else
|
107
|
+
raise "Report columns must be specified."
|
108
|
+
end
|
109
|
+
else
|
110
|
+
options[:columns]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# DynamicReports::Charts
|
116
|
+
#
|
117
|
+
# Class used to display different chart types internally. Charts are generated
|
118
|
+
# using Google Charts API
|
119
|
+
#
|
120
|
+
class Charts
|
121
|
+
class << self
|
122
|
+
# Method to select a random color from a list of hex codes
|
123
|
+
#
|
124
|
+
# Example: random_color()
|
125
|
+
# => "ff0000"
|
126
|
+
def random_color()
|
127
|
+
color_list = %w{000000 0000ff ff0000 ffff00 00ffff ff00ff 00ff00}
|
128
|
+
return color_list[rand(color_list.size)]
|
129
|
+
end
|
130
|
+
|
131
|
+
# Method to display a line chart for a given chart definition and report
|
132
|
+
def line_chart(chart, columns, report)
|
133
|
+
::GoogleChart::LineChart.new("#{chart.width}x#{chart.height}", chart.title, false) do |c|
|
134
|
+
all_data = []
|
135
|
+
columns.each do |column|
|
136
|
+
data = []
|
137
|
+
report.records.each do |record|
|
138
|
+
if record.is_a?(Hash)
|
139
|
+
data << record[column] if record[column].is_a?(Numeric)
|
140
|
+
elsif record.respond_to?(column.to_sym)
|
141
|
+
data << record.send(column.to_sym) if record.send(column.to_sym).is_a?(Numeric)
|
142
|
+
else
|
143
|
+
data << column if column.is_a?(Numeric)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
c.data column, data, random_color() unless data.empty?
|
147
|
+
all_data << data
|
148
|
+
end
|
149
|
+
all_data.flatten!
|
150
|
+
c.axis :y, :range => [all_data.min,all_data.max], :color => 'ff00ff' unless chart.no_labels
|
151
|
+
c.show_legend = columns.size > 1
|
152
|
+
|
153
|
+
return c.to_url(chart.options)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Method to display a pie chart for a given chart definition and report
|
158
|
+
def pie_chart(chart, columns, report)
|
159
|
+
return if columns.size > 1 || chart.label_column.nil?
|
160
|
+
column = columns.first.to_s
|
161
|
+
|
162
|
+
::GoogleChart::PieChart.new("#{chart.width}x#{chart.height}", chart.title, false) do |c|
|
163
|
+
report.records.each do |record|
|
164
|
+
if record.is_a?(Hash)
|
165
|
+
c.data record[chart.label_column.to_s], record[column] if record[column].is_a?(Numeric)
|
166
|
+
elsif record.respond_to?(column.to_sym)
|
167
|
+
c.data record.send(chart.label_column.to_s), record.send(column.to_sym) if record.send(column.to_sym).is_a?(Numeric)
|
168
|
+
else
|
169
|
+
c.data chart.label_column.to_s, column if column.is_a?(Numeric)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
c.show_legend = false
|
173
|
+
c.show_labels = true
|
174
|
+
|
175
|
+
return c.to_url(chart.options)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Method to display a bar or column chart for a given chart definition and report
|
180
|
+
def bar_column_chart(chart, columns, report, orientation)
|
181
|
+
::GoogleChart::BarChart.new("#{chart.width}x#{chart.height}", chart.title, orientation, true) do |c|
|
182
|
+
all_data = []
|
183
|
+
all_labels = []
|
184
|
+
columns.each do |column|
|
185
|
+
data = []
|
186
|
+
report.records.each do |record|
|
187
|
+
if record.is_a?(Hash)
|
188
|
+
data << record[column] if record[column].is_a?(Numeric)
|
189
|
+
all_labels << record[chart.label_column.to_s] if chart.label_column
|
190
|
+
elsif record.respond_to?(column.to_sym)
|
191
|
+
data << record.send(column.to_sym) if record.send(column.to_sym).is_a?(Numeric)
|
192
|
+
all_labels << record.send(chart.label_column.to_s) if chart.label_column
|
193
|
+
else
|
194
|
+
data << column if column.is_a?(Numeric)
|
195
|
+
all_labels << chart.label_column.to_s if chart.label_column
|
196
|
+
end
|
197
|
+
end
|
198
|
+
c.data column, data, random_color() unless data.empty?
|
199
|
+
all_data << data
|
200
|
+
end
|
201
|
+
all_data.flatten!
|
202
|
+
|
203
|
+
if(orientation==:vertical)
|
204
|
+
c.axis :y, :range => [all_data.min,all_data.max], :color => 'ff00ff' unless chart.no_labels
|
205
|
+
else
|
206
|
+
c.axis :x, :range => [all_data.min,all_data.max], :color => 'ff00ff' unless chart.no_labels
|
207
|
+
c.axis :y, :labels => all_labels
|
208
|
+
end
|
209
|
+
|
210
|
+
c.show_legend = columns.size > 1
|
211
|
+
|
212
|
+
return c.to_url(chart.options)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,252 @@
|
|
1
|
+
# DynamicReports
|
2
|
+
#
|
3
|
+
# Dynamic Reporting Engine for Ruby / Rails
|
4
|
+
module DynamicReports
|
5
|
+
|
6
|
+
# DynamicReports::Report
|
7
|
+
#
|
8
|
+
class Report
|
9
|
+
@@default_engine = "erb"
|
10
|
+
|
11
|
+
attr_accessor :name, :title, :sub_title, :columns, :charts, :records, :template, :style_name, :styles
|
12
|
+
|
13
|
+
# views accessor, array of view paths.
|
14
|
+
def views
|
15
|
+
@views
|
16
|
+
end
|
17
|
+
|
18
|
+
# options accessor, all report options and configuration is stored in this.
|
19
|
+
def options
|
20
|
+
@options
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
# Views setter and accessor.
|
25
|
+
def views(*array)
|
26
|
+
@views ||= ["#{File::dirname(File::expand_path(__FILE__))}/views/"]
|
27
|
+
array ? @views += array : @views
|
28
|
+
end
|
29
|
+
|
30
|
+
# class level options accessor
|
31
|
+
def options
|
32
|
+
@options ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
# class level name accessor & setter
|
36
|
+
#
|
37
|
+
# Set the name of the report, for example:
|
38
|
+
#
|
39
|
+
# name "Orders Report"
|
40
|
+
#
|
41
|
+
def name(value = nil)
|
42
|
+
if value
|
43
|
+
options[:name] = value
|
44
|
+
else
|
45
|
+
# TODO: snake_case_the_name
|
46
|
+
options[:name] || self.class.name
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Accessor used to set the title:
|
51
|
+
#
|
52
|
+
# title "All orders for the account"
|
53
|
+
#
|
54
|
+
# Or to access the already set title:
|
55
|
+
#
|
56
|
+
# OrdersReport.title
|
57
|
+
#
|
58
|
+
# #=> "All orders for the account"
|
59
|
+
#
|
60
|
+
def title(value = nil)
|
61
|
+
value ? options[:title] = value : options[:title]
|
62
|
+
end
|
63
|
+
|
64
|
+
# Accessor used to set the sub title:
|
65
|
+
#
|
66
|
+
# sub_title "All orders for the account"
|
67
|
+
#
|
68
|
+
# Or to access the already set sub title:
|
69
|
+
#
|
70
|
+
# OrdersReport.sub_title
|
71
|
+
#
|
72
|
+
# #=> "All orders for the account"
|
73
|
+
#
|
74
|
+
def sub_title(value = nil)
|
75
|
+
value ? options[:sub_title] = value : options[:sub_title]
|
76
|
+
end
|
77
|
+
|
78
|
+
def styles
|
79
|
+
options[:styles] ||= false
|
80
|
+
end
|
81
|
+
|
82
|
+
def style_name(value = nil)
|
83
|
+
if value
|
84
|
+
options[:style_name] = value
|
85
|
+
elsif options[:style_name].empty?
|
86
|
+
options[:style_name] = self.to_s
|
87
|
+
else
|
88
|
+
options[:style_name]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Accessor for the template to use for the report.
|
93
|
+
#
|
94
|
+
# Example:
|
95
|
+
#
|
96
|
+
# template :orders # => renders orders.html.erb (erb is the default engine)
|
97
|
+
#
|
98
|
+
# Used without argument returns the template set for the report.
|
99
|
+
#
|
100
|
+
# OrdersReport.template # => :orders
|
101
|
+
#
|
102
|
+
def template(value = nil)
|
103
|
+
value ? options[:template] = value : options[:template]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Accessor for columns
|
107
|
+
#
|
108
|
+
# Pass an array of symbols to define columns on the report
|
109
|
+
#
|
110
|
+
# Example:
|
111
|
+
#
|
112
|
+
# columns :total, :created_at
|
113
|
+
#
|
114
|
+
# Calling the class method with no arguments will return an array with the defined columns.
|
115
|
+
#
|
116
|
+
# Example:
|
117
|
+
#
|
118
|
+
# OrdersReport.columns
|
119
|
+
#
|
120
|
+
# # => [:total, :created_at]
|
121
|
+
#
|
122
|
+
def columns(*array)
|
123
|
+
unless array.empty?
|
124
|
+
if (array.class == Array)
|
125
|
+
options[:columns] = array
|
126
|
+
else
|
127
|
+
raise "Report columns must be specified."
|
128
|
+
end
|
129
|
+
else
|
130
|
+
options[:columns]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return the chart with the specified name, if it exists. nil otherwise.
|
135
|
+
def chart_with_name(name)
|
136
|
+
options[:charts].to_a.detect{ |c| c.name == name.to_sym }
|
137
|
+
end
|
138
|
+
|
139
|
+
# Return an array of charts defined for the report.
|
140
|
+
def charts(object=nil)
|
141
|
+
options[:charts] ||= []
|
142
|
+
options[:charts] << object if object
|
143
|
+
options[:charts]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Define a chart for the report
|
147
|
+
#
|
148
|
+
# Example:
|
149
|
+
# chart :PV_Visits, {:grxl => 'xxxxx'} do
|
150
|
+
# title "Pageviews and Visits"
|
151
|
+
# columns [:pageviews, :visits]
|
152
|
+
# no_labels false
|
153
|
+
# label_column "recorded_at"
|
154
|
+
# width "400"
|
155
|
+
# height "350"
|
156
|
+
# type "line"
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
def chart(name, *chart_options, &block)
|
160
|
+
chart_options = chart_options.shift || {}
|
161
|
+
charts(Chart.configure(name, chart_options, &block))
|
162
|
+
end
|
163
|
+
|
164
|
+
# Method for instanciating a report instance on a set of given records.
|
165
|
+
#
|
166
|
+
# Example:
|
167
|
+
#
|
168
|
+
# OrdersReport.on(@records)
|
169
|
+
#
|
170
|
+
# Where @records is an array of
|
171
|
+
#
|
172
|
+
# * Objects that respond to methods for all columns defined on the report
|
173
|
+
# * Hashes that have keys corresponding to all columns defined on the report
|
174
|
+
#
|
175
|
+
# This will return an instance of the OrdersReport bound to @records
|
176
|
+
#
|
177
|
+
def on(records)
|
178
|
+
new(records, @options)
|
179
|
+
end
|
180
|
+
|
181
|
+
#--
|
182
|
+
# Methods for definining a sub report
|
183
|
+
#def link_column
|
184
|
+
#end
|
185
|
+
#def link_rows
|
186
|
+
#end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
# Instantiate the report on a set of records.
|
191
|
+
#
|
192
|
+
# Example:
|
193
|
+
#
|
194
|
+
# OrdersReport.new(@records)
|
195
|
+
#
|
196
|
+
# options is a set of optional overrides for
|
197
|
+
#
|
198
|
+
# * views - Used to override the class defined views.
|
199
|
+
# * template - Used to override the class defined template.
|
200
|
+
#
|
201
|
+
def initialize(records, *new_options)
|
202
|
+
new_options = new_options.shift || {}
|
203
|
+
@records = records
|
204
|
+
@views = []
|
205
|
+
@views << new_options.delete(:views) if new_options[:views]
|
206
|
+
@views += self.class.views
|
207
|
+
@template = new_options.delete(:template) if new_options[:template]
|
208
|
+
@options = self.class.options.merge!(new_options)
|
209
|
+
@options.each_pair do |key,value|
|
210
|
+
if key == "chart"
|
211
|
+
# TODO: erh?
|
212
|
+
self.chart(value[:name],{},value)
|
213
|
+
else
|
214
|
+
instance_variable_set("@#{key}".to_sym, value)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Convert an instance of a report bound to a records set to an html view
|
220
|
+
#
|
221
|
+
# Example
|
222
|
+
#
|
223
|
+
# OrdersReport.on(@records).to_html
|
224
|
+
#
|
225
|
+
# [options]
|
226
|
+
# :engine - one of :erb, :haml, :csv, :pdf
|
227
|
+
#
|
228
|
+
# Note: CSV & PDF forthcoming.
|
229
|
+
#
|
230
|
+
def to_html(*options)
|
231
|
+
view = View.new(self)
|
232
|
+
# todo: this is not clean...
|
233
|
+
options = (options.shift || {}).merge!(@options || {})
|
234
|
+
# todo: if rails is loaded set the default engine: dynamicreports::report.default_engine
|
235
|
+
engine = options.delete(:engine) || @@default_engine
|
236
|
+
view.__send__("#{engine}", options)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Not Implemented Yet
|
240
|
+
def to_csv
|
241
|
+
# TODO: Write csv hook
|
242
|
+
end
|
243
|
+
|
244
|
+
# Not Implemented Yet
|
245
|
+
def to_pdf
|
246
|
+
# TODO: Write pdf hook
|
247
|
+
end
|
248
|
+
|
249
|
+
end
|
250
|
+
|
251
|
+
end
|
252
|
+
|