make_exportable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +187 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/lib/make_exportable.rb +19 -0
- data/lib/make_exportable/core.rb +309 -0
- data/lib/make_exportable/errors.rb +7 -0
- data/lib/make_exportable/exportable_format.rb +33 -0
- data/lib/make_exportable/exportable_formats/csv.rb +34 -0
- data/lib/make_exportable/exportable_formats/excel.rb +39 -0
- data/lib/make_exportable/exportable_formats/html.rb +39 -0
- data/lib/make_exportable/exportable_formats/json.rb +36 -0
- data/lib/make_exportable/exportable_formats/tsv.rb +34 -0
- data/lib/make_exportable/exportable_formats/xml.rb +43 -0
- data/lib/make_exportable/make_exportable_helper.rb +27 -0
- data/lib/make_exportable/version.rb +9 -0
- data/rails/init.rb +1 -0
- data/spec/database.yml +10 -0
- data/spec/database.yml.sample +10 -0
- data/spec/make_exportable/formats_spec.rb +81 -0
- data/spec/make_exportable/make_exportable_helper_spec.rb +31 -0
- data/spec/make_exportable/make_exportable_spec.rb +510 -0
- data/spec/models.rb +44 -0
- data/spec/schema.rb +16 -0
- data/spec/spec_helper.rb +55 -0
- metadata +98 -0
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Nova Fabrica, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,187 @@
|
|
1
|
+
=MakeExportable
|
2
|
+
|
3
|
+
MakeExportable is a Rails gem/plugin to assist in exporting application data in a variety of formats. Filter and limit the data exported using ActiveRecord. Export returned values from instance methods as easily as database columns.
|
4
|
+
|
5
|
+
|
6
|
+
==Supported Formats
|
7
|
+
|
8
|
+
* CSV: Comma-separated values
|
9
|
+
* TSV: Tab-separated values
|
10
|
+
* XLS: Excel Spreadsheet
|
11
|
+
* JSON: JavaScript Object Notation
|
12
|
+
* XML: Extensible markup language
|
13
|
+
* HTML: Hypertext markup language
|
14
|
+
|
15
|
+
|
16
|
+
==Installation
|
17
|
+
|
18
|
+
* gem - sudo gem install make_exportable
|
19
|
+
* plugin - script/plugin install git@github.com:novafabrica/make_exportable.git
|
20
|
+
|
21
|
+
|
22
|
+
==Basic Usage
|
23
|
+
|
24
|
+
To start using MakeExportable simply add a call to <em>make_exportable</em> in any class you want to use for exporting data.
|
25
|
+
|
26
|
+
class Customer < ActiveRecord::Base
|
27
|
+
make_exportable
|
28
|
+
end
|
29
|
+
|
30
|
+
To export data you simply call the class method <em>to_export</em> and specify your desired format (see supported formats above).
|
31
|
+
|
32
|
+
Customer.to_export("csv")
|
33
|
+
|
34
|
+
You can select the columns you want to return using the :only and :except options. The default is to export all columns.
|
35
|
+
|
36
|
+
Customer.to_export("csv", :only => [:first_name, :last_name, :email])
|
37
|
+
Customer.to_export("csv", :except => [:hashed_password, :salt])
|
38
|
+
|
39
|
+
You can change the result set by passing in an array of :scopes to call, you can pass in finder options (such as :conditions, :order, :limit, :offset, etc.), or you can call <em>to_export</em> on a class that has already been scoped using named scopes.
|
40
|
+
|
41
|
+
Customer.to_export(:xls, :scopes => ["approved", "sorted"])
|
42
|
+
Customer.to_export(:xls, :conditions => {:approved => true}, :order => "first_name ASC", :limit => 50)
|
43
|
+
Customer.visible.where(:approved => true).to_export(:xls)
|
44
|
+
|
45
|
+
<em>to_export</em> returns an array of the data in the specified format and the corresponding mime-type. This is done to make sending files easy.
|
46
|
+
|
47
|
+
["First Name,Last Name,Email\nJohn,Doe,x@x.com\nJoe,Smith,y@y.com\n", "text/csv; charset=utf-8; header=present"]
|
48
|
+
|
49
|
+
Then in a controller, you can use the <em>send_data</em> method to send the export as a downloadable file to the user's browser.
|
50
|
+
|
51
|
+
class CustomerController < ApplicationController
|
52
|
+
|
53
|
+
def export
|
54
|
+
# Export the data
|
55
|
+
options = {:only => [:first_name, :last_name, :city, :country, :email]}
|
56
|
+
export_data, data_type = Customer.visible.to_export('csv', options)
|
57
|
+
|
58
|
+
# Send data to user as file
|
59
|
+
send_data(export_data, { :type => data_type, :disposition => 'attachment', :filename => "customer_export.csv" })
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
==Attributes and Methods
|
66
|
+
|
67
|
+
MakeExportable doesn't just export attributes that have database columns. It can also export data returned from methods.
|
68
|
+
|
69
|
+
class Customer < ActiveRecord::Base
|
70
|
+
make_exportable
|
71
|
+
|
72
|
+
def full_name
|
73
|
+
"#{first_name} #{last_name}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def last_purchase
|
77
|
+
last_order = orders.order('created_at ASC').last
|
78
|
+
return last_order ? last_order.created_at : ''
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
Customer.to_export("csv", :only => [:full_name, :email, :last_purchase])
|
83
|
+
|
84
|
+
If you want an attribute to be handled differently whenever it is exported, you can define a method with the syntax <em>#{attribute}_export</em> which will be called when exporting instead of the regular attribute.
|
85
|
+
|
86
|
+
class Customer < ActiveRecord::Base
|
87
|
+
make_exportable
|
88
|
+
|
89
|
+
def visible_export
|
90
|
+
visible ? 'Visible' : 'Not Visible'
|
91
|
+
end
|
92
|
+
|
93
|
+
def updated_at_export
|
94
|
+
updated_at.to_s(:long)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
==Setting Export Defaults
|
100
|
+
|
101
|
+
If you have a general columns, scopes, and conditions you will be calling in multiple methods you can attach them to the <em>make_exportable</em> method as defaults when including it into your class.
|
102
|
+
|
103
|
+
* :only and :except - specify columns or methods to export (defaults to all columns)
|
104
|
+
* :as - specify formats to allow for exporting (defaults to all formats)
|
105
|
+
* :scopes - specify scopes to be called on the class before exporting
|
106
|
+
|
107
|
+
These are defaults which can still be overridden when you perform an export.
|
108
|
+
|
109
|
+
class Customer < ActiveRecord::Base
|
110
|
+
make_exportable :as => [:csv, :tsv], :only => [:first_name, :last_name, :email], :scopes => ['visible', 'recent']
|
111
|
+
end
|
112
|
+
|
113
|
+
class User < ActiveRecord::Base
|
114
|
+
make_exportable :except => [:hashed_password, :salt]
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
==Magic Methods
|
119
|
+
|
120
|
+
MakeExportable also allows you to export to a format using a dynamic name. Each export format gets two "magic methods".
|
121
|
+
|
122
|
+
to_#{format}_export
|
123
|
+
create_#{format}_report
|
124
|
+
|
125
|
+
In both cases "format" represents the lowercase abbreviation for the export format (e.g. "to_csv_export", "create_csv_report"). Then the options hash becomes the first argument instead of the second.
|
126
|
+
|
127
|
+
Customer.to_csv_export(:conditions => {:visible => true}, :order => "last_name ASC")
|
128
|
+
Customer.visible.to_csv_export(:only => [:username, :email])
|
129
|
+
|
130
|
+
|
131
|
+
==Reports From Other Data
|
132
|
+
|
133
|
+
If you just have some data you want to export in the right format, MakeExportable exposes the <em>create_report</em> method to use your own data set.
|
134
|
+
|
135
|
+
Customer.create_report("csv", [row1_array, row2_array, row3_array], {:headers => headers_array})
|
136
|
+
|
137
|
+
Just pass in the format and an ordered array of rows for the data set. You can also pass in an array of headers as :headers in the options hash. Remember the row size and the header size need to be the same.
|
138
|
+
|
139
|
+
|
140
|
+
==Which Classes and Formats Can Be Exported
|
141
|
+
|
142
|
+
MakeExportable keeps a hash of classes that have been enabled as being exportable. The keys of this hash provide an easy reference if you need to know which classes are supported. You can also query a class directly using <em>exportable?</em>.
|
143
|
+
|
144
|
+
MakeExportable.exportable_classes
|
145
|
+
# => {"Customer" => Customer, "Product" => Product, "Order" => Order}
|
146
|
+
MakeExportable.exportable_classes.keys
|
147
|
+
# => ["Customer", "Product", "Order"]
|
148
|
+
MakeExportable.exportable_classes.include?("Customer")
|
149
|
+
# => true
|
150
|
+
Customer.exportable?
|
151
|
+
# => true
|
152
|
+
LineItem.exportable?
|
153
|
+
# => false
|
154
|
+
|
155
|
+
Note that this list will only include classes which have been loaded. In production mode that will be all classes, but development mode lazy-loads classes as they are needed. If you need a full list, you can ask Rails to load all classes so they will all "register" themselves with MakeExportable.
|
156
|
+
|
157
|
+
if Rails.env == 'development'
|
158
|
+
Rails::Initializer.run(:load_application_classes)
|
159
|
+
end
|
160
|
+
|
161
|
+
MakeExportable also maintains a hash of the available export formats. The keys of this hash are an array of symbols for all supported formats.
|
162
|
+
|
163
|
+
MakeExportable.exportable_formats
|
164
|
+
# => { :csv => MakeExportable::CSV, :xls => MakeExportable::Excel, :html => MakeExportable::HTML, :json => MakeExportable::JSON, :tsv => MakeExportable::TSV, :xml => MakeExportable::XML }
|
165
|
+
MakeExportable.exportable_formats.keys
|
166
|
+
# => [:csv, :xls, :html, :json, :tsv, :xml]
|
167
|
+
|
168
|
+
|
169
|
+
==Info
|
170
|
+
|
171
|
+
Author: Kevin Skoglund & Matthew Bergman, Nova Fabrica, Inc.
|
172
|
+
|
173
|
+
License: Copyright 2010 by Kevin Skoglund. released under the attached MIT-LICENSE.
|
174
|
+
|
175
|
+
GitHub: http://github.com/novafabrica/make_exportable/tree/master
|
176
|
+
|
177
|
+
|
178
|
+
==Bug Reports and Feedback
|
179
|
+
|
180
|
+
Bug reports should be submitted at https://github.com/novafabrica/make_exportable/issues
|
181
|
+
|
182
|
+
Other feedback is welcomed at info@novafabrica.com
|
183
|
+
|
184
|
+
|
185
|
+
==Warranty
|
186
|
+
|
187
|
+
This software is provided "as is" and without any express or implied warranties, including, without limitation, the implied warranties of merchantability and fitness for a particular purpose.
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
begin
|
2
|
+
# Rspec 1.3.0
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
desc 'Default: run specs'
|
5
|
+
task :default => :spec
|
6
|
+
Spec::Rake::SpecTask.new do |t|
|
7
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
8
|
+
end
|
9
|
+
|
10
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
11
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
12
|
+
t.rcov = true
|
13
|
+
t.rcov_opts = ['--exclude', 'spec']
|
14
|
+
end
|
15
|
+
|
16
|
+
rescue LoadError
|
17
|
+
# Rspec 2.0
|
18
|
+
require 'rspec/core/rake_task'
|
19
|
+
|
20
|
+
desc 'Default: run specs'
|
21
|
+
task :default => :spec
|
22
|
+
Rspec::Core::RakeTask.new do |t|
|
23
|
+
t.pattern = "spec/**/*_spec.rb"
|
24
|
+
end
|
25
|
+
|
26
|
+
Rspec::Core::RakeTask.new('rcov') do |t|
|
27
|
+
t.pattern = "spec/**/*_spec.rb"
|
28
|
+
t.rcov = true
|
29
|
+
t.rcov_opts = ['--exclude', 'spec']
|
30
|
+
end
|
31
|
+
|
32
|
+
rescue LoadError
|
33
|
+
puts "Rspec not available. Install it with: gem install rspec"
|
34
|
+
end
|
35
|
+
|
36
|
+
begin
|
37
|
+
require 'jeweler'
|
38
|
+
Jeweler::Tasks.new do |gemspec|
|
39
|
+
gemspec.name = "make_exportable"
|
40
|
+
gemspec.summary = "Makes any Rails model easily exportable"
|
41
|
+
gemspec.description = "MakeExportable is a Rails gem/plugin to assist in exporting application data as CSV, TSV, JSON, HTML, XML or Excel. Filter and limit the data exported using ActiveRecord. Export returned values from instance methods as easily as database columns."
|
42
|
+
gemspec.email = "kevin@novafabrica.com"
|
43
|
+
gemspec.homepage = "http://github.com/novafabrica/make_exportable"
|
44
|
+
gemspec.authors = ["Kevin Skoglund", "Matthew Bergman"]
|
45
|
+
gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
|
46
|
+
end
|
47
|
+
Jeweler::GemcutterTasks.new
|
48
|
+
rescue LoadError
|
49
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "active_record"
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'make_exportable'))
|
4
|
+
|
5
|
+
require 'core'
|
6
|
+
require 'errors'
|
7
|
+
require 'exportable_format'
|
8
|
+
require 'make_exportable_helper'
|
9
|
+
|
10
|
+
Dir.foreach(File.join(File.dirname(__FILE__), 'make_exportable', 'exportable_formats')) do |file|
|
11
|
+
next unless File.extname(file) == '.rb'
|
12
|
+
require File.join('exportable_formats', File.basename(file, '.rb'))
|
13
|
+
end
|
14
|
+
|
15
|
+
$LOAD_PATH.shift
|
16
|
+
|
17
|
+
if defined?(ActiveRecord::Base)
|
18
|
+
ActiveRecord::Base.send :include, MakeExportable
|
19
|
+
end
|
@@ -0,0 +1,309 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
|
3
|
+
# Inventory of the exportable classes
|
4
|
+
mattr_accessor :exportable_classes
|
5
|
+
@@exportable_classes = {}
|
6
|
+
|
7
|
+
# Inventory of the exportable formats
|
8
|
+
mattr_accessor :exportable_formats
|
9
|
+
@@exportable_formats = {}
|
10
|
+
|
11
|
+
def self.included(target)
|
12
|
+
target.extend(ActiveRecordBaseMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
module ActiveRecordBaseMethods
|
16
|
+
|
17
|
+
# <tt>exportable?</tt> returns false for all ActiveRecord classes
|
18
|
+
# until <tt>make_exportable</tt> has been called on them.
|
19
|
+
def exportable?(format=nil)
|
20
|
+
return false
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
# <tt>make_exportable</tt> is an ActiveRecord method that, when called, add
|
26
|
+
# methods to a particular class to make exporting data from that class easier.
|
27
|
+
#
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# class Customer < ActiveRecord::Base
|
31
|
+
# make_exportable
|
32
|
+
# end
|
33
|
+
# Customer.to_export(:csv)
|
34
|
+
#
|
35
|
+
# An optional hash of options can be passed as an argument to establish the default
|
36
|
+
# export parameters.
|
37
|
+
#
|
38
|
+
# These options include:
|
39
|
+
# * :only and :except - specify columns or methods to export (defaults to all columns)
|
40
|
+
# * :as - specify formats to allow for exporting (defaults to all formats)
|
41
|
+
# * :scopes - specify scopes to be called on the class before exporting
|
42
|
+
# * find options - for Rails 2.3 and earlier compatibility, standard find options
|
43
|
+
# are supported (:conditions, :order, :limit, :offset, etc.). These will be deprecated
|
44
|
+
# and removed in future versions.
|
45
|
+
#
|
46
|
+
# Examples:
|
47
|
+
#
|
48
|
+
# class Customer < ActiveRecord::Base
|
49
|
+
# make_exportable :only => [:id, :username, :full_name]
|
50
|
+
# end
|
51
|
+
#
|
52
|
+
# class Customer < ActiveRecord::Base
|
53
|
+
# make_exportable :except => [:id, :password], :scopes => [:new_signups, :with_referals],
|
54
|
+
# :as => [:csv, :tsv, :xls]
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# class Customer < ActiveRecord::Base
|
58
|
+
# make_exportable :conditions => {:active => true}, :order => 'last_name ASC, first_name ASC',
|
59
|
+
# :as => [:json, :html, :xml]
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
def make_exportable(options={})
|
63
|
+
# register the class as exportable
|
64
|
+
MakeExportable.exportable_classes[self.class_name] = self
|
65
|
+
|
66
|
+
# remove any invalid options
|
67
|
+
valid_options = [:as, :only, :except, :scopes, :conditions, :order, :include,
|
68
|
+
:group, :having, :limit, :offset, :joins]
|
69
|
+
options.slice!(*valid_options)
|
70
|
+
|
71
|
+
# Determine the exportable formats, default to all registered formats
|
72
|
+
options[:formats] = MakeExportable.exportable_formats.keys
|
73
|
+
if format_options = options.delete(:as)
|
74
|
+
options[:formats] = options[:formats] & Array.wrap(format_options).map(&:to_sym)
|
75
|
+
end
|
76
|
+
# Handle case when :as option was sent, but with no valid formats
|
77
|
+
if options[:formats].blank?
|
78
|
+
valid_formats = MakeExportable.exportable_formats.keys.map {|f| ":#{f}"}
|
79
|
+
raise MakeExportable::FormatNotFound.new("No valid export formats. Use: #{valid_formats.join(', ')}")
|
80
|
+
end
|
81
|
+
|
82
|
+
# Determine the exportable columns, default to all columns and then
|
83
|
+
# remove columns using the :only and :except options
|
84
|
+
options[:columns] = column_names.map(&:to_sym)
|
85
|
+
if only_options = options.delete(:only)
|
86
|
+
options[:columns] = Array.wrap(only_options).map(&:to_sym)
|
87
|
+
end
|
88
|
+
if except_options = options.delete(:except)
|
89
|
+
options[:columns] = options[:columns] - Array.wrap(except_options).map(&:to_sym)
|
90
|
+
end
|
91
|
+
|
92
|
+
options[:scopes] ||= []
|
93
|
+
|
94
|
+
# exportable options will be :formats, :columns, :scopes & find options
|
95
|
+
write_inheritable_attribute :exportable_options, options
|
96
|
+
class_inheritable_reader :exportable_options
|
97
|
+
|
98
|
+
extend MakeExportable::ClassMethods
|
99
|
+
include MakeExportable::InstanceMethods
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
module ClassMethods
|
106
|
+
|
107
|
+
# <tt>exportable?<?tt> returns true if the class has called "make_exportable".
|
108
|
+
# This is overriding the default :exportable? in ActiveRecord::Base which
|
109
|
+
# always returns false.
|
110
|
+
# If a format is passed as an argument, returns true only if the format is
|
111
|
+
# allowed for this class.
|
112
|
+
def exportable?(format=nil)
|
113
|
+
return exportable_options[:formats].include?(format.to_sym) if format
|
114
|
+
return true
|
115
|
+
end
|
116
|
+
|
117
|
+
# <tt>to_export</tt> exports records from a class. It can be called
|
118
|
+
# directly on an ActiveRecord class, but it can also be called on an ActiveRelation scope.
|
119
|
+
# It takes two arguments: a format (required) and a hash of options (optional).
|
120
|
+
#
|
121
|
+
# The options include:
|
122
|
+
# * :only and :except - specify columns or methods to export
|
123
|
+
# * :scopes - specify scopes to be called on the class before exporting
|
124
|
+
# * find options - for Rails 2.3 and earlier compatibility, standard find options
|
125
|
+
# are supported (:conditions, :order, :limit, :offset, etc.). These will be deprecated
|
126
|
+
# and removed in future versions.
|
127
|
+
# * :headers - supply an array of custom headers for the columns of exported attributes,
|
128
|
+
# the sizes of the header array and the exported columns must be equal.
|
129
|
+
#
|
130
|
+
# Examples:
|
131
|
+
#
|
132
|
+
# User.to_export(:xml, :only => [:first_name, :last_name, :username],
|
133
|
+
# :order => 'users.last_name ASC')
|
134
|
+
#
|
135
|
+
# User.visible.sorted_by_username.to_export('csv',
|
136
|
+
# :only => [:first_name, :last_name, :username])
|
137
|
+
#
|
138
|
+
def to_export(format, options={})
|
139
|
+
export_data = get_export_data(options)
|
140
|
+
# remove the auto-headers from the export_data (i.e. the first row)
|
141
|
+
auto_headers = export_data.shift
|
142
|
+
# Use auto-headers unless given alternates or false (use no headers)
|
143
|
+
options[:headers] = auto_headers unless !options[:headers].blank? || options[:headers] === false
|
144
|
+
export_string = create_report(format, export_data, :headers => options[:headers])
|
145
|
+
return export_string
|
146
|
+
end
|
147
|
+
|
148
|
+
# <tt>get_export_data</tt> finds records for export using a combination of the default
|
149
|
+
# export options and the argument options, and returns an array of arrays representing
|
150
|
+
# the rows and columns of the export data. The first item ("row") in the array will be
|
151
|
+
# an array of strings to be used as column headers.
|
152
|
+
# Valid options include :only, :except, :scopes and the standard find options.
|
153
|
+
# See <tt>to_export</tt> for more details on the options.
|
154
|
+
#
|
155
|
+
# Example:
|
156
|
+
#
|
157
|
+
# User.get_export_data(:only => [:first_name, :last_name, :username])
|
158
|
+
# # => [['first_name', 'last_name', 'username'], ['John', 'Doe', 'johndoe'], ['Joe', 'Smith', 'jsmith']] }
|
159
|
+
#
|
160
|
+
def get_export_data(options={})
|
161
|
+
column_options = options.slice(:only, :except)
|
162
|
+
records = find_export_data(options)
|
163
|
+
export_data = map_export_data(records, column_options)
|
164
|
+
return export_data
|
165
|
+
end
|
166
|
+
|
167
|
+
# <tt>create_report</tt> creates a report from a set of data. It takes three arguments:
|
168
|
+
# a format, the data set to use for the report, and an optional hash of options.
|
169
|
+
# The only meaningful option is :headers which sets the strings to be used as column
|
170
|
+
# headers for the data set. The value of :headers can be:
|
171
|
+
# * true - headers are the first row in the data set
|
172
|
+
# * false - headers are not in the data set and should not be added
|
173
|
+
# * array of strings to use for the column headers
|
174
|
+
#
|
175
|
+
# The length of the headers must match the length of each row in the data set.
|
176
|
+
def create_report(format, data_set, options={})
|
177
|
+
if options[:headers] === true
|
178
|
+
options[:headers] = data_set.shift
|
179
|
+
end
|
180
|
+
|
181
|
+
validate_export_format(format)
|
182
|
+
validate_export_data_lengths(data_set, options[:headers])
|
183
|
+
|
184
|
+
format_class = MakeExportable.exportable_formats[format.to_sym]
|
185
|
+
formater = format_class.new(data_set, options[:headers])
|
186
|
+
return formater.generate, formater.mime_type
|
187
|
+
end
|
188
|
+
|
189
|
+
private
|
190
|
+
|
191
|
+
# <tt>method_missing</tt> allows the class to accept dynamically named methods
|
192
|
+
# such as: SomeClass.to_xls_export(), SomeClass.create_csv_report()
|
193
|
+
def method_missing(method_id, *arguments)
|
194
|
+
possible_formats = MakeExportable.exportable_formats.keys.map(&:to_s).join('|')
|
195
|
+
if match = /^create_(#{possible_formats})_report$/.match(method_id.to_s)
|
196
|
+
format = match.captures.first
|
197
|
+
self.create_report(format, *arguments)
|
198
|
+
elsif match = /^to_(#{possible_formats})_export$/.match(method_id.to_s)
|
199
|
+
format = match.captures.first
|
200
|
+
self.to_export(format, *arguments)
|
201
|
+
else
|
202
|
+
super
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# <tt>find_export_data</tt> finds all objects of a given
|
207
|
+
# class using a combination of the default export options and the options passed in.
|
208
|
+
# Valid options include :scopes and the standard find options. It returns a collection of
|
209
|
+
# objects matching the find criteria.
|
210
|
+
# See <tt>to_export</tt> for more details on the options.
|
211
|
+
def find_export_data(options={})
|
212
|
+
|
213
|
+
# merge with defaults then pull out the supported find and scope options
|
214
|
+
merged_options = options.reverse_merge(exportable_options)
|
215
|
+
find_options = merged_options.slice(:conditions, :order, :include, :group, :having, :limit, :offset, :joins)
|
216
|
+
scope_options = merged_options.slice(:scopes)
|
217
|
+
|
218
|
+
# apply scopes and then find options
|
219
|
+
collection = self
|
220
|
+
scope_options[:scopes].each do |scope|
|
221
|
+
collection = collection.send(scope)
|
222
|
+
end
|
223
|
+
# For Rails 2.3 compatibility
|
224
|
+
if ActiveRecord::VERSION::MAJOR < 3
|
225
|
+
collection = collection.find(:all, find_options)
|
226
|
+
else
|
227
|
+
# they should not be sending find options anymore, so we don't support them
|
228
|
+
collection = collection.all
|
229
|
+
end
|
230
|
+
return collection
|
231
|
+
end
|
232
|
+
|
233
|
+
# <tt>map_export_data</tt> takes a collection and outputs an array of arrays representing
|
234
|
+
# the rows and columns of the export data. The first item ("row") in the array will be
|
235
|
+
# an array of strings to be used as column headers.
|
236
|
+
# Valid options include :only and :except.
|
237
|
+
# See <tt>to_export</tt> for more details on the options.
|
238
|
+
#
|
239
|
+
# User.map_export_data(User.visible, :only => [:first_name, :last_name, :username])
|
240
|
+
# # => [['first_name', 'last_name', 'username'], ['John', 'Doe', 'johndoe'], ...]
|
241
|
+
#
|
242
|
+
def map_export_data(collection, options={})
|
243
|
+
# Use :only and :except options or else use class defaults for columns.
|
244
|
+
if !options[:only].blank?
|
245
|
+
options[:columns] = Array.wrap(options[:only]).map(&:to_sym)
|
246
|
+
elsif !options[:except].blank?
|
247
|
+
options[:columns] = column_names.map(&:to_sym) - Array.wrap(options[:except]).map(&:to_sym)
|
248
|
+
else
|
249
|
+
options[:columns] = exportable_options[:columns]
|
250
|
+
end
|
251
|
+
if options[:columns].empty?
|
252
|
+
raise MakeExportable::ExportFault.new("You are not exporting anything")
|
253
|
+
end
|
254
|
+
# TODO: Go ahead and humanize/titleize the column names here
|
255
|
+
headers = options[:columns].map(&:to_s)
|
256
|
+
rows = collection.map do |item|
|
257
|
+
options[:columns].map {|col| item.export_attribute(col) }
|
258
|
+
end
|
259
|
+
return rows.unshift(headers)
|
260
|
+
end
|
261
|
+
|
262
|
+
# <tt>validate_export_format</tt> ensures that the requested export format is valid.
|
263
|
+
def validate_export_format(format)
|
264
|
+
unless MakeExportable.exportable_formats.keys.include?(format.to_sym)
|
265
|
+
raise MakeExportable::FormatNotFound.new("#{format} is not a supported format.")
|
266
|
+
end
|
267
|
+
unless exportable_options[:formats].include?(format.to_sym)
|
268
|
+
raise MakeExportable::FormatNotFound.new("#{format} format is not allowed on this class.")
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# <tt>validate_export_data_lengths</tt> ensures that the headers and all data rows are of the
|
273
|
+
# same size. (This is an important data integrity check if you are using NoSQL.)
|
274
|
+
def validate_export_data_lengths(data_set, data_headers=nil)
|
275
|
+
row_length = !data_headers.blank? ? data_headers.size : data_set[0].size
|
276
|
+
if data_set.any? {|row| row_length != row.size }
|
277
|
+
raise MakeExportable::ExportFault.new("Headers and all rows in the data set must be the same size.")
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
|
283
|
+
module InstanceMethods
|
284
|
+
|
285
|
+
# <tt>export_attribute</tt> returns the export value of an attribute or method.
|
286
|
+
# By default, this is simply the value of the attribute or method itself,
|
287
|
+
# but the value can be permanently overridden with another value by defining
|
288
|
+
# a method called "#{attribute}_export". The alternate method will *always*
|
289
|
+
# be called in place of the original one. At a minimum, this is useful
|
290
|
+
# when a date should be formatted when exporting or when booleans should
|
291
|
+
# always export as "Yes"/"No". But it can do more, performing any amount of
|
292
|
+
# processing or additional queries, as long as in the end it returns a value
|
293
|
+
# for the export to use.
|
294
|
+
# Sending an attribute name that does not exist will return an empty string.
|
295
|
+
def export_attribute(attribute)
|
296
|
+
begin
|
297
|
+
if self.respond_to?("#{attribute}_export")
|
298
|
+
return self.send("#{attribute}_export").to_s
|
299
|
+
else
|
300
|
+
return self.send(attribute).to_s
|
301
|
+
end
|
302
|
+
rescue
|
303
|
+
return ""
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
end
|