jnicklas-carrierwave 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/README.rdoc +35 -20
  2. data/Rakefile +1 -1
  3. data/lib/carrierwave/compatibility/paperclip.rb +91 -0
  4. data/lib/carrierwave/core_ext/inheritable_attributes.rb +102 -0
  5. data/lib/carrierwave/core_ext/module_setup.rb +49 -0
  6. data/lib/carrierwave/mount.rb +119 -103
  7. data/lib/carrierwave/orm/activerecord.rb +6 -1
  8. data/lib/carrierwave/orm/sequel.rb +15 -2
  9. data/lib/carrierwave/processing/rmagick.rb +8 -7
  10. data/lib/carrierwave/storage/abstract.rb +16 -1
  11. data/lib/carrierwave/storage/file.rb +20 -1
  12. data/lib/carrierwave/uploader/cache.rb +114 -0
  13. data/lib/carrierwave/uploader/callbacks.rb +40 -0
  14. data/lib/carrierwave/uploader/default_path.rb +21 -0
  15. data/lib/carrierwave/uploader/extension_whitelist.rb +35 -0
  16. data/lib/carrierwave/uploader/mountable.rb +37 -0
  17. data/lib/carrierwave/uploader/paths.rb +25 -0
  18. data/lib/carrierwave/uploader/processing.rb +79 -0
  19. data/lib/carrierwave/uploader/proxy.rb +60 -0
  20. data/lib/carrierwave/uploader/remove.rb +21 -0
  21. data/lib/carrierwave/uploader/store.rb +154 -0
  22. data/lib/carrierwave/uploader/url.rb +22 -0
  23. data/lib/carrierwave/uploader/versions.rb +145 -0
  24. data/lib/carrierwave/uploader.rb +31 -593
  25. data/lib/carrierwave.rb +55 -7
  26. data/lib/generators/uploader_generator.rb +1 -1
  27. data/rails_generators/uploader/templates/uploader.rb +12 -8
  28. data/spec/compatibility/paperclip_spec.rb +41 -0
  29. data/spec/mount_spec.rb +88 -25
  30. data/spec/orm/activerecord_spec.rb +7 -9
  31. data/spec/orm/datamapper_spec.rb +7 -9
  32. data/spec/orm/sequel_spec.rb +47 -32
  33. data/spec/spec_helper.rb +13 -0
  34. data/spec/uploader/cache_spec.rb +194 -0
  35. data/spec/uploader/default_path_spec.rb +66 -0
  36. data/spec/uploader/extension_whitelist_spec.rb +42 -0
  37. data/spec/uploader/mountable_spec.rb +31 -0
  38. data/spec/uploader/paths_spec.rb +20 -0
  39. data/spec/uploader/processing_spec.rb +60 -0
  40. data/spec/uploader/proxy_spec.rb +52 -0
  41. data/spec/uploader/remove_spec.rb +65 -0
  42. data/spec/uploader/store_spec.rb +260 -0
  43. data/spec/uploader/url_spec.rb +85 -0
  44. data/spec/uploader/versions_spec.rb +275 -0
  45. metadata +34 -3
  46. data/spec/uploader_spec.rb +0 -887
@@ -1,603 +1,41 @@
1
1
  module CarrierWave
2
2
 
3
3
  ##
4
- # An uploader is a class that allows you to easily handle the caching and storage of
5
- # uploaded files. Please refer to the README for configuration options.
6
- #
7
- # Once you have an uploader you can use it in isolation:
8
- #
9
- # my_uploader = MyUploader.new
10
- # my_uploader.cache!(File.open(path_to_file))
11
- # my_uploader.retrieve_from_store!('monkey.png')
12
- #
13
- # Alternatively, you can mount it on an ORM or other persistence layer, with
14
- # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper
15
- # these are *very* simple (they are only a dozen lines of code), so adding your own should
16
- # be trivial.
4
+ # See CarrierWave::Uploader::Base
17
5
  #
18
6
  module Uploader
19
7
 
