cloudxls-rails 0.3.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/.gitignore +15 -0
- data/Gemfile +9 -0
- data/README.md +3 -0
- data/Rakefile +9 -0
- data/cloudxls-rails.gemspec +25 -0
- data/lib/cloudxls-rails/action_controller.rb +66 -0
- data/lib/cloudxls-rails/csv_writer.rb +103 -0
- data/lib/cloudxls-rails/version.rb +5 -0
- data/lib/cloudxls-rails.rb +6 -0
- data/spec/ci.sh +2 -0
- data/spec/csv_writer_spec.rb +86 -0
- data/spec/integration_spec.rb +34 -0
- data/spec/mime_types_spec.rb +28 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/test_3.1.sh +16 -0
- data/spec/test_3.2.sh +16 -0
- data/spec/test_4.0.sh +16 -0
- data/spec/test_all_rails.sh +6 -0
- data/spec/test_app/.rspec +1 -0
- data/spec/test_app/README.rdoc +261 -0
- data/spec/test_app/Rakefile +7 -0
- data/spec/test_app/app/controllers/application_controller.rb +3 -0
- data/spec/test_app/app/controllers/posts_controller.rb +12 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/mailers/.gitkeep +0 -0
- data/spec/test_app/app/models/.gitkeep +0 -0
- data/spec/test_app/app/models/post.rb +2 -0
- data/spec/test_app/app/views/layouts/application.html.erb +12 -0
- data/spec/test_app/app/views/posts/index.html.erb +1 -0
- data/spec/test_app/config/application.rb +14 -0
- data/spec/test_app/config/boot.rb +10 -0
- data/spec/test_app/config/database.yml +11 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +39 -0
- data/spec/test_app/config/environments/production.rb +69 -0
- data/spec/test_app/config/environments/test.rb +40 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/inflections.rb +15 -0
- data/spec/test_app/config/initializers/mime_types.rb +5 -0
- data/spec/test_app/config/initializers/secret_token.rb +8 -0
- data/spec/test_app/config/initializers/session_store.rb +8 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +5 -0
- data/spec/test_app/config/routes.rb +5 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/db/migrate/20120717192452_create_posts.rb +14 -0
- data/spec/test_app/db/schema.rb +28 -0
- data/spec/test_app/db/test.sqlite3 +0 -0
- data/spec/test_app/lib/assets/.gitkeep +0 -0
- data/spec/test_app/log/.gitkeep +0 -0
- data/spec/test_app/public/404.html +26 -0
- data/spec/test_app/public/422.html +26 -0
- data/spec/test_app/public/500.html +25 -0
- data/spec/test_app/public/favicon.ico +0 -0
- data/spec/test_app/script/rails +6 -0
- data/spec/test_revert.sh +3 -0
- metadata +244 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$LOAD_PATH.push(File.expand_path "../lib", __FILE__)
|
3
|
+
require "cloudxls-rails/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "cloudxls-rails"
|
7
|
+
gem.authors = ["Sebastian Burkhard"]
|
8
|
+
gem.email = ["seb@cloudxls.com"]
|
9
|
+
gem.description = %q{Rails wrapper for the CloudXLS xpipe API}
|
10
|
+
gem.summary = %q{Rails wrapper for the CloudXLS xpipe API}
|
11
|
+
gem.homepage = "https://cloudxls.com"
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = CloudXLS::Rails::VERSION
|
17
|
+
|
18
|
+
gem.add_dependency('cloudxls', '~> 0.3.0')
|
19
|
+
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_development_dependency "webmock"
|
22
|
+
gem.add_development_dependency "rspec-rails"
|
23
|
+
gem.add_development_dependency "capybara"
|
24
|
+
gem.add_development_dependency "sqlite3"
|
25
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'csv'
|
2
|
+
require 'action_controller'
|
3
|
+
|
4
|
+
unless defined? Mime::XLS
|
5
|
+
Mime::Type.register "application/vnd.ms-excel", :xls
|
6
|
+
end
|
7
|
+
|
8
|
+
unless defined? Mime::XLSX
|
9
|
+
Mime::Type.register "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", :xlsx
|
10
|
+
end
|
11
|
+
|
12
|
+
ActionController::Renderers.add :csv do |scope, options|
|
13
|
+
filename = options.fetch(:filename, 'data.csv')
|
14
|
+
columns = options[:columns]
|
15
|
+
|
16
|
+
if options.fetch(:stream, false) == true
|
17
|
+
# streaming response
|
18
|
+
headers["Content-Type"] = "text/csv"
|
19
|
+
headers["Content-disposition"] = "attachment; filename=\"#{filename}\""
|
20
|
+
headers["Cache-Control"] ||= "no-cache"
|
21
|
+
headers.delete("Content-Length")
|
22
|
+
response.status = 200
|
23
|
+
|
24
|
+
stream = CloudXLS::CSVWriter.enumerator(scope, {:columns => columns})
|
25
|
+
self.response_body = stream
|
26
|
+
else # no stream:
|
27
|
+
data = CloudXLS::CSVWriter.text(scope, {:columns => columns})
|
28
|
+
|
29
|
+
send_data data,
|
30
|
+
type: Mime::CSV,
|
31
|
+
disposition: "attachment; filename=#{filename}.csv"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
ActionController::Renderers.add :xls do |scope, options|
|
36
|
+
columns = options.fetch(:columns, nil)
|
37
|
+
|
38
|
+
data = CloudXLS::CSVWriter.text(scope, {:columns => columns})
|
39
|
+
response = CloudXLS.xpipe(data: { text: data })
|
40
|
+
|
41
|
+
redirect_to response.url
|
42
|
+
end
|
43
|
+
|
44
|
+
ActionController::Renderers.add :xlsx do |scope, options|
|
45
|
+
columns = options.fetch(:columns, nil)
|
46
|
+
|
47
|
+
data = CloudXLS::CSVWriter.text(scope, {:columns => columns})
|
48
|
+
response = CloudXLS.xpipe(:data => {:text => data }, doc: {:format => "xlsx"})
|
49
|
+
|
50
|
+
redirect_to response.url
|
51
|
+
end
|
52
|
+
|
53
|
+
# For respond_to default
|
54
|
+
class ActionController::Responder
|
55
|
+
def to_csv
|
56
|
+
controller.render({:csv => resources.last, :stream => false }.merge(options))
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_xls
|
60
|
+
controller.render({:xls => resources.last, :stream => false }.merge(options))
|
61
|
+
end
|
62
|
+
|
63
|
+
def to_xlsx
|
64
|
+
controller.render({:xlsx => resources.last, :stream => false}.merge(options))
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module CloudXLS
|
4
|
+
class CSVWriter
|
5
|
+
DATETIME_FORMAT = "%FT%T.%L%z".freeze
|
6
|
+
DATE_FORMAT = "%F".freeze
|
7
|
+
|
8
|
+
# Generates CSV string.
|
9
|
+
#
|
10
|
+
# @param [Array<String/Symbol>] columns
|
11
|
+
# Method/attribute keys to for the export.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
# The full CSV as a string. Titleizes *columns* for the header.
|
15
|
+
#
|
16
|
+
def self.text(scope, options = {})
|
17
|
+
columns = options[:columns]
|
18
|
+
|
19
|
+
str = ::CSV.generate do |csv|
|
20
|
+
|
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)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
enum = scope_enumerator(scope)
|
31
|
+
scope.send(enum) do |record|
|
32
|
+
csv << csv_row(record, columns)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
str.strip!
|
36
|
+
end
|
37
|
+
|
38
|
+
# Example
|
39
|
+
#
|
40
|
+
# Post.csv_enumerator(Post.all, [:title, :author, :published_at])
|
41
|
+
#
|
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")
|
45
|
+
#
|
46
|
+
# @return [Enumerator] enumerator to use for streaming response.
|
47
|
+
#
|
48
|
+
def self.enumerator(scope, options = {})
|
49
|
+
columns = options[:columns]
|
50
|
+
|
51
|
+
Enumerator.new do |row|
|
52
|
+
if options[:skip_headers] != true
|
53
|
+
row << ::CSV.generate_line(csv_titles(columns, :titleize))
|
54
|
+
end
|
55
|
+
|
56
|
+
enum = scope_enumerator(scope)
|
57
|
+
scope.send(enum) do |record|
|
58
|
+
row << csv_row(record, columns).to_csv
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
|
66
|
+
def self.csv_row(obj, columns = [])
|
67
|
+
if obj.is_a?(Array)
|
68
|
+
obj.map{ |el| encode_for_csv(el) }
|
69
|
+
else
|
70
|
+
columns.map do |key|
|
71
|
+
encode_for_csv(obj.send(key))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def self.encode_for_csv(val)
|
78
|
+
case val
|
79
|
+
when DateTime,Time then val.strftime(DATETIME_FORMAT)
|
80
|
+
when Date then val.strftime(DATE_FORMAT)
|
81
|
+
else
|
82
|
+
val
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.csv_titles(column_names, strategy = :titleize)
|
87
|
+
column_names.map do |c|
|
88
|
+
title = c.to_s
|
89
|
+
title = title.send(strategy) if title.respond_to?(strategy)
|
90
|
+
title
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def self.scope_enumerator(scope)
|
96
|
+
if scope.respond_to?(:find_each)
|
97
|
+
:find_each
|
98
|
+
else
|
99
|
+
:each
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/spec/ci.sh
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "CloudXLS::CSVWriter" do
|
4
|
+
before do
|
5
|
+
@writer = CloudXLS::CSVWriter
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "with array" do
|
9
|
+
it "should not titleize" do
|
10
|
+
expect( @writer.text([['foo','bar'],[1,2]]) ).to eq("foo,bar\n1,2")
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should escape titles" do
|
14
|
+
expect( @writer.text([['bar"baz']]) ).to eq("\"bar\"\"baz\"")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should escape rows" do
|
18
|
+
expect( @writer.text([['title'],['bar"baz']]) ).to eq("title\n\"bar\"\"baz\"")
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should write YYYY-MM-DD for Date" do
|
22
|
+
expect( @writer.text([[Date.new(2012,12,24)]]) ).to eq("2012-12-24")
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should write xmlschema for DateTime" do
|
26
|
+
# TODO: make UTC consistent
|
27
|
+
expect( @writer.text([[DateTime.new(2012,12,24,18,30,5,'+0000')]]) ).to eq("2012-12-24T18:30:05.000+0000")
|
28
|
+
expect( @writer.text([[DateTime.new(2012,12,24,18,30,5,'+0000').to_time.utc]]) ).to eq("2012-12-24T18:30:05.000+0000")
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should write nothing for nil" do
|
32
|
+
expect( @writer.text([[nil,nil]]) ).to eq(",")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should write \"\" for empty string" do
|
36
|
+
expect( @writer.text([["",""]]) ).to eq('"",""')
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should write integers" do
|
40
|
+
expect( @writer.text([[-1,0,1,1_000_000]]) ).to eq('-1,0,1,1000000')
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should write floats" do
|
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
|
+
end
|
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
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe 'Request', :type => :request do
|
5
|
+
before :each do
|
6
|
+
Post.delete_all
|
7
|
+
@post = Post.create(
|
8
|
+
:title => "hello world",
|
9
|
+
:visits => 12_032,
|
10
|
+
:conversion_rate => 0.24,
|
11
|
+
:published_on => Date.new(2013,12,24),
|
12
|
+
:expired_at => DateTime.new(2013,12,25,12,30,30),
|
13
|
+
:unix_timestamp => DateTime.new(2013,12,25,12,30,30),
|
14
|
+
:published => false)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "has a working test_app" do
|
18
|
+
visit '/'
|
19
|
+
page.should have_content "Users"
|
20
|
+
end
|
21
|
+
|
22
|
+
it "/posts.csv has a working csv export" do
|
23
|
+
visit '/posts.csv'
|
24
|
+
page.should have_content [
|
25
|
+
"Title,Visits,Conversion Rate,Published On,Published,Expired At",
|
26
|
+
"hello world,12032,0.24,2013-12-24,false,2013-12-25T12:30:30.000+0000"
|
27
|
+
].join("\n")
|
28
|
+
end
|
29
|
+
|
30
|
+
it "/posts.xls has a working csv export" do
|
31
|
+
CloudXLS.should_receive(:xpipe).and_return(OpenStruct.new(:url => "/successful_redirect"))
|
32
|
+
visit '/posts.xls'
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require'spec_helper'
|
2
|
+
|
3
|
+
describe 'renderer and mime types' do
|
4
|
+
|
5
|
+
it "is registered" do
|
6
|
+
ActionController::Renderers::RENDERERS.include?(:xlsx)
|
7
|
+
ActionController::Renderers::RENDERERS.include?(:xls)
|
8
|
+
ActionController::Renderers::RENDERERS.include?(:csv)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "xlsx mime type" do
|
12
|
+
Mime::XLSX.should be
|
13
|
+
Mime::XLSX.to_sym.should == :xlsx
|
14
|
+
Mime::XLSX.to_s.should == "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "xls mime type" do
|
18
|
+
Mime::XLS.should be
|
19
|
+
Mime::XLS.to_sym.should == :xls
|
20
|
+
Mime::XLS.to_s.should == "application/vnd.ms-excel"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "csv mime type" do
|
24
|
+
Mime::CSV.should be
|
25
|
+
Mime::CSV.to_sym.should == :csv
|
26
|
+
Mime::CSV.to_s.should == "text/csv"
|
27
|
+
end
|
28
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
ENV["RAILS_ENV"] = 'test'
|
2
|
+
require File.expand_path("../test_app/config/environment", __FILE__)
|
3
|
+
require 'bundler'
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'rspec/rails'
|
6
|
+
require 'capybara/rspec'
|
7
|
+
|
8
|
+
# Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
config.color_enabled = true
|
12
|
+
config.use_transactional_fixtures = true
|
13
|
+
config.infer_base_class_for_anonymous_controllers = false
|
14
|
+
config.order = "random"
|
15
|
+
end
|
16
|
+
|
17
|
+
module ::RSpec::Core
|
18
|
+
class ExampleGroup
|
19
|
+
include Capybara::DSL
|
20
|
+
include Capybara::RSpecMatchers
|
21
|
+
end
|
22
|
+
end
|
data/spec/test_3.1.sh
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
export RAILS_ENV=test
|
2
|
+
|
3
|
+
for version in 3.1
|
4
|
+
do
|
5
|
+
echo "Testing Rails version " $version
|
6
|
+
rm Gemfile.lock
|
7
|
+
if [ -f Gemfile.lock.$version ];
|
8
|
+
then
|
9
|
+
echo Reusing Gemfile.lock.$version
|
10
|
+
cp Gemfile.lock.$version Gemfile.lock
|
11
|
+
fi
|
12
|
+
rm spec/test_app/db/test.sqlite3
|
13
|
+
export RAILS_VERSION=$version
|
14
|
+
spec/ci.sh
|
15
|
+
cp Gemfile.lock Gemfile.lock.$version
|
16
|
+
done
|
data/spec/test_3.2.sh
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
export RAILS_ENV=test
|
2
|
+
|
3
|
+
for version in 3.2
|
4
|
+
do
|
5
|
+
echo "Testing Rails version " $version
|
6
|
+
rm Gemfile.lock
|
7
|
+
if [ -f Gemfile.lock.$version ];
|
8
|
+
then
|
9
|
+
echo Reusing Gemfile.lock.$version
|
10
|
+
cp Gemfile.lock.$version Gemfile.lock
|
11
|
+
fi
|
12
|
+
rm spec/test_app/db/test.sqlite3
|
13
|
+
export RAILS_VERSION=$version
|
14
|
+
spec/ci.sh
|
15
|
+
cp Gemfile.lock Gemfile.lock.$version
|
16
|
+
done
|
data/spec/test_4.0.sh
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
export RAILS_ENV=test
|
2
|
+
|
3
|
+
for version in 4.0
|
4
|
+
do
|
5
|
+
echo "Testing Rails version " $version
|
6
|
+
rm Gemfile.lock
|
7
|
+
if [ -f Gemfile.lock.$version ];
|
8
|
+
then
|
9
|
+
echo Reusing Gemfile.lock.$version
|
10
|
+
cp Gemfile.lock.$version Gemfile.lock
|
11
|
+
fi
|
12
|
+
rm spec/test_app/db/test.sqlite3
|
13
|
+
export RAILS_VERSION=$version
|
14
|
+
spec/ci.sh
|
15
|
+
cp Gemfile.lock Gemfile.lock.$version
|
16
|
+
done
|
@@ -0,0 +1 @@
|
|
1
|
+
--color
|