as_csv 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/.gitignore +20 -0
  2. data/.travis.yml +3 -0
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +107 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +166 -0
  7. data/Rakefile +1 -0
  8. data/as_csv.gemspec +23 -0
  9. data/lib/as_csv/action_renderer.rb +5 -0
  10. data/lib/as_csv/active_model.rb +11 -0
  11. data/lib/as_csv/core_ext/array.rb +7 -0
  12. data/lib/as_csv/csv_builder.rb +46 -0
  13. data/lib/as_csv/version.rb +3 -0
  14. data/lib/as_csv.rb +6 -0
  15. data/spec/controllers/render_widgets_controller_spec.rb +5 -0
  16. data/spec/controllers/respond_with_widgets_controller_spec.rb +5 -0
  17. data/spec/core_ext/array_spec.rb +28 -0
  18. data/spec/dummy/README.rdoc +261 -0
  19. data/spec/dummy/Rakefile +7 -0
  20. data/spec/dummy/app/assets/images/rails.png +0 -0
  21. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  22. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  23. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  24. data/spec/dummy/app/controllers/render_widgets_controller.rb +15 -0
  25. data/spec/dummy/app/controllers/respond_with_widgets_controller.rb +12 -0
  26. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  27. data/spec/dummy/app/models/widget.rb +3 -0
  28. data/spec/dummy/app/models/widget_with_options.rb +12 -0
  29. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  30. data/spec/dummy/config/application.rb +53 -0
  31. data/spec/dummy/config/boot.rb +10 -0
  32. data/spec/dummy/config/database.yml +7 -0
  33. data/spec/dummy/config/environment.rb +8 -0
  34. data/spec/dummy/config/environments/development.rb +32 -0
  35. data/spec/dummy/config/environments/production.rb +54 -0
  36. data/spec/dummy/config/environments/test.rb +37 -0
  37. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  38. data/spec/dummy/config/initializers/inflections.rb +15 -0
  39. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  40. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  41. data/spec/dummy/config/initializers/session_store.rb +8 -0
  42. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/spec/dummy/config/locales/en.yml +5 -0
  44. data/spec/dummy/config/routes.rb +4 -0
  45. data/spec/dummy/config.ru +4 -0
  46. data/spec/dummy/db/schema.rb +22 -0
  47. data/spec/dummy/db/seeds.rb +7 -0
  48. data/spec/dummy/public/404.html +26 -0
  49. data/spec/dummy/public/422.html +26 -0
  50. data/spec/dummy/public/500.html +25 -0
  51. data/spec/dummy/public/favicon.ico +0 -0
  52. data/spec/dummy/public/index.html +241 -0
  53. data/spec/dummy/public/robots.txt +5 -0
  54. data/spec/dummy/script/rails +6 -0
  55. data/spec/lib/csv_builder_spec.rb +114 -0
  56. data/spec/models/active_model_spec.rb +16 -0
  57. data/spec/models/widget_spec.rb +47 -0
  58. data/spec/models/widget_with_options_spec.rb +29 -0
  59. data/spec/spec_helper.rb +27 -0
  60. data/spec/support/widgets_controller_mixin.rb +45 -0
  61. metadata +199 -0
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ spec/dummy/db/*.sqlite3
18
+ spec/dummy/log/*.log
19
+ spec/dummy/tmp/
20
+ spec/dummy/.sass-cache
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm: 1.9.3
3
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in as_csv.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,107 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ as_csv (0.0.1.pre)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ actionmailer (3.2.11)
10
+ actionpack (= 3.2.11)
11
+ mail (~> 2.4.4)
12
+ actionpack (3.2.11)
13
+ activemodel (= 3.2.11)
14
+ activesupport (= 3.2.11)
15
+ builder (~> 3.0.0)
16
+ erubis (~> 2.7.0)
17
+ journey (~> 1.0.4)
18
+ rack (~> 1.4.0)
19
+ rack-cache (~> 1.2)
20
+ rack-test (~> 0.6.1)
21
+ sprockets (~> 2.2.1)
22
+ activemodel (3.2.11)
23
+ activesupport (= 3.2.11)
24
+ builder (~> 3.0.0)
25
+ activerecord (3.2.11)
26
+ activemodel (= 3.2.11)
27
+ activesupport (= 3.2.11)
28
+ arel (~> 3.0.2)
29
+ tzinfo (~> 0.3.29)
30
+ activeresource (3.2.11)
31
+ activemodel (= 3.2.11)
32
+ activesupport (= 3.2.11)
33
+ activesupport (3.2.11)
34
+ i18n (~> 0.6)
35
+ multi_json (~> 1.0)
36
+ arel (3.0.2)
37
+ builder (3.0.4)
38
+ diff-lcs (1.1.3)
39
+ erubis (2.7.0)
40
+ hike (1.2.1)
41
+ i18n (0.6.1)
42
+ journey (1.0.4)
43
+ json (1.7.6)
44
+ mail (2.4.4)
45
+ i18n (>= 0.4.0)
46
+ mime-types (~> 1.16)
47
+ treetop (~> 1.4.8)
48
+ mime-types (1.19)
49
+ multi_json (1.5.0)
50
+ polyglot (0.3.3)
51
+ rack (1.4.3)
52
+ rack-cache (1.2)
53
+ rack (>= 0.4)
54
+ rack-ssl (1.3.2)
55
+ rack
56
+ rack-test (0.6.2)
57
+ rack (>= 1.0)
58
+ rails (3.2.11)
59
+ actionmailer (= 3.2.11)
60
+ actionpack (= 3.2.11)
61
+ activerecord (= 3.2.11)
62
+ activeresource (= 3.2.11)
63
+ activesupport (= 3.2.11)
64
+ bundler (~> 1.0)
65
+ railties (= 3.2.11)
66
+ railties (3.2.11)
67
+ actionpack (= 3.2.11)
68
+ activesupport (= 3.2.11)
69
+ rack-ssl (~> 1.3.2)
70
+ rake (>= 0.8.7)
71
+ rdoc (~> 3.4)
72
+ thor (>= 0.14.6, < 2.0)
73
+ rake (10.0.3)
74
+ rdoc (3.12)
75
+ json (~> 1.4)
76
+ rspec-core (2.12.2)
77
+ rspec-expectations (2.12.1)
78
+ diff-lcs (~> 1.1.3)
79
+ rspec-mocks (2.12.1)
80
+ rspec-rails (2.12.2)
81
+ actionpack (>= 3.0)
82
+ activesupport (>= 3.0)
83
+ railties (>= 3.0)
84
+ rspec-core (~> 2.12.0)
85
+ rspec-expectations (~> 2.12.0)
86
+ rspec-mocks (~> 2.12.0)
87
+ sprockets (2.2.2)
88
+ hike (~> 1.2)
89
+ multi_json (~> 1.0)
90
+ rack (~> 1.0)
91
+ tilt (~> 1.1, != 1.3.0)
92
+ sqlite3 (1.3.7)
93
+ thor (0.16.0)
94
+ tilt (1.3.3)
95
+ treetop (1.4.12)
96
+ polyglot
97
+ polyglot (>= 0.3.1)
98
+ tzinfo (0.3.35)
99
+
100
+ PLATFORMS
101
+ ruby
102
+
103
+ DEPENDENCIES
104
+ as_csv!
105
+ rails (~> 3.2)
106
+ rspec-rails (~> 2.12)
107
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Daniel Fone
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # AsCSV
2
+
3
+ This gem allows you to expose CSV in your apps as you'd expose JSON or XML.
4
+
5
+ Rails is not strictly required, but currently the magic only works with rails 3.x.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'as_csv'
12
+
13
+ ## Basic Usage
14
+
15
+ Simply add `respond_to :csv` in a controller
16
+
17
+ ```ruby
18
+ class WidgetsController < ApplicationController
19
+ respond_to :xml, :json, :csv
20
+
21
+ def index
22
+ respond_with Widget.all
23
+ end
24
+
25
+ def show
26
+ respond_with Widget.find(params[:id])
27
+ end
28
+
29
+ end
30
+ ```
31
+
32
+ Alternatively:
33
+
34
+ ```ruby
35
+ class WidgetsController < ApplicationController
36
+ def index
37
+ respond_to do |format|
38
+ format.csv { render csv: Widget.all }
39
+ end
40
+ end
41
+
42
+ def show
43
+ respond_to do |format|
44
+ format.csv { render csv: Widget.find(params[:id]) }
45
+ end
46
+ end
47
+ end
48
+ ```
49
+
50
+ By default, you don't need to change your models at all.
51
+
52
+ ```ruby
53
+ class Widget < ActiveRecord::Base
54
+ # attributes :code, :description, :name
55
+ end
56
+ ```
57
+ ```
58
+ > puts Widget.all.to_csv
59
+ id,name,description,code
60
+ 1,widget-1,widget-description-1,1001
61
+ 2,widget-2,widget-description-2,1002
62
+ 3,widget-3,widget-description-3,1003
63
+ 4,widget-4,widget-description-4,1004
64
+ => nil
65
+
66
+ > puts Widget.where(code: [1001, 1002]).to_csv
67
+ id,name,description,code
68
+ 1,widget-1,widget-description-1,1001
69
+ 2,widget-2,widget-description-2,1002
70
+
71
+ > puts Widget.first.to_csv
72
+ id,name,description,code
73
+ 1,widget-1,widget-description-1,1001
74
+ => nil
75
+ ```
76
+
77
+ Behind the scenes, any classes that `include ActiveModel::Serialization` will expose their `attributes` with `to_csv`.
78
+
79
+ ## Advanced Usage
80
+
81
+ ### Customizing the CSV
82
+
83
+ To change what's included in the CSV, define an `as_csv` method. This method must return a Hash.
84
+
85
+ ```ruby
86
+ class Widget < ActiveRecord::Base
87
+ # attributes :code, :description, :name
88
+
89
+ def as_csv(options={})
90
+ attributes.slice('name', 'code')
91
+ end
92
+
93
+ end
94
+ ```
95
+ ```
96
+ > puts Widget.all.to_csv
97
+ name,code
98
+ widget-1,1001
99
+ widget-2,1002
100
+ widget-3,1003
101
+ widget-4,1004
102
+ => nil
103
+ >
104
+ ```
105
+
106
+ You can also accept options:
107
+
108
+ ```ruby
109
+ class WidgetsController < ApplicationController
110
+ respond_to :csv
111
+
112
+ def index
113
+ respond_with Widget.all, style: :short
114
+ end
115
+ end
116
+ ```
117
+ ```ruby
118
+ class Widget < ActiveRecord::Base
119
+ attr_accessible :code, :description, :name
120
+
121
+ def as_csv(options={})
122
+ if options[:style] == :short
123
+ attributes.slice('name', 'code')
124
+ else
125
+ attributes
126
+ end
127
+ end
128
+
129
+ end
130
+ ```
131
+
132
+ ### Heterogenous Arrays
133
+ You can render any Array of objects that respond to `as_csv`.
134
+
135
+ ```ruby
136
+ class Foo < ActiveRecord::Base
137
+ # attributes: name, description, code
138
+ end
139
+ ```
140
+ ```ruby
141
+ class Bar < ActiveRecord::Base
142
+ # attributes: name, description, barcode
143
+ end
144
+ ```
145
+ ```
146
+ > puts (Foo.all + Bar.all).to_csv
147
+ name,description,code,barcode
148
+ foo1,foo1-description,111,
149
+ foo2,foo2-description,222,
150
+ bar1,bar1-description,,acb12345
151
+ bar2,bar2-description,,xyz98765
152
+ ```
153
+
154
+ ## Contributing
155
+
156
+ 1. Fork it
157
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
158
+ 3. Ensure you've got development dependencies: (`bundle`)
159
+ 4. Ensure existing tests run: (`bundle exec rspec`)
160
+ 5. Make your changes, including new specs
161
+ 6. Commit your changes (`git commit -am 'Add some feature'`)
162
+ 7. Push to the branch (`git push origin my-new-feature`)
163
+ 8. Create new Pull Request
164
+
165
+ [![Build Status](https://travis-ci.org/danielfone/as_csv.png)](https://travis-ci.org/danielfone/as_csv)
166
+ [![Dependency Status](https://gemnasium.com/danielfone/as_csv.png)](https://gemnasium.com/danielfone/as_csv)
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/as_csv.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'as_csv/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "as_csv"
8
+ gem.version = AsCSV::VERSION
9
+ gem.authors = ["Daniel Fone"]
10
+ gem.email = ["daniel@fone.net.nz"]
11
+ gem.description = %q{Instant CSV support for Rails}
12
+ gem.summary = %q{Instant CSV support for Rails}
13
+ gem.homepage = "https://github.com/danielfone/as_csv"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rails", "~> 3.2"
21
+ gem.add_development_dependency "rspec-rails", "~> 2.12"
22
+ gem.add_development_dependency "sqlite3"
23
+ end
@@ -0,0 +1,5 @@
1
+ if defined? ActionController::Renderers
2
+ ActionController::Renderers.add :csv do |obj, options|
3
+ obj.respond_to?(:to_csv) ? obj.to_csv(options) : obj
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ if defined? ActiveModel::Serialization
2
+ module ActiveModel::Serialization
3
+ def as_csv(options={})
4
+ attributes
5
+ end
6
+
7
+ def to_csv(*args)
8
+ [self].to_csv(*args)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class Array
2
+ def to_csv_with_builder(opts={})
3
+ AsCSV::CSVBuilder.new(self, opts).to_csv || to_csv_without_builder
4
+ end
5
+ alias_method :to_csv_without_builder, :to_csv
6
+ alias_method :to_csv, :to_csv_with_builder
7
+ end
@@ -0,0 +1,46 @@
1
+ module AsCSV
2
+ class CSVBuilder
3
+ attr_reader :records, :options
4
+
5
+ def initialize(records, options={})
6
+ @records = [*records]
7
+ @options = options
8
+ end
9
+
10
+ def to_csv
11
+ rows.collect { |row| CSV.generate_line row }.join if valid?
12
+ end
13
+
14
+ def rows
15
+ @rows ||= [headers] + data_rows if valid?
16
+ end
17
+
18
+ def headers
19
+ @headers ||= csv_hashes.collect(&:keys).flatten.uniq if valid?
20
+ end
21
+
22
+ def data_rows
23
+ @data_rows ||= csv_hashes.collect { |csv_hash| data_row csv_hash } if valid?
24
+ end
25
+
26
+ def valid?
27
+ csv_hashes.any? && validate_hashes!
28
+ end
29
+
30
+ private
31
+
32
+ def validate_hashes!
33
+ csv_hashes.each do |hash|
34
+ raise TypeError, "expected as_csv to return Hash" unless hash.is_a? Hash
35
+ end
36
+ end
37
+
38
+ def csv_hashes
39
+ @csv_hashes ||= records.collect { |r| r.as_csv(options) if r.respond_to? :as_csv }.compact
40
+ end
41
+
42
+ def data_row(csv_hash)
43
+ headers.collect { |header| csv_hash[header] }
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,3 @@
1
+ module AsCSV
2
+ VERSION = "1.0.0"
3
+ end
data/lib/as_csv.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "csv"
2
+ require "as_csv/version"
3
+ require "as_csv/csv_builder"
4
+ require "as_csv/core_ext/array"
5
+ require "as_csv/active_model"
6
+ require "as_csv/action_renderer"
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RenderWidgetsController, :widgets_controllers do
4
+ exercise_controller
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe RespondWithWidgetsController, :widgets_controllers do
4
+ exercise_controller
5
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Array do
4
+ it { should respond_to(:to_csv) }
5
+ it { should respond_to(:to_csv_with_builder) }
6
+ it { should respond_to(:to_csv_without_builder) }
7
+
8
+ context 'with objects responding to `as_csv`' do
9
+ before { subject << stub(:foo, as_csv: {header: 'value'})}
10
+
11
+ specify do
12
+ AsCSV::CSVBuilder.any_instance.should_receive(:to_csv).and_call_original
13
+ subject.should_not_receive(:to_csv_without_builder)
14
+ subject.to_csv
15
+ end
16
+ end
17
+
18
+ context 'without objects responding to `as_csv`' do
19
+ before { subject << 1 }
20
+
21
+ specify do
22
+ AsCSV::CSVBuilder.any_instance.should_receive(:to_csv).and_call_original
23
+ subject.should_receive(:to_csv_without_builder)
24
+ subject.to_csv
25
+ end
26
+ end
27
+
28
+ end