to-csv 1.0.0 → 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+
@@ -1,169 +1,175 @@
1
- module ToCSV
2
- class Converter
3
-
4
- def initialize(data, options = {}, csv_options = {}, &block)
5
- @opts = options.to_options.reverse_merge({
6
- :byte_order_marker => ToCSV.byte_order_marker,
7
- :locale => ToCSV.locale || ::I18n.locale,
8
- :primary_key => ToCSV.primary_key,
9
- :timestamps => ToCSV.timestamps
10
- })
11
-
12
- @opts[:only] = Array(@opts[:only]).map(&:to_s)
13
- @opts[:except] = Array(@opts[:except]).map(&:to_s)
14
- @opts[:methods] = Array(@opts[:methods]).map(&:to_s)
15
-
16
- @data = data
17
- @block = block
18
- @csv_options = csv_options.to_options.reverse_merge(ToCSV.csv_options)
19
- end
20
-
21
- def to_csv
22
- build_headers_and_rows
23
-
24
- output = ::FasterCSV.generate(@csv_options) do |csv|
25
- csv << @header_row if @header_row.try(:any?)
26
- @rows.each { |row| csv << row }
27
- end
28
-
29
- @opts[:byte_order_marker] ? "\xEF\xBB\xBF#{output}" : output
30
- end
31
-
32
- private
33
-
34
- def build_headers_and_rows
35
- send "headers_and_rows_from_#{ discover_data_type }"
36
- end
37
-
38
- def discover_data_type
39
- test_data = @data.first
40
- return 'ar_object' if instance_of_active_record? test_data
41
- return 'hash' if test_data.is_a? Hash
42
- return 'unidimensional_array' if test_data.is_a?(Array) && !test_data.first.is_a?(Array)
43
- return 'bidimensional_array' if test_data.is_a?(Array) && test_data.first.is_a?(Array) && test_data.first.size == 2
44
- 'simple_data'
45
- end
46
-
47
- def instance_of_active_record?(obj)
48
- obj.class.base_class.superclass == ActiveRecord::Base
49
- rescue Exception
50
- false
51
- end
52
-
53
- def headers_and_rows_from_simple_data
54
- @header_row = nil
55
- @rows = [@data.dup]
56
- end
57
-
58
- def headers_and_rows_from_hash
59
- @header_row = @data.first.keys if display_headers?
60
- @rows = @data.map(&:values)
61
- end
62
-
63
- def headers_and_rows_from_unidimensional_array
64
- @header_row = @data.first if display_headers?
65
- @rows = @data[1..-1]
66
- end
67
-
68
- def headers_and_rows_from_bidimensional_array
69
- @header_row = @data.first.map(&:first) if display_headers?
70
- @rows = @data.map { |array| array.map(&:last) }
71
- end
72
-
73
- def headers_and_rows_from_ar_object
74
- attributes = sort_attributes(filter_attributes(attribute_names))
75
- @header_row = human_attribute_names(attributes) if display_headers?
76
-
77
- @rows = if @block
78
- @data.map do |item|
79
- os = OpenStruct.new
80
- @block.call(os, item)
81
- marshal_dump = os.marshal_dump
82
- attributes.map { |attribute| marshal_dump[attribute.to_sym] || try_formatting_date(item.send(attribute)) }
83
- end
84
- else
85
- @data.map do |item|
86
- attributes.map { |attribute| try_formatting_date item.send(attribute) }
87
- end
88
- end
89
- end
90
-
91
- def display_headers?
92
- @opts[:headers].nil? || (Array(@opts[:headers]).any? && Array(@opts[:headers]).all? { |h| h != false })
93
- end
94
-
95
- def human_attribute_names(attributes)
96
- @opts[:locale] ? translate(attributes) : humanize(attributes)
97
- end
98
-
99
- def humanize(attributes)
100
- attributes.map(&:humanize)
101
- end
102
-
103
- def translate(attributes)
104
- ::I18n.with_options :locale => @opts[:locale], :scope => [:activerecord, :attributes, @data.first.class.to_s.underscore] do |locale|
105
- attributes.map { |attribute| locale.t(attribute, :default => attribute.humanize) }
106
- end
107
- end
108
-
109
- def try_formatting_date(value)
110
- is_a_date?(value) ? value.to_s : value
111
- end
112
-
113
- def is_a_date?(value)
114
- value.is_a?(Time) || value.is_a?(Date) || value.is_a?(DateTime)
115
- end
116
-
117
- def primary_key_filter(attributes)
118
- return attributes if @opts[:primary_key]
119
- attributes - Array(@data.first.class.primary_key.to_s)
120
- end
121
-
122
- def timestamps_filter(attributes)
123
- return attributes if @opts[:timestamps]
124
- return attributes if (@opts[:only] + @opts[:except]).any? { |attribute| timestamps.include? attribute }
125
- attributes - timestamps
126
- end
127
-
128
- def timestamps
129
- %w[ created_at updated_at created_on updated_on ]
130
- end
131
-
132
- def methods_filter(attributes)
133
- attributes | @opts[:methods]
134
- end
135
-
136
- def only_filter(attributes)
137
- return attributes if @opts[:only].empty?
138
- attributes & @opts[:only]
139
- end
140
-
141
- def except_filter(attributes)
142
- attributes - @opts[:except]
143
- end
144
-
145
- def attribute_names
146
- @data.first.attribute_names.map(&:to_s)
147
- end
148
-
149
- def filter_attributes(attributes)
150
- attributes = methods_filter(attributes)
151
- attributes = primary_key_filter(attributes)
152
- attributes = timestamps_filter(attributes)
153
- attributes = @opts[:only].any?? only_filter(attributes) : except_filter(attributes)
154
- attributes
155
- end
156
-
157
- def sort_attributes(attributes)
158
- attributes = attributes.map(&:to_s).sort
159
- return attributes if @opts[:headers].nil?
160
- headers = Array(@opts[:headers]).map(&:to_s)
161
- headers.delete_if { |attribute| attribute == 'false' }
162
- if index = headers.index('all')
163
- (headers & attributes).insert(index, (attributes - headers)).flatten
164
- else
165
- headers + (attributes - headers)
166
- end
167
- end
168
- end
169
- end
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
+