shrine 3.0.0 → 3.2.2

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

Potentially problematic release.


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

Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +87 -33
  3. data/LICENSE.txt +1 -1
  4. data/README.md +94 -4
  5. data/doc/advantages.md +35 -18
  6. data/doc/attacher.md +16 -17
  7. data/doc/carrierwave.md +75 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/external/articles.md +56 -41
  11. data/doc/external/extensions.md +38 -34
  12. data/doc/getting_started.md +182 -112
  13. data/doc/metadata.md +79 -43
  14. data/doc/multiple_files.md +5 -3
  15. data/doc/paperclip.md +110 -42
  16. data/doc/plugins/activerecord.md +5 -5
  17. data/doc/plugins/add_metadata.md +92 -35
  18. data/doc/plugins/backgrounding.md +12 -2
  19. data/doc/plugins/column.md +36 -7
  20. data/doc/plugins/data_uri.md +2 -2
  21. data/doc/plugins/default_url.md +6 -3
  22. data/doc/plugins/derivation_endpoint.md +26 -28
  23. data/doc/plugins/derivatives.md +205 -169
  24. data/doc/plugins/determine_mime_type.md +2 -2
  25. data/doc/plugins/entity.md +3 -3
  26. data/doc/plugins/form_assign.md +5 -5
  27. data/doc/plugins/included.md +25 -5
  28. data/doc/plugins/infer_extension.md +2 -2
  29. data/doc/plugins/instrumentation.md +1 -1
  30. data/doc/plugins/metadata_attributes.md +21 -10
  31. data/doc/plugins/model.md +4 -4
  32. data/doc/plugins/persistence.md +1 -0
  33. data/doc/plugins/refresh_metadata.md +5 -4
  34. data/doc/plugins/remote_url.md +8 -3
  35. data/doc/plugins/remove_invalid.md +9 -1
  36. data/doc/plugins/sequel.md +4 -4
  37. data/doc/plugins/signature.md +11 -2
  38. data/doc/plugins/store_dimensions.md +2 -2
  39. data/doc/plugins/type_predicates.md +96 -0
  40. data/doc/plugins/upload_endpoint.md +7 -11
  41. data/doc/plugins/upload_options.md +1 -1
  42. data/doc/plugins/url_options.md +2 -2
  43. data/doc/plugins/validation.md +14 -4
  44. data/doc/plugins/validation_helpers.md +3 -3
  45. data/doc/plugins/versions.md +11 -11
  46. data/doc/processing.md +289 -125
  47. data/doc/refile.md +39 -18
  48. data/doc/release_notes/2.19.0.md +1 -1
  49. data/doc/release_notes/3.0.0.md +275 -258
  50. data/doc/release_notes/3.0.1.md +22 -0
  51. data/doc/release_notes/3.1.0.md +73 -0
  52. data/doc/release_notes/3.2.0.md +96 -0
  53. data/doc/release_notes/3.2.1.md +32 -0
  54. data/doc/release_notes/3.2.2.md +14 -0
  55. data/doc/securing_uploads.md +3 -3
  56. data/doc/storage/file_system.md +1 -1
  57. data/doc/storage/memory.md +19 -0
  58. data/doc/storage/s3.md +105 -86
  59. data/doc/testing.md +2 -2
  60. data/doc/upgrading_to_3.md +115 -33
  61. data/doc/validation.md +3 -2
  62. data/lib/shrine.rb +8 -8
  63. data/lib/shrine/attacher.rb +19 -14
  64. data/lib/shrine/attachment.rb +5 -5
  65. data/lib/shrine/plugins.rb +22 -0
  66. data/lib/shrine/plugins/add_metadata.rb +12 -3
  67. data/lib/shrine/plugins/default_storage.rb +6 -6
  68. data/lib/shrine/plugins/default_url.rb +1 -1
  69. data/lib/shrine/plugins/derivation_endpoint.rb +10 -6
  70. data/lib/shrine/plugins/derivatives.rb +19 -17
  71. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  72. data/lib/shrine/plugins/entity.rb +6 -6
  73. data/lib/shrine/plugins/metadata_attributes.rb +1 -1
  74. data/lib/shrine/plugins/model.rb +3 -3
  75. data/lib/shrine/plugins/presign_endpoint.rb +2 -2
  76. data/lib/shrine/plugins/pretty_location.rb +1 -1
  77. data/lib/shrine/plugins/processing.rb +1 -1
  78. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  79. data/lib/shrine/plugins/remote_url.rb +3 -3
  80. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  81. data/lib/shrine/plugins/signature.rb +7 -6
  82. data/lib/shrine/plugins/store_dimensions.rb +18 -9
  83. data/lib/shrine/plugins/type_predicates.rb +113 -0
  84. data/lib/shrine/plugins/upload_endpoint.rb +3 -3
  85. data/lib/shrine/plugins/upload_options.rb +2 -2
  86. data/lib/shrine/plugins/url_options.rb +2 -2
  87. data/lib/shrine/plugins/validation.rb +9 -7
  88. data/lib/shrine/storage/linter.rb +4 -4
  89. data/lib/shrine/storage/s3.rb +62 -38
  90. data/lib/shrine/uploaded_file.rb +5 -1
  91. data/lib/shrine/version.rb +2 -2
  92. data/shrine.gemspec +6 -7
  93. metadata +23 -29
