make_exportable 1.0.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.
@@ -0,0 +1,510 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe "Make Exportable" do
4
+
5
+ before(:each) do
6
+ clean_database!
7
+ end
8
+
9
+ describe MakeExportable do
10
+
11
+ # For simply having MakeExportable loaded as a gem/plugin
12
+ describe "mattr_accessor :exportable_classes" do
13
+
14
+ it "should be a hash" do
15
+ MakeExportable.exportable_classes.class.should == Hash
16
+ end
17
+
18
+ it "should be readable and writable" do
19
+ MakeExportable.exportable_classes[:testkey] = 'testvalue'
20
+ MakeExportable.exportable_classes[:testkey].should == 'testvalue'
21
+ MakeExportable.exportable_classes.delete(:testkey)
22
+ end
23
+ end
24
+
25
+ describe "mattr_accessor :exportable_formats" do
26
+
27
+ it "should be a hash" do
28
+ MakeExportable.exportable_classes.class.should == Hash
29
+ end
30
+
31
+ it "should be readable and writable" do
32
+ MakeExportable.exportable_formats[:testkey] = 'testvalue'
33
+ MakeExportable.exportable_formats[:testkey].should == 'testvalue'
34
+ MakeExportable.exportable_formats.delete(:testkey)
35
+ end
36
+
37
+ it "should contain keys for the supported format types" do
38
+ MakeExportable.exportable_formats.should_not be_nil
39
+ MakeExportable.exportable_formats.key?(:csv ).should be_true
40
+ MakeExportable.exportable_formats.key?(:xls ).should be_true
41
+ MakeExportable.exportable_formats.key?(:html).should be_true
42
+ MakeExportable.exportable_formats.key?(:json).should be_true
43
+ MakeExportable.exportable_formats.key?(:tsv ).should be_true
44
+ MakeExportable.exportable_formats.key?(:xml ).should be_true
45
+ end
46
+
47
+ it "should have the correct format class as a value for each key" do
48
+ MakeExportable.exportable_formats[:csv ].should == MakeExportable::CSV
49
+ MakeExportable.exportable_formats[:xls ].should == MakeExportable::Excel
50
+ MakeExportable.exportable_formats[:html].should == MakeExportable::HTML
51
+ MakeExportable.exportable_formats[:json].should == MakeExportable::JSON
52
+ MakeExportable.exportable_formats[:tsv ].should == MakeExportable::TSV
53
+ MakeExportable.exportable_formats[:xml ].should == MakeExportable::XML
54
+ end
55
+
56
+ end
57
+
58
+ describe "extenstions to ActiveRecord's class methods" do
59
+
60
+ it "should include MakeExportable's ActiveRecordBaseMethods" do
61
+ if ActiveRecord::VERSION::MAJOR >= 3
62
+ ActiveRecord::Base.methods.include?(:exportable?).should be_true
63
+ ActiveRecord::Base.methods.include?(:make_exportable).should be_true
64
+ else
65
+ ActiveRecord::Base.methods.include?('exportable?').should be_true
66
+ ActiveRecord::Base.methods.include?('make_exportable').should be_true
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+
73
+ # Once classes add MakeExportable's functionality
74
+ describe "classes declaring make_exportable" do
75
+
76
+ before(:each) do
77
+ class User
78
+ make_exportable
79
+ end
80
+ end
81
+
82
+ it "should be included in MakeExportable.exportable_tables" do
83
+ MakeExportable.exportable_classes['User'].should == User
84
+ end
85
+
86
+ it "should have MakeExportable's ClassMethods" do
87
+ if ActiveRecord::VERSION::MAJOR >= 3
88
+ User.methods.include?(:exportable?).should be_true
89
+ User.methods.include?(:to_export).should be_true
90
+ User.methods.include?(:get_export_data).should be_true
91
+ User.methods.include?(:create_report).should be_true
92
+ else
93
+ User.methods.include?("exportable?").should be_true
94
+ User.methods.include?("to_export").should be_true
95
+ User.methods.include?("get_export_data").should be_true
96
+ User.methods.include?("create_report").should be_true
97
+ end
98
+ end
99
+
100
+ it "should not expose MakeExportable's ClassMethods which are private" do
101
+ if ActiveRecord::VERSION::MAJOR >= 3
102
+ User.methods.include?(:find_export_data).should be_false
103
+ User.methods.include?(:map_export_data).should be_false
104
+ User.methods.include?(:validate_export_format).should be_false
105
+ User.methods.include?(:validate_export_data_lengths).should be_false
106
+ else
107
+ User.methods.include?("find_export_data").should be_false
108
+ User.methods.include?("map_export_data").should be_false
109
+ User.methods.include?("validate_export_format").should be_false
110
+ User.methods.include?("validate_export_data_lengths").should be_false
111
+ end
112
+ end
113
+
114
+ it "should have MakeExportable's InstanceMethods" do
115
+ if ActiveRecord::VERSION::MAJOR >= 3
116
+ User.instance_methods.include?(:export_attribute).should be_true
117
+ else
118
+ User.instance_methods.include?("export_attribute").should be_true
119
+ end
120
+ end
121
+
122
+ it "should have an inheritable class accessor for exportable_options" do
123
+ if ActiveRecord::VERSION::MAJOR >= 3
124
+ User.instance_methods.include?(:exportable_options).should be_true
125
+ else
126
+ User.instance_methods.include?("exportable_options").should be_true
127
+ end
128
+ end
129
+
130
+ describe "dynamically-named methods" do
131
+
132
+ describe 'create_#{format}_report' do
133
+
134
+ MakeExportable.exportable_formats.map do |format, v|
135
+ it "should define the method create_#{format}_report" do
136
+ User.should_receive(:create_report).with(format.to_s)
137
+ User.send("create_#{format}_report")
138
+ end
139
+ end
140
+
141
+ it "should return NoMethodError if the format is not supported" do
142
+ lambda do
143
+ User.create_xyz_report
144
+ end.should raise_error(NoMethodError)
145
+ end
146
+
147
+ end
148
+
149
+ describe 'to_#{format}_export' do
150
+
151
+ MakeExportable.exportable_formats.map do |format, v|
152
+ it "should define the method to_#{format}_export" do
153
+ User.should_receive(:to_export).with(format.to_s)
154
+ User.class_eval("to_#{format}_export")
155
+ end
156
+ end
157
+
158
+ it "should return NoMethodError if the format is not supported" do
159
+ lambda do
160
+ User.to_xyz_export
161
+ end.should raise_error(NoMethodError)
162
+ end
163
+
164
+ end
165
+
166
+ end
167
+
168
+ describe "not passing in options" do
169
+
170
+ it "should set default values for key options" do
171
+ User.exportable_options.keys.map(&:to_s).sort.should == ["columns", "formats", "scopes"]
172
+ end
173
+
174
+ it "should set :columns to all database columns" do
175
+ User.exportable_options[:columns].should == [:id, :first_name, :last_name, :password, :email, :is_admin, :created_at, :updated_at]
176
+ end
177
+
178
+ it "should set :formats to all supported formats" do
179
+ User.exportable_options[:formats].map(&:to_s).sort.should == ["csv", "html", "json", "tsv", "xls", "xml"]
180
+ end
181
+
182
+ it "should set :scopes to an empty array" do
183
+ User.exportable_options[:scopes].should == []
184
+ end
185
+
186
+ end
187
+
188
+ describe "passing in options" do
189
+
190
+ it "should toss out invalid options" do
191
+
192
+ class Post < ActiveRecord::Base
193
+ make_exportable :nonsense => "this should not be included", :offset => 20
194
+ end
195
+
196
+ # valid_options = [:as, :only, :except, :scopes, :conditions, :order, :include,
197
+ # :group, :having, :limit, :offset, :joins]
198
+ Post.exportable_options.include?(:nonsense).should be_false
199
+ Post.exportable_options.include?(:offset).should be_true
200
+ end
201
+
202
+ describe ":only/:except options" do
203
+
204
+ it "should not appear in exportable_options" do
205
+ # these get removed and converted into :columns
206
+ class Post < ActiveRecord::Base
207
+ make_exportable :only => [:this, :that, :another], :except => [:title]
208
+ end
209
+ Post.exportable_options.include?(:only).should be_false
210
+ Post.exportable_options.include?(:except).should be_false
211
+ end
212
+
213
+ it "should allow column names to be either strings or symbols" do
214
+ class Post < ActiveRecord::Base
215
+ make_exportable :only => ["full_name", :symbol_method]
216
+ end
217
+ Post.exportable_options[:columns].should == [:full_name, :symbol_method]
218
+ end
219
+
220
+ it "should allow a single column name or an array of names" do
221
+ class Post < ActiveRecord::Base
222
+ make_exportable :only => "full_name"
223
+ end
224
+ Post.exportable_options[:columns].should == [:full_name]
225
+ end
226
+
227
+ # unique for :only option
228
+ it "should allow attributes/methods which are not database columns" do
229
+
230
+ class Post < ActiveRecord::Base
231
+ make_exportable :only => ["a_method_name", "another_method"]
232
+ end
233
+
234
+ Post.exportable_options[:columns].should == [:a_method_name, :another_method]
235
+
236
+ end
237
+
238
+ it "should replace the default columns saved in exportable_options[:columns]" do
239
+
240
+ class Post < ActiveRecord::Base
241
+ make_exportable :only => ["a_method_name", "another_method"]
242
+ end
243
+
244
+ Post.exportable_options[:columns].should == [:a_method_name, :another_method]
245
+ end
246
+
247
+ # unless we add a check for this
248
+ it "should not try to catch bogus attribute/method names" do
249
+ class Post < ActiveRecord::Base
250
+ make_exportable :only => ["full_name", "another_method"]
251
+ end
252
+ Post.exportable_options[:columns].should == [:full_name, :another_method]
253
+ end
254
+
255
+ # unique for :except option
256
+ it "should subtract from the default columns saved in exportable_options[:columns]" do
257
+
258
+ class Post < ActiveRecord::Base
259
+ make_exportable :except => [:created_at, :updated_at]
260
+ end
261
+
262
+ Post.exportable_options[:columns].should == [:id, :title, :content, :approved]
263
+ end
264
+
265
+ it "should ignore bogus attribute/method names" do
266
+ class Post < ActiveRecord::Base
267
+ make_exportable :except => [:created_at, :updated_at, :bogus]
268
+ end
269
+ Post.exportable_options[:columns].should == [:id, :title, :content, :approved]
270
+ end
271
+
272
+ end
273
+
274
+ describe ":as option" do
275
+
276
+ it "should not appear in exportable_options" do
277
+ class Post < ActiveRecord::Base
278
+ make_exportable :as => [:csv, "xml"]
279
+ end
280
+ Post.exportable_options.include?(:as).should be_false
281
+ end
282
+
283
+ it "should allow format names to be either strings or symbols" do
284
+ class Post < ActiveRecord::Base
285
+ make_exportable :as => [:csv, "xml"]
286
+ end
287
+ Post.exportable_options[:formats].map(&:to_s).sort.should == ["csv", "xml"]
288
+ end
289
+
290
+ it "should allow a single format name or an array of names" do
291
+ class Post < ActiveRecord::Base
292
+ make_exportable :as => :json
293
+ end
294
+ Post.exportable_options[:formats].should == [:json]
295
+ end
296
+
297
+ it "should ignore formats not in the supported formats" do
298
+ class Post < ActiveRecord::Base
299
+ make_exportable :as => [:csv, "unsupported"]
300
+ end
301
+ Post.exportable_options[:formats].should == [:csv]
302
+ end
303
+
304
+ it "should raise an error if only unsupported formats are passed in" do
305
+ lambda do
306
+ class Post < ActiveRecord::Base
307
+ make_exportable :as => "unsupported"
308
+ end
309
+ end.should raise_error(MakeExportable::FormatNotFound)
310
+ end
311
+ end
312
+
313
+ describe ":scopes option" do
314
+ it "should be saved unchanged in exportable_options[:scopes]" do
315
+ Post.class_eval("make_exportable :scopes => [:scope1, :scope2]")
316
+ Post.exportable_options[:scopes].should == [:scope1, :scope2]
317
+ end
318
+ end
319
+
320
+ describe "finder options" do
321
+ [:conditions, :order, :include, :group, :having, :limit, :offset, :joins].each do |opt|
322
+ it "should save :#{opt} unchanged in exportable_options[:#{opt}]" do
323
+ Post.class_eval("make_exportable :#{opt} => 'accepts anything trusting the user'")
324
+ Post.exportable_options[opt].should == "accepts anything trusting the user"
325
+ end
326
+ end
327
+ end
328
+
329
+ end
330
+
331
+ end
332
+
333
+ describe "MakeExportable::ClassMethods" do
334
+
335
+ before(:each) do
336
+ class User
337
+ make_exportable
338
+ end
339
+ clean_database!
340
+ User.create(:first_name => "user_1", :last_name => "Doe", :created_at => Time.at(0), :updated_at => Time.at(0))
341
+ User.create(:first_name => "user_2", :last_name => "Doe", :created_at => Time.at(0), :updated_at => Time.at(0))
342
+ end
343
+
344
+ describe "exportable?" do
345
+
346
+ it "should be false for regular ActiveRecord classes" do
347
+ Unexportable.exportable?.should be_false
348
+ end
349
+
350
+ it "should be true for classes that call make_exportable" do
351
+ User.exportable?.should be_true
352
+ end
353
+
354
+ it "should be true when the argument value is an allowed format for this class" do
355
+ User.exportable?("csv").should be_true
356
+ end
357
+
358
+ it "should be false when the argument value is not an allowed format for this class" do
359
+ User.exportable?("unsupported").should be_false
360
+ end
361
+
362
+ end
363
+
364
+ describe "to_export" do
365
+
366
+ it "should use default headers if no :header option is sent"
367
+
368
+ it "should use :headers option for headers if sent"
369
+
370
+ it "should use no headers if :headers is false"
371
+
372
+ end
373
+
374
+ describe "get_export_data" do
375
+
376
+ context "scopes and find options" do
377
+ # Test how :scopes and find options are merged and applied
378
+ # (testing the private method :find_export_data)
379
+ it "should chainable on named_scopes" do
380
+ User.a_limiter.get_export_data(:only => [:first_name, :last_name]).should == [["first_name", "last_name"],["user_1", "Doe"]]
381
+ end
382
+
383
+ it "should allow a scope to be sent" do
384
+ User.get_export_data(:only => [:first_name, :last_name], :scopes => ['a_limiter']).should == [["first_name", "last_name"], ["user_1", "Doe"]]
385
+ end
386
+
387
+ it "should allow multiple scopes to be sent" do
388
+ User.get_export_data(:only =>[:first_name, :last_name], :scopes => ['a_limiter', "order_by"]).should == [["first_name", "last_name"], ["user_2", "Doe"]]
389
+ end
390
+
391
+ it "should find records using :conditions option" do
392
+ if ActiveRecord::VERSION::MAJOR < 3
393
+ User.get_export_data(:only => [:first_name, :last_name], :conditions => {:first_name => "user_1"} ).should == [["first_name", "last_name"], ["user_1", "Doe"]]
394
+ end
395
+ end
396
+
397
+ it "should sort records using :order option" do
398
+ if ActiveRecord::VERSION::MAJOR < 3
399
+ User.get_export_data(:only => [:first_name, :last_name], :order => "id DESC").should == [["first_name", "last_name"], ["user_2", "Doe"], ["user_1", "Doe"]]
400
+ end
401
+ end
402
+
403
+ it "should limit records using :limit option" do
404
+ if ActiveRecord::VERSION::MAJOR < 3
405
+ User.get_export_data(:only => [:first_name, :last_name], :limit => 1, :order => "id ASC" ).should == [["first_name", "last_name"], ["user_1", "Doe"]]
406
+ end
407
+ end
408
+
409
+ # TODO: Test how :scopes and find options get merged when there are default options
410
+
411
+ end
412
+
413
+ context "column selection" do
414
+ # Test how :only/:except are merged and selected
415
+ # (testing the private method :map_export_data)
416
+ it "should export the default columns by default" do
417
+ time = User.first.created_at.to_s
418
+ User.get_export_data().should == [["id", "first_name", "last_name", "password", "email", "is_admin", "created_at", "updated_at"], ["1", "user_1", "Doe", "", "", "false", Time.at(0).to_s, Time.at(0).to_s], ["2", "user_2", "Doe", "", "", "false", Time.at(0).to_s, Time.at(0).to_s]]
419
+ end
420
+
421
+ it "should export only the columns given by :only" do
422
+ User.get_export_data(:only => [:first_name, :last_name]).should == [["first_name", "last_name"], ["user_1", "Doe"], ["user_2", "Doe"]]
423
+ end
424
+
425
+ it "should export the default columns minus those given by :except" do
426
+ User.get_export_data(:except => [:id, :created_at, :updated_at, :password, :is_admin]).should == [["first_name", "last_name", "email"], ["user_1", "Doe", ""], ["user_2", "Doe", ""]]
427
+ end
428
+
429
+ it "should raise an error if no columns are passed" do
430
+ lambda do
431
+ User.nothing.get_export_data(:except =>["id", "first_name", "last_name", "password", "email", "is_admin", "created_at", "updated_at"])
432
+ end.should raise_error(MakeExportable::ExportFault)
433
+ end
434
+
435
+ # TODO: Test how :only/:except get merged when there are default options
436
+
437
+ end
438
+
439
+ end
440
+
441
+ describe "create_report" do
442
+
443
+ it "should raise an FormatNotFound if the format is not supported" do
444
+ lambda do
445
+ User.create_report("NONSUPPORTED", "")
446
+ end.should raise_error(MakeExportable::FormatNotFound)
447
+ end
448
+
449
+ it 'should export an array of header and array of arrays of rows in the specified format' do
450
+ User.create_report("csv", [[ "data", 'lovely data'],["", "more lovely data"]], :headers => ["Title", "Another Title"]).should == ["Title,Another Title\ndata,lovely data\n\"\",more lovely data\n", "text/csv; charset=utf-8; header=present"]
451
+ end
452
+
453
+ it 'should export array of arrays of rows in the specified format if header is set to true with header set as present' do
454
+ User.create_report("csv", [["Title", "Another Title"],[ "data", 'lovely data'],["", "more lovely data"]], :headers => true).should == ["Title,Another Title\ndata,lovely data\n\"\",more lovely data\n", "text/csv; charset=utf-8; header=present"]
455
+ end
456
+
457
+ it 'should export array of arrays of rows in the specified format if header is set to false' do
458
+ User.create_report("csv", [[ "data", 'lovely data'],["", "more lovely data"]], :headers => false).should == ["data,lovely data\n\"\",more lovely data\n", "text/csv; charset=utf-8; header=absent"]
459
+ end
460
+
461
+ it "should raise an ExportFault if the datasets are not all the same size" do
462
+ lambda do
463
+ User.create_report("xml", [[ "data", 'lovely data'],["more lovely data"]], :headers =>["Title", "Another Title"])
464
+ end.should raise_error(MakeExportable::ExportFault)
465
+ end
466
+
467
+ end
468
+
469
+ end
470
+
471
+ describe "MakeExportable::InstanceMethods" do
472
+
473
+ describe "#export_attribute" do
474
+
475
+ before(:each) do
476
+ class User
477
+ make_exportable
478
+ end
479
+ @user = User.create(:first_name => "Carl", :last_name => "Joans")
480
+ end
481
+
482
+ it "should return attribute values" do
483
+ @user.export_attribute('is_admin').should == "false"
484
+ end
485
+
486
+ it 'should allow #{method}_export to override the original method' do
487
+ class User < ActiveRecord::Base
488
+ def is_admin_export
489
+ return "I'm not an Admin I'm a monkey"
490
+ end
491
+ end
492
+ @user.export_attribute('is_admin').should == "I'm not an Admin I'm a monkey"
493
+ end
494
+
495
+ # TODO: Would it be better behavior to raise an error?
496
+ it "should return an empty string if the attribute doesn't exist" do
497
+ @user.export_attribute('ful_name').should == ""
498
+ end
499
+
500
+ it "should be able to call methods as attributes during exporting" do
501
+ @user.export_attribute('full_name').should == "Carl Joans"
502
+ end
503
+
504
+ end
505
+
506
+ end
507
+
508
+ end
509
+
510
+ end