cloudxls-rails 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. data/.gitignore +15 -0
  2. data/Gemfile +9 -0
  3. data/README.md +3 -0
  4. data/Rakefile +9 -0
  5. data/cloudxls-rails.gemspec +25 -0
  6. data/lib/cloudxls-rails/action_controller.rb +66 -0
  7. data/lib/cloudxls-rails/csv_writer.rb +103 -0
  8. data/lib/cloudxls-rails/version.rb +5 -0
  9. data/lib/cloudxls-rails.rb +6 -0
  10. data/spec/ci.sh +2 -0
  11. data/spec/csv_writer_spec.rb +86 -0
  12. data/spec/integration_spec.rb +34 -0
  13. data/spec/mime_types_spec.rb +28 -0
  14. data/spec/spec_helper.rb +22 -0
  15. data/spec/test_3.1.sh +16 -0
  16. data/spec/test_3.2.sh +16 -0
  17. data/spec/test_4.0.sh +16 -0
  18. data/spec/test_all_rails.sh +6 -0
  19. data/spec/test_app/.rspec +1 -0
  20. data/spec/test_app/README.rdoc +261 -0
  21. data/spec/test_app/Rakefile +7 -0
  22. data/spec/test_app/app/controllers/application_controller.rb +3 -0
  23. data/spec/test_app/app/controllers/posts_controller.rb +12 -0
  24. data/spec/test_app/app/helpers/application_helper.rb +2 -0
  25. data/spec/test_app/app/mailers/.gitkeep +0 -0
  26. data/spec/test_app/app/models/.gitkeep +0 -0
  27. data/spec/test_app/app/models/post.rb +2 -0
  28. data/spec/test_app/app/views/layouts/application.html.erb +12 -0
  29. data/spec/test_app/app/views/posts/index.html.erb +1 -0
  30. data/spec/test_app/config/application.rb +14 -0
  31. data/spec/test_app/config/boot.rb +10 -0
  32. data/spec/test_app/config/database.yml +11 -0
  33. data/spec/test_app/config/environment.rb +5 -0
  34. data/spec/test_app/config/environments/development.rb +39 -0
  35. data/spec/test_app/config/environments/production.rb +69 -0
  36. data/spec/test_app/config/environments/test.rb +40 -0
  37. data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/test_app/config/initializers/inflections.rb +15 -0
  39. data/spec/test_app/config/initializers/mime_types.rb +5 -0
  40. data/spec/test_app/config/initializers/secret_token.rb +8 -0
  41. data/spec/test_app/config/initializers/session_store.rb +8 -0
  42. data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
  43. data/spec/test_app/config/locales/en.yml +5 -0
  44. data/spec/test_app/config/routes.rb +5 -0
  45. data/spec/test_app/config.ru +4 -0
  46. data/spec/test_app/db/migrate/20120717192452_create_posts.rb +14 -0
  47. data/spec/test_app/db/schema.rb +28 -0
  48. data/spec/test_app/db/test.sqlite3 +0 -0
  49. data/spec/test_app/lib/assets/.gitkeep +0 -0
  50. data/spec/test_app/log/.gitkeep +0 -0
  51. data/spec/test_app/public/404.html +26 -0
  52. data/spec/test_app/public/422.html +26 -0
  53. data/spec/test_app/public/500.html +25 -0
  54. data/spec/test_app/public/favicon.ico +0 -0
  55. data/spec/test_app/script/rails +6 -0
  56. data/spec/test_revert.sh +3 -0
  57. metadata +244 -0
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ Gemfile.lock*
2
+ .bundle
3
+
4
+ Gemfile.lock
5
+ Gemfile.lock.*
6
+ .bundle/
7
+ log/*.log
8
+ pkg/
9
+ spec/test_app/db/*.sqlite3
10
+ spec/test_app/log/*.log
11
+ spec/test_app/tmp/
12
+ .DS_Store
13
+ /tmp
14
+
15
+ coverage/*
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+ gemspec
3
+
4
+ case ENV['RAILS_VERSION']
5
+ when '3.1', '3.2'
6
+ gem 'rails', "~> #{ENV['RAILS_VERSION']}.0"
7
+ when '4.0'
8
+ gem 'rails'
9
+ end
data/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # cloudxls-rails gem
2
+
3
+ This is a simple wrapper for the cloudxls api.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ task :default => [:test]
2
+
3
+ task :test do
4
+ ret = true
5
+ Dir["test/**/*.rb"].each do |f|
6
+ ret = ret && ruby(f, '')
7
+ end
8
+ exit(ret)
9
+ end
@@ -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
@@ -0,0 +1,5 @@
1
+ module CloudXLS
2
+ module Rails
3
+ VERSION = '0.3.0'
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'cloudxls-rails/version'
2
+ require 'cloudxls-rails/csv_writer'
3
+ require 'cloudxls-rails/action_controller'
4
+
5
+ module CloudXLS
6
+ end
data/spec/ci.sh ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env sh
2
+ cd spec/test_app && bundle install --quiet --without debug && bundle exec rake db:create --quiet && bundle exec rake db:migrate --quiet && cd ../../ && bundle exec rspec spec
@@ -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
@@ -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,6 @@
1
+ spec/test_3.1.sh
2
+ spec/test_3.2.sh
3
+ spec/test_4.0.sh
4
+
5
+ cp Gemfile.lock.orig Gemfile.lock
6
+ bundle install --quiet
@@ -0,0 +1 @@
1
+ --color