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.
@@ -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
@@ -1,108 +1,139 @@
1
1
  require 'csv'
2
2
 
3
3
  module CloudXLS
4
- class CSVWriter
5
- DATETIME_FORMAT = "%FT%T.%L%z".freeze
6
- DATE_FORMAT = "%F".freeze
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 [Array<String/Symbol>] columns
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, options = {})
17
- columns = options[:columns]
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
- if options[:skip_headers] != true
22
- if scope.respond_to?(:column_names)
23
- columns ||= scope.column_names
24
- end
25
- if columns
26
- csv << csv_titles(columns, :titleize)
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
- enum = scope_enumerator(scope)
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
- # Post.csv_enumerator(Post.all, [:title, :author, :published_at])
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
- # @param [ActiveRecord::Scope] scope
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
- columns = options[:columns]
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
- private
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
- def self.csv_row(obj, columns = [])
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.encode_for_csv(val)
83
- case val
84
- when DateTime,Time then val.strftime(DATETIME_FORMAT)
85
- when Date then val.strftime(DATE_FORMAT)
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
- val
98
+ :each
88
99
  end
89
100
  end
101
+ end
102
+ end
90
103
 
91
- def self.csv_titles(column_names, strategy = :titleize)
92
- column_names.map do |c|
93
- title = c.to_s
94
- title = title.send(strategy) if title.respond_to?(strategy)
95
- title
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
- def self.scope_enumerator(scope)
101
- if scope.respond_to?(:find_each)
102
- :find_each
103
- else
104
- :each
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
@@ -1,3 +1,3 @@
1
1
  module CloudXLS
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
data/lib/cloudxls.rb CHANGED
@@ -2,10 +2,12 @@ require 'cgi'
2
2
  require 'set'
3
3
  require 'openssl'
4
4
  require 'json'
5
+ require 'date'
5
6
  require 'rest_client'
6
7
  require 'multi_json'
7
8
 
8
9
  require 'cloudxls/version'
10
+ require 'cloudxls/core_ext'
9
11
  require 'cloudxls/csv_writer'
10
12
 
11
13
  module CloudXLS
@@ -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.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-20 00:00:00.000000000 Z
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