carrierwave 0.9.0 → 3.0.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of carrierwave might be problematic. Click here for more details.

Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +508 -158
  3. data/lib/carrierwave/compatibility/paperclip.rb +31 -21
  4. data/lib/carrierwave/downloader/base.rb +101 -0
  5. data/lib/carrierwave/downloader/remote_file.rb +68 -0
  6. data/lib/carrierwave/error.rb +1 -0
  7. data/lib/carrierwave/locale/en.yml +11 -5
  8. data/lib/carrierwave/mount.rb +220 -187
  9. data/lib/carrierwave/mounter.rb +255 -0
  10. data/lib/carrierwave/orm/activerecord.rb +24 -34
  11. data/lib/carrierwave/processing/mini_magick.rb +142 -79
  12. data/lib/carrierwave/processing/rmagick.rb +76 -35
  13. data/lib/carrierwave/processing/vips.rb +284 -0
  14. data/lib/carrierwave/processing.rb +1 -1
  15. data/lib/carrierwave/sanitized_file.rb +89 -70
  16. data/lib/carrierwave/storage/abstract.rb +16 -3
  17. data/lib/carrierwave/storage/file.rb +71 -3
  18. data/lib/carrierwave/storage/fog.rb +215 -58
  19. data/lib/carrierwave/storage.rb +1 -7
  20. data/lib/carrierwave/test/matchers.rb +88 -19
  21. data/lib/carrierwave/uploader/cache.rb +88 -44
  22. data/lib/carrierwave/uploader/callbacks.rb +1 -3
  23. data/lib/carrierwave/uploader/configuration.rb +81 -9
  24. data/lib/carrierwave/uploader/content_type_allowlist.rb +62 -0
  25. data/lib/carrierwave/uploader/content_type_denylist.rb +62 -0
  26. data/lib/carrierwave/uploader/default_url.rb +3 -5
  27. data/lib/carrierwave/uploader/dimension.rb +66 -0
  28. data/lib/carrierwave/uploader/download.rb +5 -69
  29. data/lib/carrierwave/uploader/extension_allowlist.rb +63 -0
  30. data/lib/carrierwave/uploader/extension_denylist.rb +64 -0
  31. data/lib/carrierwave/uploader/file_size.rb +43 -0
  32. data/lib/carrierwave/uploader/mountable.rb +13 -8
  33. data/lib/carrierwave/uploader/processing.rb +54 -21
  34. data/lib/carrierwave/uploader/proxy.rb +30 -8
  35. data/lib/carrierwave/uploader/remove.rb +0 -2
  36. data/lib/carrierwave/uploader/serialization.rb +3 -5
  37. data/lib/carrierwave/uploader/store.rb +59 -28
  38. data/lib/carrierwave/uploader/url.rb +8 -7
  39. data/lib/carrierwave/uploader/versions.rb +173 -124
  40. data/lib/carrierwave/uploader.rb +12 -6
  41. data/lib/carrierwave/utilities/file_name.rb +47 -0
  42. data/lib/carrierwave/utilities/uri.rb +14 -12
  43. data/lib/carrierwave/utilities.rb +2 -3
  44. data/lib/carrierwave/validations/active_model.rb +7 -13
  45. data/lib/carrierwave/version.rb +1 -1
  46. data/lib/carrierwave.rb +41 -16
  47. data/lib/generators/templates/{uploader.rb → uploader.rb.erb} +5 -9
  48. data/lib/generators/uploader_generator.rb +3 -3
  49. metadata +224 -100
  50. data/lib/carrierwave/locale/cs.yml +0 -11
  51. data/lib/carrierwave/locale/de.yml +0 -11
  52. data/lib/carrierwave/locale/nl.yml +0 -11
  53. data/lib/carrierwave/locale/sk.yml +0 -11
  54. data/lib/carrierwave/processing/mime_types.rb +0 -73
  55. data/lib/carrierwave/uploader/extension_blacklist.rb +0 -47
  56. data/lib/carrierwave/uploader/extension_whitelist.rb +0 -49
