cloudxls 0.4.0 → 0.5.0
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/lib/cloudxls/core_ext.rb +106 -0
- data/lib/cloudxls/csv_writer.rb +95 -64
- data/lib/cloudxls/version.rb +1 -1
- data/lib/cloudxls.rb +2 -0
- data/spec/csv_writer_spec.rb +0 -39
- metadata +3 -2
@@ -0,0 +1,106 @@
|
|
1
|
+
# Object#as_csv ensures that object formats are written the same across ruby
|
2
|
+
# versions.
|
3
|
+
#
|
4
|
+
# If an object is known to have issues cross-platform #as_json returns the
|
5
|
+
# desired string.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# DateTime.now.as_csv # => "2012-12-24T12:30:01.000+0000"
|
10
|
+
# 5.as_csv # => 5
|
11
|
+
# (0.0/0.0).as_csv # => "#DIV/0!"
|
12
|
+
#
|
13
|
+
|
14
|
+
class Time
|
15
|
+
def as_csv(options = nil)
|
16
|
+
strftime(CloudXLS::DATETIME_FORMAT)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class DateTime
|
21
|
+
def as_csv(options = nil)
|
22
|
+
strftime(CloudXLS::DATETIME_FORMAT)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class String
|
27
|
+
def as_csv(options = nil)
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class Date
|
33
|
+
def as_csv(options = nil)
|
34
|
+
strftime(CloudXLS::DATE_FORMAT)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class NilClass
|
39
|
+
def as_csv(options = nil)
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class FalseClass
|
45
|
+
def as_csv(options = nil)
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class TrueClass
|
51
|
+
def as_csv(options = nil)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Symbol
|
57
|
+
def as_csv(options = nil)
|
58
|
+
to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class Numeric
|
63
|
+
def as_csv(options = nil)
|
64
|
+
self
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Float
|
69
|
+
# Encoding Infinity or NaN to JSON should return "null". The default returns
|
70
|
+
# "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]').
|
71
|
+
def as_csv(options = nil) #:nodoc:
|
72
|
+
finite? ? self : "#DIV/0!"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Regexp
|
77
|
+
def as_csv(options = nil)
|
78
|
+
to_s
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module Enumerable
|
83
|
+
def as_csv(options = nil)
|
84
|
+
to_a.as_json(options)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Range
|
89
|
+
def as_csv(options = nil)
|
90
|
+
to_s
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Array
|
95
|
+
def as_csv(options = nil)
|
96
|
+
map do |val|
|
97
|
+
val.as_csv(options)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class Object
|
103
|
+
def as_csv(options = nil)
|
104
|
+
to_s
|
105
|
+
end
|
106
|
+
end
|
data/lib/cloudxls/csv_writer.rb
CHANGED
@@ -1,108 +1,139 @@
|
|
1
1
|
require 'csv'
|
2
2
|
|
3
3
|
module CloudXLS
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
DATETIME_FORMAT = "%FT%T.%L%z".freeze
|
5
|
+
DATE_FORMAT = "%F".freeze
|
6
|
+
|
7
|
+
# Wrapper around stdlib CSV methods.
|
8
|
+
#
|
9
|
+
class CSV
|
10
|
+
def self.generate_line(arr, options = nil)
|
11
|
+
::CSV.generate_line(arr.as_csv, options)
|
12
|
+
end
|
13
|
+
end
|
7
14
|
|
15
|
+
|
16
|
+
class CSVWriter
|
8
17
|
# Generates CSV string.
|
9
18
|
#
|
10
|
-
# @param [
|
19
|
+
# @param [Enumerable] scope
|
11
20
|
# Method/attribute keys to for the export.
|
12
21
|
#
|
22
|
+
# @option opts [String] :encoding ("UTF-8")
|
23
|
+
# Charset encoding of output.
|
24
|
+
#
|
25
|
+
# @option opts [Boolean] :skip_headers (false)
|
26
|
+
# Do not output headers if first element is an ActiveRecord object.
|
27
|
+
#
|
28
|
+
# @option opts [Array] :only same as #as_json
|
29
|
+
# @option opts [Array] :except same as #as_json
|
30
|
+
# @option opts [Array] :methods same as #as_json
|
31
|
+
#
|
13
32
|
# @return [String]
|
14
33
|
# The full CSV as a string. Titleizes *columns* for the header.
|
15
34
|
#
|
16
|
-
def self.text(scope,
|
17
|
-
|
35
|
+
def self.text(scope, opts = {})
|
36
|
+
encoding = opts.delete(:encoding) || "UTF-8"
|
37
|
+
skip_headers = opts.delete(:skip_headers) || false
|
18
38
|
|
19
|
-
str = ::CSV.generate do |csv|
|
39
|
+
str = ::CSV.generate(:encoding => encoding) do |csv|
|
20
40
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
csv <<
|
41
|
+
enum_method = scope_enumerator(scope)
|
42
|
+
scope.send(enum_method) do |record|
|
43
|
+
|
44
|
+
if skip_headers == false && !record.is_a?(Array)
|
45
|
+
titles = CloudXLS::Util.titles_for_serialize_options(record, opts)
|
46
|
+
csv << titles.map(&:titleize)
|
47
|
+
skip_headers = true
|
27
48
|
end
|
28
|
-
end
|
29
49
|
|
30
|
-
|
31
|
-
scope.send(enum) do |record|
|
32
|
-
csv << csv_row(record, columns)
|
50
|
+
csv << record.as_csv(opts)
|
33
51
|
end
|
34
52
|
end
|
35
53
|
str.strip!
|
54
|
+
str
|
36
55
|
end
|
37
56
|
|
57
|
+
|
58
|
+
# Generates Enumerator for streaming response.
|
59
|
+
#
|
38
60
|
# Example
|
39
61
|
#
|
40
|
-
#
|
62
|
+
# def index
|
63
|
+
# # setup headers...
|
64
|
+
# stream = CloudXLS::CSVWriter.csv_enumerator(Post.all, only: [:title, :author])
|
65
|
+
# self.response_body = stream
|
66
|
+
# end
|
41
67
|
#
|
42
|
-
#
|
43
|
-
# An activerecord scope object for the records to be exported.
|
44
|
-
# Example: Post.all.limit(500).where(author: "foo")
|
68
|
+
# Same options and parameters as #text.
|
45
69
|
#
|
46
70
|
# @return [Enumerator] enumerator to use for streaming response.
|
47
71
|
#
|
48
72
|
def self.enumerator(scope, options = {})
|
49
|
-
|
50
|
-
|
51
|
-
Enumerator.new do |row|
|
52
|
-
if options[:skip_headers] != true
|
53
|
-
if scope.respond_to?(:column_names)
|
54
|
-
columns ||= scope.column_names
|
55
|
-
end
|
56
|
-
if columns
|
57
|
-
row << csv_titles(columns, :titleize).to_csv
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
enum = scope_enumerator(scope)
|
62
|
-
scope.send(enum) do |record|
|
63
|
-
row << csv_row(record, columns).to_csv
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
73
|
+
encoding = options.delete(:encoding) || "UTF-8"
|
74
|
+
skip_headers = options.delete(:skip_headers) || false
|
67
75
|
|
68
|
-
|
76
|
+
Enumerator.new do |stream|
|
77
|
+
enum_method = scope_enumerator(scope)
|
69
78
|
|
79
|
+
scope.send(enum_method) do |record|
|
80
|
+
if !skip_headers && !record.is_a?(Array)
|
81
|
+
titles = CloudXLS::Util.titles_for_serialize_options(record, options)
|
82
|
+
stream << titles.map(&:titleize).to_csv
|
83
|
+
skip_headers = true
|
84
|
+
end
|
70
85
|
|
71
|
-
|
72
|
-
if obj.is_a?(Array)
|
73
|
-
obj.map{ |el| encode_for_csv(el) }
|
74
|
-
else
|
75
|
-
columns.map do |key|
|
76
|
-
encode_for_csv(obj.send(key))
|
86
|
+
stream << record.as_csv(options).to_csv
|
77
87
|
end
|
78
88
|
end
|
79
89
|
end
|
80
90
|
|
81
91
|
|
82
|
-
def self.
|
83
|
-
|
84
|
-
|
85
|
-
|
92
|
+
def self.scope_enumerator(scope)
|
93
|
+
if (scope.respond_to?(:arel) &&
|
94
|
+
scope.arel.orders.blank? &&
|
95
|
+
scope.arel.taken.blank?)
|
96
|
+
:find_each
|
86
97
|
else
|
87
|
-
|
98
|
+
:each
|
88
99
|
end
|
89
100
|
end
|
101
|
+
end
|
102
|
+
end
|
90
103
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
104
|
+
module CloudXLS
|
105
|
+
class Util
|
106
|
+
# Column and method-names of a model that correspond to the values from
|
107
|
+
# a #as_json/#as_csv call. In the same order.
|
108
|
+
#
|
109
|
+
# Example
|
110
|
+
#
|
111
|
+
# CloudXLS::Util.titles_for_serialize_options(Post.new, only: [:author, :title], method: [:slug])
|
112
|
+
# # => ['title', 'author', 'slug']
|
113
|
+
#
|
114
|
+
def self.titles_for_serialize_options(record, options = nil)
|
115
|
+
options ||= {}
|
116
|
+
|
117
|
+
attribute_names = record.attributes.keys
|
118
|
+
if only = options[:only]
|
119
|
+
arr = []
|
120
|
+
Array(only).map(&:to_s).each do |key|
|
121
|
+
arr.push(key) if attribute_names.include?(key)
|
122
|
+
end
|
123
|
+
attribute_names = arr
|
124
|
+
elsif except = options[:except]
|
125
|
+
attribute_names -= Array(except).map(&:to_s)
|
96
126
|
end
|
97
|
-
end
|
98
127
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
128
|
+
Array(options[:methods]).each do |m|
|
129
|
+
m = m.to_s
|
130
|
+
if record.respond_to?(m)
|
131
|
+
unless attribute_names.include?(m)
|
132
|
+
attribute_names.push(val)
|
133
|
+
end
|
134
|
+
end
|
105
135
|
end
|
136
|
+
attribute_names
|
106
137
|
end
|
107
138
|
end
|
108
|
-
end
|
139
|
+
end
|
data/lib/cloudxls/version.rb
CHANGED
data/lib/cloudxls.rb
CHANGED
data/spec/csv_writer_spec.rb
CHANGED
@@ -44,43 +44,4 @@ describe "CloudXLS::CSVWriter" do
|
|
44
44
|
expect( @writer.text([[-1.0,0.0,1.0,1_000_000.0,1.234567]]) ).to eq('-1.0,0.0,1.0,1000000.0,1.234567')
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
48
|
-
# describe "#text with AR" do
|
49
|
-
# before do
|
50
|
-
# Post.delete_all
|
51
|
-
# @post = Post.create(
|
52
|
-
# :title => "hello world",
|
53
|
-
# :visits => 12_032,
|
54
|
-
# :conversion_rate => 0.24,
|
55
|
-
# :published_on => Date.new(2013,12,24),
|
56
|
-
# :expired_at => DateTime.new(2013,12,25,12,30,30),
|
57
|
-
# :unix_timestamp => DateTime.new(2013,12,25,12,30,30),
|
58
|
-
# :published => false)
|
59
|
-
# end
|
60
|
-
|
61
|
-
# it "given no records should just return titles" do
|
62
|
-
# Post.delete_all
|
63
|
-
# expect( @writer.text(Post.all, :columns => [:title, :visits]) ).to eq("Title,Visits")
|
64
|
-
# end
|
65
|
-
|
66
|
-
# it "should work with a Post.all" do
|
67
|
-
# expect( @writer.text(Post.all, :columns => [:title, :visits]) ).to eq("Title,Visits\nhello world,12032")
|
68
|
-
# end
|
69
|
-
|
70
|
-
# it "should work with a Post.limit" do
|
71
|
-
# expect( @writer.text(Post.limit(10), :columns => [:title, :visits]) ).to eq("Title,Visits\nhello world,12032")
|
72
|
-
# end
|
73
|
-
|
74
|
-
# it "should work with a Post.all.to_a" do
|
75
|
-
# expect( @writer.text(Post.all.to_a, :columns => [:title, :visits]) ).to eq("Title,Visits\nhello world,12032")
|
76
|
-
# end
|
77
|
-
|
78
|
-
# it "should write xmlschema for DateTime" do
|
79
|
-
# expect( @writer.text(Post.all, :columns => [:expired_at]) ).to eq("Expired At\n2013-12-25T12:30:30.000+0000")
|
80
|
-
# end
|
81
|
-
|
82
|
-
# it "should write YYYY-MM-DD for Date" do
|
83
|
-
# expect( @writer.text(Post.all, :columns => [:published_on]) ).to eq("Published On\n2013-12-24")
|
84
|
-
# end
|
85
|
-
# end
|
86
47
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cloudxls
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-11-
|
12
|
+
date: 2013-11-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rest-client
|
@@ -110,6 +110,7 @@ files:
|
|
110
110
|
- Rakefile
|
111
111
|
- cloudxls.gemspec
|
112
112
|
- lib/cloudxls.rb
|
113
|
+
- lib/cloudxls/core_ext.rb
|
113
114
|
- lib/cloudxls/csv_writer.rb
|
114
115
|
- lib/cloudxls/version.rb
|
115
116
|
- lib/data/ca-certificates.txt
|