make_exportable 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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