shrine 3.1.0 → 3.2.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +3 -3
- data/doc/advantages.md +3 -3
- data/doc/carrierwave.md +17 -8
- data/doc/design.md +134 -85
- data/doc/external/articles.md +1 -0
- data/doc/external/extensions.md +38 -35
- data/doc/getting_started.md +135 -79
- data/doc/metadata.md +79 -43
- data/doc/paperclip.md +13 -4
- data/doc/plugins/add_metadata.md +92 -35
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/default_url.md +3 -0
- data/doc/plugins/derivatives.md +35 -27
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/url_options.md +2 -2
- data/doc/plugins/validation.md +5 -4
- data/doc/processing.md +286 -121
- data/doc/refile.md +9 -9
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/securing_uploads.md +1 -1
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +102 -81
- data/doc/testing.md +2 -2
- data/doc/upgrading_to_3.md +94 -32
- data/lib/shrine.rb +3 -2
- data/lib/shrine/attacher.rb +14 -9
- data/lib/shrine/plugins/add_metadata.rb +13 -0
- data/lib/shrine/plugins/derivatives.rb +8 -7
- data/lib/shrine/plugins/determine_mime_type.rb +3 -3
- data/lib/shrine/plugins/model.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/validation.rb +8 -6
- data/lib/shrine/storage/s3.rb +34 -28
- data/lib/shrine/version.rb +1 -1
- metadata +7 -3
data/doc/testing.md
CHANGED
@@ -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.
|
131
|
+
"size" => File.size(file.path),
|
132
132
|
"mime_type" => "image/jpeg",
|
133
133
|
"filename" => "test.jpg",
|
134
134
|
)
|
data/doc/upgrading_to_3.md
CHANGED
@@ -283,8 +283,9 @@ attacher.destroy_background # calls destroy block
|
|
283
283
|
## Versions
|
284
284
|
|
285
285
|
The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
|
286
|
-
deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
|
287
|
-
|
286
|
+
deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
|
287
|
+
|
288
|
+
Let's assume you have the following `versions` configuration:
|
288
289
|
|
289
290
|
```rb
|
290
291
|
class ImageUploader < Shrine
|
@@ -307,11 +308,15 @@ class ImageUploader < Shrine
|
|
307
308
|
end
|
308
309
|
end
|
309
310
|
```
|
311
|
+
|
312
|
+
When an attached file is promoted to permanent storage, the versions would
|
313
|
+
automatically get generated:
|
314
|
+
|
310
315
|
```rb
|
311
316
|
photo = Photo.new(photo_params)
|
312
317
|
|
313
318
|
if photo.valid?
|
314
|
-
photo.save #
|
319
|
+
photo.save # generates versions on promotion
|
315
320
|
# ...
|
316
321
|
else
|
317
322
|
# ...
|
@@ -319,7 +324,7 @@ end
|
|
319
324
|
```
|
320
325
|
|
321
326
|
With `derivatives`, the original file is automatically downloaded and retained,
|
322
|
-
so the code is
|
327
|
+
so the processing code is much simpler:
|
323
328
|
|
324
329
|
```rb
|
325
330
|
Shrine.plugin :derivatives, versions_compatibility: true # handle versions column format
|
@@ -338,22 +343,25 @@ class ImageUploader < Shrine
|
|
338
343
|
end
|
339
344
|
end
|
340
345
|
```
|
346
|
+
|
347
|
+
However, you now need to trigger processing manually during attachment:
|
348
|
+
|
341
349
|
```rb
|
342
350
|
photo = Photo.new(photo_params)
|
343
351
|
|
344
352
|
if photo.valid?
|
345
353
|
photo.image_derivatives! if photo.image_changed? # create derivatives
|
346
|
-
photo.save
|
354
|
+
photo.save
|
347
355
|
# ...
|
348
356
|
else
|
349
357
|
# ...
|
350
358
|
end
|
351
359
|
```
|
352
360
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
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:
|
357
365
|
|
358
366
|
```rb
|
359
367
|
class Shrine::Attacher
|
@@ -372,7 +380,7 @@ The derivative URLs are accessed in the same way as versions:
|
|
372
380
|
photo.image_url(:small)
|
373
381
|
```
|
374
382
|
|
375
|
-
But the
|
383
|
+
But the files themselves are accessed differently:
|
376
384
|
|
377
385
|
```rb
|
378
386
|
# versions
|
@@ -426,8 +434,8 @@ database column in different formats:
|
|
426
434
|
|
427
435
|
The `:versions_compatibility` flag to the `derivatives` plugin enables it to
|
428
436
|
read the `versions` format, which aids in transition. Once the `derivatives`
|
429
|
-
plugin has been deployed to production, you can
|
430
|
-
new column format:
|
437
|
+
plugin has been deployed to production, you can update existing records with
|
438
|
+
the new column format:
|
431
439
|
|
432
440
|
```rb
|
433
441
|
Photo.find_each do |photo|
|
@@ -488,48 +496,77 @@ else
|
|
488
496
|
end
|
489
497
|
```
|
490
498
|
|
491
|
-
|
499
|
+
### Default URL
|
492
500
|
|
493
|
-
|
494
|
-
|
501
|
+
If you were using the `default_url` plugin, the `Attacher.default_url` now
|
502
|
+
receives a `:derivative` option:
|
495
503
|
|
496
504
|
```rb
|
497
|
-
|
498
|
-
|
505
|
+
Attacher.default_url do |derivative: nil, **|
|
506
|
+
"https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
|
507
|
+
end
|
499
508
|
```
|
500
|
-
```rb
|
501
|
-
require "concurrent"
|
502
509
|
|
503
|
-
|
510
|
+
#### Fallback to original
|
504
511
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
end
|
509
|
-
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:
|
510
515
|
|
511
|
-
|
516
|
+
```rb
|
517
|
+
Attacher.default_url do |derivative: nil, **|
|
518
|
+
file&.url if derivative
|
519
|
+
end
|
512
520
|
```
|
513
521
|
|
514
|
-
####
|
522
|
+
#### Fallback to version
|
515
523
|
|
516
|
-
The `
|
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:
|
517
527
|
|
518
528
|
```rb
|
529
|
+
DERIVATIVE_FALLBACKS = { foo: :bar, ... }
|
530
|
+
|
519
531
|
Attacher.default_url do |derivative: nil, **|
|
520
|
-
|
532
|
+
derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
|
533
|
+
end
|
534
|
+
```
|
535
|
+
|
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
|
521
547
|
end
|
522
548
|
```
|
523
549
|
|
524
|
-
|
525
|
-
|
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.
|
526
562
|
|
527
563
|
## Other
|
528
564
|
|
529
565
|
### Processing
|
530
566
|
|
531
567
|
The `processing` plugin has been deprecated over the new
|
532
|
-
[`derivatives`][derivatives] plugin. If you were
|
568
|
+
[`derivatives`][derivatives] plugin. If you were previously replacing the
|
569
|
+
original file:
|
533
570
|
|
534
571
|
```rb
|
535
572
|
class MyUploader < Shrine
|
@@ -557,6 +594,30 @@ class MyUploader < Shrine
|
|
557
594
|
end
|
558
595
|
```
|
559
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
|
+
|
560
621
|
### Logging
|
561
622
|
|
562
623
|
The `logging` plugin has been removed in favour of the
|
@@ -662,3 +723,4 @@ end
|
|
662
723
|
[derivatives]: https://shrinerb.com/docs/plugins/derivatives
|
663
724
|
[instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
|
664
725
|
[mirroring]: https://shrinerb.com/docs/plugins/mirroring
|
726
|
+
[overwriting original]: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50/4
|
data/lib/shrine.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
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"
|
@@ -14,7 +14,8 @@ require "logger"
|
|
14
14
|
# Core class that handles uploading files to specified storage.
|
15
15
|
class Shrine
|
16
16
|
# A generic exception used by Shrine.
|
17
|
-
class Error < StandardError
|
17
|
+
class Error < StandardError
|
18
|
+
end
|
18
19
|
|
19
20
|
# Raised when a file is not a valid IO.
|
20
21
|
class InvalidFile < Error
|
data/lib/shrine/attacher.rb
CHANGED
@@ -39,10 +39,11 @@ 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
|
43
|
-
@cache
|
44
|
-
@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.
|
@@ -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)
|
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
|
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
|
-
|
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
|
-
|
262
|
+
!!@previous
|
258
263
|
end
|
259
264
|
|
260
265
|
# Returns whether a file is attached.
|
@@ -54,6 +54,19 @@ class Shrine
|
|
54
54
|
end
|
55
55
|
end
|
56
56
|
end
|
57
|
+
|
58
|
+
module AttacherMethods
|
59
|
+
def add_metadata(new_metadata, &block)
|
60
|
+
file!.add_metadata(new_metadata, &block)
|
61
|
+
set(file) # trigger model write
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module FileMethods
|
66
|
+
def add_metadata(new_metadata, &block)
|
67
|
+
@metadata = @metadata.merge(new_metadata, &block)
|
68
|
+
end
|
69
|
+
end
|
57
70
|
end
|
58
71
|
|
59
72
|
register_plugin(:add_metadata, AddMetadata)
|
@@ -227,8 +227,6 @@ class Shrine
|
|
227
227
|
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
228
228
|
def upload_derivatives(files, **options)
|
229
229
|
map_derivative(files) do |path, file|
|
230
|
-
path = derivative_path(path)
|
231
|
-
|
232
230
|
upload_derivative(path, file, **options)
|
233
231
|
end
|
234
232
|
end
|
@@ -238,6 +236,7 @@ class Shrine
|
|
238
236
|
# hash = attacher.upload_derivative(:thumb, thumb)
|
239
237
|
# hash[:thumb] #=> #<Shrine::UploadedFile>
|
240
238
|
def upload_derivative(path, file, storage: nil, **options)
|
239
|
+
path = derivative_path(path)
|
241
240
|
storage ||= derivative_storage(path)
|
242
241
|
|
243
242
|
file.open if file.is_a?(Tempfile) # refresh file descriptor
|
@@ -374,7 +373,7 @@ class Shrine
|
|
374
373
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
375
374
|
def set_derivatives(derivatives)
|
376
375
|
self.derivatives = derivatives
|
377
|
-
set file # trigger model
|
376
|
+
set file # trigger model write
|
378
377
|
derivatives
|
379
378
|
end
|
380
379
|
|
@@ -442,7 +441,7 @@ class Shrine
|
|
442
441
|
# attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
|
443
442
|
# attacher.change(file)
|
444
443
|
# attacher.derivatives #=> {}
|
445
|
-
def change(*
|
444
|
+
def change(*)
|
446
445
|
result = super
|
447
446
|
set_derivatives({})
|
448
447
|
result
|
@@ -473,7 +472,7 @@ class Shrine
|
|
473
472
|
def _process_derivatives(processor_name, source, **options)
|
474
473
|
processor = self.class.derivatives_processor(processor_name)
|
475
474
|
|
476
|
-
result = instrument_derivatives(processor_name, options) do
|
475
|
+
result = instrument_derivatives(processor_name, source, options) do
|
477
476
|
instance_exec(source, **options, &processor)
|
478
477
|
end
|
479
478
|
|
@@ -485,20 +484,22 @@ class Shrine
|
|
485
484
|
end
|
486
485
|
|
487
486
|
# Sends a `derivatives.shrine` event for instrumentation plugin.
|
488
|
-
def instrument_derivatives(processor_name, processor_options, &block)
|
487
|
+
def instrument_derivatives(processor_name, source, processor_options, &block)
|
489
488
|
return yield unless shrine_class.respond_to?(:instrument)
|
490
489
|
|
491
490
|
shrine_class.instrument(
|
492
491
|
:derivatives,
|
493
492
|
processor: processor_name,
|
494
493
|
processor_options: processor_options,
|
494
|
+
io: source,
|
495
|
+
attacher: self,
|
495
496
|
&block
|
496
497
|
)
|
497
498
|
end
|
498
499
|
|
499
500
|
# Returns symbolized array or single key.
|
500
501
|
def derivative_path(path)
|
501
|
-
path = path.map { |key| key.is_a?(String) ? key.to_sym : key }
|
502
|
+
path = Array(path).map { |key| key.is_a?(String) ? key.to_sym : key }
|
502
503
|
path = path.first if path.one?
|
503
504
|
path
|
504
505
|
end
|
@@ -141,7 +141,7 @@ class Shrine
|
|
141
141
|
require "mimemagic"
|
142
142
|
|
143
143
|
mime = MimeMagic.by_magic(io)
|
144
|
-
mime
|
144
|
+
mime&.type
|
145
145
|
end
|
146
146
|
|
147
147
|
def extract_with_marcel(io, options)
|
@@ -158,7 +158,7 @@ class Shrine
|
|
158
158
|
|
159
159
|
if filename = extract_filename(io)
|
160
160
|
mime_type = MIME::Types.of(filename).first
|
161
|
-
mime_type
|
161
|
+
mime_type&.content_type
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
@@ -167,7 +167,7 @@ class Shrine
|
|
167
167
|
|
168
168
|
if filename = extract_filename(io)
|
169
169
|
info = MiniMime.lookup_by_filename(filename)
|
170
|
-
info
|
170
|
+
info&.content_type
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|