make_exportable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'rails'
4
+
5
+ group :development do
6
+ gem 'jeweler'
7
+ end
8
+
9
+ group :test do
10
+ gem 'rspec'
11
+ gem 'sqlite3-ruby', :require => 'sqlite3'
12
+ end
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