shrine 3.1.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +11 -4
- data/doc/advantages.md +4 -4
- data/doc/attacher.md +2 -2
- data/doc/carrierwave.md +24 -12
- data/doc/changing_derivatives.md +1 -1
- data/doc/changing_location.md +6 -5
- data/doc/design.md +134 -85
- data/doc/direct_s3.md +26 -0
- data/doc/external/articles.md +57 -45
- data/doc/external/extensions.md +41 -35
- data/doc/external/misc.md +23 -8
- data/doc/getting_started.md +156 -85
- data/doc/metadata.md +80 -44
- data/doc/multiple_files.md +1 -1
- data/doc/paperclip.md +28 -9
- data/doc/plugins/add_metadata.md +112 -35
- data/doc/plugins/atomic_helpers.md +41 -3
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivatives.md +83 -44
- data/doc/plugins/download_endpoint.md +5 -5
- data/doc/plugins/dynamic_storage.md +1 -1
- data/doc/plugins/entity.md +12 -4
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +9 -0
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +1 -0
- data/doc/plugins/mirroring.md +1 -1
- data/doc/plugins/model.md +8 -3
- data/doc/plugins/persistence.md +10 -1
- data/doc/plugins/remote_url.md +6 -1
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/sequel.md +1 -1
- data/doc/plugins/store_dimensions.md +10 -0
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +1 -1
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +4 -4
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/versions.md +7 -7
- data/doc/processing.md +287 -123
- data/doc/refile.md +9 -9
- data/doc/release_notes/2.8.0.md +1 -1
- data/doc/release_notes/3.0.0.md +1 -1
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/securing_uploads.md +2 -2
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +104 -77
- data/doc/testing.md +12 -2
- data/doc/upgrading_to_3.md +99 -53
- data/lib/shrine.rb +9 -8
- data/lib/shrine/attacher.rb +20 -10
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/activerecord.rb +3 -3
- data/lib/shrine/plugins/add_metadata.rb +20 -5
- data/lib/shrine/plugins/backgrounding.rb +2 -2
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +13 -8
- data/lib/shrine/plugins/derivatives.rb +59 -30
- data/lib/shrine/plugins/determine_mime_type.rb +5 -3
- data/lib/shrine/plugins/entity.rb +12 -11
- data/lib/shrine/plugins/instrumentation.rb +12 -18
- data/lib/shrine/plugins/mirroring.rb +8 -8
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +16 -4
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_attachment.rb +5 -0
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/store_dimensions.rb +4 -2
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +10 -5
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/memory.rb +5 -3
- data/lib/shrine/storage/s3.rb +117 -38
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +8 -8
- metadata +42 -34
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
|
)
|
@@ -251,6 +251,16 @@ TestMode.disable_processing(Photo.image_attacher) do
|
|
251
251
|
end
|
252
252
|
```
|
253
253
|
|
254
|
+
## Testing direct upload
|
255
|
+
|
256
|
+
If you'd like to unit-test direct upload on the server side, you can
|
257
|
+
emulate it by uploading a file to `cache` and then assigning it to the record.
|
258
|
+
|
259
|
+
```rb
|
260
|
+
cached_file = Shrine.upload(some_file, :cache)
|
261
|
+
record.attachment = cached_file.to_json
|
262
|
+
```
|
263
|
+
|
254
264
|
[DatabaseCleaner]: https://github.com/DatabaseCleaner/database_cleaner
|
255
265
|
[`#attach_file`]: http://www.rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Actions#attach_file-instance_method
|
256
266
|
[aws-sdk-ruby stubs]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/ClientStubs.html
|
data/doc/upgrading_to_3.md
CHANGED
@@ -237,7 +237,7 @@ class PromoteJob
|
|
237
237
|
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
238
238
|
# attachment has changed or record has been deleted, nothing to do
|
239
239
|
end
|
240
|
-
|
240
|
+
end
|
241
241
|
```
|
242
242
|
```rb
|
243
243
|
class DestroyJob
|
@@ -258,7 +258,7 @@ class DestroyJob
|
|
258
258
|
attacher = attacher_class.from_data(data)
|
259
259
|
attacher.destroy
|
260
260
|
end
|
261
|
-
|
261
|
+
end
|
262
262
|
```
|
263
263
|
|
264
264
|
### Attacher backgrounding
|
@@ -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,26 +308,32 @@ 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
|
# ...
|
318
323
|
end
|
319
324
|
```
|
320
325
|
|
321
|
-
With `derivatives`, the original file is automatically downloaded and retained
|
322
|
-
so the
|
326
|
+
With `derivatives`, the original file is automatically downloaded and retained
|
327
|
+
during processing, so the setup is simpler:
|
323
328
|
|
324
329
|
```rb
|
325
|
-
Shrine.plugin :derivatives,
|
330
|
+
Shrine.plugin :derivatives,
|
331
|
+
create_on_promote: true, # automatically create derivatives on promotion
|
332
|
+
versions_compatibility: true # handle versions column format
|
326
333
|
```
|
327
334
|
```rb
|
328
335
|
class ImageUploader < Shrine
|
329
|
-
Attacher.
|
336
|
+
Attacher.derivatives do |original|
|
330
337
|
magick = ImageProcessing::MiniMagick.source(original)
|
331
338
|
|
332
339
|
# the :original file should NOT be included anymore
|
@@ -342,28 +349,13 @@ end
|
|
342
349
|
photo = Photo.new(photo_params)
|
343
350
|
|
344
351
|
if photo.valid?
|
345
|
-
photo.
|
346
|
-
photo.save # automatically calls processing block
|
352
|
+
photo.save # creates derivatives on promotion
|
347
353
|
# ...
|
348
354
|
else
|
349
355
|
# ...
|
350
356
|
end
|
351
357
|
```
|
352
358
|
|
353
|
-
If you have multiple places where you need to generate derivatives, and want it
|
354
|
-
to happen automatically like it did with the `versions` plugin, you can
|
355
|
-
override `Attacher#promote` to call `Attacher#create_derivatives` before
|
356
|
-
promotion:
|
357
|
-
|
358
|
-
```rb
|
359
|
-
class Shrine::Attacher
|
360
|
-
def promote(*)
|
361
|
-
create_derivatives
|
362
|
-
super
|
363
|
-
end
|
364
|
-
end
|
365
|
-
```
|
366
|
-
|
367
359
|
### Accessing derivatives
|
368
360
|
|
369
361
|
The derivative URLs are accessed in the same way as versions:
|
@@ -372,7 +364,7 @@ The derivative URLs are accessed in the same way as versions:
|
|
372
364
|
photo.image_url(:small)
|
373
365
|
```
|
374
366
|
|
375
|
-
But the
|
367
|
+
But the files themselves are accessed differently:
|
376
368
|
|
377
369
|
```rb
|
378
370
|
# versions
|
@@ -426,8 +418,8 @@ database column in different formats:
|
|
426
418
|
|
427
419
|
The `:versions_compatibility` flag to the `derivatives` plugin enables it to
|
428
420
|
read the `versions` format, which aids in transition. Once the `derivatives`
|
429
|
-
plugin has been deployed to production, you can
|
430
|
-
new column format:
|
421
|
+
plugin has been deployed to production, you can update existing records with
|
422
|
+
the new column format:
|
431
423
|
|
432
424
|
```rb
|
433
425
|
Photo.find_each do |photo|
|
@@ -447,7 +439,7 @@ creation in the `PromoteJob` instead of the controller:
|
|
447
439
|
class PromoteJob
|
448
440
|
include Sidekiq::Worker
|
449
441
|
|
450
|
-
def perform(attacher_class, record_class,
|
442
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
451
443
|
attacher_class = Object.const_get(attacher_class)
|
452
444
|
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
453
445
|
|
@@ -467,11 +459,11 @@ creating another derivatives processor that you will trigger in the controller:
|
|
467
459
|
|
468
460
|
```rb
|
469
461
|
class ImageUploader < Shrine
|
470
|
-
Attacher.
|
462
|
+
Attacher.derivatives do |original|
|
471
463
|
# this will be triggered in the background job
|
472
464
|
end
|
473
465
|
|
474
|
-
Attacher.
|
466
|
+
Attacher.derivatives :foreground do |original|
|
475
467
|
# this will be triggered in the controller
|
476
468
|
end
|
477
469
|
end
|
@@ -488,48 +480,77 @@ else
|
|
488
480
|
end
|
489
481
|
```
|
490
482
|
|
491
|
-
|
483
|
+
### Default URL
|
492
484
|
|
493
|
-
|
494
|
-
|
485
|
+
If you were using the `default_url` plugin, the `Attacher.default_url` now
|
486
|
+
receives a `:derivative` option:
|
495
487
|
|
496
488
|
```rb
|
497
|
-
|
498
|
-
|
489
|
+
Attacher.default_url do |derivative: nil, **|
|
490
|
+
"https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
|
491
|
+
end
|
499
492
|
```
|
500
|
-
```rb
|
501
|
-
require "concurrent"
|
502
493
|
|
503
|
-
|
494
|
+
#### Fallback to original
|
504
495
|
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
end
|
509
|
-
end
|
496
|
+
With the `versions` plugin, a missing version URL would automatically fall back
|
497
|
+
to the original file. The `derivatives` plugin has no such fallback, but you
|
498
|
+
can configure it manually:
|
510
499
|
|
511
|
-
|
500
|
+
```rb
|
501
|
+
Attacher.default_url do |derivative: nil, **|
|
502
|
+
file&.url if derivative
|
503
|
+
end
|
512
504
|
```
|
513
505
|
|
514
|
-
####
|
506
|
+
#### Fallback to version
|
515
507
|
|
516
|
-
The `
|
508
|
+
The `versions` plugin had the ability to fall back missing version URL to
|
509
|
+
another version that already exists. The `derivatives` plugin doesn't have this
|
510
|
+
built in, but you can implement it as follows:
|
517
511
|
|
518
512
|
```rb
|
513
|
+
DERIVATIVE_FALLBACKS = { foo: :bar, ... }
|
514
|
+
|
519
515
|
Attacher.default_url do |derivative: nil, **|
|
520
|
-
|
516
|
+
derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
|
521
517
|
end
|
522
518
|
```
|
523
519
|
|
524
|
-
|
525
|
-
|
520
|
+
### Location
|
521
|
+
|
522
|
+
The `Shrine#generate_location` method will now receive a `:derivative`
|
523
|
+
parameter instead of `:version`:
|
524
|
+
|
525
|
+
```rb
|
526
|
+
class MyUploader < Shrine
|
527
|
+
def generate_location(io, derivative: nil, **)
|
528
|
+
derivative #=> :large, :medium, :small, ...
|
529
|
+
# ...
|
530
|
+
end
|
531
|
+
end
|
532
|
+
```
|
533
|
+
|
534
|
+
### Overwriting original
|
535
|
+
|
536
|
+
With the `derivatives` plugin, saving processed files separately from the
|
537
|
+
original file, so the original file is automatically kept. This means it's not
|
538
|
+
possible anymore to overwrite the original file as part of processing.
|
539
|
+
|
540
|
+
However, **it's highly recommended to always keep the original file**, even if
|
541
|
+
you don't plan to use it. That way, if there is ever a need to reprocess
|
542
|
+
derivatives, you have the original file to use as a base.
|
543
|
+
|
544
|
+
That being said, if you still want to overwrite the original file, [this
|
545
|
+
thread][overwriting original] has some tips.
|
526
546
|
|
527
547
|
## Other
|
528
548
|
|
529
549
|
### Processing
|
530
550
|
|
531
551
|
The `processing` plugin has been deprecated over the new
|
532
|
-
[`derivatives`][derivatives] plugin. If you were
|
552
|
+
[`derivatives`][derivatives] plugin. If you were previously replacing the
|
553
|
+
original file:
|
533
554
|
|
534
555
|
```rb
|
535
556
|
class MyUploader < Shrine
|
@@ -549,7 +570,7 @@ you should now add the processed file as a derivative:
|
|
549
570
|
class MyUploader < Shrine
|
550
571
|
plugin :derivatives
|
551
572
|
|
552
|
-
Attacher.
|
573
|
+
Attacher.derivatives do |original|
|
553
574
|
magick = ImageProcessing::MiniMagick.source(original)
|
554
575
|
|
555
576
|
{ normalized: magick.resize_to_limit!(1600, 1600) }
|
@@ -557,6 +578,30 @@ class MyUploader < Shrine
|
|
557
578
|
end
|
558
579
|
```
|
559
580
|
|
581
|
+
### Parallelize
|
582
|
+
|
583
|
+
The `parallelize` plugin has been removed. With `derivatives` plugin you can
|
584
|
+
parallelize uploading processed files manually:
|
585
|
+
|
586
|
+
```rb
|
587
|
+
# Gemfile
|
588
|
+
gem "concurrent-ruby"
|
589
|
+
```
|
590
|
+
```rb
|
591
|
+
require "concurrent"
|
592
|
+
|
593
|
+
attacher = photo.image_attacher
|
594
|
+
derivatives = attacher.process_derivatives
|
595
|
+
|
596
|
+
tasks = derivatives.map do |name, file|
|
597
|
+
Concurrent::Promises.future(name, file) do |name, file|
|
598
|
+
attacher.add_derivative(name, file)
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
Concurrent::Promises.zip(*tasks).wait!
|
603
|
+
```
|
604
|
+
|
560
605
|
### Logging
|
561
606
|
|
562
607
|
The `logging` plugin has been removed in favour of the
|
@@ -604,7 +649,7 @@ attacher.copy(other_attacher)
|
|
604
649
|
with
|
605
650
|
|
606
651
|
```rb
|
607
|
-
attacher.
|
652
|
+
attacher.set attacher.upload(other_attacher.file)
|
608
653
|
attacher.add_derivatives other_attacher.derivatives # if using derivatives
|
609
654
|
```
|
610
655
|
|
@@ -662,3 +707,4 @@ end
|
|
662
707
|
[derivatives]: https://shrinerb.com/docs/plugins/derivatives
|
663
708
|
[instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
|
664
709
|
[mirroring]: https://shrinerb.com/docs/plugins/mirroring
|
710
|
+
[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
|
@@ -67,9 +68,9 @@ class Shrine
|
|
67
68
|
#
|
68
69
|
# Shrine.plugin MyPlugin
|
69
70
|
# Shrine.plugin :my_plugin
|
70
|
-
def plugin(plugin, *args, &block)
|
71
|
+
def plugin(plugin, *args, **kwargs, &block)
|
71
72
|
plugin = Plugins.load_plugin(plugin) if plugin.is_a?(Symbol)
|
72
|
-
|
73
|
+
Plugins.load_dependencies(plugin, self, *args, **kwargs, &block)
|
73
74
|
self.include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
|
74
75
|
self.extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
|
75
76
|
self::UploadedFile.include(plugin::FileMethods) if defined?(plugin::FileMethods)
|
@@ -78,7 +79,7 @@ class Shrine
|
|
78
79
|
self::Attachment.extend(plugin::AttachmentClassMethods) if defined?(plugin::AttachmentClassMethods)
|
79
80
|
self::Attacher.include(plugin::AttacherMethods) if defined?(plugin::AttacherMethods)
|
80
81
|
self::Attacher.extend(plugin::AttacherClassMethods) if defined?(plugin::AttacherClassMethods)
|
81
|
-
|
82
|
+
Plugins.configure(plugin, self, *args, **kwargs, &block)
|
82
83
|
plugin
|
83
84
|
end
|
84
85
|
|
@@ -94,8 +95,8 @@ class Shrine
|
|
94
95
|
# class Photo
|
95
96
|
# include Shrine::Attachment(:image) # creates a Shrine::Attachment object
|
96
97
|
# end
|
97
|
-
def Attachment(name,
|
98
|
-
self::Attachment.new(name,
|
98
|
+
def Attachment(name, **args)
|
99
|
+
self::Attachment.new(name, **args)
|
99
100
|
end
|
100
101
|
alias attachment Attachment
|
101
102
|
alias [] Attachment
|
@@ -296,7 +297,7 @@ class Shrine
|
|
296
297
|
# Retrieves the location for the given IO and context. First it looks
|
297
298
|
# for the `:location` option, otherwise it calls #generate_location.
|
298
299
|
def get_location(io, location: nil, **options)
|
299
|
-
location ||= generate_location(io, options)
|
300
|
+
location ||= generate_location(io, **options)
|
300
301
|
location or fail Error, "location generated for #{io.inspect} was nil"
|
301
302
|
end
|
302
303
|
|
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,8 +216,8 @@ class Shrine
|
|
211
216
|
# attacher.change(uploaded_file)
|
212
217
|
# attacher.file #=> #<Shrine::UploadedFile>
|
213
218
|
# attacher.changed? #=> true
|
214
|
-
def change(file
|
215
|
-
@previous = dup
|
219
|
+
def change(file)
|
220
|
+
@previous = dup if change?(file)
|
216
221
|
set(file)
|
217
222
|
end
|
218
223
|
|
@@ -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.
|
@@ -368,6 +373,11 @@ class Shrine
|
|
368
373
|
attached? && !cached?
|
369
374
|
end
|
370
375
|
|
376
|
+
# Whether assigning the given file is considered a change.
|
377
|
+
def change?(file)
|
378
|
+
@file != file
|
379
|
+
end
|
380
|
+
|
371
381
|
# Returns whether the file is uploaded to specified storage.
|
372
382
|
def uploaded?(file, storage_key)
|
373
383
|
file&.storage_key == storage_key
|