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