shrine 3.0.0.beta3 → 3.0.0.rc
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.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
 - data/CHANGELOG.md +14 -0
 - data/README.md +33 -5
 - data/doc/advantages.md +8 -3
 - data/doc/changing_derivatives.md +11 -5
 - data/doc/changing_location.md +11 -5
 - data/doc/creating_storages.md +13 -4
 - data/doc/direct_s3.md +1 -1
 - data/doc/metadata.md +38 -14
 - data/doc/plugins/backgrounding.md +34 -51
 - data/doc/plugins/mirroring.md +12 -4
 - data/doc/plugins/pretty_location.md +11 -0
 - data/doc/processing.md +32 -13
 - data/doc/release_notes/3.0.0.md +90 -43
 - data/doc/storage/file_system.md +9 -0
 - data/doc/storage/s3.md +9 -0
 - data/doc/upgrading_to_3.md +38 -11
 - data/lib/shrine.rb +1 -1
 - data/lib/shrine/plugins/pretty_location.rb +9 -1
 - data/lib/shrine/plugins/remote_url.rb +3 -3
 - data/lib/shrine/storage/file_system.rb +15 -8
 - data/lib/shrine/storage/linter.rb +34 -7
 - data/lib/shrine/storage/memory.rb +6 -2
 - data/lib/shrine/storage/s3.rb +10 -0
 - data/lib/shrine/version.rb +1 -1
 - data/shrine.gemspec +1 -1
 - metadata +4 -4
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 99f93b258aea07a618b1f35bf08f6b8394dea5d0c6301ed2f96cc72ec12371a4
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ac05d107593d54ce6260d5159c8484f03c24ea0e9df4026b615f79fd5bc04a92
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 640d3a4f07816d7f4a20d385ac1249cd5cdbc46f320a6b8d377338b4ceaa724f5ce1e84782391fd2af6be1419aecd61474a36f03bcffb41c6600f6d999b0be0f
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: fa93fe25894f9a1125a852e438ca5ecd20518e206418d464afbcf59d8576959e373a3084ff133ef2c8aff062e1a4ba93d64841e19cc14fdc256f87d8871dcb4c
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,3 +1,17 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ## 3.0.0.rc (2019-09-28)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            * `core` – Add `Storage#delete_prefixed` method for deleting all files in specified directory (@jrochkind)
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            * `linter` – Return `true` in `Storage::Linter#call` so that it can be used with `assert` (@jrochkind)
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            * `linter` – Allow `Storage::Linter` to accept a key that will be used for testing nonexistent file (@janko)
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            * `core` – Infer file extension from `filename` metadata (@janko)
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            * `pretty_location` – Add `:class_underscore` option for underscoring class name (@Uysim)
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
            * Update `down` dependency to `~> 5.0` (@janko)
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
       1 
15 
     | 
    
         
             
            ## 3.0.0.beta3 (2019-09-25)
         
     | 
| 
       2 
16 
     | 
    
         | 
| 
       3 
17 
     | 
    
         
             
            * `multi_cache` – Add new plugin for whitelisting additional temporary storages (@janko, @jrochkind)
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -34,6 +34,7 @@ If you're curious how it compares to other file attachment libraries, see the [A 
     | 
|
| 
       34 
