vibedeck-comma 0.4.2

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.
@@ -0,0 +1,35 @@
1
+ module Comma
2
+
3
+ class Generator
4
+
5
+ def initialize(instance, style)
6
+ @instance = instance
7
+ @style = style
8
+ @options = {}
9
+
10
+ if @style.is_a? Hash
11
+ @options = @style.clone
12
+ @style = @options.delete(:style) || :default
13
+ @filename = @options.delete(:filename)
14
+ end
15
+ end
16
+
17
+ def run(iterator_method)
18
+ if @filename
19
+ FasterCSV.open(@filename, 'w', @options){ |csv| append_csv(csv, iterator_method) } and return true
20
+ else
21
+ FasterCSV.generate(@options){ |csv| append_csv(csv, iterator_method) }
22
+ end
23
+ end
24
+
25
+ private
26
+ def append_csv(csv, iterator_method)
27
+ return '' if @instance.empty?
28
+ csv << @instance.first.to_comma_headers(@style) # REVISIT: request to optionally include headers
29
+ @instance.send(iterator_method) do |object|
30
+ csv << object.to_comma(@style)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,5 @@
1
+ class ActiveRecord::NamedScope::Scope
2
+ def to_comma(style = :default)
3
+ Comma::Generator.new(self, style).run(:find_each)
4
+ end
5
+ end
@@ -0,0 +1,17 @@
1
+ class Object
2
+ class_attribute :comma_formats
3
+
4
+ def self.comma(style = :default, &block)
5
+ (self.comma_formats ||= {})[style] = block
6
+ end
7
+
8
+ def to_comma(style = :default)
9
+ raise "No comma format for class #{self.class} defined for style #{style}" unless self.comma_formats and self.comma_formats[style]
10
+ Comma::DataExtractor.new(self, &self.comma_formats[style]).results
11
+ end
12
+
13
+ def to_comma_headers(style = :default)
14
+ raise "No comma format for class #{self.class} defined for style #{style}" unless self.comma_formats and self.comma_formats[style]
15
+ Comma::HeaderExtractor.new(self, &self.comma_formats[style]).results
16
+ end
17
+ end
@@ -0,0 +1,57 @@
1
+ if defined?(ActionController::Renderers) && ActionController::Renderers.respond_to?(:add)
2
+ ActionController::Renderers.add :csv do |obj, options|
3
+ filename = options[:filename] || 'data'
4
+ send_data obj.to_comma, :type => Mime::CSV, :disposition => "attachment; filename=#{filename}.csv"
5
+ end
6
+ else
7
+ module RenderAsCSV
8
+ def self.included(base)
9
+ base.alias_method_chain :render, :csv
10
+ end
11
+
12
+ def render_with_csv(options = nil, extra_options = {}, &block)
13
+ return render_without_csv(options, extra_options, &block) unless options.is_a?(Hash) and options[:csv].present?
14
+
15
+ content = options.delete(:csv)
16
+ style = options.delete(:style) || :default
17
+ filename = options.delete(:filename)
18
+
19
+ headers.merge!(
20
+ 'Content-Transfer-Encoding' => 'binary',
21
+ 'Content-Type' => 'text/csv; charset=utf-8'
22
+ )
23
+ filename_header_value = "attachment"
24
+ filename_header_value += "; filename=\"#{filename}\"" if filename.present?
25
+ headers.merge!('Content-Disposition' => filename_header_value)
26
+
27
+ @performed_render = false
28
+
29
+ render_stream :status => 200,
30
+ :content => Array(content),
31
+ :style => style
32
+ end
33
+
34
+ protected
35
+
36
+ def render_stream(options)
37
+ status = options[:status]
38
+ content = options[:content]
39
+ style = options[:style]
40
+
41
+ # If Rails 2.x
42
+ if defined? Rails and (Rails.version.split('.').map(&:to_i) <=> [2,3,5]) < 0
43
+ render :status => status, :text => Proc.new { |response, output|
44
+ output.write FasterCSV.generate_line(content.first.to_comma_headers(style))
45
+ content.each { |line| output.write FasterCSV.generate_line(line.to_comma(style)) }
46
+ }
47
+ else # If Rails 3.x
48
+ self.status = status
49
+ self.response_body = proc { |response, output|
50
+ output.write FasterCSV.generate_line(content.first.to_comma_headers(style))
51
+ content.each { |line| output.write FasterCSV.generate_line(line.to_comma(style)) }
52
+ }
53
+ end
54
+ end
55
+ end
56
+ #credit : http://ramblingsonrails.com/download-a-large-amount-of-data-in-csv-from-rails
57
+ end
@@ -0,0 +1,38 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Comma, 'generating CSV from an ActiveRecord object' do
4
+ before(:all) do
5
+ class Person < ActiveRecord::Base
6
+ named_scope :teenagers, :conditions => { :age => 13..19 }
7
+ comma do
8
+ name
9
+ age
10
+ end
11
+ end
12
+
13
+ require 'active_record/connection_adapters/abstract_adapter'
14
+ Column = ActiveRecord::ConnectionAdapters::Column
15
+ end
16
+
17
+ before do
18
+ Person.stub!(:columns).and_return [Column.new('age', 0, 'integer', false),
19
+ Column.new('name', nil, 'string', false) ]
20
+ Person.stub!(:table_exists?).and_return(true)
21
+ end
22
+
23
+ describe 'case' do
24
+ before do
25
+ people = [ Person.new(:age => 18, :name => 'Junior') ]
26
+ Person.stub!(:find_every).and_return people
27
+ Person.stub!(:calculate).with(:count, :all, {}).and_return people.size
28
+ end
29
+
30
+ it 'should extend ActiveRecord::NamedScope::Scope to add a #to_comma method which will return CSV content for objects within the scope' do
31
+ Person.teenagers.to_comma.should == "Name,Age\nJunior,18\n"
32
+ end
33
+
34
+ it 'should find in batches' do
35
+ Person.teenagers.to_comma
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,217 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Comma do
4
+
5
+ it 'should extend object to add a comma method' do
6
+ Object.should respond_to(:comma)
7
+ end
8
+
9
+ it 'should extend object to have a to_comma method' do
10
+ Object.should respond_to(:to_comma)
11
+ end
12
+
13
+ it 'should extend object to have a to_comma_headers method' do
14
+ Object.should respond_to(:to_comma_headers)
15
+ end
16
+
17
+ end
18
+
19
+ describe Comma, 'generating CSV' do
20
+
21
+ before do
22
+ @isbn = Isbn.new('123123123', '321321321')
23
+ @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn)
24
+
25
+ @books = []
26
+ @books << @book
27
+ end
28
+
29
+ it 'should extend Array to add a #to_comma method which will return CSV content for objects within the array' do
30
+ @books.to_comma.should == "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n"
31
+ end
32
+
33
+ it 'should return an empty string when generating CSV from an empty array' do
34
+ Array.new.to_comma.should == ''
35
+ end
36
+
37
+ it "should change the style when specified" do
38
+ @books.to_comma(:brief).should == "Name,Description\nSmalltalk-80,Language and Implementation\n"
39
+ end
40
+
41
+ describe 'with :filename specified' do
42
+ after{ File.delete('comma.csv') }
43
+
44
+ it "should write to the file" do
45
+ @books.to_comma(:filename => 'comma.csv')
46
+ File.read('comma.csv').should == "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n"
47
+ end
48
+
49
+ it "should accept FasterCSV options" do
50
+ @books.to_comma(:filename => 'comma.csv', :col_sep => ';', :force_quotes => true)
51
+ File.read('comma.csv').should == "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n"
52
+ end
53
+
54
+ end
55
+
56
+ describe "with FasterCSV options" do
57
+ it "should not change when options are empty" do
58
+ @books.to_comma({}).should == "Title,Description,Issuer,ISBN-10,ISBN-13\nSmalltalk-80,Language and Implementation,ISBN,123123123,321321321\n"
59
+ end
60
+
61
+ it 'should accept the options in #to_comma and generate the appropriate CSV' do
62
+ @books.to_comma(:col_sep => ';', :force_quotes => true).should == "\"Title\";\"Description\";\"Issuer\";\"ISBN-10\";\"ISBN-13\"\n\"Smalltalk-80\";\"Language and Implementation\";\"ISBN\";\"123123123\";\"321321321\"\n"
63
+ end
64
+
65
+ it "should change the style when specified" do
66
+ @books.to_comma(:style => :brief, :col_sep => ';', :force_quotes => true).should == "\"Name\";\"Description\"\n\"Smalltalk-80\";\"Language and Implementation\"\n"
67
+ end
68
+ end
69
+ end
70
+
71
+ describe Comma, 'defining CSV descriptions' do
72
+
73
+ describe 'with an unnamed description' do
74
+
75
+ before do
76
+ class Foo
77
+ comma do; end
78
+ end
79
+ end
80
+
81
+ it 'should name the current description :default if no name has been provided' do
82
+ Foo.comma_formats.should_not be_empty
83
+ Foo.comma_formats[:default].should_not be_nil
84
+ end
85
+ end
86
+
87
+ describe 'with a named description' do
88
+
89
+ before do
90
+ class Bar
91
+ comma do; end
92
+ comma :detailed do; end
93
+ end
94
+ end
95
+
96
+ it 'should use the provided name to index the comma format' do
97
+ Bar.comma_formats.should_not be_empty
98
+ Bar.comma_formats[:default].should_not be_nil
99
+ Bar.comma_formats[:detailed].should_not be_nil
100
+ end
101
+ end
102
+ end
103
+
104
+ describe Comma, 'to_comma data/headers object extensions' do
105
+
106
+ describe 'with unnamed descriptions' do
107
+
108
+ before do
109
+ class Foo
110
+ attr_accessor :content
111
+ comma do; content; end
112
+
113
+ def initialize(content)
114
+ @content = content
115
+ end
116
+ end
117
+
118
+ @foo = Foo.new('content')
119
+ end
120
+
121
+ it 'should return and array of data content, using the :default CSV description if none requested' do
122
+ @foo.to_comma.should == %w(content)
123
+ end
124
+
125
+ it 'should return and array of header content, using the :default CSV description if none requested' do
126
+ @foo.to_comma_headers.should == %w(Content)
127
+ end
128
+
129
+ it 'should return the CSV representation including header and content when called on an array' do
130
+ Array(@foo).to_comma.should == "Content\ncontent\n"
131
+ end
132
+
133
+ end
134
+
135
+ describe 'with named descriptions' do
136
+
137
+ before do
138
+ class Foo
139
+ attr_accessor :content
140
+ comma :detailed do; content; end
141
+
142
+ def initialize(content)
143
+ @content = content
144
+ end
145
+ end
146
+
147
+ @foo = Foo.new('content')
148
+ end
149
+
150
+ it 'should return and array of data content, using the :default CSV description if none requested' do
151
+ @foo.to_comma(:detailed).should == %w(content)
152
+ end
153
+
154
+ it 'should return and array of header content, using the :default CSV description if none requested' do
155
+ @foo.to_comma_headers(:detailed).should == %w(Content)
156
+ end
157
+
158
+ it 'should return the CSV representation including header and content when called on an array' do
159
+ Array(@foo).to_comma(:detailed).should == "Content\ncontent\n"
160
+ end
161
+
162
+ it 'should raise an error if the requested description is not avaliable' do
163
+ lambda { @foo.to_comma(:bad) }.should raise_error
164
+ lambda { @foo.to_comma_headers(:bad) }.should raise_error
165
+ lambda { Array(@foo).to_comma(:bad) }.should raise_error
166
+ end
167
+
168
+ end
169
+
170
+ describe 'with block' do
171
+ before do
172
+ class Foo
173
+ attr_accessor :content, :created_at, :updated_at
174
+ comma do
175
+ content
176
+ content('Truncated Content') {|i| i && i.length > 10 ? i[0..10] : '---' }
177
+ created_at { |i| i && i.to_s(:db) }
178
+ updated_at { |i| i && i.to_s(:db) }
179
+ created_at 'Created Custom Label' do |i| i && i.to_s(:short) end
180
+ updated_at 'Updated at Custom Label' do |i| i && i.to_s(:short) end
181
+ end
182
+
183
+ def initialize(content, created_at = Time.now, updated_at = Time.now)
184
+ @content = content
185
+ @created_at = created_at
186
+ @updated_at = updated_at
187
+ end
188
+ end
189
+
190
+ @time = Time.now
191
+ @content = 'content ' * 5
192
+ @foo = Foo.new @content, @time, @time
193
+ end
194
+
195
+ it 'should return yielded values by block' do
196
+ header, foo = Array(@foo).to_comma.split("\n")
197
+ foo.should == [@content, @content[0..10], @time.to_s(:db), @time.to_s(:db), @time.to_s(:short), @time.to_s(:short)].join(',')
198
+ end
199
+
200
+ it 'should return headers with custom labels from block' do
201
+ header, foo = Array(@foo).to_comma.split("\n")
202
+ header.should == ['Content', 'Truncated Content', 'Created at', 'Updated at', 'Created Custom Label', 'Updated at Custom Label'].join(',')
203
+ end
204
+
205
+ end
206
+
207
+
208
+ describe 'on an object with no comma declaration' do
209
+
210
+ it 'should raise an error mentioning there is no comma description defined for that class' do
211
+ lambda { 'a string'.to_comma }.should raise_error('No comma format for class String defined for style default')
212
+ lambda { 'a string'.to_comma_headers }.should raise_error('No comma format for class String defined for style default')
213
+ end
214
+
215
+ end
216
+
217
+ end
@@ -0,0 +1,91 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ # comma do
4
+ # name 'Title'
5
+ # description
6
+ #
7
+ # isbn :number_10 => 'ISBN-10', :number_13 => 'ISBN-13'
8
+ # end
9
+
10
+ describe Comma::HeaderExtractor do
11
+
12
+ before do
13
+ @isbn = Isbn.new('123123123', '321321321')
14
+ @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn)
15
+
16
+ @headers = @book.to_comma_headers
17
+ end
18
+
19
+ describe 'when no parameters are provided' do
20
+
21
+ it 'should use the method name as the header name, humanized' do
22
+ @headers.should include('Description')
23
+ end
24
+ end
25
+
26
+ describe 'when given a string description as a parameter' do
27
+
28
+ it 'should use the string value, unmodified' do
29
+ @headers.should include('Title')
30
+ end
31
+ end
32
+
33
+ describe 'when an hash is passed as a parameter' do
34
+
35
+ describe 'with a string value' do
36
+
37
+ it 'should use the string value, unmodified' do
38
+ @headers.should include('ISBN-10')
39
+ end
40
+ end
41
+
42
+ describe 'with a non-string value' do
43
+
44
+ it 'should use the non string value converted to a string, humanized' do
45
+ @headers.should include('Issuer')
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
52
+
53
+ describe Comma::DataExtractor do
54
+
55
+ before do
56
+ @isbn = Isbn.new('123123123', '321321321')
57
+ @book = Book.new('Smalltalk-80', 'Language and Implementation', @isbn)
58
+
59
+ @data = @book.to_comma
60
+ end
61
+
62
+ describe 'when no parameters are provided' do
63
+
64
+ it 'should use the string value returned by sending the method name on the object' do
65
+ @data.should include('Language and Implementation')
66
+ end
67
+ end
68
+
69
+ describe 'when given a string description as a parameter' do
70
+
71
+ it 'should use the string value returned by sending the method name on the object' do
72
+ @data.should include('Smalltalk-80')
73
+ end
74
+ end
75
+
76
+ describe 'when an hash is passed as a parameter' do
77
+
78
+ describe 'with a string value' do
79
+
80
+ it 'should use the string value, returned by sending the hash key to the object' do
81
+ @data.should include('123123123')
82
+ @data.should include('321321321')
83
+ end
84
+
85
+ it 'should not fail when an associated object is nil' do
86
+ lambda { Book.new('Smalltalk-80', 'Language and Implementation', nil).to_comma }.should_not raise_error
87
+ end
88
+ end
89
+ end
90
+
91
+ end