@@ -119,7 +119,7 @@ module TestData
119
119
  small: uploaded_image,
120
120
  )
121
121
 
122
- attacher.column_data
122
+ attacher.column_data # or attacher.data in case of postgres jsonb column
123
123
  end
124
124
 
125
125
  def uploaded_image
@@ -128,7 +128,7 @@ module TestData
128
128
  # for performance we skip metadata extraction and assign test metadata
129
129
  uploaded_file = Shrine.upload(file, :store, metadata: false)
130
130
  uploaded_file.metadata.merge!(
131
- "size" => file.size,
131
+ "size" => File.size(file.path),
132
132
  "mime_type" => "image/jpeg",
133
133
  "filename" => "test.jpg",
134
134
  )
@@ -4,7 +4,11 @@ title: Upgrading to Shrine 3.x
4
4
  ---
5
5
 
6
6
  This guide provides instructions for upgrading Shrine in your apps to version
7
- 3.x. If you're looking for a full list of changes, see the [3.0 release notes].
7
+ 3.x. If you're looking for a full list of changes, see the **[3.0 release
8
+ notes]**.
9
+
10
+ If you would like assistance with the upgrade, I'm available for consultation,
11
+ you can email me at <janko.marohnic@gmail.com>.
8
12
 
9
13
  ## Attacher
10
14
 
@@ -72,6 +76,20 @@ attacher.reload
72
76
  attacher.file #=> #<Shrine::UploadedFile ...>
73
77
  ```
74
78
 
79
+ ### Assigning
80
+
81
+ The `Attacher#assign` method now raises an exception when non-cached uploaded
82
+ file data is assigned:
83
+
84
+ ```rb
85
+ # Shrine 2.x
86
+ attacher.assign('{"id": "...", "storage": "store", "metadata": {...}}') # ignored
87
+
88
+ # Shrine 3.0
89
+ attacher.assign('{"id": "...", "storage": "store", "metadata": {...}}')
90
+ #~> Shrine::Error: expected cached file, got #<Shrine::UploadedFile storage=:store ...>
91
+ ```
92
+
75
93
  ### Validation
76
94
 
77
95
  The validation functionality has been extracted into the `validation` plugin.
@@ -265,8 +283,9 @@ attacher.destroy_background # calls destroy block
265
283
  ## Versions
266
284
 
267
285
  The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
268
- deprecated in favour of the new [`derivatives`][derivatives] plugin. Let's
269
- assume you have the following `versions` code:
286
+ deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
287
+
288
+ Let's assume you have the following `versions` configuration:
270
289
 