@@ -1,5 +1,3 @@
1
- # encoding: utf-8
2
-
3
1
  module CarrierWave
4
2
 
5
3
  ##
@@ -20,15 +18,11 @@ module CarrierWave
20
18
  # [Hash{Symbol => CarrierWave}] what uploaders are mounted on which columns
21
19
  #
22
20
  def uploaders
23
- @uploaders ||= {}
24
- @uploaders = superclass.uploaders.merge(@uploaders) if superclass.respond_to?(:uploaders)
25
- @uploaders
21
+ @uploaders ||= superclass.respond_to?(:uploaders) ? superclass.uploaders.dup : {}
26
22
  end
27
23
 
28
24
  def uploader_options
29
- @uploader_options ||= {}
30
- @uploader_options = superclass.uploader_options.merge(@uploader_options) if superclass.respond_to?(:uploader_options)
31
- @uploader_options
25
+ @uploader_options ||= superclass.respond_to?(:uploader_options) ? superclass.uploader_options.dup : {}
32
26
  end
33
27
 
34
28
  ##
@@ -66,7 +60,7 @@ module CarrierWave
66
60
  # will create an anonymous uploader class.
67
61
  #
68
62
  # Passing a block makes it possible to customize the uploader. This can be
69
- # convenient for brevity, but if there is any significatnt logic in the
63
+ # convenient for brevity, but if there is any significant logic in the
70
64
  # uploader, you should do the right thing and have it in its own file.
71
65
  #
72
66
  # === Added instance methods
@@ -96,7 +90,6 @@ module CarrierWave
96
90
  # [image_processing_error] Returns an error object if the last file to be assigned caused a processing error
97
91
  # [image_download_error] Returns an error object if the last file to be remotely assigned caused a download error
98
92
  #
99
- # [write_image_identifier] Uses the write_uploader method to set the identifier.
100
93
  # [image_identifier] Reads out the identifier of the file
101
94
  #
102
95
  # === Parameters
@@ -139,270 +132,310 @@ module CarrierWave
139
132
  # end
140
133
  #
141
134
  def mount_uploader(column, uploader=nil, options={}, &block)
142
- if block_given?
143
- uploader = Class.new(uploader || CarrierWave::Uploader::Base)
144
- const_set("Uploader#{uploader.object_id}".gsub('-', '_'), uploader)
145
- uploader.class_eval(&block)
146
- uploader.recursively_apply_block_to_versions(&block)
147
- else
148
- uploader ||= begin
149
- u = Class.new(CarrierWave::Uploader::Base)
150
- const_set("Uploader#{u.object_id}".gsub('-', '_'), u)
151
- u
152
- end
153
- end
154
-
155
- uploaders[column.to_sym] = uploader
156
- uploader_options[column.to_sym] = options
157
-
158
- include CarrierWave::Mount::Extension
159
-
160
- # Make sure to write over accessors directly defined on the class.
161
- # Simply super to the included module below.
162
- class_eval <<-RUBY, __FILE__, __LINE__+1
163
- def #{column}; super; end
164
- def #{column}=(new_file); super; end
165
- RUBY
135
+ mount_base(column, uploader, options.merge(multiple: false), &block)
166
136
 
167
- # Mixing this in as a Module instead of class_evaling directly, so we
168
- # can maintain the ability to super to any of these methods from within
169
- # the class.
170
137
  mod = Module.new
171
138
  include mod
172
139
  mod.class_eval <<-RUBY, __FILE__, __LINE__+1
173
140
 
174
141
  def #{column}
