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
@@ -0,0 +1,33 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class ExportableFormat
|
3
|
+
|
4
|
+
class_inheritable_accessor :reference
|
5
|
+
class_inheritable_accessor :name
|
6
|
+
|
7
|
+
attr_accessor :long
|
8
|
+
attr_accessor :mime_type
|
9
|
+
|
10
|
+
class << self
|
11
|
+
# Register this format with the mothership
|
12
|
+
def register_format
|
13
|
+
unless MakeExportable.exportable_formats[self.reference]
|
14
|
+
MakeExportable.exportable_formats[self.reference] = self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate(data_set, data_headers=nil)
|
21
|
+
end
|
22
|
+
|
23
|
+
def sanitize(value)
|
24
|
+
value
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_header_option(data_headers=[])
|
28
|
+
self.mime_type += (self.data_headers.blank? || data_headers === false) ? " header=absent" : " header=present"
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "csv"
|
2
|
+
# for compatibility with Rails 2
|
3
|
+
require 'fastercsv' if CSV.const_defined?(:Reader)
|
4
|
+
|
5
|
+
module MakeExportable #:nodoc:
|
6
|
+
class CSV < ExportableFormat
|
7
|
+
|
8
|
+
cattr_accessor :csv_type
|
9
|
+
|
10
|
+
self.reference = :csv
|
11
|
+
self.name = 'CSV'
|
12
|
+
self.register_format
|
13
|
+
self.csv_type = ::CSV.const_defined?(:Reader) ? FasterCSV : ::CSV
|
14
|
+
|
15
|
+
attr_accessor :data_set, :data_headers
|
16
|
+
|
17
|
+
def initialize(data_set, data_headers=[])
|
18
|
+
self.long = 'Comma-separated (CSV)'
|
19
|
+
self.mime_type = 'text/csv; charset=utf-8;'
|
20
|
+
self.data_set = data_set
|
21
|
+
self.data_headers = data_headers
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def generate
|
26
|
+
generate_header_option(data_headers)
|
27
|
+
@@csv_type.generate do |csv|
|
28
|
+
csv << data_headers.map {|h| sanitize(h.humanize.titleize)} unless data_headers.blank?
|
29
|
+
data_set.each {|row| csv << row }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class Excel < ExportableFormat
|
3
|
+
|
4
|
+
self.reference = :xls
|
5
|
+
self.name = 'Excel'
|
6
|
+
self.register_format
|
7
|
+
|
8
|
+
attr_accessor :data_set, :data_headers
|
9
|
+
|
10
|
+
def initialize(data_set, data_headers=[])
|
11
|
+
self.long = 'Microsoft Excel'
|
12
|
+
self.mime_type = 'application/vnd.ms-excel; charset=utf-8;'
|
13
|
+
self.data_set = data_set
|
14
|
+
self.data_headers = data_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
generate_header_option(data_headers)
|
19
|
+
output = "<table>\n"
|
20
|
+
unless data_headers.blank?
|
21
|
+
output << "\t<tr>\n"
|
22
|
+
output << data_headers.map {|h| "\t\t<th>#{sanitize(h.humanize.titleize)}</th>\n" }.join
|
23
|
+
output << "\t</tr>\n"
|
24
|
+
end
|
25
|
+
data_set.each do |row|
|
26
|
+
output << "\t<tr>\n"
|
27
|
+
output << row.map {|field| "\t\t<td>#{sanitize(field)}</td>\n"}.join
|
28
|
+
output << "\t</tr>\n"
|
29
|
+
end
|
30
|
+
output << "</table>\n"
|
31
|
+
return output
|
32
|
+
end
|
33
|
+
|
34
|
+
def sanitize(value)
|
35
|
+
value.gsub(/</, '<').gsub(/>/, '>')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class HTML < ExportableFormat
|
3
|
+
|
4
|
+
self.reference = :html
|
5
|
+
self.name = 'HTML'
|
6
|
+
self.register_format
|
7
|
+
|
8
|
+
attr_accessor :data_set, :data_headers
|
9
|
+
|
10
|
+
def initialize(data_set, data_headers=[])
|
11
|
+
self.long = 'HTML'
|
12
|
+
self.mime_type = 'text/html; charset=utf-8;'
|
13
|
+
self.data_set = data_set
|
14
|
+
self.data_headers = data_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
generate_header_option(data_headers)
|
19
|
+
output = "<table>\n"
|
20
|
+
unless data_headers.blank?
|
21
|
+
output << "\t<tr>\n"
|
22
|
+
output << data_headers.map {|h| "\t\t<th>#{sanitize(h.humanize.titleize)}</th>\n" }.join
|
23
|
+
output << "\t</tr>\n"
|
24
|
+
end
|
25
|
+
data_set.each do |row|
|
26
|
+
output << "\t<tr>\n"
|
27
|
+
output << row.map {|field| "\t\t<td>#{sanitize(field)}</td>\n"}.join
|
28
|
+
output << "\t</tr>\n"
|
29
|
+
end
|
30
|
+
output << "</table>\n"
|
31
|
+
return output
|
32
|
+
end
|
33
|
+
|
34
|
+
def sanitize(value)
|
35
|
+
value.gsub(/</, '<').gsub(/>/, '>')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class JSON < ExportableFormat
|
3
|
+
|
4
|
+
self.reference = :json
|
5
|
+
self.name = "JSON"
|
6
|
+
self.register_format
|
7
|
+
|
8
|
+
attr_accessor :data_set, :data_headers
|
9
|
+
|
10
|
+
def initialize(data_set, data_headers=[])
|
11
|
+
self.long = "JavaScript Object Notation (JSON)"
|
12
|
+
self.mime_type = "application/json; charset=utf-8;"
|
13
|
+
self.data_set = data_set
|
14
|
+
self.data_headers = data_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
output = []
|
19
|
+
unless data_headers.blank?
|
20
|
+
data_set.each do |row|
|
21
|
+
h = {}
|
22
|
+
row.each_with_index do |field, i|
|
23
|
+
h[data_headers[i]] = field
|
24
|
+
end
|
25
|
+
output << h
|
26
|
+
end
|
27
|
+
else
|
28
|
+
end
|
29
|
+
return output.to_json
|
30
|
+
end
|
31
|
+
|
32
|
+
def sanitize(value)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class TSV < ExportableFormat
|
3
|
+
|
4
|
+
self.reference = :tsv
|
5
|
+
self.name = "TSV"
|
6
|
+
self.register_format
|
7
|
+
|
8
|
+
attr_accessor :data_set, :data_headers
|
9
|
+
|
10
|
+
def initialize(data_set, data_headers=[])
|
11
|
+
self.long = "Tab-separated (TSV)"
|
12
|
+
self.mime_type = "text/tab-separated-values; charset=utf-8;"
|
13
|
+
self.data_set = data_set
|
14
|
+
self.data_headers = data_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
generate_header_option(data_headers)
|
19
|
+
output = ""
|
20
|
+
unless data_headers.blank?
|
21
|
+
output << data_headers.map {|h| sanitize(h.humanize.titleize) }.join("\t")
|
22
|
+
end
|
23
|
+
output << "\n" unless output.blank?
|
24
|
+
data_set.each do |row|
|
25
|
+
output << row.map {|field| sanitize(field)}.join("\t") << "\n"
|
26
|
+
end
|
27
|
+
return output
|
28
|
+
end
|
29
|
+
|
30
|
+
def sanitize(value)
|
31
|
+
value.gsub(/(\t|\\t)/, ' ')
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module MakeExportable #:nodoc:
|
2
|
+
class XML < ExportableFormat
|
3
|
+
|
4
|
+
self.reference = :xml
|
5
|
+
self.name = 'XML'
|
6
|
+
self.register_format
|
7
|
+
|
8
|
+
attr_accessor :data_set, :data_headers
|
9
|
+
|
10
|
+
def initialize(data_set, data_headers=[])
|
11
|
+
self.long = 'XML'
|
12
|
+
self.mime_type = 'application/xml;'
|
13
|
+
self.data_set = data_set
|
14
|
+
self.data_headers = data_headers
|
15
|
+
end
|
16
|
+
|
17
|
+
def generate
|
18
|
+
generate_header_option(data_headers)
|
19
|
+
xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
20
|
+
xml << "<records>\n"
|
21
|
+
data_set.each do |row|
|
22
|
+
xml << "\t<record>\n"
|
23
|
+
row.each_with_index do |field,i|
|
24
|
+
if !data_headers.blank?
|
25
|
+
attr_name = sanitize(data_headers[i].dasherize)
|
26
|
+
else
|
27
|
+
attr_name = "attribute_#{i}"
|
28
|
+
end
|
29
|
+
xml << "\t\t<#{attr_name}>#{sanitize(field)}</#{attr_name}>\n"
|
30
|
+
end
|
31
|
+
xml << "\t</record>\n"
|
32
|
+
end
|
33
|
+
xml << "</records>\n"
|
34
|
+
return xml
|
35
|
+
end
|
36
|
+
|
37
|
+
def sanitize(value)
|
38
|
+
value.gsub(/&/, '&').gsub(/</, '<').gsub(/>/, '>').
|
39
|
+
gsub(/"/, '"').gsub(/'/, ''')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module MakeExportableHelper
|
2
|
+
|
3
|
+
def self.exportable_class_list
|
4
|
+
MakeExportable.exportable_classes.keys.sort {|item1, item2| item1[0] <=> item2[0] }
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.exportable_table_list
|
8
|
+
MakeExportable.exportable_classes.values.map do |klass|
|
9
|
+
klass.table_name
|
10
|
+
end.sort {|item1, item2| item1[0] <=> item2[0] }
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.exportable_format_list
|
14
|
+
MakeExportable.exportable_formats.map do |key, fmt|
|
15
|
+
[fmt.name, key]
|
16
|
+
end.sort {|item1, item2| item1[0] <=> item2[0] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.exportable_units
|
20
|
+
hash = {}
|
21
|
+
MakeExportable.exportable_classes.values.map do |klass|
|
22
|
+
hash[klass] = klass.table_name
|
23
|
+
end
|
24
|
+
hash
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', 'init')
|
data/spec/database.yml
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Exportable Formats" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
class User
|
7
|
+
make_exportable
|
8
|
+
end
|
9
|
+
clean_database!
|
10
|
+
User.create(:first_name => "user_1", :last_name => "Doe", :created_at => Time.at(0), :updated_at => Time.at(0))
|
11
|
+
User.create(:first_name => "user_2", :last_name => "Doe", :created_at => Time.at(0), :updated_at => Time.at(0))
|
12
|
+
end
|
13
|
+
|
14
|
+
context "csv format" do
|
15
|
+
|
16
|
+
it "should export the columns as csv" do
|
17
|
+
User.to_export( "csv", :only => [:first_name, "is_admin"]).should == ["First Name,Is Admin\nuser_1,false\nuser_2,false\n", "text/csv; charset=utf-8; header=present"]
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should export the columns as csv and detect that there is no header" do
|
21
|
+
User.to_export( "csv", :only => [:first_name, "is_admin"], :headers => false).should == ["user_1,false\nuser_2,false\n", "text/csv; charset=utf-8; header=absent"]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context "excel format" do
|
27
|
+
|
28
|
+
it "should export the columns as xls" do
|
29
|
+
User.to_export( "xls", :only => [:first_name, "is_admin"]).should == ["<table>\n\t<tr>\n\t\t<th>First Name</th>\n\t\t<th>Is Admin</th>\n\t</tr>\n\t<tr>\n\t\t<td>user_1</td>\n\t\t<td>false</td>\n\t</tr>\n\t<tr>\n\t\t<td>user_2</td>\n\t\t<td>false</td>\n\t</tr>\n</table>\n", "application/vnd.ms-excel; charset=utf-8; header=present"]
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should export the columns as xls and detect no header" do
|
33
|
+
User.to_export( "xls", :only => [:first_name, "is_admin"], :headers => false).should == ["<table>\n\t<tr>\n\t\t<td>user_1</td>\n\t\t<td>false</td>\n\t</tr>\n\t<tr>\n\t\t<td>user_2</td>\n\t\t<td>false</td>\n\t</tr>\n</table>\n", "application/vnd.ms-excel; charset=utf-8; header=absent"]
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
context "tsv format" do
|
39
|
+
|
40
|
+
it "should export the columns as tsv" do
|
41
|
+
User.to_export( "tsv", :only => [:first_name, "is_admin"]).should == ["First Name\tIs Admin\nuser_1\tfalse\nuser_2\tfalse\n", "text/tab-separated-values; charset=utf-8; header=present"]
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should export the columns as tsv and detect no header" do
|
45
|
+
User.to_export( "tsv", :only => [:first_name, "is_admin"], :headers => false).should == ["user_1\tfalse\nuser_2\tfalse\n", "text/tab-separated-values; charset=utf-8; header=absent"]
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
context "html format" do
|
51
|
+
|
52
|
+
it "should export the columns as html" do
|
53
|
+
User.to_export( "html", :only => [:first_name, "is_admin"]).should == ["<table>\n\t<tr>\n\t\t<th>First Name</th>\n\t\t<th>Is Admin</th>\n\t</tr>\n\t<tr>\n\t\t<td>user_1</td>\n\t\t<td>false</td>\n\t</tr>\n\t<tr>\n\t\t<td>user_2</td>\n\t\t<td>false</td>\n\t</tr>\n</table>\n", "text/html; charset=utf-8; header=present"]
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should export the columns as html and detect no header" do
|
57
|
+
User.to_export( "html", :only => [:first_name, "is_admin"], :headers => false).should == ["<table>\n\t<tr>\n\t\t<td>user_1</td>\n\t\t<td>false</td>\n\t</tr>\n\t<tr>\n\t\t<td>user_2</td>\n\t\t<td>false</td>\n\t</tr>\n</table>\n", "text/html; charset=utf-8; header=absent"]
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context "xml format" do
|
63
|
+
|
64
|
+
it "should export the columns as xml" do
|
65
|
+
User.to_export( "xml", :only => [:first_name, "is_admin"]).should == ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<records>\n\t<record>\n\t\t<first-name>user_1</first-name>\n\t\t<is-admin>false</is-admin>\n\t</record>\n\t<record>\n\t\t<first-name>user_2</first-name>\n\t\t<is-admin>false</is-admin>\n\t</record>\n</records>\n", "application/xml; header=present"]
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should export the columns as xml and detect no header" do
|
69
|
+
User.to_export( "xml", :only => [:first_name, "is_admin"], :headers => false).should == ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<records>\n\t<record>\n\t\t<attribute_0>user_1</attribute_0>\n\t\t<attribute_1>false</attribute_1>\n\t</record>\n\t<record>\n\t\t<attribute_0>user_2</attribute_0>\n\t\t<attribute_1>false</attribute_1>\n\t</record>\n</records>\n", "application/xml; header=absent"]
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
context "json format" do
|
75
|
+
it "should export the columns as json" do
|
76
|
+
User.to_export( "json", :only => [:first_name]).should ==["[{\"first_name\":\"user_1\"},{\"first_name\":\"user_2\"}]", "application/json; charset=utf-8;"]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Make Exportable Helper" do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
clean_database!
|
7
|
+
end
|
8
|
+
|
9
|
+
it "it should output an array of exportable classes" do
|
10
|
+
MakeExportable.exportable_classes = {}
|
11
|
+
User.class_eval("make_exportable")
|
12
|
+
Post.class_eval("make_exportable")
|
13
|
+
MakeExportableHelper.exportable_class_list.should == ["Post", "User"]
|
14
|
+
MakeExportable.exportable_classes = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
it "it should output an array of exportable tables" do
|
18
|
+
User.class_eval("make_exportable")
|
19
|
+
Post.class_eval("make_exportable")
|
20
|
+
MakeExportableHelper.exportable_table_list.should ==["posts", "users"]
|
21
|
+
MakeExportable.exportable_classes = {}
|
22
|
+
end
|
23
|
+
|
24
|
+
it "it should output an array of exportable classes and tables to check against" do
|
25
|
+
User.class_eval("make_exportable")
|
26
|
+
Post.class_eval("make_exportable")
|
27
|
+
MakeExportableHelper.exportable_units.should == {User => "users", Post => "posts"}
|
28
|
+
MakeExportable.exportable_classes = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|