271
290
  ```rb
272
291
  class ImageUploader < Shrine
@@ -289,18 +308,23 @@ class ImageUploader < Shrine
289
308
  end
290
309
  end
291
310
  ```
311
+
312
+ When an attached file is promoted to permanent storage, the versions would
313
+ automatically get generated:
314
+
292
315
  ```rb
293
316
  photo = Photo.new(photo_params)
294
317
 
295
318
  if photo.valid?
296
- photo.save # automatically calls processing block
319
+ photo.save # generates versions on promotion
297
320
  # ...
298
321
  else
299
322
  # ...
300
323
  end
301
324
  ```
302
325
 
303
- With `derivatives` it becomes this:
326
+ With `derivatives`, the original file is automatically downloaded and retained,
327
+ so the processing code is much simpler:
304
328
 
305
329
  ```rb
306
330
  Shrine.plugin :derivatives, versions_compatibility: true # handle versions column format
@@ -310,6 +334,7 @@ class ImageUploader < Shrine
310
334
  Attacher.derivatives_processor do |original|
311
335
  magick = ImageProcessing::MiniMagick.source(original)
312
336
 
337
+ # the :original file should NOT be included anymore
313
338
  {
314
339
  large: magick.resize_to_limit!(800, 800),
315
340
  medium: magick.resize_to_limit!(500, 500),
@@ -318,22 +343,25 @@ class ImageUploader < Shrine
318
343
  end
319
344
  end
320
345
  ```
346
+
347
+ However, you now need to trigger processing manually during attachment:
348
+
321
349
  ```rb
322
350
  photo = Photo.new(photo_params)
323
351
 
324
352
  if photo.valid?
325
353
  photo.image_derivatives! if photo.image_changed? # create derivatives
326
- photo.save # automatically calls processing block
354
+ photo.save
327
355
  # ...
328
356
  else
329
357
  # ...
330
358
  end
331
359
  ```
332
360
 
333
- If you have multiple places where you need to generate derivatives, and want it
334
- to happen automatically like it did with the `versions` plugin, you can
335
- override `Attacher#promote` to call `Attacher#create_derivatives` before
336
- promotion:
361
+ ### Automatic processing
362
+
363
+ If you prefer processing to happen automatically with promotion (like it did
364
+ with the `versions` plugin), you can put the following in your initializer:
337
365
 
338
366
  ```rb
339
367
  class Shrine::Attacher
@@ -352,7 +380,7 @@ The derivative URLs are accessed in the same way as versions:
352
380
  photo.image_url(:small)
353
381
  ```
354
382
 
355
- But the derivatives themselves are accessed differently:
383
+ But the files themselves are accessed differently:
356
384
 
357
385
  ```rb
358
386
  # versions
@@ -406,8 +434,8 @@ database column in different formats:
406
434
 
407
435
  The `:versions_compatibility` flag to the `derivatives` plugin enables it to
408
436
  read the `versions` format, which aids in transition. Once the `derivatives`
409
- plugin has been deployed to production, you can switch existing records to the
410
- new column format:
437
+ plugin has been deployed to production, you can update existing records with
438
+ the new column format:
411
439
 
412
440
  ```rb
413
441
  Photo.find_each do |photo|
@@ -468,48 +496,77 @@ else
468
496
  end
469
497
  ```
470
498
 
471
- #### Parallelize
499
+ ### Default URL
472
500
 
473
- The `parallelize` plugin has been removed. The `derivatives` plugin is
474
- thread-safe, so you can parallelize uploading processed files manually:
501
+ If you were using the `default_url` plugin, the `Attacher.default_url` now
502
+ receives a `:derivative` option:
475
503
 
476
504
  ```rb
477
- # Gemfile
478
- gem "concurrent-ruby"
505
+ Attacher.default_url do |derivative: nil, **|
506
+ "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
507
+ end
479
508
  ```
480
- ```rb
481
- require "concurrent"
482
509
 