34 
     | 
    
         
             
              - [IO abstraction](#io-abstraction)
         
     | 
| 
       35 
35 
     | 
    
         
             
            * [Uploaded file](#uploaded-file)
         
     | 
| 
       36 
36 
     | 
    
         
             
            * [Attachment](#attachment)
         
     | 
| 
      
 37 
     | 
    
         
            +
              - [Temporary storage](#temporary-storage)
         
     | 
| 
       37 
38 
     | 
    
         
             
            * [Attacher](#attacher)
         
     | 
| 
       38 
39 
     | 
    
         
             
            * [Plugin system](#plugin-system)
         
     | 
| 
       39 
40 
     | 
    
         
             
            * [Metadata](#metadata)
         
     | 
| 
         @@ -421,6 +422,20 @@ photo.update(image: new_file) # changes the attachment and deletes previous 
     | 
|
| 
       421 
422 
     | 
    
         
             
            photo.update(image: nil)      # removes the attachment and deletes previous
         
     | 
| 
       422 
423 
     | 
    
         
             
            ```
         
     | 
| 
       423 
424 
     | 
    
         | 
| 
      
 425 
     | 
    
         
            +
            ### Temporary storage
         
     | 
| 
      
 426 
     | 
    
         
            +
             
     | 
| 
      
 427 
     | 
    
         
            +
            Shrine uses temporary storage to enable retaining uploaded files across form
         
     | 
| 
      
 428 
     | 
    
         
            +
            redisplays and for [direct uploads](#direct-uploads), but you can disable this
         
     | 
| 
      
 429 
     | 
    
         
            +
            behaviour and have files go straight to permanent storage:
         
     | 
| 
      
 430 
     | 
    
         
            +
             
     | 
| 
      
 431 
     | 
    
         
            +
            ```rb
         
     | 
| 
      
 432 
     | 
    
         
            +
            Shrine.plugin :model, cache: false
         
     | 
| 
      
 433 
     | 
    
         
            +
            ```
         
     | 
| 
      
 434 
     | 
    
         
            +
            ```rb
         
     | 
| 
      
 435 
     | 
    
         
            +
            photo.image = File.open("waterfall.jpg")
         
     | 
| 
      
 436 
     | 
    
         
            +
            photo.image.storage_key #=> :store
         
     | 
| 
      
 437 
     | 
    
         
            +
            ```
         
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
       424 
439 
     | 
    
         
             
            ## Attacher
         
     | 
| 
       425 
440 
     | 
    
         | 
| 
       426 
441 
     | 
    
         
             
            The model attachment attributes and callbacks added by `Shrine::Attachment`
         
     | 
| 
         @@ -848,12 +863,21 @@ choice][Backgrounding Libraries]: 
     | 
|
| 
       848 
863 
     | 
    
         | 
| 
       849 
864 
     | 
    
         
             
            ```rb
         
     | 
| 
       850 
865 
     | 
    
         
             
            Shrine.plugin :backgrounding
         
     | 
| 
       851 
     | 
    
         
            -
            Shrine::Attacher.promote_block  
     | 
| 
       852 
     | 
    
         
            -
             
     | 
| 
      
 866 
     | 
    
         
            +
            Shrine::Attacher.promote_block do
         
     | 
| 
      
 867 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 868 
     | 
    
         
            +
            end
         
     | 
| 
      
 869 
     | 
    
         
            +
            Shrine::Attacher.destroy_block do
         
     | 
| 
      
 870 
     | 
    
         
            +
              DestroyJob.perform_async(self.class.name, data)
         
     | 
| 
      
 871 
     | 
    
         
            +
            end
         
     | 
| 
       853 
872 
     | 
    
         
             
            ```
         
     | 
| 
       854 
873 
     | 
    
         
             
            ```rb
         
     | 
| 
       855 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       856 
     | 
    
         
            -
               
     | 
| 
      
 874 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 875 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 876 
     | 
    
         
            +
             
     | 
| 
      
 877 
     | 
    
         
            +
              def perform(attacher_class, record_class, record.id, name, file_data)
         
     | 
| 
      
 878 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 879 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 880 
     | 
    
         
            +
             
     | 
| 
       857 
881 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       858 
882 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
| 
       859 
883 
     | 
    
         
             
              rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
         
     | 
| 
         @@ -862,8 +886,12 @@ class PromoteJob < ActiveJob::Base 
     | 
|
| 
       862 
886 
     | 
    
         
             
            end
         
     | 
| 
       863 
887 
     | 
    
         
             
            ```
         
     | 
| 
       864 
888 
     | 
    
         
             
            ```rb
         
     | 
| 
       865 
     | 
    
         
            -
            class DestroyJob 
     | 
| 
      
 889 
     | 
    
         
            +
            class DestroyJob
         
     | 
| 
      
 890 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 891 
     | 
    
         
            +
             
     | 
| 
       866 
892 
     | 
    
         
             
              def perform(attacher_class, data)
         
     | 
| 
      
 893 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 894 
     | 
    
         
            +
             
     | 
| 
       867 
895 
     | 
    
         
             
                attacher = attacher_class.from_data(data)
         
     | 
| 
       868 
896 
     | 
    
         
             
                attacher.destroy
         
     | 
| 
       869 
897 
     | 
    
         
             
              end
         
     | 
    
        data/doc/advantages.md
    CHANGED
    
    | 
         @@ -310,12 +310,17 @@ library][backgrounding libraries]. 
     | 
|
| 
       310 
310 
     | 
    
         | 
| 
       311 
311 
     | 
    
         
             
            ```rb
         
     | 
| 
       312 
312 
     | 
    
         
             
            Shrine::Attacher.promote_block do
         
     | 
| 
       313 
     | 
    
         
            -
              PromoteJob. 
     | 
| 
      
 313 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
       314 
314 
     | 
    
         
             
            end
         
     | 
| 
       315 
315 
     | 
    
         
             
            ```
         
     | 
| 
       316 
316 
     | 
    
         
             
            ```rb
         
     | 
| 
       317 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       318 
     | 
    
         
            -
               
     | 
| 
      
 317 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 318 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 319 
     | 
    
         
            +
             
     | 
| 
      
 320 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 321 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 322 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 323 
     | 
    
         
            +
             
     | 
| 
       319 
324 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       320 
325 
     | 
    
         
             
                attacher.create_derivatives # perform processing
         
     | 
| 
       321 
326 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
    
        data/doc/changing_derivatives.md
    CHANGED
    
    | 
         @@ -288,17 +288,23 @@ Photo.find_each do |photo| 
     | 
|
| 
       288 
288 
     | 
    
         | 
| 
       289 
289 
     | 
    
         
             
              next unless attacher.stored?
         
     | 
| 
       290 
290 
     | 
    
         | 
| 
       291 
     | 
    
         
            -
              MakeChangeJob. 
     | 
| 
       292 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       293 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 291 
     | 
    
         
            +
              MakeChangeJob.perform_async(
         
     | 
| 
      
 292 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 293 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 294 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       294 
295 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       295 
296 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       296 
297 
     | 
    
         
             
              )
         
     | 
| 
       297 
298 
     | 
    
         
             
            end
         
     | 
| 
       298 
299 
     | 
    
         
             
            ```
         
     | 
| 
       299 
300 
     | 
    
         
             
            ```rb
         
     | 
| 
       300 
     | 
    
         
            -
            class MakeChangeJob 
     | 
| 
       301 
     | 
    
         
            -
               
     | 
| 
      
 301 
     | 
    
         
            +
            class MakeChangeJob
         
     | 
| 
      
 302 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 303 
     | 
    
         
            +
             
     | 
| 
      
 304 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 305 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 306 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
       302 
308 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       303 
309 
     | 
    
         
             
                # ... make our change ...
         
     | 
| 
       304 
310 
     | 
    
         
             
              end
         
     | 
    
        data/doc/changing_location.md
    CHANGED
    
    | 
         @@ -76,17 +76,23 @@ Photo.find_each do |photo| 
     | 
|
| 
       76 
76 
     | 
    
         | 
| 
       77 
77 
     | 
    
         
             
              next unless attacher.stored? # move only attachments uploaded to permanent storage
         
     | 
| 
       78 
78 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
              MoveFilesJob. 
     | 
| 
       80 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       81 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 79 
     | 
    
         
            +
              MoveFilesJob.perform_async(
         
     | 
| 
      
 80 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 81 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 82 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       82 
83 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       83 
84 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       84 
85 
     | 
    
         
             
              )
         
     | 
| 
       85 
86 
     | 
    
         
             
            end
         
     | 
| 
       86 
87 
     | 
    
         
             
            ```
         
     | 
| 
       87 
88 
     | 
    
         
             
            ```rb
         
     | 
| 
       88 
     | 
    
         
            -
            class MoveFilesJob 
     | 
| 
       89 
     | 
    
         
            -
               
     | 
| 
      
 89 
     | 
    
         
            +
            class MoveFilesJob
         
     | 
| 
      
 90 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 93 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 94 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
       90 
96 
     | 
    
         
             
                attacher     = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       91 
97 
     | 
    
         
             
                old_attacher = attacher.dup
         
     | 
| 
       92 
98 
     | 
    
         | 
    
        data/doc/creating_storages.md
    CHANGED
    
    | 
         @@ -206,15 +206,24 @@ The storage can support additional options to customize how the presign will be 
     | 
|
| 
       206 
206 
     | 
    
         
             
            generated, those can be forwarded via the `:presign_options` option on the
         
     | 
| 
       207 
207 
     | 
    
         
             
            `presign_endpoint` plugin.
         
     | 
| 
       208 
208 
     | 
    
         | 
| 
       209 
     | 
    
         
            -
            ## Clear
         
     | 
| 
      
 209 
     | 
    
         
            +
            ## Delete Prefixed and Clear
         
     | 
| 
       210 
210 
     | 
    
         | 
| 
       211 
     | 
    
         
            -
             
     | 
| 
       212 
     | 
    
         
            -
             
     | 
| 
       213 
     | 
    
         
            -
             
     | 
| 
      
 211 
     | 
    
         
            +
            There are two methods that are not currently used by shrine, but which it's good
         
     | 
| 
      
 212 
     | 
    
         
            +
            for storages to provide to allow client code to delete files from storage. If
         
     | 
| 
      
 213 
     | 
    
         
            +
            storages provide these conventional methods, then clients can delete files using
         
     | 
| 
      
 214 
     | 
    
         
            +
            consistent API for any storage.
         
     | 
| 
      
 215 
     | 
    
         
            +
             
     | 
| 
      
 216 
     | 
    
         
            +
            `#clear!` deletes all files from storage, and `#delete_prefixed` will delete all
         
     | 
| 
      
 217 
     | 
    
         
            +
            files in a given directory/prefix/path. While not strictly required for shrine storage
         
     | 
| 
      
 218 
     | 
    
         
            +
            service functionality, storages should usually implement if possible.
         
     | 
| 
       214 
219 
     | 
    
         | 
| 
       215 
220 
     | 
    
         
             
            ```rb
         
     | 
| 
       216 
221 
     | 
    
         
             
            class MyStorage
         
     | 
| 
       217 
222 
     | 
    
         
             
              # ...
         
     | 
| 
      
 223 
     | 
    
         
            +
              def delete_prefixed(prefix_path)
         
     | 
| 
      
 224 
     | 
    
         
            +
                # deletes all files under the supplied argument prefix
         
     | 
| 
      
 225 
     | 
    
         
            +
              end
         
     | 
| 
      
 226 
     | 
    
         
            +
             
     | 
| 
       218 
227 
     | 
    
         
             
              def clear!
         
     | 
| 
       219 
228 
     | 
    
         
             
                # deletes all files in the storage
         
     | 
| 
       220 
229 
     | 
    
         
             
              end
         
     | 
    
        data/doc/direct_s3.md
    CHANGED
    
    | 
         @@ -310,7 +310,7 @@ Shrine.plugin :backgrounding 
     | 
|
| 
       310 
310 
     | 
    
         | 
| 
       311 
311 
     | 
    
         
             
            Shrine::Attacher.promote_block do
         
     | 
| 
       312 
312 
     | 
    
         
             
              # tells a Sidekiq worker to perform in 3 seconds
         
     | 
| 
       313 
     | 
    
         
            -
              PromoteJob.perform_in(3, self.class, record.class, record.id, name, file_data)
         
     | 
| 
      
 313 
     | 
    
         
            +
              PromoteJob.perform_in(3, self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
       314 
314 
     | 
    
         
             
            end
         
     | 
| 
       315 
315 
     | 
    
         
             
            ```
         
     | 
| 
       316 
316 
     | 
    
         | 
    
        data/doc/metadata.md
    CHANGED
    
    | 
         @@ -255,11 +255,19 @@ plugin uses internally): 
     | 
|
| 
       255 
255 
     | 
    
         
             
            ```rb
         
     | 
| 
       256 
256 
     | 
    
         
             
            Shrine.plugin :refresh_metadata # allow re-extracting metadata
         
     | 
| 
       257 
257 
     | 
    
         
             
            Shrine.plugin :backgrounding
         
     | 
| 
       258 
     | 
    
         
            -
             
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
            Shrine::Attacher.promote_block do
         
     | 
| 
      
 260 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 261 
     | 
    
         
            +
            end
         
     | 
| 
       259 
262 
     | 
    
         
             
            ```
         
     | 
| 
       260 
263 
     | 
    
         
             
            ```rb
         
     | 
| 
       261 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       262 
     | 
    
         
            -
               
     | 
| 
      
 264 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 265 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 268 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 269 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
       263 
271 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       264 
272 
     | 
    
         
             
                attacher.refresh_metadata!
         
     | 
| 
       265 
273 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
| 
         @@ -270,16 +278,22 @@ end 
     | 
|
| 
       270 
278 
     | 
    
         
             
            You can also extract metadata in the background separately from promotion:
         
     | 
| 
       271 
279 
     | 
    
         | 
| 
       272 
280 
     | 
    
         
             
            ```rb
         
     | 
| 
       273 
     | 
    
         
            -
            MetadataJob. 
     | 
| 
       274 
     | 
    
         
            -
              attacher.class,
         
     | 
| 
       275 
     | 
    
         
            -
              attacher.record,
         
     | 
| 
      
 281 
     | 
    
         
            +
            MetadataJob.perform_async(
         
     | 
| 
      
 282 
     | 
    
         
            +
              attacher.class.name,
         
     | 
| 
      
 283 
     | 
    
         
            +
              attacher.record.class.name,
         
     | 
| 
      
 284 
     | 
    
         
            +
              attacher.record.id,
         
     | 
| 
       276 
285 
     | 
    
         
             
              attacher.name,
         
     | 
| 
       277 
286 
     | 
    
         
             
              attacher.file_data,
         
     | 
| 
       278 
287 
     | 
    
         
             
            )
         
     | 
| 
       279 
288 
     | 
    
         
             
            ```
         
     | 
| 
       280 
289 
     | 
    
         
             
            ```rb
         
     | 
| 
       281 
     | 
    
         
            -
            class MetadataJob 
     | 
| 
       282 
     | 
    
         
            -
               
     | 
| 
      
 290 
     | 
    
         
            +
            class MetadataJob
         
     | 
| 
      
 291 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 292 
     | 
    
         
            +
             
     | 
| 
      
 293 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 294 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 295 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
       283 
297 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       284 
298 
     | 
    
         
             
                attacher.refresh_metadata!
         
     | 
| 
       285 
299 
     | 
    
         
             
                attacher.atomic_persist
         
     | 
| 
         @@ -308,9 +322,14 @@ class MyUploader < Shrine 
     | 
|
| 
       308 
322 
     | 
    
         
             
            end
         
     | 
| 
       309 
323 
     | 
    
         
             
            ```
         
     | 
| 
       310 
324 
     | 
    
         
             
            ```rb
         
     | 
| 
       311 
     | 
    
         
            -
            class MetadataJob 
     | 
| 
       312 
     | 
    
         
            -
               
     | 
| 
       313 
     | 
    
         
            -
             
     | 
| 
      
 325 
     | 
    
         
            +
            class MetadataJob
         
     | 
| 
      
 326 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 327 
     | 
    
         
            +
             
     | 
| 
      
 328 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 329 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 330 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 331 
     | 
    
         
            +
             
     | 
| 
      
 332 
     | 
    
         
            +
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       314 
333 
     | 
    
         
             
                attacher.refresh_metadata!(background: true)
         
     | 
| 
       315 
334 
     | 
    
         
             
                attacher.atomic_persist
         
     | 
| 
       316 
335 
     | 
    
         
             
              end
         
     | 
| 
         @@ -324,9 +343,14 @@ promotion, you can wrap both in an `UploadedFile#open` block to make 
     | 
|
| 
       324 
343 
     | 
    
         
             
            sure the file content is retrieved from the storage only once.
         
     | 
| 
       325 
344 
     | 
    
         | 
| 
       326 
345 
     | 
    
         
             
            ```rb
         
     | 
| 
       327 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       328 
     | 
    
         
            -
               
     | 
| 
       329 
     | 
    
         
            -
             
     | 
| 
      
 346 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 347 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 348 
     | 
    
         
            +
             
     | 
| 
      
 349 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 350 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 351 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 352 
     | 
    
         
            +
             
     | 
| 
      
 353 
     | 
    
         
            +
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       330 
354 
     | 
    
         | 
| 
       331 
355 
     | 
    
         
             
                attacher.file.open do
         
     | 
| 
       332 
356 
     | 
    
         
             
                  attacher.refresh_metadata!
         
     | 
| 
         @@ -15,12 +15,21 @@ jobs: 
     | 
|
| 
       15 
15 
     | 
    
         | 
| 
       16 
16 
     | 
    
         
             
            ```rb
         
     | 
| 
       17 
17 
     | 
    
         
             
            # register backgrounding blocks for all uploaders
         
     | 
| 
       18 
     | 
    
         
            -
            Shrine::Attacher.promote_block  
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
      
 18 
     | 
    
         
            +
            Shrine::Attacher.promote_block do
         
     | 
| 
      
 19 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
      
 21 
     | 
    
         
            +
            Shrine::Attacher.destroy_block do
         
     | 
| 
      
 22 
     | 
    
         
            +
              DestroyJob.perform_async(self.class.name, data)
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
       20 
24 
     | 
    
         
             
            ```
         
     | 
| 
       21 
25 
     | 
    
         
             
            ```rb
         
     | 
| 
       22 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       23 
     | 
    
         
            -
               
     | 
| 
      
 26 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 27 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 30 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 31 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
       24 
33 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       25 
34 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
| 
       26 
35 
     | 
    
         
             
              rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
         
     | 
| 
         @@ -29,8 +38,12 @@ class PromoteJob < ActiveJob::Base 
     | 
|
| 
       29 
38 
     | 
    
         
             
            end
         
     | 
| 
       30 
39 
     | 
    
         
             
            ```
         
     | 
| 
       31 
40 
     | 
    
         
             
            ```rb
         
     | 
| 
       32 
     | 
    
         
            -
            class DestroyJob 
     | 
| 
      
 41 
     | 
    
         
            +
            class DestroyJob
         
     | 
| 
      
 42 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
       33 
44 
     | 
    
         
             
              def perform(attacher_class, data)
         
     | 
| 
      
 45 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
       34 
47 
     | 
    
         
             
                attacher = attacher_class.from_data(data)
         
     | 
| 
       35 
48 
     | 
    
         
             
                attacher.destroy
         
     | 
| 
       36 
49 
     | 
    
         
             
              end
         
     | 
| 
         @@ -43,8 +56,12 @@ backgrounding blocks only for a specific uploader: 
     | 
|
| 
       43 
56 
     | 
    
         
             
            ```rb
         
     | 
| 
       44 
57 
     | 
    
         
             
            class MyUploader < Shrine
         
     | 
| 
       45 
58 
     | 
    
         
             
              # register backgrounding blocks only for this uploader
         
     | 
| 
       46 
     | 
    
         
            -
              Attacher.promote_block  
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
      
 59 
     | 
    
         
            +
              Attacher.promote_block do
         
     | 
| 
      
 60 
     | 
    
         
            +
                PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
              Attacher.destroy_block do
         
     | 
| 
      
 63 
     | 
    
         
            +
                DestroyJob.perform_async(self.class.name, data)
         
     | 
| 
      
 64 
     | 
    
         
            +
              end
         
     | 
| 
       48 
65 
     | 
    
         
             
            end
         
     | 
| 
       49 
66 
     | 
    
         
             
            ```
         
     | 
| 
       50 
67 
     | 
    
         | 
| 
         @@ -101,17 +118,18 @@ also use the explicit version by declaring an attacher argument: 
     | 
|
| 
       101 
118 
     | 
    
         | 
| 
       102 
119 
     | 
    
         
             
            ```rb
         
     | 
| 
       103 
120 
     | 
    
         
             
            Shrine::Attacher.promote_block do |attacher|
         
     | 
| 
       104 
     | 
    
         
            -
              PromoteJob. 
     | 
| 
       105 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       106 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 121 
     | 
    
         
            +
              PromoteJob.perform_async(
         
     | 
| 
      
 122 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 123 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 124 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       107 
125 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       108 
126 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       109 
127 
     | 
    
         
             
              )
         
     | 
| 
       110 
128 
     | 
    
         
             
            end
         
     | 
| 
       111 
129 
     | 
    
         | 
| 
       112 
130 
     | 
    
         
             
            Shrine::Attacher.destroy_block do |attacher|
         
     | 
| 
       113 
     | 
    
         
            -
              DestroyJob. 
     | 
| 
       114 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
      
 131 
     | 
    
         
            +
              DestroyJob.perform_async(
         
     | 
| 
      
 132 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
       115 
133 
     | 
    
         
             
                attacher.data,
         
     | 
| 
       116 
134 
     | 
    
         
             
              )
         
     | 
| 
       117 
135 
     | 
    
         
             
            end
         
     | 
| 
         @@ -122,9 +140,10 @@ flexibility: 
     | 
|
| 
       122 
140 
     | 
    
         | 
| 
       123 
141 
     | 
    
         
             
            ```rb
         
     | 
| 
       124 
142 
     | 
    
         
             
            photo.image_attacher.promote_block do |attacher|
         
     | 
| 
       125 
     | 
    
         
            -
              PromoteJob. 
     | 
| 
       126 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       127 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 143 
     | 
    
         
            +
              PromoteJob.perform_async(
         
     | 
| 
      
 144 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 145 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 146 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       128 
147 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       129 
148 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       130 
149 
     | 
    
         
             
                current_user.id, # pass arguments known at the controller level
         
     | 
| 
         @@ -135,42 +154,6 @@ photo.image = file 
     | 
|
| 
       135 
154 
     | 
    
         
             
            photo.save # executes the promote block above
         
     | 
| 
       136 
155 
     | 
    
         
             
            ```
         
     | 
| 
       137 
156 
     | 
    
         | 
| 
       138 
     | 
    
         
            -
            ## Other backgrounding libraries
         
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
       140 
     | 
    
         
            -
            If you're not using Active Job for backgrounding, you might need to retrieve
         
     | 
| 
       141 
     | 
    
         
            -
            records and resolve constants from job arguments manually:
         
     | 
| 
       142 
     | 
    
         
            -
             
     | 
| 
       143 
     | 
    
         
            -
            ```rb
         
     | 
| 
       144 
     | 
    
         
            -
            # register backgrounding blocks for all uploaders
         
     | 
| 
       145 
     | 
    
         
            -
            Shrine::Attacher.promote_block { PromoteJob.perform_async(self.class, record.class, record.id, name, file_data) }
         
     | 
| 
       146 
     | 
    
         
            -
            Shrine::Attacher.destroy_block { DestroyJob.perform_async(self.class, data) }
         
     | 
| 
       147 
     | 
    
         
            -
            ```
         
     | 
| 
       148 
     | 
    
         
            -
            ```rb
         
     | 
| 
       149 
     | 
    
         
            -
            class PromoteJob
         
     | 
| 
       150 
     | 
    
         
            -
              include Sidekiq::Worker
         
     | 
| 
       151 
     | 
    
         
            -
             
     | 
| 
       152 
     | 
    
         
            -
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
       153 
     | 
    
         
            -
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
       154 
     | 
    
         
            -
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
       155 
     | 
    
         
            -
             
     | 
| 
       156 
     | 
    
         
            -
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       157 
     | 
    
         
            -
                attacher.atomic_promote
         
     | 
| 
       158 
     | 
    
         
            -
              rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
         
     | 
| 
       159 
     | 
    
         
            -
              end
         
     | 
| 
       160 
     | 
    
         
            -
            end
         
     | 
| 
       161 
     | 
    
         
            -
             
     | 
| 
       162 
     | 
    
         
            -
            class DestroyJob
         
     | 
| 
       163 
     | 
    
         
            -
              include Sidekiq::Worker
         
     | 
| 
       164 
     | 
    
         
            -
             
     | 
| 
       165 
     | 
    
         
            -
              def perform(attacher_class, data)
         
     | 
| 
       166 
     | 
    
         
            -
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                attacher = attacher_class.from_data(data)
         
     | 
| 
       169 
     | 
    
         
            -
                attacher.destroy
         
     | 
| 
       170 
     | 
    
         
            -
              end
         
     | 
| 
       171 
     | 
    
         
            -
            end
         
     | 
| 
       172 
     | 
    
         
            -
            ```
         
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
157 
     | 
    
         
             
            [backgrounding]: /lib/shrine/plugins/backgrounding.rb
         
     | 
| 
       175 
158 
     | 
    
         
             
            [derivatives]: /doc/plugins/derivatives.md#readme
         
     | 
| 
       176 
159 
     | 
    
         
             
            [atomic_helpers]: /doc/plugins/atomic_helpers.md#readme
         
     | 
    
        data/doc/plugins/mirroring.md
    CHANGED
    
    | 
         @@ -49,24 +49,32 @@ You can have mirroring performed in a background job: 
     | 
|
| 
       49 
49 
     | 
    
         | 
| 
       50 
50 
     | 
    
         
             
            ```rb
         
     | 
| 
       51 
51 
     | 
    
         
             
            Shrine.mirror_upload_block do |file|
         
     | 
| 
       52 
     | 
    
         
            -
              MirrorUploadJob. 
     | 
| 
      
 52 
     | 
    
         
            +
              MirrorUploadJob.perform_async(file.shrine_class.name, file.data)
         
     | 
| 
       53 
53 
     | 
    
         
             
            end
         
     | 
| 
       54 
54 
     | 
    
         | 
| 
       55 
55 
     | 
    
         
             
            Shrine.mirror_delete_block do |file|
         
     | 
| 
       56 
     | 
    
         
            -
              MirrorDeleteJob. 
     | 
| 
      
 56 
     | 
    
         
            +
              MirrorDeleteJob.perform_async(file.shrine_class.name, file.data)
         
     | 
| 
       57 
57 
     | 
    
         
             
            end
         
     | 
| 
       58 
58 
     | 
    
         
             
            ```
         
     | 
| 
       59 
59 
     | 
    
         
             
            ```rb
         
     | 
| 
       60 
     | 
    
         
            -
            class MirrorUploadJob 
     | 
| 
      
 60 
     | 
    
         
            +
            class MirrorUploadJob
         
     | 
| 
      
 61 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
       61 
63 
     | 
    
         
             
              def perform(shrine_class, file_data)
         
     | 
| 
      
 64 
     | 
    
         
            +
                shrine_class = Object.const_get(shrine_class)
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
       62 
66 
     | 
    
         
             
                file = shrine_class.uploaded_file(file_data)
         
     | 
| 
       63 
67 
     | 
    
         
             
                file.mirror_upload
         
     | 
| 
       64 
68 
     | 
    
         
             
              end
         
     | 
| 
       65 
69 
     | 
    
         
             
            end
         
     | 
| 
       66 
70 
     | 
    
         
             
            ```
         
     | 
| 
       67 
71 
     | 
    
         
             
            ```rb
         
     | 
| 
       68 
     | 
    
         
            -
            class MirrorDeleteJob 
     | 
| 
      
 72 
     | 
    
         
            +
            class MirrorDeleteJob
         
     | 
| 
      
 73 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
       69 
75 
     | 
    
         
             
              def perform(shrine_class, file_data)
         
     | 
| 
      
 76 
     | 
    
         
            +
                shrine_class = Object.const_get(shrine_class)
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
       70 
78 
     | 
    
         
             
                file = shrine_class.uploaded_file(file_data)
         
     | 
| 
       71 
79 
     | 
    
         
             
                file.mirror_delete
         
     | 
| 
       72 
80 
     | 
    
         
             
              end
         
     | 
| 
         @@ -40,6 +40,17 @@ plugin :pretty_location, identifier: :email 
     | 
|
| 
       40 
40 
     | 
    
         
             
            # "user/foo@bar.com/profile_picture/493g82jf23.jpg"
         
     | 
| 
       41 
41 
     | 
    
         
             
            ```
         
     | 
| 
       42 
42 
     | 
    
         | 
| 
      
 43 
     | 
    
         
            +
            By default, the class name will be only downcased. We can also have the class
         
     | 
| 
      
 44 
     | 
    
         
            +
            name underscored with the `:class_underscore` option:
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 47 
     | 
    
         
            +
            plugin :pretty_location
         
     | 
| 
      
 48 
     | 
    
         
            +
            # "blogpost/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            plugin :pretty_location, class_underscore: :true
         
     | 
| 
      
 51 
     | 
    
         
            +
            # "blog_post/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
         
     | 
| 
      
 52 
     | 
    
         
            +
            ```
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
       43 
54 
     | 
    
         
             
            For a more custom identifier logic, you can overwrite the method
         
     | 
| 
       44 
55 
     | 
    
         
             
            `#generate_location` and call `#pretty_location` with the identifier you have
         
     | 
| 
       45 
56 
     | 
    
         
             
            calculated.
         
     | 
    
        data/doc/processing.md
    CHANGED
    
    | 
         @@ -82,11 +82,18 @@ promotion: 
     | 
|
| 
       82 
82 
     | 
    
         | 
| 
       83 
83 
     | 
    
         
             
            ```rb
         
     | 
| 
       84 
84 
     | 
    
         
             
            Shrine.plugin :backgrounding
         
     | 
| 
       85 
     | 
    
         
            -
            Shrine::Attacher.promote_block  
     | 
| 
      
 85 
     | 
    
         
            +
            Shrine::Attacher.promote_block do
         
     | 
| 
      
 86 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
| 
       86 
88 
     | 
    
         
             
            ```
         
     | 
| 
       87 
89 
     | 
    
         
             
            ```rb
         
     | 
| 
       88 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       89 
     | 
    
         
            -
               
     | 
| 
      
 90 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 91 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 94 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 95 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
       90 
97 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       91 
98 
     | 
    
         
             
                attacher.create_derivatives # calls derivatives processor
         
     | 
| 
       92 
99 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
| 
         @@ -102,16 +109,22 @@ Derivatives don't need to be created as part of the attachment flow, you can 
     | 
|
| 
       102 
109 
     | 
    
         
             
            create them at any point after promotion:
         
     | 
| 
       103 
110 
     | 
    
         | 
| 
       104 
111 
     | 
    
         
             
            ```rb
         
     | 
| 
       105 
     | 
    
         
            -
            DerivativesJob. 
     | 
| 
       106 
     | 
    
         
            -
              attacher.class,
         
     | 
| 
       107 
     | 
    
         
            -
              attacher.record,
         
     | 
| 
      
 112 
     | 
    
         
            +
            DerivativesJob.perform_async(
         
     | 
| 
      
 113 
     | 
    
         
            +
              attacher.class.name,
         
     | 
| 
      
 114 
     | 
    
         
            +
              attacher.record.class.name,
         
     | 
| 
      
 115 
     | 
    
         
            +
              attacher.record.id,
         
     | 
| 
       108 
116 
     | 
    
         
             
              attacher.name,
         
     | 
| 
       109 
117 
     | 
    
         
             
              attacher.file_data,
         
     | 
| 
       110 
118 
     | 
    
         
             
            )
         
     | 
| 
       111 
119 
     | 
    
         
             
            ```
         
     | 
| 
       112 
120 
     | 
    
         
             
            ```rb
         
     | 
| 
       113 
     | 
    
         
            -
            class DerivativesJob 
     | 
| 
       114 
     | 
    
         
            -
               
     | 
| 
      
 121 
     | 
    
         
            +
            class DerivativesJob
         
     | 
| 
      
 122 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 125 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 126 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
       115 
128 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       116 
129 
     | 
    
         
             
                attacher.create_derivatives # calls derivatives processor
         
     | 
| 
       117 
130 
     | 
    
         
             
                attacher.atomic_persist
         
     | 
| 
         @@ -144,9 +157,10 @@ end 
     | 
|
| 
       144 
157 
     | 
    
         
             
            ```
         
     | 
| 
       145 
158 
     | 
    
         
             
            ```rb
         
     | 
| 
       146 
159 
     | 
    
         
             
            ImageUploader::THUMBNAILS.each_key do |derivative_name|
         
     | 
| 
       147 
     | 
    
         
            -
              DerivativeJob. 
     | 
| 
       148 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       149 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 160 
     | 
    
         
            +
              DerivativeJob.perform_async(
         
     | 
| 
      
 161 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 162 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 163 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       150 
164 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       151 
165 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       152 
166 
     | 
    
         
             
                derivative_name,
         
     | 
| 
         @@ -154,8 +168,13 @@ ImageUploader::THUMBNAILS.each_key do |derivative_name| 
     | 
|
| 
       154 
168 
     | 
    
         
             
            end
         
     | 
| 
       155 
169 
     | 
    
         
             
            ```
         
     | 
| 
       156 
170 
     | 
    
         
             
            ```rb
         
     | 
| 
       157 
     | 
    
         
            -
            class DerivativeJob 
     | 
| 
       158 
     | 
    
         
            -
               
     | 
| 
      
 171 
     | 
    
         
            +
            class DerivativeJob
         
     | 
| 
      
 172 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 173 
     | 
    
         
            +
             
     | 
| 
      
 174 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data, derivative_name)
         
     | 
| 
      
 175 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 176 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 177 
     | 
    
         
            +
             
     | 
| 
       159 
178 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       160 
179 
     | 
    
         
             
                attacher.create_derivatives(name: derivative_name)
         
     | 
| 
       161 
180 
     | 
    
         
             
                attacher.atomic_persist do |reloaded_attacher|
         
     | 
    
        data/doc/release_notes/3.0.0.md
    CHANGED
    
    | 
         @@ -135,22 +135,35 @@ how to upgrade. 
     | 
|
| 
       135 
135 
     | 
    
         | 
| 
       136 
136 
     | 
    
         
             
              ```rb
         
     | 
| 
       137 
137 
     | 
    
         
             
              Shrine.plugin :backgrounding
         
     | 
| 
       138 
     | 
    
         
            -
              Shrine::Attacher.promote_block  
     | 
| 
       139 
     | 
    
         
            -
             
     | 
| 
      
 138 
     | 
    
         
            +
              Shrine::Attacher.promote_block do
         
     | 
| 
      
 139 
     | 
    
         
            +
                PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 140 
     | 
    
         
            +
              end
         
     | 
| 
      
 141 
     | 
    
         
            +
              Shrine::Attacher.destroy_block do
         
     | 
| 
      
 142 
     | 
    
         
            +
                DestroyJob.perform_async(self.class.name, data)
         
     | 
| 
      
 143 
     | 
    
         
            +
              end
         
     | 
| 
       140 
144 
     | 
    
         
             
              ```
         
     | 
| 
       141 
145 
     | 
    
         
             
              ```rb
         
     | 
| 
       142 
     | 
    
         
            -
              class PromoteJob 
     | 
| 
       143 
     | 
    
         
            -
                 
     | 
| 
      
 146 
     | 
    
         
            +
              class PromoteJob
         
     | 
| 
      
 147 
     | 
    
         
            +
                include Sidekiq::Worker
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
                def perform(attacher_class, record_class, record.id, name, file_data)
         
     | 
| 
      
 150 
     | 
    
         
            +
                  attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 151 
     | 
    
         
            +
                  record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
       144 
153 
     | 
    
         
             
                  attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       145 
     | 
    
         
            -
                  attacher.atomic_promote 
     | 
| 
      
 154 
     | 
    
         
            +
                  attacher.atomic_promote
         
     | 
| 
       146 
155 
     | 
    
         
             
                rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
         
     | 
| 
       147 
     | 
    
         
            -
                  # attachment has changed or record has been deleted, nothing to do
         
     | 
| 
      
 156 
     | 
    
         
            +
                  # attachment has changed or the record has been deleted, nothing to do
         
     | 
| 
       148 
157 
     | 
    
         
             
                end
         
     | 
| 
       149 
158 
     | 
    
         
             
              end
         
     | 
| 
       150 
159 
     | 
    
         
             
              ```
         
     | 
| 
       151 
160 
     | 
    
         
             
              ```rb
         
     | 
| 
       152 
     | 
    
         
            -
              class DestroyJob 
     | 
| 
      
 161 
     | 
    
         
            +
              class DestroyJob
         
     | 
| 
      
 162 
     | 
    
         
            +
                include Sidekiq::Worker
         
     | 
| 
      
 163 
     | 
    
         
            +
             
     | 
| 
       153 
164 
     | 
    
         
             
                def perform(attacher_class, data)
         
     | 
| 
      
 165 
     | 
    
         
            +
                  attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
       154 
167 
     | 
    
         
             
                  attacher = attacher_class.from_data(data)
         
     | 
| 
       155 
168 
     | 
    
         
             
                  attacher.destroy
         
     | 
| 
       156 
169 
     | 
    
         
             
                end
         
     | 
| 
         @@ -170,9 +183,10 @@ how to upgrade. 
     | 
|
| 
       170 
183 
     | 
    
         
             
              photo = Photo.new(photo_params)
         
     | 
| 
       171 
184 
     | 
    
         | 
| 
       172 
185 
     | 
    
         
             
              photo.image_attacher.promote_block do |attacher|
         
     | 
| 
       173 
     | 
    
         
            -
                PromoteJob. 
     | 
| 
       174 
     | 
    
         
            -
                  attacher.class,
         
     | 
| 
       175 
     | 
    
         
            -
                  attacher.record,
         
     | 
| 
      
 186 
     | 
    
         
            +
                PromoteJob.perform_async(
         
     | 
| 
      
 187 
     | 
    
         
            +
                  attacher.class.name,
         
     | 
| 
      
 188 
     | 
    
         
            +
                  attacher.record.class.name,
         
     | 
| 
      
 189 
     | 
    
         
            +
                  attacher.record.id,
         
     | 
| 
       176 
190 
     | 
    
         
             
                  attacher.name,
         
     | 
| 
       177 
191 
     | 
    
         
             
                  attacher.file_data,
         
     | 
| 
       178 
192 
     | 
    
         
             
                  current_user.id, # <== parameters from the controller
         
     | 
| 
         @@ -196,16 +210,22 @@ how to upgrade. 
     | 
|
| 
       196 
210 
     | 
    
         
             
              implement metadata extraction in the background in a concurrency-safe way:
         
     | 
| 
       197 
211 
     | 
    
         | 
| 
       198 
212 
     | 
    
         
             
              ```rb
         
     | 
| 
       199 
     | 
    
         
            -
              MetadataJob. 
     | 
| 
       200 
     | 
    
         
            -
                attacher.class,
         
     | 
| 
       201 
     | 
    
         
            -
                attacher.record,
         
     | 
| 
      
 213 
     | 
    
         
            +
              MetadataJob.perform_async(
         
     | 
| 
      
 214 
     | 
    
         
            +
                attacher.class.name,
         
     | 
| 
      
 215 
     | 
    
         
            +
                attacher.record.class.name,
         
     | 
| 
      
 216 
     | 
    
         
            +
                attacher.record.id,
         
     | 
| 
       202 
217 
     | 
    
         
             
                attacher.name,
         
     | 
| 
       203 
218 
     | 
    
         
             
                attacher.file_data,
         
     | 
| 
       204 
219 
     | 
    
         
             
              )
         
     | 
| 
       205 
220 
     | 
    
         
             
              ```
         
     | 
| 
       206 
221 
     | 
    
         
             
              ```rb
         
     | 
| 
       207 
     | 
    
         
            -
              class MetadataJob 
     | 
| 
       208 
     | 
    
         
            -
                 
     | 
| 
      
 222 
     | 
    
         
            +
              class MetadataJob
         
     | 
| 
      
 223 
     | 
    
         
            +
                include Sidekiq::Worker
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 226 
     | 
    
         
            +
                  attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 227 
     | 
    
         
            +
                  record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 228 
     | 
    
         
            +
             
     | 
| 
       209 
229 
     | 
    
         
             
                  attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       210 
230 
     | 
    
         
             
                  attacher.refresh_metadata! # extract metadata
         
     | 
| 
       211 
231 
     | 
    
         
             
                  attacher.atomic_persist    # persist if attachment hasn't changed
         
     | 
| 
         @@ -215,7 +235,7 @@ how to upgrade. 
     | 
|
| 
       215 
235 
     | 
    
         
             
              end
         
     | 
| 
       216 
236 
     | 
    
         
             
              ```
         
     | 
| 
       217 
237 
     | 
    
         | 
| 
       218 
     | 
    
         
            -
            ## Other  
     | 
| 
      
 238 
     | 
    
         
            +
            ## Other new plugins
         
     | 
| 
       219 
239 
     | 
    
         | 
| 
       220 
240 
     | 
    
         
             
            * The new [`mirroring`][mirroring] plugin has been added for replicating
         
     | 
| 
       221 
241 
     | 
    
         
             
              uploads and deletes to other storages.
         
     | 
| 
         @@ -272,6 +292,8 @@ how to upgrade. 
     | 
|
| 
       272 
292 
     | 
    
         
             
              photo.image.storage_key #=> :store (permanent storage)
         
     | 
| 
       273 
293 
     | 
    
         
             
              ```
         
     | 
| 
       274 
294 
     | 
    
         | 
| 
      
 295 
     | 
    
         
            +
            ## Other features
         
     | 
| 
      
 296 
     | 
    
         
            +
             
     | 
| 
       275 
297 
     | 
    
         
             
            * New `Shrine.download_response` method has been added to the
         
     | 
| 
       276 
298 
     | 
    
         
             
              `download_endpoint` plugin for generating file response from the controller.
         
     | 
| 
       277 
299 
     | 
    
         | 
| 
         @@ -314,10 +336,11 @@ how to upgrade. 
     | 
|
| 
       314 
336 
     | 
    
         
             
              ```rb
         
     | 
| 
       315 
337 
     | 
    
         
             
              class Photo
         
     | 
| 
       316 
338 
     | 
    
         
             
                include ImageUploader::Attachment(:image)
         
     | 
| 
       317 
     | 
    
         
            -
             
     | 
| 
       318 
     | 
    
         
            -
                image_attacher #=> #<ImageUploader::Attacher ...>
         
     | 
| 
       319 
339 
     | 
    
         
             
              end
         
     | 
| 
       320 
340 
     | 
    
         
             
              ```
         
     | 
| 
      
 341 
     | 
    
         
            +
              ```rb
         
     | 
| 
      
 342 
     | 
    
         
            +
              Photo.image_attacher #=> #<ImageUploader::Attacher ...>
         
     | 
| 
      
 343 
     | 
    
         
            +
              ```
         
     | 
| 
       321 
344 
     | 
    
         | 
| 
       322 
345 
     | 
    
         
             
            * The attachment data serializer is now configurable (by default `JSON`
         
     | 
| 
       323 
346 
     | 
    
         
             
              standard library is used):
         
     | 
| 
         @@ -382,6 +405,17 @@ how to upgrade. 
     | 
|
| 
       382 
405 
     | 
    
         | 
| 
       383 
406 
     | 
    
         
             
            ## Other Improvements
         
     | 
| 
       384 
407 
     | 
    
         | 
| 
      
 408 
     | 
    
         
            +
            ### Core improvements
         
     | 
| 
      
 409 
     | 
    
         
            +
             
     | 
| 
      
 410 
     | 
    
         
            +
            * Shrine now works again with MRI 2.3.
         
     | 
| 
      
 411 
     | 
    
         
            +
             
     | 
| 
      
 412 
     | 
    
         
            +
            * The memory storage from the [shrine-memory] gem has been merged into core.
         
     | 
| 
      
 413 
     | 
    
         
            +
             
     | 
| 
      
 414 
     | 
    
         
            +
              ```rb
         
     | 
| 
      
 415 
     | 
    
         
            +
              # Gemfile
         
     | 
| 
      
 416 
     | 
    
         
            +
              gem "shrine-memory" # this can be removed
         
     | 
| 
      
 417 
     | 
    
         
            +
              ```
         
     | 
| 
      
 418 
     | 
    
         
            +
             
     | 
| 
       385 
419 
     | 
    
         
             
            * The `Attacher#assign` method now accepts cached file data as a Hash.
         
     | 
| 
       386 
420 
     | 
    
         | 
| 
       387 
421 
     | 
    
         
             
              ```rb
         
     | 
| 
         @@ -396,15 +430,36 @@ how to upgrade. 
     | 
|
| 
       396 
430 
     | 
    
         | 
| 
       397 
431 
     | 
    
         
             
            * The temporary storage doesn't need to be defined anymore if it's not used.
         
     | 
| 
       398 
432 
     | 
    
         | 
| 
       399 
     | 
    
         
            -
            *  
     | 
| 
      
 433 
     | 
    
         
            +
            * Any changes to `Shrine.storages` will now be applied to existing `Shrine` and
         
     | 
| 
      
 434 
     | 
    
         
            +
              `Attacher` instances.
         
     | 
| 
      
 435 
     | 
    
         
            +
             
     | 
| 
      
 436 
     | 
    
         
            +
            * When copying the S3 object to another location, any specified upload options
         
     | 
| 
      
 437 
     | 
    
         
            +
              will now be applied.
         
     | 
| 
      
 438 
     | 
    
         
            +
             
     | 
| 
      
 439 
     | 
    
         
            +
            * Deprecation of passing unknown options to `FileSystem#open` has been
         
     | 
| 
      
 440 
     | 
    
         
            +
              reverted. This allows users to continue using `FileSystem` storage in tests
         
     | 
| 
      
 441 
     | 
    
         
            +
              as a mock storage.
         
     | 
| 
      
 442 
     | 
    
         
            +
             
     | 
| 
      
 443 
     | 
    
         
            +
            * The `Shrine#upload` method now infers file extension from `filename` metadata,
         
     | 
| 
      
 444 
     | 
    
         
            +
              making possible to use `filename` to specify file extension.
         
     | 
| 
       400 
445 
     | 
    
         | 
| 
       401 
446 
     | 
    
         
             
              ```rb
         
     | 
| 
       402 
     | 
    
         
            -
               
     | 
| 
       403 
     | 
    
         
            -
               
     | 
| 
      
 447 
     | 
    
         
            +
              file = uploader.upload(StringIO.new("some text"), metadata: { "filename" => "file.txt" })
         
     | 
| 
      
 448 
     | 
    
         
            +
              file.id #=> "2a2467ee6acbc5cb.txt"
         
     | 
| 
       404 
449 
     | 
    
         
             
              ```
         
     | 
| 
       405 
450 
     | 
    
         | 
| 
       406 
     | 
    
         
            -
            *  
     | 
| 
       407 
     | 
    
         
            -
               
     | 
| 
      
 451 
     | 
    
         
            +
            * The `Shrine.opts` hash is now deep-copied on subclassing. This allows plugins
         
     | 
| 
      
 452 
     | 
    
         
            +
              to freely mutate hashes and arrays in `Shrine.opts`, knowing they won't be
         
     | 
| 
      
 453 
     | 
    
         
            +
              shared across subclasses.
         
     | 
| 
      
 454 
     | 
    
         
            +
             
     | 
| 
      
 455 
     | 
    
         
            +
            * The `down` dependency has been updated to `~> 5.0`.
         
     | 
| 
      
 456 
     | 
    
         
            +
             
     | 
| 
      
 457 
     | 
    
         
            +
            ### Plugin improvements
         
     | 
| 
      
 458 
     | 
    
         
            +
             
     | 
| 
      
 459 
     | 
    
         
            +
            * The `activerecord` plugin now works with Active Record 3.
         
     | 
| 
      
 460 
     | 
    
         
            +
             
     | 
| 
      
 461 
     | 
    
         
            +
            * Callback code from `activerecord` and `sequel` plugin has been moved into
         
     | 
| 
      
 462 
     | 
    
         
            +
              attacher methods, allowing the user to override them.
         
     | 
| 
       408 
463 
     | 
    
         | 
| 
       409 
464 
     | 
    
         
             
            * The `url_options` plugin now allows you to override URL options by deleting
         
     | 
| 
       410 
465 
     | 
    
         
             
              them.
         
     | 
| 
         @@ -448,6 +503,9 @@ how to upgrade. 
     | 
|
| 
       448 
503 
     | 
    
         
             
            * The `Derivation#upload` method from `derivation_endpoint` plugin now accepts
         
     | 
| 
       449 
504 
     | 
    
         
             
              any IO-like object.
         
     | 
| 
       450 
505 
     | 
    
         | 
| 
      
 506 
     | 
    
         
            +
            * The `derivation_endpoint` plugin doesn't re-open `File` objects returned in
         
     | 
| 
      
 507 
     | 
    
         
            +
              derivation block anymore.
         
     | 
| 
      
 508 
     | 
    
         
            +
             
     | 
| 
       451 
509 
     | 
    
         
             
            * The `instrumentation` plugin now instruments `UploadedFile#open` calls as a
         
     | 
| 
       452 
510 
     | 
    
         
             
              new `open.shrine` event. `UploadedFile#download` is still instrumented as
         
     | 
| 
       453 
511 
     | 
    
         
             
              `download.shrine`.
         
     | 
| 
         @@ -462,29 +520,19 @@ how to upgrade. 
     | 
|
| 
       462 
520 
     | 
    
         
             
            * Any options passed to `Attacher#attach_cached` are now forwarded to metadata
         
     | 
| 
       463 
521 
     | 
    
         
             
              extraction when `restore_cached_data` plugin is loaded.
         
     | 
| 
       464 
522 
     | 
    
         | 
| 
       465 
     | 
    
         
            -
            * Deprecation of passing unknown options to `FileSystem#open` has been
         
     | 
| 
       466 
     | 
    
         
            -
              reverted. This allows users to continue using `FileSystem` storage in tests
         
     | 
| 
       467 
     | 
    
         
            -
              as a mock storage.
         
     | 
| 
       468 
     | 
    
         
            -
             
     | 
| 
       469 
     | 
    
         
            -
            * The `activerecord` plugin now works with Active Record 3.
         
     | 
| 
       470 
     | 
    
         
            -
             
     | 
| 
       471 
     | 
    
         
            -
            * Shrine now works again with MRI 2.3.
         
     | 
| 
       472 
     | 
    
         
            -
             
     | 
| 
       473 
523 
     | 
    
         
             
            * The `infer_extension` plugin now works correctly with `pretty_location`
         
     | 
| 
       474 
524 
     | 
    
         
             
              plugin when `pretty_location` was loaded after `infer_extension`.
         
     | 
| 
       475 
525 
     | 
    
         | 
| 
       476 
     | 
    
         
            -
            * The ` 
     | 
| 
       477 
     | 
    
         
            -
               
     | 
| 
       478 
     | 
    
         
            -
             
     | 
| 
       479 
     | 
    
         
            -
            * Any changes to `Shrine.storages` will now be applied to existing `Shrine` and
         
     | 
| 
       480 
     | 
    
         
            -
              `Attacher` instances.
         
     | 
| 
      
 526 
     | 
    
         
            +
            * The `pretty_location` plugin now accepts `:class_underscore` option for
         
     | 
| 
      
 527 
     | 
    
         
            +
              underscoring class names.
         
     | 
| 
       481 
528 
     | 
    
         | 
| 
       482 
     | 
    
         
            -
             
     | 
| 
       483 
     | 
    
         
            -
               
     | 
| 
      
 529 
     | 
    
         
            +
              ```rb
         
     | 
| 
      
 530 
     | 
    
         
            +
              plugin :pretty_location
         
     | 
| 
      
 531 
     | 
    
         
            +
              # "blogpost/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
         
     | 
| 
       484 
532 
     | 
    
         | 
| 
       485 
     | 
    
         
            -
             
     | 
| 
       486 
     | 
    
         
            -
               
     | 
| 
       487 
     | 
    
         
            -
               
     | 
| 
      
 533 
     | 
    
         
            +
              plugin :pretty_location, class_underscore: :true
         
     | 
| 
      
 534 
     | 
    
         
            +
              # "blog_post/aa357797-5845-451b-8662-08eecdc9f762/image/493g82jf23.jpg"
         
     | 
| 
      
 535 
     | 
    
         
            +
              ```
         
     | 
| 
       488 
536 
     | 
    
         | 
| 
       489 
537 
     | 
    
         
             
            ## Backwards compatibility
         
     | 
| 
       490 
538 
     | 
    
         | 
| 
         @@ -636,8 +684,7 @@ how to upgrade. 
     | 
|
| 
       636 
684 
     | 
    
         | 
| 
       637 
685 
     | 
    
         
             
            ### S3 API
         
     | 
| 
       638 
686 
     | 
    
         | 
| 
       639 
     | 
    
         
            -
            * The support for `aws-sdk` 2.x and `aws-sdk-s3`  
     | 
| 
       640 
     | 
    
         
            -
              removed.
         
     | 
| 
      
 687 
     | 
    
         
            +
            * The support for `aws-sdk` 2.x and `aws-sdk-s3` < 1.14 has been removed.
         
     | 
| 
       641 
688 
     | 
    
         | 
| 
       642 
689 
     | 
    
         
             
            * `S3#open` now raises `Shrine::FileNotFound` exception when S3 object doesn't
         
     | 
| 
       643 
690 
     | 
    
         
             
              exist on the bucket.
         
     | 
    
        data/doc/storage/file_system.md
    CHANGED
    
    | 
         @@ -74,6 +74,15 @@ You can retrieve path to the file using `#path`: 
     | 
|
| 
       74 
74 
     | 
    
         
             
            storage.path("image.jpg") #=> #<Pathname:public/image.jpg>
         
     | 
| 
       75 
75 
     | 
    
         
             
            ```
         
     | 
| 
       76 
76 
     | 
    
         | 
| 
      
 77 
     | 
    
         
            +
            ## Deleting prefixed
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
            If you want to delete all files in some directory, you can use
         
     | 
| 
      
 80 
     | 
    
         
            +
            `FileSystem#delete_prefixed`:
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
            ```rb
         
     | 
| 
      
 83 
     | 
    
         
            +
            storage.delete_prefixed("some_directory") # deletes all files in "some_directory/"
         
     | 
| 
      
 84 
     | 
    
         
            +
            ```
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
       77 
86 
     | 
    
         
             
            ## Clearing cache
         
     | 
| 
       78 
87 
     | 
    
         | 
| 
       79 
88 
     | 
    
         
             
            If you're using FileSystem as cache, you will probably want to periodically
         
     | 
    
        data/doc/storage/s3.md
    CHANGED
    
    | 
         @@ -259,6 +259,15 @@ To use Amazon S3's [Transfer Acceleration] feature, set 
     | 
|
| 
       259 
259 
     | 
    
         
             
            Shrine::Storage::S3.new(use_accelerate_endpoint: true, **other_options)
         
     | 
| 
       260 
260 
     | 
    
         
             
            ```
         
     | 
| 
       261 
261 
     | 
    
         | 
| 
      
 262 
     | 
    
         
            +
            ## Deleting prefixed
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
      
 264 
     | 
    
         
            +
            If you want to delete all objects in some prefix, you can use
         
     | 
| 
      
 265 
     | 
    
         
            +
            `S3#delete_prefixed`:
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
            ```rb
         
     | 
| 
      
 268 
     | 
    
         
            +
            s3.delete_prefixed("some_prefix") # deletes all objects in "some_prefix/"
         
     | 
| 
      
 269 
     | 
    
         
            +
            ```
         
     | 
| 
      
 270 
     | 
    
         
            +
             
     | 
| 
       262 
271 
     | 
    
         
             
            ## Clearing cache
         
     | 
| 
       263 
272 
     | 
    
         | 
| 
       264 
273 
     | 
    
         
             
            If you're using S3 as a cache, you will probably want to periodically delete
         
     | 
    
        data/doc/upgrading_to_3.md
    CHANGED
    
    | 
         @@ -161,12 +161,21 @@ The `backgrounding` plugin has been rewritten in Shrine 3.0 and has a new API. 
     | 
|
| 
       161 
161 
     | 
    
         | 
| 
       162 
162 
     | 
    
         
             
            ```rb
         
     | 
| 
       163 
163 
     | 
    
         
             
            Shrine.plugin :backgrounding
         
     | 
| 
       164 
     | 
    
         
            -
            Shrine::Attacher.promote_block  
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
      
 164 
     | 
    
         
            +
            Shrine::Attacher.promote_block do
         
     | 
| 
      
 165 
     | 
    
         
            +
              PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
         
     | 
| 
      
 166 
     | 
    
         
            +
            end
         
     | 
| 
      
 167 
     | 
    
         
            +
            Shrine::Attacher.destroy_block do
         
     | 
| 
      
 168 
     | 
    
         
            +
              DestroyJob.perform_async(self.class.name, data)
         
     | 
| 
      
 169 
     | 
    
         
            +
            end
         
     | 
| 
       166 
170 
     | 
    
         
             
            ```
         
     | 
| 
       167 
171 
     | 
    
         
             
            ```rb
         
     | 
| 
       168 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       169 
     | 
    
         
            -
               
     | 
| 
      
 172 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 173 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
              def perform(attacher_class, record_class, record_id, name, file_data)
         
     | 
| 
      
 176 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 177 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
       170 
179 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       171 
180 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
| 
       172 
181 
     | 
    
         
             
              rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
         
     | 
| 
         @@ -175,8 +184,12 @@ class PromoteJob < ActiveJob::Base 
     | 
|
| 
       175 
184 
     | 
    
         
             
            end
         
     | 
| 
       176 
185 
     | 
    
         
             
            ```
         
     | 
| 
       177 
186 
     | 
    
         
             
            ```rb
         
     | 
| 
       178 
     | 
    
         
            -
            class DestroyJob 
     | 
| 
      
 187 
     | 
    
         
            +
            class DestroyJob
         
     | 
| 
      
 188 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 189 
     | 
    
         
            +
             
     | 
| 
       179 
190 
     | 
    
         
             
              def perform(attacher_class, data)
         
     | 
| 
      
 191 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
       180 
193 
     | 
    
         
             
                attacher = attacher_class.from_data(data)
         
     | 
| 
       181 
194 
     | 
    
         
             
                attacher.destroy
         
     | 
| 
       182 
195 
     | 
    
         
             
              end
         
     | 
| 
         @@ -191,16 +204,21 @@ both argument formats, and then switch to the new one once the jobs with old 
     | 
|
| 
       191 
204 
     | 
    
         
             
            format have been drained.
         
     | 
| 
       192 
205 
     | 
    
         | 
| 
       193 
206 
     | 
    
         
             
            ```rb
         
     | 
| 
       194 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
      
 207 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 208 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 209 
     | 
    
         
            +
             
     | 
| 
       195 
210 
     | 
    
         
             
              def perform(*args)
         
     | 
| 
       196 
211 
     | 
    
         
             
                if args.one?
         
     | 
| 
       197 
212 
     | 
    
         
             
                  file_data, (record_class, record_id), name, shrine_class =
         
     | 
| 
       198 
213 
     | 
    
         
             
                    args.first.values_at("attachment", "record", "name", "shrine_class")
         
     | 
| 
       199 
214 
     | 
    
         | 
| 
       200 
     | 
    
         
            -
                  record         = Object.const_get(record_class).find(record_id)
         
     | 
| 
      
 215 
     | 
    
         
            +
                  record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
       201 
216 
     | 
    
         
             
                  attacher_class = Object.const_get(shrine_class)::Attacher
         
     | 
| 
       202 
217 
     | 
    
         
             
                else
         
     | 
| 
       203 
     | 
    
         
            -
                  attacher_class,  
     | 
| 
      
 218 
     | 
    
         
            +
                  attacher_class, record_class, record_id, name, file_data = args
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                  attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 221 
     | 
    
         
            +
                  record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
       204 
222 
     | 
    
         
             
                end
         
     | 
| 
       205 
223 
     | 
    
         | 
| 
       206 
224 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
         @@ -211,7 +229,9 @@ class PromoteJob < ActiveJob::Base 
     | 
|
| 
       211 
229 
     | 
    
         
             
            and
         
     | 
| 
       212 
230 
     | 
    
         
             
            ```
         
     | 
| 
       213 
231 
     | 
    
         
             
            ```rb
         
     | 
| 
       214 
     | 
    
         
            -
            class DestroyJob 
     | 
| 
      
 232 
     | 
    
         
            +
            class DestroyJob
         
     | 
| 
      
 233 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 234 
     | 
    
         
            +
             
     | 
| 
       215 
235 
     | 
    
         
             
              def perform(*args)
         
     | 
| 
       216 
236 
     | 
    
         
             
                if args.one?
         
     | 
| 
       217 
237 
     | 
    
         
             
                  data, shrine_class = args.first.values_at("attachment", "shrine_class")
         
     | 
| 
         @@ -220,6 +240,8 @@ class DestroyJob < ActiveJob::Base 
     | 
|
| 
       220 
240 
     | 
    
         
             
                  attacher_class = Object.const_get(shrine_class)::Attacher
         
     | 
| 
       221 
241 
     | 
    
         
             
                else
         
     | 
| 
       222 
242 
     | 
    
         
             
                  attacher_class, data = args
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                  attacher_class = Object.const_get(attacher_class)
         
     | 
| 
       223 
245 
     | 
    
         
             
                end
         
     | 
| 
       224 
246 
     | 
    
         | 
| 
       225 
247 
     | 
    
         
             
                attacher = attacher_class.from_data(data)
         
     | 
| 
         @@ -393,8 +415,13 @@ If you're using the `backgrounding` plugin, you can trigger derivatives 
     | 
|
| 
       393 
415 
     | 
    
         
             
            creation in the `PromoteJob` instead of the controller:
         
     | 
| 
       394 
416 
     | 
    
         | 
| 
       395 
417 
     | 
    
         
             
            ```rb
         
     | 
| 
       396 
     | 
    
         
            -
            class PromoteJob 
     | 
| 
       397 
     | 
    
         
            -
               
     | 
| 
      
 418 
     | 
    
         
            +
            class PromoteJob
         
     | 
| 
      
 419 
     | 
    
         
            +
              include Sidekiq::Worker
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
              def perform(attacher_class, record_class, record.id, name, file_data)
         
     | 
| 
      
 422 
     | 
    
         
            +
                attacher_class = Object.const_get(attacher_class)
         
     | 
| 
      
 423 
     | 
    
         
            +
                record         = Object.const_get(record_class).find(record_id) # if using Active Record
         
     | 
| 
      
 424 
     | 
    
         
            +
             
     | 
| 
       398 
425 
     | 
    
         
             
                attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
         
     | 
| 
       399 
426 
     | 
    
         
             
                attacher.create_derivatives # call derivatives processor
         
     | 
| 
       400 
427 
     | 
    
         
             
                attacher.atomic_promote
         
     | 
    
        data/lib/shrine.rb
    CHANGED
    
    | 
         @@ -273,7 +273,7 @@ class Shrine 
     | 
|
| 
       273 
273 
     | 
    
         
             
                # Generates a basic location for an uploaded file
         
     | 
| 
       274 
274 
     | 
    
         
             
                def basic_location(io, metadata:)
         
     | 
| 
       275 
275 
     | 
    
         
             
                  extension   = ".#{io.extension}" if io.is_a?(UploadedFile) && io.extension
         
     | 
| 
       276 
     | 
    
         
            -
                  extension ||= File.extname( 
     | 
| 
      
 276 
     | 
    
         
            +
                  extension ||= File.extname(metadata["filename"].to_s).downcase
         
     | 
| 
       277 
277 
     | 
    
         
             
                  basename    = generate_uid(io)
         
     | 
| 
       278 
278 
     | 
    
         | 
| 
       279 
279 
     | 
    
         
             
                  basename + extension
         
     | 
| 
         @@ -34,9 +34,17 @@ class Shrine 
     | 
|
| 
       34 
34 
     | 
    
         
             
                      record.public_send(opts[:pretty_location][:identifier])
         
     | 
| 
       35 
35 
     | 
    
         
             
                    end
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
      
 37 
     | 
    
         
            +
                    def transform_class_name(class_name)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      if opts[:pretty_location][:class_underscore]
         
     | 
| 
      
 39 
     | 
    
         
            +
                        class_name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
         
     | 
| 
      
 40 
     | 
    
         
            +
                      else
         
     | 
| 
      
 41 
     | 
    
         
            +
                        class_name.downcase
         
     | 
| 
      
 42 
     | 
    
         
            +
                      end
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
       37 
45 
     | 
    
         
             
                    def record_namespace(record)
         
     | 
| 
       38 
46 
     | 
    
         
             
                      class_name = record.class.name or return
         
     | 
| 
       39 
     | 
    
         
            -
                      parts      = class_name. 
     | 
| 
      
 47 
     | 
    
         
            +
                      parts      = transform_class_name(class_name).split("::")
         
     | 
| 
       40 
48 
     | 
    
         | 
| 
       41 
49 
     | 
    
         
             
                      if separator = opts[:pretty_location][:namespace]
         
     | 
| 
       42 
50 
     | 
    
         
             
                        parts.join(separator)
         
     | 
| 
         @@ -66,12 +66,12 @@ class Shrine 
     | 
|
| 
       66 
66 
     | 
    
         | 
| 
       67 
67 
     | 
    
         
             
                    def download_remote_url(url, options)
         
     | 
| 
       68 
68 
     | 
    
         
             
                      opts[:remote_url][:downloader].call(url, options)
         
     | 
| 
       69 
     | 
    
         
            -
                    rescue Down::NotFound
         
     | 
| 
       70 
     | 
    
         
            -
                      fail DownloadError, "remote file not found"
         
     | 
| 
       71 
69 
     | 
    
         
             
                    rescue Down::TooLarge
         
     | 
| 
       72 
70 
     | 
    
         
             
                      fail DownloadError, "remote file too large"
         
     | 
| 
      
 71 
     | 
    
         
            +
                    rescue Down::Error
         
     | 
| 
      
 72 
     | 
    
         
            +
                      fail DownloadError, "remote file not found"
         
     | 
| 
       73 
73 
     | 
    
         
             
                    rescue DownloadError
         
     | 
| 
       74 
     | 
    
         
            -
                      fail
         
     | 
| 
      
 74 
     | 
    
         
            +
                      fail # re-raise
         
     | 
| 
       75 
75 
     | 
    
         
             
                    end
         
     | 
| 
       76 
76 
     | 
    
         | 
| 
       77 
77 
     | 
    
         
             
                    # Sends a `remote_url.shrine` event for instrumentation plugin.
         
     | 
| 
         @@ -70,6 +70,16 @@ class Shrine 
     | 
|
| 
       70 
70 
     | 
    
         
             
                    path(id).exist?
         
     | 
| 
       71 
71 
     | 
    
         
             
                  end
         
     | 
| 
       72 
72 
     | 
    
         | 
| 
      
 73 
     | 
    
         
            +
                  # If #prefix is not present, returns a path composed of #directory and
         
     | 
| 
      
 74 
     | 
    
         
            +
                  # the given `id`. If #prefix is present, it excludes the #directory part
         
     | 
| 
      
 75 
     | 
    
         
            +
                  # from the returned path (e.g. #directory can be set to "public" folder).
         
     | 
| 
      
 76 
     | 
    
         
            +
                  # Both cases accept a `:host` value which will be prefixed to the
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # generated path.
         
     | 
| 
      
 78 
     | 
    
         
            +
                  def url(id, host: nil, **options)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    path = (prefix ? relative_path(id) : path(id)).to_s
         
     | 
| 
      
 80 
     | 
    
         
            +
                    host ? host + path : path
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
       73 
83 
     | 
    
         
             
                  # Delets the file, and by default deletes the containing directory if
         
     | 
| 
       74 
84 
     | 
    
         
             
                  # it's empty.
         
     | 
| 
       75 
85 
     | 
    
         
             
                  def delete(id)
         
     | 
| 
         @@ -79,14 +89,11 @@ class Shrine 
     | 
|
| 
       79 
89 
     | 
    
         
             
                  rescue Errno::ENOENT
         
     | 
| 
       80 
90 
     | 
    
         
             
                  end
         
     | 
| 
       81 
91 
     | 
    
         | 
| 
       82 
     | 
    
         
            -
                  #  
     | 
| 
       83 
     | 
    
         
            -
                  # 
     | 
| 
       84 
     | 
    
         
            -
                  # 
     | 
| 
       85 
     | 
    
         
            -
                   
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
                  def url(id, host: nil, **options)
         
     | 
| 
       88 
     | 
    
         
            -
                    path = (prefix ? relative_path(id) : path(id)).to_s
         
     | 
| 
       89 
     | 
    
         
            -
                    host ? host + path : path
         
     | 
| 
      
 92 
     | 
    
         
            +
                  # Deletes the specified directory on the filesystem.
         
     | 
| 
      
 93 
     | 
    
         
            +
                  #
         
     | 
| 
      
 94 
     | 
    
         
            +
                  #    file_system.delete_prefixed("somekey/derivatives")
         
     | 
| 
      
 95 
     | 
    
         
            +
                  def delete_prefixed(delete_prefix)
         
     | 
| 
      
 96 
     | 
    
         
            +
                    FileUtils.rm_rf directory.join(delete_prefix)
         
     | 
| 
       90 
97 
     | 
    
         
             
                  end
         
     | 
| 
       91 
98 
     | 
    
         | 
| 
       92 
99 
     | 
    
         
             
                  # Deletes all files from the #directory. If a block is passed in, deletes
         
     | 
| 
         @@ -1,5 +1,3 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # frozen_string_literal: true
         
     | 
| 
       2 
     | 
    
         
            -
             
     | 
| 
       3 
1 
     | 
    
         
             
            require "shrine"
         
     | 
| 
       4 
2 
     | 
    
         | 
| 
       5 
3 
     | 
    
         
             
            require "forwardable"
         
     | 
| 
         @@ -30,19 +28,32 @@ class Shrine 
     | 
|
| 
       30 
28 
     | 
    
         
             
                    new(*args).call
         
     | 
| 
       31 
29 
     | 
    
         
             
                  end
         
     | 
| 
       32 
30 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                  def initialize(storage, action: :error)
         
     | 
| 
       34 
     | 
    
         
            -
                    @storage 
     | 
| 
       35 
     | 
    
         
            -
                    @action 
     | 
| 
      
 31 
     | 
    
         
            +
                  def initialize(storage, action: :error, nonexisting: "nonexisting")
         
     | 
| 
      
 32 
     | 
    
         
            +
                    @storage     = storage
         
     | 
| 
      
 33 
     | 
    
         
            +
                    @action      = action
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @nonexisting = nonexisting
         
     | 
| 
       36 
35 
     | 
    
         
             
                  end
         
     | 
| 
       37 
36 
     | 
    
         | 
| 
       38 
37 
     | 
    
         
             
                  def call(io_factory = default_io_factory)
         
     | 
| 
       39 
     | 
    
         
            -
                    storage.upload(io_factory.call, id = "foo" 
     | 
| 
      
 38 
     | 
    
         
            +
                    storage.upload(io_factory.call, id = "foo", {})
         
     | 
| 
       40 
39 
     | 
    
         | 
| 
       41 
40 
     | 
    
         
             
                    lint_open(id)
         
     | 
| 
       42 
41 
     | 
    
         
             
                    lint_exists(id)
         
     | 
| 
       43 
42 
     | 
    
         
             
                    lint_url(id)
         
     | 
| 
       44 
43 
     | 
    
         
             
                    lint_delete(id)
         
     | 
| 
       45 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
                    if storage.respond_to?(:delete_prefixed)
         
     | 
| 
      
 46 
     | 
    
         
            +
                      storage.upload(io_factory.call, id1 = "a/a/a")
         
     | 
| 
      
 47 
     | 
    
         
            +
                      storage.upload(io_factory.call, id2 = "a/a/b")
         
     | 
| 
      
 48 
     | 
    
         
            +
                      storage.upload(io_factory.call, id3 = "a/aaa/a")
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                      lint_delete_prefixed(prefix: "a/a",
         
     | 
| 
      
 51 
     | 
    
         
            +
                                           expect_deleted: [id1, id2],
         
     | 
| 
      
 52 
     | 
    
         
            +
                                           expect_remaining: [id3])
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                      storage.delete(id3)
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
       46 
57 
     | 
    
         
             
                    if storage.respond_to?(:clear!)
         
     | 
| 
       47 
58 
     | 
    
         
             
                      storage.upload(io_factory.call, id = "quux".dup)
         
     | 
| 
       48 
59 
     | 
    
         
             
                      lint_clear(id)
         
     | 
| 
         @@ -51,6 +62,8 @@ class Shrine 
     | 
|
| 
       51 
62 
     | 
    
         
             
                    if storage.respond_to?(:presign)
         
     | 
| 
       52 
63 
     | 
    
         
             
                      lint_presign(id)
         
     | 
| 
       53 
64 
     | 
    
         
             
                    end
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                    true
         
     | 
| 
       54 
67 
     | 
    
         
             
                  end
         
     | 
| 
       55 
68 
     | 
    
         | 
| 
       56 
69 
     | 
    
         
             
                  def lint_open(id)
         
     | 
| 
         @@ -60,7 +73,7 @@ class Shrine 
     | 
|
| 
       60 
73 
     | 
    
         
             
                    opened.close
         
     | 
| 
       61 
74 
     | 
    
         | 
| 
       62 
75 
     | 
    
         
             
                    begin
         
     | 
| 
       63 
     | 
    
         
            -
                      storage.open( 
     | 
| 
      
 76 
     | 
    
         
            +
                      storage.open(@nonexisting, {})
         
     | 
| 
       64 
77 
     | 
    
         
             
                      error :open, "should raise an exception on nonexisting file"
         
     | 
| 
       65 
78 
     | 
    
         
             
                    rescue Shrine::FileNotFound
         
     | 
| 
       66 
79 
     | 
    
         
             
                    rescue => exception
         
     | 
| 
         @@ -100,6 +113,20 @@ class Shrine 
     | 
|
| 
       100 
113 
     | 
    
         
             
                    error :presign, "result should include :url key" unless data.to_h.key?(:url)
         
     | 
| 
       101 
114 
     | 
    
         
             
                  end
         
     | 
| 
       102 
115 
     | 
    
         | 
| 
      
 116 
     | 
    
         
            +
                  def lint_delete_prefixed(prefix:, expect_deleted:, expect_remaining:)
         
     | 
| 
      
 117 
     | 
    
         
            +
                    storage.delete_prefixed(prefix)
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                    expect_deleted.each do |key|
         
     | 
| 
      
 120 
     | 
    
         
            +
                      next unless storage.exists?(key)
         
     | 
| 
      
 121 
     | 
    
         
            +
                      error :delete_prefixed, "#{key} still #exists? after #clear_prefix('a/a/')"
         
     | 
| 
      
 122 
     | 
    
         
            +
                    end
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                    expect_remaining.each do |key|
         
     | 
| 
      
 125 
     | 
    
         
            +
                      next if storage.exists?(key)
         
     | 
| 
      
 126 
     | 
    
         
            +
                      error :delete_prefixed, "#{key} doesn't #exists? but should after #clear_prefix('a/a/')"
         
     | 
| 
      
 127 
     | 
    
         
            +
                    end
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
       103 
130 
     | 
    
         
             
                  private
         
     | 
| 
       104 
131 
     | 
    
         | 
| 
       105 
132 
     | 
    
         
             
                  attr_reader :storage
         
     | 
| 
         @@ -26,12 +26,16 @@ class Shrine 
     | 
|
| 
       26 
26 
     | 
    
         
             
                    store.key?(id)
         
     | 
| 
       27 
27 
     | 
    
         
             
                  end
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
      
 29 
     | 
    
         
            +
                  def url(id, *)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    "memory://#{id}"
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
       29 
33 
     | 
    
         
             
                  def delete(id)
         
     | 
| 
       30 
34 
     | 
    
         
             
                    store.delete(id)
         
     | 
| 
       31 
35 
     | 
    
         
             
                  end
         
     | 
| 
       32 
36 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                  def  
     | 
| 
       34 
     | 
    
         
            -
                    " 
     | 
| 
      
 37 
     | 
    
         
            +
                  def delete_prefixed(delete_prefix)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    store.delete_if { |k, _v| k.start_with?(delete_prefix + "/") }
         
     | 
| 
       35 
39 
     | 
    
         
             
                  end
         
     | 
| 
       36 
40 
     | 
    
         | 
| 
       37 
41 
     | 
    
         
             
                  def clear!
         
     | 
    
        data/lib/shrine/storage/s3.rb
    CHANGED
    
    | 
         @@ -205,6 +205,16 @@ class Shrine 
     | 
|
| 
       205 
205 
     | 
    
         
             
                    object(id).delete
         
     | 
| 
       206 
206 
     | 
    
         
             
                  end
         
     | 
| 
       207 
207 
     | 
    
         | 
| 
      
 208 
     | 
    
         
            +
                  # Deletes objects at keys starting with the specified prefix.
         
     | 
| 
      
 209 
     | 
    
         
            +
                  #
         
     | 
| 
      
 210 
     | 
    
         
            +
                  #    s3.delete_prefixed("somekey/derivatives")
         
     | 
| 
      
 211 
     | 
    
         
            +
                  def delete_prefixed(delete_prefix)
         
     | 
| 
      
 212 
     | 
    
         
            +
                    # We need to make sure to combine with storage prefix, and
         
     | 
| 
      
 213 
     | 
    
         
            +
                    # that it ends in '/' cause S3 can be squirrely about matching interior.
         
     | 
| 
      
 214 
     | 
    
         
            +
                    absolute_prefix = [*prefix, delete_prefix + "/"].join("/")
         
     | 
| 
      
 215 
     | 
    
         
            +
                    bucket.objects(prefix: absolute_prefix).batch_delete!
         
     | 
| 
      
 216 
     | 
    
         
            +
                  end
         
     | 
| 
      
 217 
     | 
    
         
            +
             
     | 
| 
       208 
218 
     | 
    
         
             
                  # If block is given, deletes all objects from the storage for which the
         
     | 
| 
       209 
219 
     | 
    
         
             
                  # block evaluates to true. Otherwise deletes all objects from the storage.
         
     | 
| 
       210 
220 
     | 
    
         
             
                  #
         
     | 
    
        data/lib/shrine/version.rb
    CHANGED
    
    
    
        data/shrine.gemspec
    CHANGED
    
    | 
         @@ -33,7 +33,7 @@ direct uploads for fully asynchronous user experience. 
     | 
|
| 
       33 
33 
     | 
    
         
             
              gem.files        = Dir["README.md", "LICENSE.txt", "CHANGELOG.md", "lib/**/*.rb", "shrine.gemspec", "doc/**/*.md"]
         
     | 
| 
       34 
34 
     | 
    
         
             
              gem.require_path = "lib"
         
     | 
| 
       35 
35 
     | 
    
         | 
| 
       36 
     | 
    
         
            -
              gem.add_dependency "down", "~>  
     | 
| 
      
 36 
     | 
    
         
            +
              gem.add_dependency "down", "~> 5.0"
         
     | 
| 
       37 
37 
     | 
    
         
             
              gem.add_dependency "content_disposition", "~> 1.0"
         
     | 
| 
       38 
38 
     | 
    
         | 
| 
       39 
39 
     | 
    
         
             
              # general testing helpers
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: shrine
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 3.0.0. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 3.0.0.rc
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Janko Marohnić
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2019-09- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2019-09-28 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: down
         
     | 
| 
         @@ -16,14 +16,14 @@ dependencies: 
     | 
|
| 
       16 
16 
     | 
    
         
             
                requirements:
         
     | 
| 
       17 
17 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       18 
18 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       19 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 19 
     | 
    
         
            +
                    version: '5.0'
         
     | 
| 
       20 
20 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       21 
21 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       22 
22 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
       23 
23 
     | 
    
         
             
                requirements:
         
     | 
| 
       24 
24 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       25 
25 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       26 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 26 
     | 
    
         
            +
                    version: '5.0'
         
     | 
| 
       27 
27 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       28 
28 
     | 
    
         
             
              name: content_disposition
         
     | 
| 
       29 
29 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     |