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.
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