483
- derivatives = attacher.process_derivatives
510
+ #### Fallback to original
484
511
 
485
- tasks = derivatives.map do |name, file|
486
- Concurrent::Promises.future(name, file) do |name, file|
487
- attacher.add_derivative(name, file)
488
- end
489
- end
512
+ With the `versions` plugin, a missing version URL would automatically fall back
513
+ to the original file. The `derivatives` plugin has no such fallback, but you
514
+ can configure it manually:
490
515
 
491
- Concurrent::Promises.zip(*tasks).wait!
516
+ ```rb
517
+ Attacher.default_url do |derivative: nil, **|
518
+ file&.url if derivative
519
+ end
492
520
  ```
493
521
 
494
- #### Default URL
522
+ #### Fallback to version
495
523
 
496
- The `derivatives` plugin integrates with the `default_url` plugin:
524
+ The `versions` plugin had the ability to fall back missing version URL to
525
+ another version that already exists. The `derivatives` plugin doesn't have this
526
+ built in, but you can implement it as follows:
497
527
 
498
528
  ```rb
529
+ DERIVATIVE_FALLBACKS = { foo: :bar, ... }
530
+
499
531
  Attacher.default_url do |derivative: nil, **|
500
- "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
532
+ derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
501
533
  end
502
534
  ```
503
535
 
504
- However, it doesn't implement any other URL fallbacks that the `versions`
505
- plugin has for missing derivatives.
536
+ ### Location
537
+
538
+ The `Shrine#generate_location` method will now receive a `:derivative`
539
+ parameter instead of `:version`:
540
+
541
+ ```rb
542
+ class MyUploader < Shrine
543
+ def generate_location(io, derivative: nil, **)
544
+ derivative #=> :large, :medium, :small, ...
545
+ # ...
546
+ end
547
+ end
548
+ ```
549
+
550
+ ### Overwriting original
551
+
552
+ With the `derivatives` plugin, saving processed files separately from the
553
+ original file, so the original file is automatically kept. This means it's not
554
+ possible anymore to overwrite the original file as part of processing.
555
+
556
+ However, **it's highly recommended to always keep the original file**, even if
557
+ you don't plan to use it. That way, if there is ever a need to reprocess
558
+ derivatives, you have the original file to use as a base.
559
+
560
+ That being said, if you still want to overwrite the original file, [this
561
+ thread][overwriting original] has some tips.
506
562
 
507
563
  ## Other
508
564
 
509
565
  ### Processing
510
566
 
511
567
  The `processing` plugin has been deprecated over the new
512
- [`derivatives`][derivatives] plugin. If you were modifying the original file:
568
+ [`derivatives`][derivatives] plugin. If you were previously replacing the
569
+ original file:
513
570
 
514
571
  ```rb
515
572
  class MyUploader < Shrine
@@ -537,6 +594,30 @@ class MyUploader < Shrine
537
594
  end
538
595
  ```
539
596
 
597
+ ### Parallelize
598
+
599
+ The `parallelize` plugin has been removed. With `derivatives` plugin you can
600
+ parallelize uploading processed files manually:
601
+
602
+ ```rb
603
+ # Gemfile
604
+ gem "concurrent-ruby"
605
+ ```
606
+ ```rb
607
+ require "concurrent"
608
+
609
+ attacher = photo.image_attacher
610
+ derivatives = attacher.process_derivatives
611
+
612
+ tasks = derivatives.map do |name, file|
613
+ Concurrent::Promises.future(name, file) do |name, file|
614
+ attacher.add_derivative(name, file)
615
+ end
616
+ end
617
+
618
+ Concurrent::Promises.zip(*tasks).wait!
619
+ ```
620
+
540
621
  ### Logging
541
622
 
542
623
  The `logging` plugin has been removed in favour of the
@@ -642,3 +723,4 @@ end
642
723
  [derivatives]: https://shrinerb.com/docs/plugins/derivatives