175
- _mounter(:#{column}).uploader
142
+ _mounter(:#{column}).uploaders[0] ||= _mounter(:#{column}).blank_uploader
176
143
  end
177
144
 
178
145
  def #{column}=(new_file)
179
- _mounter(:#{column}).cache(new_file)
180
- end
181
-
182
- def #{column}?
183
- !_mounter(:#{column}).blank?
146
+ _mounter(:#{column}).cache([new_file])
184
147
  end
185
148
 
186
149
  def #{column}_url(*args)
187
- _mounter(:#{column}).url(*args)
150
+ #{column}.url(*args)
188
151
  end
189
152
 
190
153
  def #{column}_cache
191
- _mounter(:#{column}).cache_name
154
+ _mounter(:#{column}).cache_names[0]
192
155
  end
193
156
 
194
157
  def #{column}_cache=(cache_name)
195
- _mounter(:#{column}).cache_name = cache_name
158
+ _mounter(:#{column}).cache_names = [cache_name]
196
159
  end
197
160
 
198
161
  def remote_#{column}_url
199
- _mounter(:#{column}).remote_url
162
+ [_mounter(:#{column}).remote_urls].flatten[0]
200
163
  end
201
164
 
202
165
  def remote_#{column}_url=(url)
203
- _mounter(:#{column}).remote_url = url
166
+ _mounter(:#{column}).remote_urls = url
204
167
  end
205
168
 
206
- def remove_#{column}
207
- _mounter(:#{column}).remove
169
+ def remote_#{column}_request_header=(header)
170
+ _mounter(:#{column}).remote_request_headers = [header]
208
171
  end
209
172
 
210
- def remove_#{column}!
211
- _mounter(:#{column}).remove!
173
+ def #{column}_identifier
174
+ _mounter(:#{column}).read_identifiers[0]
212
175
  end
213
176
 
214
- def remove_#{column}=(value)
215
- _mounter(:#{column}).remove = value
177
+ def #{column}_integrity_error
178
+ #{column}_integrity_errors.last
216
179
  end
217
180
 
218
- def remove_#{column}?
219
- _mounter(:#{column}).remove?
181
+ def #{column}_processing_error
182
+ #{column}_processing_errors.last
220
183
  end
221
184
 
222
- def store_#{column}!
223
- _mounter(:#{column}).store!
185
+ def #{column}_download_error
186
+ #{column}_download_errors.last
224
187
  end
188
+ RUBY
189
+ end
225
190
 
226
- def #{column}_integrity_error
227
- _mounter(:#{column}).integrity_error
228
- end
191
+ ##
192
+ # Mounts the given uploader on the given array column. This means that
193
+ # assigning and reading from the array column will upload and retrieve
194
+ # multiple files. Supposing that a User class has an uploader mounted on
195
+ # images, and that images can store an array, for example it could be a
196
+ # PostgreSQL JSON column. You can assign and retrieve files like this:
197
+ #
198
+ # @user.images # => []
199
+ # @user.images = [some_file_object]
200
+ # @user.images # => [<Uploader>]
201
+ #
202
+ # @user.images[0].url # => '/some_url.png'
203
+ #
204
+ # It is also possible (but not recommended) to omit the uploader, which
205
+ # will create an anonymous uploader class.
206
+ #
207
+ # Passing a block makes it possible to customize the uploader. This can be
208
+ # convenient for brevity, but if there is any significant logic in the
209
+ # uploader, you should do the right thing and have it in its own file.
210
+ #
211
+ # === Added instance methods
212
+ #
213
+ # Supposing a class has used +mount_uploaders+ to mount an uploader on a column
214
+ # named +images+, in that case the following methods will be added to the class:
215
+ #
216
+ # [images] Returns an array of uploaders for each uploaded file
217
+ # [images=] Caches the given files
218
+ #
219
+ # [images_urls] Returns the urls to the uploaded files
220
+ #
221
+ # [images_cache] Returns a string that identifies the cache location of the files
222
+ # [images_cache=] Retrieves the files from the cache based on the given cache name
223
+ #
224
+ # [remote_image_urls] Returns previously cached remote urls
225
+ # [remote_image_urls=] Retrieve files from the given remote urls
226
+ #
227
+ # [remove_images] An attribute reader that can be used with a checkbox to mark the files for removal
228
+ # [remove_images=] An attribute writer that can be used with a checkbox to mark the files for removal
229
+ # [remove_images?] Whether the files should be removed when store_image! is called.
230
+ #
231
+ # [store_images!] Stores all files that have been assigned with +images=+
232
+ # [remove_images!] Removes the uploaded file from the filesystem.
233
+ #
234
+ # [image_integrity_errors] Returns error objects of files which failed to pass integrity check
235
+ # [image_processing_errors] Returns error objects of files which failed to be processed
236
+ # [image_download_errors] Returns error objects of files which failed to be downloaded
237
+ #
238
+ # [image_identifiers] Reads out the identifiers of the files
239
+ #
240
+ # === Parameters
241
+ #
242
+ # [column (Symbol)] the attribute to mount this uploader on
243
+ # [uploader (CarrierWave::Uploader)] the uploader class to mount
244
+ # [options (Hash{Symbol => Object})] a set of options
245
+ # [&block (Proc)] customize anonymous uploaders
246
+ #
247
+ # === Options
248
+ #
249
+ # [:mount_on => Symbol] if the name of the column to be serialized to differs you can override it using this option
250
+ # [:ignore_integrity_errors => Boolean] if set to true, integrity errors will result in caching failing silently
251
+ # [:ignore_processing_errors => Boolean] if set to true, processing errors will result in caching failing silently
252
+ #
253
+ # === Examples
254
+ #
255
+ # Mounting uploaders on different columns.
256
+ #
257
+ # class Song
258
+ # mount_uploaders :lyrics, LyricsUploader
259
+ # mount_uploaders :alternative_lyrics, LyricsUploader
260
+ # mount_uploaders :files, SongUploader
261
+ # end
262
+ #
263
+ # This will add an anonymous uploader with only the default settings:
264
+ #
265
+ # class Data
266
+ # mount_uploaders :csv_files
267
+ # end
268
+ #
269
+ # this will add an anonymous uploader overriding the store_dir:
270
+ #
271
+ # class Product
272
+ # mount_uploaders :blueprints do
273
+ # def store_dir
274
+ # 'blueprints'
275
+ # end
276
+ # end
277
+ # end
278
+ #
279
+ def mount_uploaders(column, uploader=nil, options={}, &block)
280
+ mount_base(column, uploader, options.merge(multiple: true), &block)
229
281
 
230
- def #{column}_processing_error
231
- _mounter(:#{column}).processing_error
282
+ mod = Module.new
283
+ include mod
284
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
285
+
286
+ def #{column}
287
+ _mounter(:#{column}).uploaders
232
288
  end
233
289
 
234
- def #{column}_download_error
235
- _mounter(:#{column}).download_error
290
+ def #{column}=(new_files)
291
+ _mounter(:#{column}).cache(new_files)
236
292
  end
237
293
 
238
- def write_#{column}_identifier
239
- _mounter(:#{column}).write_identifier
294
+ def #{column}_urls(*args)
295
+ _mounter(:#{column}).urls(*args)
240
296
  end
241
297
 
242
- def #{column}_identifier
243
- _mounter(:#{column}).identifier
298
+ def #{column}_cache
299
+ names = _mounter(:#{column}).cache_names
300
+ names.to_json if names.present?
244
301
  end
245
302
 
246
- def store_previous_model_for_#{column}
247
- serialization_column = _mounter(:#{column}).serialization_column
303
+ def #{column}_cache=(cache_name)
304
+ _mounter(:#{column}).cache_names = JSON.parse(cache_name) if cache_name.present?
305
+ end
248
306
 
249
- if #{column}.remove_previously_stored_files_after_update && send(:"\#{serialization_column}_changed?")
250
- @previous_model_for_#{column} ||= self.find_previous_model_for_#{column}
251
- end
307
+ def remote_#{column}_urls
308
+ _mounter(:#{column}).remote_urls
252
309
  end
253
310
 
254
- def find_previous_model_for_#{column}
255
- self.class.find(to_key.first)
311
+ def remote_#{column}_urls=(urls)
312
+ _mounter(:#{column}).remote_urls = urls
256
313
  end
257
314
 
258
- def remove_previously_stored_#{column}
259
- if @previous_model_for_#{column} && @previous_model_for_#{column}.#{column}.path != #{column}.path
260
- @previous_model_for_#{column}.#{column}.remove!
261
- @previous_model_for_#{column} = nil
262
- end
315
+ def remote_#{column}_request_headers=(headers)
316
+ _mounter(:#{column}).remote_request_headers = headers
263
317
  end
264
318
 
319
+ def #{column}_identifiers
320
+ _mounter(:#{column}).read_identifiers
321
+ end
265
322
  RUBY
266
323
  end
267
324
 
268
- module Extension
269
-
270
- ##
271
- # overwrite this to read from a serialized attribute
272
- #
273
- def read_uploader(column); end
274
-
275
- ##
276
- # overwrite this to write to a serialized attribute
277
- #
278
- def write_uploader(column, identifier); end
325
+ private
279
326
 
280
- private
327
+ def mount_base(column, uploader=nil, options={}, &block)
328
+ include CarrierWave::Mount::Extension
281
329
 
282
- def _mounter(column)
283
- # We cannot memoize in frozen objects :(
284
- return Mounter.new(self, column) if frozen?
285
- @_mounters ||= {}
286
- @_mounters[column] ||= Mounter.new(self, column)
287
- end
330
+ uploader = build_uploader(uploader, column, &block)
331
+ uploaders[column.to_sym] = uploader
332
+ uploader_options[column.to_sym] = options
288
333
 
289
- end # Extension
334
+ # Make sure to write over accessors directly defined on the class.
335
+ # Simply super to the included module below.
336
+ class_eval <<-RUBY, __FILE__, __LINE__+1
337
+ def #{column}; super; end
338
+ def #{column}=(new_file); super; end
339
+ RUBY
290
340
 
291
- # this is an internal class, used by CarrierWave::Mount so that
292
- # we don't pollute the model with a lot of methods.
293
- class Mounter #:nodoc:
294
- attr_reader :column, :record, :remote_url, :integrity_error, :processing_error, :download_error
295
- attr_accessor :remove
341
+ mod = Module.new
342
+ include mod
343
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
296
344
 
297
- def initialize(record, column, options={})
298
- @record = record
299
- @column = column
300
- @options = record.class.uploader_options[column]
301
- end
345
+ def #{column}?
346
+ _mounter(:#{column}).present?
347
+ end
302
348
 
303
- def write_identifier
304
- return if record.frozen?
349
+ def remove_#{column}
350
+ _mounter(:#{column}).remove
351
+ end
305
352
 
306
- if remove?
307
- record.write_uploader(serialization_column, '')
308
- elsif not uploader.identifier.blank?
309
- record.write_uploader(serialization_column, uploader.identifier)
353
+ def remove_#{column}!
354
+ _mounter(:#{column}).remove!
355
+ self.remove_#{column} = true
356
+ write_#{column}_identifier
310
357
  end
311
- end
312
358
 
313
- def identifier
314
- record.read_uploader(serialization_column)
315
- end
359
+ def remove_#{column}=(value)
360
+ _mounter(:#{column}).remove = value
361
+ end
316
362
 
317
- def uploader
318
- @uploader ||= record.class.uploaders[column].new(record, column)
363
+ def remove_#{column}?
364
+ _mounter(:#{column}).remove?
365
+ end
319
366
 
320
- if @uploader.blank? and not identifier.blank?
321
- @uploader.retrieve_from_store!(identifier)
367
+ def store_#{column}!
368
+ _mounter(:#{column}).store!
322
369
  end
323
- return @uploader
324
- end
325
370
 
326
- def cache(new_file)
327
- uploader.cache!(new_file)
328
- @integrity_error = nil
329
- @processing_error = nil
330
- rescue CarrierWave::IntegrityError => e
331
- @integrity_error = e
332
- raise e unless option(:ignore_integrity_errors)
333
- rescue CarrierWave::ProcessingError => e
334
- @processing_error = e
335
- raise e unless option(:ignore_processing_errors)
336
- end
371
+ def #{column}_integrity_errors
372
+ _mounter(:#{column}).integrity_errors
373
+ end
337
374
 
338
- def cache_name
339
- uploader.cache_name
340
- end
375
+ def #{column}_processing_errors
376
+ _mounter(:#{column}).processing_errors
377
+ end
341
378
 
342
- def cache_name=(cache_name)
343
- uploader.retrieve_from_cache!(cache_name) unless uploader.cached?
344
- rescue CarrierWave::InvalidParameter
345
- end
379
+ def #{column}_download_errors
380
+ _mounter(:#{column}).download_errors
381
+ end
346
382
 
347
- def remote_url=(url)
348
- @download_error = nil
349
- @integrity_error = nil
383
+ def write_#{column}_identifier
384
+ _mounter(:#{column}).write_identifier
385
+ end
350
386
 
351
- @remote_url = url
387
+ def mark_remove_#{column}_false
388
+ _mounter(:#{column}).remove = false
389
+ end
352
390
 
353
- uploader.download!(url)
391
+ def reset_previous_changes_for_#{column}
392
+ _mounter(:#{column}).reset_changes!
393
+ end
354
394
 
355
- rescue CarrierWave::DownloadError => e
356
- @download_error = e
357
- raise e unless option(:ignore_download_errors)
358
- rescue CarrierWave::ProcessingError => e
359
- @processing_error = e
360
- raise e unless option(:ignore_processing_errors)
361
- rescue CarrierWave::IntegrityError => e
362
- @integrity_error = e
363
- raise e unless option(:ignore_integrity_errors)
364
- end
395
+ def remove_previously_stored_#{column}
396
+ _mounter(:#{column}).remove_previous
397
+ end
365
398
 
366
- def store!
367
- unless uploader.blank?
368
- if remove?
369
- uploader.remove!
370
- else
371
- uploader.store!
372
- end
399
+ def remove_rolled_back_#{column}
400
+ _mounter(:#{column}).remove_added
373
401
  end
374
- end
402
+ RUBY
403
+ end
375
404
 
376
- def url(*args)
377
- uploader.url(*args)
378
- end
405
+ def build_uploader(uploader, column, &block)
406
+ uploader ||= CarrierWave::Uploader::Base
407
+ return uploader unless block_given?
379
408
 
380
- def blank?
381
- uploader.blank?
382
- end
409
+ uploader = Class.new(uploader)
410
+ const_set("CarrierWave#{column.to_s.camelize}Uploader", uploader)
383
411
 
384
- def remove?
385
- !remove.blank? and remove !~ /\A0|false$\z/
386
- end
412
+ uploader.class_eval(&block)
387
413
 
388
- def remove!
389
- uploader.remove!
390
- end
414
+ uploader
415
+ end
391
416
 
392
- def serialization_column
393
- option(:mount_on) || column
394
- end
417
+ module Extension
418
+
419
+ ##
420
+ # overwrite this to read from a serialized attribute
421
+ #
422
+ def read_uploader(column); end
395
423
 
396
- attr_accessor :uploader_options
424
+ ##
425
+ # overwrite this to write to a serialized attribute
426
+ #
427
+ def write_uploader(column, identifier); end
397
428
 
398
429
  private
399
430
 
400
- def option(name)
401
- self.uploader_options ||= {}
402
- self.uploader_options[name] ||= record.class.uploader_option(column, name)
431
+ def _mounter(column)
432
+ # We cannot memoize in frozen objects :(
433
+ return Mounter.build(self, column) if frozen?
434
+ @_mounters ||= {}
435
+ @_mounters[column] ||= Mounter.build(self, column)
403
436
  end
404
437
 
405
- end # Mounter
438
+ end # Extension
406
439
 
407
440
  end # Mount
408
441
  end # CarrierWave