cloudxls 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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