643
724
  [instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
644
725
  [mirroring]: https://shrinerb.com/docs/plugins/mirroring
726
+ [overwriting original]: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50/4
@@ -88,10 +88,11 @@ when defining more validations:
88
88
  class ApplicationUploader < Shrine
89
89
  Attacher.validate { validate_max_size 5*1024*1024 }
90
90
  end
91
-
91
+ ```
92
+ ```rb
92
93
  class ImageUploader < ApplicationUploader
93
94
  Attacher.validate do
94
- super() # empty braces are required
95
+ super() # empty parentheses are required
95
96
  validate_mime_type %w[image/jpeg image/png image/webp]
96
97
  end
97
98
  end
@@ -1,21 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "shrine/version"
4
3
  require "shrine/uploaded_file"
5
4
  require "shrine/attacher"
6
5
  require "shrine/attachment"
7
6
  require "shrine/plugins"
7
+ require "shrine/version"
8
8
 
9
9
  require "securerandom"
10
10
  require "json"
11
11
  require "tempfile"
12
12
  require "logger"
13
13
 
14
- # Core class that represents uploader.
15
- # Base implementation is defined in InstanceMethods and ClassMethods.
14
+ # Core class that handles uploading files to specified storage.
16
15
  class Shrine
17
16
  # A generic exception used by Shrine.
18
- class Error < StandardError; end
17
+ class Error < StandardError
18
+ end
19
19
 
20
20
  # Raised when a file is not a valid IO.
21
21
  class InvalidFile < Error
@@ -68,9 +68,9 @@ class Shrine
68
68
  #
69
69
  # Shrine.plugin MyPlugin
70
70
  # Shrine.plugin :my_plugin
71
- def plugin(plugin, *args, &block)
71
+ def plugin(plugin, *args, **kwargs, &block)
72
72
  plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
73
- plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
73
+ Plugins.load_dependencies(plugin, self, *args, **kwargs, &block)
74
74
  self.include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
75
75
  self.extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
76
76
  self::UploadedFile.include(plugin::FileMethods) if defined?(plugin::FileMethods)
@@ -79,7 +79,7 @@ class Shrine
79
79
  self::Attachment.extend(plugin::AttachmentClassMethods) if defined?(plugin::AttachmentClassMethods)
80
80
  self::Attacher.include(plugin::AttacherMethods) if defined?(plugin::AttacherMethods)
81
81
  self::Attacher.extend(plugin::AttacherClassMethods) if defined?(plugin::AttacherClassMethods)
82
- plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
82
+ Plugins.configure(plugin, self, *args, **kwargs, &block)
83
83
  plugin
84
84
  end
85
85
 
@@ -297,7 +297,7 @@ class Shrine
297
297
  # Retrieves the location for the given IO and context. First it looks
298
298
  # for the `:location` option, otherwise it calls #generate_location.
299
299
  def get_location(io, location: nil, **options)
300
- location ||= generate_location(io, options)
300
+ location ||= generate_location(io, **options)
301
301
  location or fail Error, "location generated for #{io.inspect} was nil"
302
302
  end
303
303
 
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Shrine
4
- # Core class which handles attaching files to model instances.
5
- # Base implementation is defined in InstanceMethods and ClassMethods.
4
+ # Core class that handles attaching files. It uses Shrine and
5
+ # Shrine::UploadedFile objects internally.
6
6
  class Attacher
7
7
  @shrine_class = ::Shrine
8
8
 
@@ -39,16 +39,17 @@ class Shrine
39
39
 
40
40
  # Initializes the attached file, temporary and permanent storage.
41
41
  def initialize(file: nil, cache: :cache, store: :store)
42
- @file = file
43
- @cache = cache
44
- @store = store
45
- @context = {}
42
+ @file = file
43
+ @cache = cache
44
+ @store = store
45
+ @context = {}
46
+ @previous = nil
46
47
  end
47
48
 
48
49
  # Returns the temporary storage identifier.
49
- def cache_key; @cache; end
50
+ def cache_key; @cache.to_sym; end
50
51
  # Returns the permanent storage identifier.
51
- def store_key; @store; end
52
+ def store_key; @store.to_sym; end
52
53
 
53
54
  # Returns the uploader that is used for the temporary storage.
54
55
  def cache; shrine_class.new(cache_key); end
@@ -69,6 +70,10 @@ class Shrine
69
70
  def assign(value, **options)
70
71
  return if value == "" # skip empty hidden field
71
72
 
73
+ if value.is_a?(Hash) || value.is_a?(String)
74
+ return if uploaded_file(value) == file # skip assignment for current file
75
+ end
76
+
72
77
  attach_cached(value, **options)
73
78
  end
74
79
 
@@ -89,7 +94,7 @@ class Shrine
89
94
  # attacher.attach_cached({ "id" => "...", "storage" => "cache", "metadata" => {} })
90
95
  def attach_cached(value, **options)
91
96
  if value.is_a?(String) || value.is_a?(Hash)
92
- change(cached(value, **options), **options)
97
+ change(cached(value, **options))
93
98
  else
94
99
  attach(value, storage: cache_key, action: :cache, **options)
95
100
  end
@@ -111,7 +116,7 @@ class Shrine
111
116
  def attach(io, storage: store_key, **options)
112
117
  file = upload(io, storage, **options) if io
113
118
 
114
- change(file, **options)
119
+ change(file)
115
120
  end
116
121
 
117
122
  # Deletes any previous file and promotes newly attached cached file.
@@ -138,7 +143,7 @@ class Shrine
138
143
  def finalize
139
144
  destroy_previous
140
145
  promote_cached
141
- remove_instance_variable(:@previous) if changed?
146
+ @previous = nil
142
147
  end
143
148
 
144
149
  # Plugins can override this if they want something to be done in a
@@ -211,7 +216,7 @@ class Shrine
211
216
  # attacher.change(uploaded_file)
212
217
  # attacher.file #=> #<Shrine::UploadedFile>
213
218
  # attacher.changed? #=> true
214
- def change(file, **)
219
+ def change(file)
215
220
  @previous = dup unless @file == file
216
221
  set(file)
217
222
  end
@@ -254,7 +259,7 @@ class Shrine
254
259
  # attacher.attach(file)
255
260
  # attacher.changed? #=> true
256
261
  def changed?
257
- instance_variable_defined?(:@previous)
262
+ !!@previous
258
263
  end
259
264
 
260
265
  # Returns whether a file is attached.
@@ -352,7 +357,7 @@ class Shrine
352
357
  # reject files not uploaded to temporary storage, because otherwise
353
358
  # attackers could hijack other users' attachments
354
359
  unless cached?(uploaded_file)
355
- fail Shrine::Error, "expected cached file, got #{value.inspect}"
360
+ fail Shrine::Error, "expected cached file, got #{uploaded_file.inspect}"
356
361
  end
357
362
 
358
363
  uploaded_file
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Shrine
4
- # Core class which creates attachment modules for specified attribute names
5
- # that are included into model classes.
6
- # Base implementation is defined in InstanceMethods and ClassMethods.
4
+ # Core class that provides an attachment interface for a specified attribute
5
+ # name, which can be added to model/entity classes. The model/entity plugins
6
+ # define the main interface, which delegates to a Shrine::Attacher object.
7
7
  class Attachment < Module
8
8
  @shrine_class = ::Shrine
9
9
 
@@ -22,8 +22,8 @@ class Shrine
22
22
  # Shorthand for `Attachment.new`.
23
23
  #
24
24
  # Shrine::Attachment[:image]
25
- def [](*args)
26
- new(*args)
25
+ def [](*args, **options)
26
+ new(*args, **options)
27
27
  end
28
28
  end
29
29