to-csv 1.0.0 → 1.0.1
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/CHANGELOG.rdoc +9 -3
- data/MIT-LICENSE +20 -20
- data/README.rdoc +191 -188
- data/Rakefile +52 -36
- data/lib/to_csv.rb +59 -59
- data/lib/to_csv/csv_converter.rb +175 -169
- data/test/database.yml +3 -3
- data/test/fixtures/movie.rb +1 -1
- data/test/fixtures/movies.yml +18 -18
- data/test/fixtures/people.yml +6 -6
- data/test/fixtures/person.rb +2 -2
- data/test/fixtures/schema.rb +11 -11
- data/test/lib/activerecord_test_case.rb +19 -19
- data/test/lib/activerecord_test_connector.rb +31 -31
- data/test/lib/load_fixtures.rb +8 -8
- data/test/locales/en-US.yml +27 -27
- data/test/locales/pt-BR.yml +147 -147
- data/test/tasks.rake +7 -7
- data/test/to_csv_test.rb +150 -148
- metadata +37 -16
data/lib/to_csv.rb
CHANGED
@@ -1,59 +1,59 @@
|
|
1
|
-
require 'fastercsv'
|
2
|
-
require 'ostruct'
|
3
|
-
require 'active_support'
|
4
|
-
require 'to_csv/csv_converter'
|
5
|
-
|
6
|
-
module ToCSV
|
7
|
-
mattr_accessor :byte_order_marker, :locale, :primary_key, :timestamps
|
8
|
-
mattr_accessor :csv_options
|
9
|
-
self.csv_options = { :col_sep => ';' }
|
10
|
-
end
|
11
|
-
|
12
|
-
class Array
|
13
|
-
|
14
|
-
#
|
15
|
-
# Returns a CSV string.
|
16
|
-
#
|
17
|
-
# ==== Available Options:
|
18
|
-
#
|
19
|
-
# 1. *options*
|
20
|
-
# +byte_order_marker+::
|
21
|
-
# If true, a Byte Order Maker (BOM) will be inserted at
|
22
|
-
# the beginning of the output. It's useful if you want to force
|
23
|
-
# MS Excel to read UTF-8 encoded files, otherwise it will just
|
24
|
-
# decode them as Latin1 (ISO-8859-1). Default: +false+.
|
25
|
-
# +only+::
|
26
|
-
# Same behavior as with the +to_json+ method.
|
27
|
-
# +except+::
|
28
|
-
# Same as +only+ option.
|
29
|
-
# +methods+::
|
30
|
-
# Accepts a symbol or an array with additional methods to be included.
|
31
|
-
# +timestamps+::
|
32
|
-
# Include timestamps +created_at+, +created_on+, +updated_at+ and
|
33
|
-
# +updated_on+. If false Default: +false+.
|
34
|
-
# +primary_key+::
|
35
|
-
# If +true+ the object's primary key will be added as an attribute,
|
36
|
-
# which in turn will be mapped to a CSV column. Default: +false+.
|
37
|
-
# +headers+::
|
38
|
-
# If this list is <tt>nil</tt> then headers will be in alphabetical order.
|
39
|
-
# If it is an empty array or <tt>false</tt>, no headers will be shown.
|
40
|
-
# If it is non empty, headers will be sorted in the order specified.
|
41
|
-
# <tt>:all</tt> can be used as a placeholder for all attributes not
|
42
|
-
# explicitly named.
|
43
|
-
# +locale+::
|
44
|
-
# In a Rails environment, it will automatically take the current locale
|
45
|
-
# and will use it to translate the columns to friendly headers.
|
46
|
-
# Methods will be translated from
|
47
|
-
# <tt>[:activerecord, :attributes, <model>]</tt>. If the translation
|
48
|
-
# is missing, then a simple humanize will be called.
|
49
|
-
#
|
50
|
-
# 2. *csv_options*
|
51
|
-
# Accepts all options listed in <tt>FasterCSV::DEFAULT_OPTIONS</tt>.
|
52
|
-
#
|
53
|
-
def to_csv(options = {}, csv_options = {}, &block)
|
54
|
-
return '' if empty?
|
55
|
-
csv_converter = ToCSV::Converter.new(self, options, csv_options, &block)
|
56
|
-
csv_converter.to_csv
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
1
|
+
require RUBY_VERSION < '1.9' ? 'fastercsv' : 'csv'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'active_support'
|
4
|
+
require 'to_csv/csv_converter'
|
5
|
+
|
6
|
+
module ToCSV
|
7
|
+
mattr_accessor :byte_order_marker, :locale, :primary_key, :timestamps
|
8
|
+
mattr_accessor :csv_options
|
9
|
+
self.csv_options = { :col_sep => ';' }
|
10
|
+
end
|
11
|
+
|
12
|
+
class Array
|
13
|
+
|
14
|
+
#
|
15
|
+
# Returns a CSV string.
|
16
|
+
#
|
17
|
+
# ==== Available Options:
|
18
|
+
#
|
19
|
+
# 1. *options*
|
20
|
+
# +byte_order_marker+::
|
21
|
+
# If true, a Byte Order Maker (BOM) will be inserted at
|
22
|
+
# the beginning of the output. It's useful if you want to force
|
23
|
+
# MS Excel to read UTF-8 encoded files, otherwise it will just
|
24
|
+
# decode them as Latin1 (ISO-8859-1). Default: +false+.
|
25
|
+
# +only+::
|
26
|
+
# Same behavior as with the +to_json+ method.
|
27
|
+
# +except+::
|
28
|
+
# Same as +only+ option.
|
29
|
+
# +methods+::
|
30
|
+
# Accepts a symbol or an array with additional methods to be included.
|
31
|
+
# +timestamps+::
|
32
|
+
# Include timestamps +created_at+, +created_on+, +updated_at+ and
|
33
|
+
# +updated_on+. If false Default: +false+.
|
34
|
+
# +primary_key+::
|
35
|
+
# If +true+ the object's primary key will be added as an attribute,
|
36
|
+
# which in turn will be mapped to a CSV column. Default: +false+.
|
37
|
+
# +headers+::
|
38
|
+
# If this list is <tt>nil</tt> then headers will be in alphabetical order.
|
39
|
+
# If it is an empty array or <tt>false</tt>, no headers will be shown.
|
40
|
+
# If it is non empty, headers will be sorted in the order specified.
|
41
|
+
# <tt>:all</tt> can be used as a placeholder for all attributes not
|
42
|
+
# explicitly named.
|
43
|
+
# +locale+::
|
44
|
+
# In a Rails environment, it will automatically take the current locale
|
45
|
+
# and will use it to translate the columns to friendly headers.
|
46
|
+
# Methods will be translated from
|
47
|
+
# <tt>[:activerecord, :attributes, <model>]</tt>. If the translation
|
48
|
+
# is missing, then a simple humanize will be called.
|
49
|
+
#
|
50
|
+
# 2. *csv_options*
|
51
|
+
# Accepts all options listed in <tt>FasterCSV::DEFAULT_OPTIONS</tt>.
|
52
|
+
#
|
53
|
+
def to_csv(options = {}, csv_options = {}, &block)
|
54
|
+
return '' if empty?
|
55
|
+
csv_converter = ToCSV::Converter.new(self, options, csv_options, &block)
|
56
|
+
csv_converter.to_csv
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
data/lib/to_csv/csv_converter.rb
CHANGED
@@ -1,169 +1,175 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
@
|
18
|
-
@
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
def
|
59
|
-
@header_row =
|
60
|
-
@rows = @data.
|
61
|
-
end
|
62
|
-
|
63
|
-
def
|
64
|
-
@header_row = @data.first if display_headers?
|
65
|
-
@rows = @data
|
66
|
-
end
|
67
|
-
|
68
|
-
def
|
69
|
-
@header_row = @data.first
|
70
|
-
@rows = @data
|
71
|
-
end
|
72
|
-
|
73
|
-
def
|
74
|
-
|
75
|
-
@
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
|
122
|
-
def
|
123
|
-
return attributes if @opts[:
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
attributes
|
139
|
-
end
|
140
|
-
|
141
|
-
def
|
142
|
-
attributes
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
attributes =
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
module ToCSV
|
4
|
+
|
5
|
+
CSVClass = RUBY_VERSION < '1.9' ? ::FasterCSV : ::CSV
|
6
|
+
|
7
|
+
class Converter
|
8
|
+
|
9
|
+
def initialize(data, options = {}, csv_options = {}, &block)
|
10
|
+
@opts = options.to_options.reverse_merge({
|
11
|
+
:byte_order_marker => ToCSV.byte_order_marker,
|
12
|
+
:locale => ToCSV.locale || ::I18n.locale,
|
13
|
+
:primary_key => ToCSV.primary_key,
|
14
|
+
:timestamps => ToCSV.timestamps
|
15
|
+
})
|
16
|
+
|
17
|
+
@opts[:only] = Array(@opts[:only]).map(&:to_s)
|
18
|
+
@opts[:except] = Array(@opts[:except]).map(&:to_s)
|
19
|
+
@opts[:methods] = Array(@opts[:methods]).map(&:to_s)
|
20
|
+
|
21
|
+
@data = data
|
22
|
+
@block = block
|
23
|
+
@csv_options = csv_options.to_options.reverse_merge(ToCSV.csv_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_csv
|
27
|
+
build_headers_and_rows
|
28
|
+
|
29
|
+
output = CSVClass.generate(@csv_options) do |csv|
|
30
|
+
csv << @header_row if @header_row.try(:any?)
|
31
|
+
@rows.each { |row| csv << row }
|
32
|
+
end
|
33
|
+
|
34
|
+
@opts[:byte_order_marker] ? "\xEF\xBB\xBF#{output}" : output
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def build_headers_and_rows
|
40
|
+
send "headers_and_rows_from_#{ discover_data_type }"
|
41
|
+
end
|
42
|
+
|
43
|
+
def discover_data_type
|
44
|
+
test_data = @data.first
|
45
|
+
return 'ar_object' if instance_of_active_record? test_data
|
46
|
+
return 'hash' if test_data.is_a? Hash
|
47
|
+
return 'unidimensional_array' if test_data.is_a?(Array) && !test_data.first.is_a?(Array)
|
48
|
+
return 'bidimensional_array' if test_data.is_a?(Array) && test_data.first.is_a?(Array) && test_data.first.size == 2
|
49
|
+
'simple_data'
|
50
|
+
end
|
51
|
+
|
52
|
+
def instance_of_active_record?(obj)
|
53
|
+
obj.class.base_class.superclass == ActiveRecord::Base
|
54
|
+
rescue Exception
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def headers_and_rows_from_simple_data
|
59
|
+
@header_row = nil
|
60
|
+
@rows = [@data.dup]
|
61
|
+
end
|
62
|
+
|
63
|
+
def headers_and_rows_from_hash
|
64
|
+
@header_row = @data.first.keys if display_headers?
|
65
|
+
@rows = @data.map(&:values)
|
66
|
+
end
|
67
|
+
|
68
|
+
def headers_and_rows_from_unidimensional_array
|
69
|
+
@header_row = @data.first if display_headers?
|
70
|
+
@rows = @data[1..-1]
|
71
|
+
end
|
72
|
+
|
73
|
+
def headers_and_rows_from_bidimensional_array
|
74
|
+
@header_row = @data.first.map(&:first) if display_headers?
|
75
|
+
@rows = @data.map { |array| array.map(&:last) }
|
76
|
+
end
|
77
|
+
|
78
|
+
def headers_and_rows_from_ar_object
|
79
|
+
attributes = sort_attributes(filter_attributes(attribute_names))
|
80
|
+
@header_row = human_attribute_names(attributes) if display_headers?
|
81
|
+
|
82
|
+
@rows = if @block
|
83
|
+
@data.map do |item|
|
84
|
+
os = OpenStruct.new
|
85
|
+
@block.call(os, item)
|
86
|
+
marshal_dump = os.marshal_dump
|
87
|
+
attributes.map { |attribute| marshal_dump[attribute.to_sym] || try_formatting_date(item.send(attribute)) }
|
88
|
+
end
|
89
|
+
else
|
90
|
+
@data.map do |item|
|
91
|
+
attributes.map { |attribute| try_formatting_date item.send(attribute) }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def display_headers?
|
97
|
+
@opts[:headers].nil? || (Array(@opts[:headers]).any? && Array(@opts[:headers]).all? { |h| h != false })
|
98
|
+
end
|
99
|
+
|
100
|
+
def human_attribute_names(attributes)
|
101
|
+
@opts[:locale] ? translate(attributes) : humanize(attributes)
|
102
|
+
end
|
103
|
+
|
104
|
+
def humanize(attributes)
|
105
|
+
attributes.map(&:humanize)
|
106
|
+
end
|
107
|
+
|
108
|
+
def translate(attributes)
|
109
|
+
::I18n.with_options :locale => @opts[:locale], :scope => [:activerecord, :attributes, @data.first.class.to_s.underscore] do |locale|
|
110
|
+
attributes.map { |attribute| locale.t(attribute, :default => attribute.humanize) }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def try_formatting_date(value)
|
115
|
+
is_a_date?(value) ? value.to_s(:db) : value
|
116
|
+
end
|
117
|
+
|
118
|
+
def is_a_date?(value)
|
119
|
+
value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
|
120
|
+
end
|
121
|
+
|
122
|
+
def primary_key_filter(attributes)
|
123
|
+
return attributes if @opts[:primary_key]
|
124
|
+
attributes - Array(@data.first.class.primary_key.to_s)
|
125
|
+
end
|
126
|
+
|
127
|
+
def timestamps_filter(attributes)
|
128
|
+
return attributes if @opts[:timestamps]
|
129
|
+
return attributes if (@opts[:only] + @opts[:except]).any? { |attribute| timestamps.include? attribute }
|
130
|
+
attributes - timestamps
|
131
|
+
end
|
132
|
+
|
133
|
+
def timestamps
|
134
|
+
%w[ created_at updated_at created_on updated_on ]
|
135
|
+
end
|
136
|
+
|
137
|
+
def methods_filter(attributes)
|
138
|
+
attributes | @opts[:methods]
|
139
|
+
end
|
140
|
+
|
141
|
+
def only_filter(attributes)
|
142
|
+
return attributes if @opts[:only].empty?
|
143
|
+
attributes & @opts[:only]
|
144
|
+
end
|
145
|
+
|
146
|
+
def except_filter(attributes)
|
147
|
+
attributes - @opts[:except]
|
148
|
+
end
|
149
|
+
|
150
|
+
def attribute_names
|
151
|
+
@data.first.attribute_names.map(&:to_s)
|
152
|
+
end
|
153
|
+
|
154
|
+
def filter_attributes(attributes)
|
155
|
+
attributes = methods_filter(attributes)
|
156
|
+
attributes = primary_key_filter(attributes)
|
157
|
+
attributes = timestamps_filter(attributes)
|
158
|
+
attributes = @opts[:only].any?? only_filter(attributes) : except_filter(attributes)
|
159
|
+
attributes
|
160
|
+
end
|
161
|
+
|
162
|
+
def sort_attributes(attributes)
|
163
|
+
attributes = attributes.map(&:to_s).sort
|
164
|
+
return attributes if @opts[:headers].nil?
|
165
|
+
headers = Array(@opts[:headers]).map(&:to_s)
|
166
|
+
headers.delete_if { |attribute| attribute == 'false' }
|
167
|
+
if index = headers.index('all')
|
168
|
+
(headers & attributes).insert(index, (attributes - headers)).flatten
|
169
|
+
else
|
170
|
+
headers + (attributes - headers)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|