20
- def self.append_features(base) #:nodoc:
21
- super
22
- base.extend(ClassMethods)
23
- end
24
-
25
- ##
26
- # Generates a unique cache id for use in the caching system
27
- #
28
- # === Returns
29
- #
30
- # [String] a cache id in the format YYYYMMDD-HHMM-PID-RND
31
- #
32
- def self.generate_cache_id
33
- Time.now.strftime('%Y%m%d-%H%M') + '-' + Process.pid.to_s + '-' + ("%04d" % rand(9999))
34
- end
35
-
36
- module ClassMethods
37
-
38
- ##
39
- # Lists processor callbacks declared
40
- #
41
- # === Returns
42
- #
43
- # [Array[Array[Symbol, Array]]] a list of processor callbacks which have been declared for this uploader
44
- #
45
- def processors
46
- @processors ||= []
47
- end
48
-
49
- ##
50
- # Adds a processor callback which applies operations as a file is uploaded.
51
- # The argument may be the name of any method of the uploader, expressed as a symbol,
52
- # or a list of such methods, or a hash where the key is a method and the value is
53
- # an array of arguments to call the method with
54
- #
55
- # === Parameters
56
- #
57
- # args (*Symbol, Hash{Symbol => Array[]})
58
- #
59
- # === Examples
60
- #
61
- # class MyUploader
62
- # include CarrierWave::Uploader
63
- #
64
- # process :sepiatone, :vignette
65
- # process :scale => [200, 200]
66
- #
67
- # def sepiatone
68
- # ...
69
- # end
70
- #
71
- # def vignette
72
- # ...
73
- # end
74
- #
75
- # def scale(height, width)
76
- # ...
77
- # end
78
- # end
79
- #
80
- def process(*args)
81
- args.each do |arg|
82
- if arg.is_a?(Hash)
83
- arg.each do |method, args|
84
- processors.push([method, args])
85
- end
86
- else
87
- processors.push([arg, []])
88
- end
89
- end
90
- end
91
-
92
- ##
93
- # Sets the storage engine to be used when storing files with this uploader.
94
- # Can be any class that implements a #store!(CarrierWave::SanitizedFile) and a #retrieve!
95
- # method. See lib/carrierwave/storage/file.rb for an example. Storage engines should
96
- # be added to CarrierWave.config[:storage_engines] so they can be referred
97
- # to by a symbol, which should be more convenient
98
- #
99
- # If no argument is given, it will simply return the currently used storage engine.
100
- #
101
- # === Parameters
102
- #
103
- # [storage (Symbol, Class)] The storage engine to use for this uploader
104
- #
105
- # === Returns
106
- #
107
- # [Class] the storage engine to be used with this uploader
108
- #
109
- # === Examples
110
- #
111
- # storage :file
112
- # storage CarrierWave::Storage::File
113
- # storage MyCustomStorageEngine
114
- #
115
- def storage(storage = nil)
116
- if storage.is_a?(Symbol)
117
- @storage = get_storage_by_symbol(storage)
118
- @storage.setup!
119
- elsif storage
120
- @storage = storage
121
- @storage.setup!
122
- elsif @storage.nil?
123
- # Get the storage from the superclass if there is one
124
- @storage = superclass.storage rescue nil
125
- end
126
- if @storage.nil?
127
- # If we were not able to find a store any other way, setup the default store
128
- @storage ||= get_storage_by_symbol(CarrierWave.config[:storage])
129
- @storage.setup!
130
- end
131
- return @storage
132
- end
133
-
134
- alias_method :storage=, :storage
135
-
136
- def version_names
137
- @version_names ||= []
138
- end
139
-
140
- ##
141
- # Adds a new version to this uploader
142
- #
143
- # === Parameters
144
- #
145
- # [name (#to_sym)] name of the version
146
- # [&block (Proc)] a block to eval on this version of the uploader
147
- #
148
- def version(name, &block)
149
- name = name.to_sym
150
- unless versions[name]
151
- versions[name] = Class.new(self)
152
- versions[name].version_names.push(*version_names)
153
- versions[name].version_names.push(name)
154
- class_eval <<-RUBY
155
- def #{name}
156
- versions[:#{name}]
157
- end
158
- RUBY
159
- end
160
- versions[name].class_eval(&block) if block
161
- versions[name]
162
- end
163
-
164
- ##
165
- # === Returns
166
- #
167
- # [Hash{Symbol => Class}] a list of versions available for this uploader
168
- #
169
- def versions
170
- @versions ||= {}
171
- end
172
-
173
- private
174
-
175
- def get_storage_by_symbol(symbol)
176
- eval(CarrierWave.config[:storage_engines][symbol])
177
- end
178
-
179
- end # ClassMethods
180
-
181
- attr_reader :file, :model, :mounted_as
182
-
183
- ##
184
- # If a model is given as the first parameter, it will stored in the uploader, and
185
- # available throught +#model+. Likewise, mounted_as stores the name of the column
186
- # where this instance of the uploader is mounted. These values can then be used inside
187
- # your uploader.
188
- #
189
- # If you do not wish to mount your uploaders with the ORM extensions in -more then you
190
- # can override this method inside your uploader.
191
- #
192
- # === Parameters
193
- #
194
- # [model (Object)] Any kind of model object
195
- # [mounted_as (Symbol)] The name of the column where this uploader is mounted
196
- #
197
- # === Examples
198
- #
199
- # class MyUploader
200
- # include CarrierWave::Uploader
201
- #
202
- # def store_dir
203
- # File.join('public', 'files', mounted_as, model.permalink)
204
- # end
205
- # end
206
- #
207
- def initialize(model=nil, mounted_as=nil)
208
- @model = model
209
- @mounted_as = mounted_as
210
- end
211
-
212
- ##
213
- # === Returns
214
- #
215
- # [Boolean] Whether the uploaded file is blank
216
- #
217
- def blank?
218
- !file or file.blank?
219
- end
220
-
221
- ##
222
- # Apply all process callbacks added through CarrierWave.process
223
- #
224
- def process!
225
- self.class.processors.each do |method, args|
226
- self.send(method, *args)
227
- end
228
- end
229
-
230
- ##
231
- # === Returns
232
- #
233
- # [String] the path where the file is currently located.
234
- #
235
- def current_path
236
- file.path if file.respond_to?(:path)
237
- end
238
-
239
- alias_method :path, :current_path
240
-
241
- ##
242
- # Returns a hash mapping the name of each version of the uploader to an instance of it
243
- #
244
- # === Returns
245
- #
246
- # [Hash{Symbol => CarrierWave::Uploader}] a list of uploader instances
247
- #
248
- def versions
249
- return @versions if @versions
250
- @versions = {}
251
- self.class.versions.each do |name, klass|
252
- @versions[name] = klass.new(model, mounted_as)
253
- end
254
- @versions
255
- end
256
-
257
- ##
258
- # === Returns
259
- #
260
- # [String] the location where this file is accessible via a url
261
- #
262
- def url(*args)
263
- if(args.first)
264
- # recursively proxy to version
265
- versions[args.first.to_sym].url(*args[1..-1])
266
- else
267
- if file.respond_to?(:url) and not file.url.blank?
268
- file.url
269
- elsif current_path
270
- File.expand_path(current_path).gsub(File.expand_path(public), '')
271
- end
272
- end
273
- end
274
-
275
- alias_method :to_s, :url
276
-
277
8
  ##
278
- # Returns a string that uniquely identifies the last stored file
279
- #
280
- # === Returns
281
- #
282
- # [String] uniquely identifies a file
283
- #
284
- def identifier
285
- file.identifier if file.respond_to?(:identifier)
286
- end
287
-
288
- ##
289
- # Read the contents of the file
290
- #
291
- # === Returns
292
- #
293
- # [String] contents of the file
294
- #
295
- def read
296
- file.read if file.respond_to?(:read)
297
- end
298
-
299
- ##
300
- # Fetches the size of the currently stored/cached file
301
- #
302
- # === Returns
303
- #
304
- # [Integer] size of the file
305
- #
306
- def size
307
- file.respond_to?(:size) ? file.size : 0
308
- end
309
-
310
- ##
311
- # Override this in your Uploader to change the filename.
312
- #
313
- # Be careful using record ids as filenames. If the filename is stored in the database
314
- # the record id will be nil when the filename is set. Don't use record ids unless you
315
- # understand this limitation.
316
- #
317
- # Do not use the version_name in the filename, as it will prevent versions from being
318
- # loaded correctly.
319
- #
320
- # === Returns
321
- #
322
- # [String] a filename
323
- #
324
- def filename
325
- @filename
326
- end
327
-
328
- ##
329
- # === Returns
330
- #
331
- # [String] the name of this version of the uploader
332
- #
333
- def version_name
334
- self.class.version_names.join('_').to_sym unless self.class.version_names.blank?
335
- end
336
-
337
- ##
338
- # === Returns
339
- #
340
- # [String] the directory that is the root of the application
341
- #
342
- def root
343
- CarrierWave.config[:root]
344
- end
345
-
346
- ##
347
- # === Returns
348
- #
349
- # [String] the directory where files will be publically accessible
350
- #
351
- def public
352
- CarrierWave.config[:public]
353
- end
354
-
355
- ##
356
- # Override this method in your uploader to provide a white list of extensions which
357
- # are allowed to be uploaded.
358
- #
359
- # === Returns
360
- #
361
- # [NilClass, Array[String]] a white list of extensions which are allowed to be uploaded
362
- #
363
- # === Examples
364
- #
365
- # def extension_white_list
366
- # %w(jpg jpeg gif png)
367
- # end
368
- #
369
- def extension_white_list; end
370
-
371
- ####################
372
- ## Cache
373
- ####################
374
-
375
- ##
376
- #
377
- def cached?
378
- @cache_id
379
- end
380
-
381
- ##
382
- # Override this in your Uploader to change the directory where files are cached.
383
- #
384
- # === Returns
385
- #
386
- # [String] a directory
387
- #
388
- def cache_dir
389
- CarrierWave.config[:cache_dir]
390
- end
391
-
392
- ##
393
- # Returns a String which uniquely identifies the currently cached file for later retrieval
394
- #
395
- # === Returns
396
- #
397
- # [String] a cache name, in the format YYYYMMDD-HHMM-PID-RND/filename.txt
398
- #
399
- def cache_name
400
- File.join(cache_id, [version_name, original_filename].compact.join('_')) if cache_id and original_filename
401
- end
402
-
403
- ##
404
- # Caches the given file unless a file has already been cached, stored or retrieved.
405
- #
406
- # === Parameters
407
- #
408
- # [new_file (File, IOString, Tempfile)] any kind of file object
409
- #
410
- # === Raises
411
- #
412
- # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
413
- #
414
- def cache(new_file)
415
- cache!(new_file) unless file
416
- end
417
-
418
- ##
419
- # Caches the given file. Calls process! to trigger any process callbacks.
420
- #
421
- # === Parameters
422
- #
423
- # [new_file (File, IOString, Tempfile)] any kind of file object
424
- #
425
- # === Raises
426
- #
427
- # [CarrierWave::FormNotMultipart] if the assigned parameter is a string
428
- #
429
- def cache!(new_file)
430
- new_file = CarrierWave::SanitizedFile.new(new_file)
431
- raise CarrierWave::FormNotMultipart if new_file.is_path?
432
-
433
- unless new_file.empty?
434
- if extension_white_list and not extension_white_list.include?(new_file.extension.to_s)
435
- raise CarrierWave::IntegrityError, "You are not allowed to upload #{new_file.extension.inspect} files, allowed types: #{extension_white_list.inspect}"
436
- end
437
-
438
- self.cache_id = CarrierWave::Uploader.generate_cache_id unless cache_id
439
-
440
- @file = new_file
441
-
442
- @filename = new_file.filename
443
- self.original_filename = new_file.filename
444
-
445
- if CarrierWave.config[:cache_to_cache_dir]
446
- @file = @file.copy_to(cache_path, CarrierWave.config[:permissions])
447
- end
448
-
449
- process!
450
-
451
- versions.each do |name, v|
452
- v.send(:cache_id=, cache_id)
453
- v.cache!(new_file)
454
- end
455
- end
456
- end
457
-
458
- ##
459
- # Retrieves the file with the given cache_name from the cache, unless a file has
460
- # already been cached, stored or retrieved.
461
- #
462
- # === Parameters
463
- #
464
- # [cache_name (String)] uniquely identifies a cache file
465
- #
466
- def retrieve_from_cache(cache_name)
467
- retrieve_from_cache!(cache_name) unless file
468
- rescue CarrierWave::InvalidParameter
469
- end
470
-
471
- ##
472
- # Retrieves the file with the given cache_name from the cache.
473
- #
474
- # === Parameters
475
- #
476
- # [cache_name (String)] uniquely identifies a cache file
477
- #
478
- # === Raises
479
- #
480
- # [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
481
- #
482
- def retrieve_from_cache!(cache_name)
483
- self.cache_id, self.original_filename = cache_name.split('/', 2)
484
- @filename = original_filename
485
- @file = CarrierWave::SanitizedFile.new(cache_path)
486
- versions.each { |name, v| v.retrieve_from_cache!(cache_name) }
487
- end
488
-
489
- ####################
490
- ## STORE
491
- ####################
492
-
493
- ##
494
- # Override this in your Uploader to change the directory where the file backend stores files.
495
- #
496
- # Other backends may or may not use this method, depending on their specific needs.
497
- #
498
- # === Returns
499
- #
500
- # [String] a directory
501
- #
502
- def store_dir
503
- CarrierWave.config[:store_dir]
504
- end
505
-
506
- ##
507
- # Calculates the path where the file should be stored. If +for_file+ is given, it will be
508
- # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.
509
- #
510
- # === Parameters
511
- #
512
- # [for_file (String)] name of the file <optional>
513
- #
514
- # === Returns
515
- #
516
- # [String] the store path
517
- #
518
- def store_path(for_file=filename)
519
- File.join(store_dir, [version_name, for_file].compact.join('_'))
520
- end
521
-
522
- ##
523
- # Stores the file by passing it to this Uploader's storage engine, unless a file has
524
- # already been cached, stored or retrieved.
525
- #
526
- # If CarrierWave.config[:use_cache] is true, it will first cache the file
527
- # and apply any process callbacks before uploading it.
528
- #
529
- # === Parameters
530
- #
531
- # [new_file (File, IOString, Tempfile)] any kind of file object
532
- #
533
- def store(new_file)
534
- store!(new_file) unless file
535
- end
536
-
537
- ##
538
- # Stores the file by passing it to this Uploader's storage engine.
539
- #
540
- # If new_file is omitted, a previously cached file will be stored.
541
- #
542
- # === Parameters
543
- #
544
- # [new_file (File, IOString, Tempfile)] any kind of file object
545
- #
546
- def store!(new_file=nil)
547
- cache!(new_file) if new_file
548
- if @file and @cache_id
549
- @file = storage.store!(self, @file)
550
- @cache_id = nil
551
- versions.each { |name, v| v.store!(new_file) }
552
- end
553
- end
554
-
555
- ##
556
- # Retrieves the file from the storage, unless a file has
557
- # already been cached, stored or retrieved.
558
- #
559
- # === Parameters
560
- #
561
- # [identifier (String)] uniquely identifies the file to retrieve
562
- #
563
- def retrieve_from_store(identifier)
564
- retrieve_from_store!(identifier) unless file
565
- rescue CarrierWave::InvalidParameter
566
- end
567
-
568
- ##
569
- # Retrieves the file from the storage.
570
- #
571
- # === Parameters
572
- #
573
- # [identifier (String)] uniquely identifies the file to retrieve
574
- #
575
- def retrieve_from_store!(identifier)
576
- @file = storage.retrieve!(self, identifier)
577
- versions.each { |name, v| v.retrieve_from_store!(identifier) }
578
- end
579
-
580
- private
581
-
582
- def cache_path
583
- File.expand_path(File.join(cache_dir, cache_name), public)
584
- end
585
-
586
- def storage
587
- self.class.storage
588
- end
589
-
590
- attr_reader :cache_id, :original_filename
591
-
592
- def cache_id=(cache_id)
593
- raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]{8}\-[\d]{4}\-[\d]+\-[\d]{4}\z/
594
- @cache_id = cache_id
595
- end
596
-
597
- def original_filename=(filename)
598
- raise CarrierWave::InvalidParameter, "invalid filename" unless filename =~ /\A[a-z0-9\.\-\+_]+\z/i
599
- @original_filename = filename
600
- end
9
+ # An uploader is a class that allows you to easily handle the caching and storage of
10
+ # uploaded files. Please refer to the README for configuration options.
11
+ #
12
+ # Once you have an uploader you can use it in isolation:
13
+ #
14
+ # my_uploader = MyUploader.new
15
+ # my_uploader.cache!(File.open(path_to_file))
16
+ # my_uploader.retrieve_from_store!('monkey.png')
17
+ #
18
+ # Alternatively, you can mount it on an ORM or other persistence layer, with
19
+ # +CarrierWave::Mount#mount_uploader+. There are extensions for activerecord and datamapper
20
+ # these are *very* simple (they are only a dozen lines of code), so adding your own should
21
+ # be trivial.
22
+ #
23
+ class Base
24
+ attr_reader :file
25
+
26
+ use CarrierWave::Uploader::Paths
27
+ use CarrierWave::Uploader::Callbacks
28
+ use CarrierWave::Uploader::Proxy
29
+ use CarrierWave::Uploader::Url
30
+ use CarrierWave::Uploader::Mountable
31
+ use CarrierWave::Uploader::Cache
32
+ use CarrierWave::Uploader::Store
33
+ use CarrierWave::Uploader::Remove
34
+ use CarrierWave::Uploader::ExtensionWhitelist
35
+ use CarrierWave::Uploader::DefaultPath
36
+ use CarrierWave::Uploader::Processing
37
+ use CarrierWave::Uploader::Versions
38
+ end # Base
601
39
 
602
40
  end # Uploader
603
41
  end # CarrierWave