shrine 1.4.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/README.md +236 -234
- data/doc/changing_location.md +6 -4
- data/doc/creating_storages.md +4 -4
- data/doc/design.md +223 -0
- data/doc/migrating_storage.md +6 -11
- data/doc/regenerating_versions.md +22 -40
- data/lib/shrine.rb +60 -77
- data/lib/shrine/plugins/activerecord.rb +37 -14
- data/lib/shrine/plugins/background_helpers.rb +1 -0
- data/lib/shrine/plugins/backgrounding.rb +49 -37
- data/lib/shrine/plugins/backup.rb +6 -4
- data/lib/shrine/plugins/cached_attachment_data.rb +5 -5
- data/lib/shrine/plugins/data_uri.rb +9 -9
- data/lib/shrine/plugins/default_storage.rb +4 -4
- data/lib/shrine/plugins/default_url.rb +7 -1
- data/lib/shrine/plugins/default_url_options.rb +1 -1
- data/lib/shrine/plugins/delete_promoted.rb +2 -2
- data/lib/shrine/plugins/delete_raw.rb +4 -4
- data/lib/shrine/plugins/determine_mime_type.rb +50 -43
- data/lib/shrine/plugins/direct_upload.rb +10 -20
- data/lib/shrine/plugins/download_endpoint.rb +16 -13
- data/lib/shrine/plugins/dynamic_storage.rb +4 -12
- data/lib/shrine/plugins/included.rb +6 -19
- data/lib/shrine/plugins/keep_files.rb +4 -4
- data/lib/shrine/plugins/logging.rb +4 -4
- data/lib/shrine/plugins/migration_helpers.rb +37 -34
- data/lib/shrine/plugins/moving.rb +19 -32
- data/lib/shrine/plugins/parallelize.rb +5 -5
- data/lib/shrine/plugins/pretty_location.rb +2 -6
- data/lib/shrine/plugins/remote_url.rb +31 -43
- data/lib/shrine/plugins/remove_attachment.rb +5 -5
- data/lib/shrine/plugins/remove_invalid.rb +1 -1
- data/lib/shrine/plugins/restore_cached_data.rb +4 -10
- data/lib/shrine/plugins/sequel.rb +46 -21
- data/lib/shrine/plugins/store_dimensions.rb +19 -20
- data/lib/shrine/plugins/upload_options.rb +11 -9
- data/lib/shrine/plugins/validation_helpers.rb +3 -3
- data/lib/shrine/plugins/versions.rb +18 -3
- data/lib/shrine/storage/file_system.rb +9 -11
- data/lib/shrine/storage/linter.rb +1 -7
- data/lib/shrine/storage/s3.rb +25 -19
- data/lib/shrine/version.rb +3 -3
- data/shrine.gemspec +13 -3
- metadata +28 -9
- data/lib/shrine/plugins/delete_uploaded.rb +0 -3
- data/lib/shrine/plugins/keep_location.rb +0 -46
- data/lib/shrine/plugins/restore_cached.rb +0 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 25b8acff9f68f0cc11cbadc0eeb39a5bdcf839d1
         | 
| 4 | 
            +
              data.tar.gz: 666390819abcf036b20f46ecf04b1a30fef68a28
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: be25b88ca4629b8a1e052ed442b5530d1661a0f23a4d3d7cb0cb2e6f6538379c1a46a22e921091a3a718755e1f4378bd97a25312bde436134cdd3ac0ba487194
         | 
| 7 | 
            +
              data.tar.gz: cf90025e05a3f0790bde9ba0763a66e75c6aba48a1b5586a4a814947693154d1a345ae90c447341f349cf9e3abf29a62e2fe6ab211993fc1685ad38cef479b33
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            # Shrine
         | 
| 2 2 |  | 
| 3 | 
            -
            Shrine is a toolkit for file uploads in Ruby applications.
         | 
| 3 | 
            +
            Shrine is a toolkit for handling file uploads in Ruby applications.
         | 
| 4 4 |  | 
| 5 5 | 
             
            If you're new, you're encouraged to read the [introductory blog post] which
         | 
| 6 6 | 
             
            explains the motivation behind Shrine.
         | 
| @@ -10,7 +10,7 @@ explains the motivation behind Shrine. | |
| 10 10 | 
             
            - Documentation: [shrinerb.com](http://shrinerb.com)
         | 
| 11 11 | 
             
            - Source: [github.com/janko-m/shrine](https://github.com/janko-m/shrine)
         | 
| 12 12 | 
             
            - Bugs: [github.com/janko-m/shrine/issues](https://github.com/janko-m/shrine/issues)
         | 
| 13 | 
            -
            - Help &  | 
| 13 | 
            +
            - Help & Discussion: [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine)
         | 
| 14 14 |  | 
| 15 15 | 
             
            ## Installation
         | 
| 16 16 |  | 
| @@ -18,11 +18,11 @@ explains the motivation behind Shrine. | |
| 18 18 | 
             
            gem "shrine"
         | 
| 19 19 | 
             
            ```
         | 
| 20 20 |  | 
| 21 | 
            -
            Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3 | 
| 21 | 
            +
            Shrine has been tested on MRI 2.1, MRI 2.2, MRI 2.3 and JRuby.
         | 
| 22 22 |  | 
| 23 23 | 
             
            ## Basics
         | 
| 24 24 |  | 
| 25 | 
            -
            Here's  | 
| 25 | 
            +
            Here's an example showing how basic file upload works in Shrine:
         | 
| 26 26 |  | 
| 27 27 | 
             
            ```rb
         | 
| 28 28 | 
             
            require "shrine"
         | 
| @@ -34,7 +34,6 @@ uploader = Shrine.new(:file_system) | |
| 34 34 |  | 
| 35 35 | 
             
            uploaded_file = uploader.upload(File.open("movie.mp4"))
         | 
| 36 36 | 
             
            uploaded_file      #=> #<Shrine::UploadedFile>
         | 
| 37 | 
            -
            uploaded_file.url  #=> "/uploads/9260ea09d8effd.mp4"
         | 
| 38 37 | 
             
            uploaded_file.data #=>
         | 
| 39 38 | 
             
            # {
         | 
| 40 39 | 
             
            #   "storage"  => "file_system",
         | 
| @@ -43,27 +42,25 @@ uploaded_file.data #=> | |
| 43 42 | 
             
            # }
         | 
| 44 43 | 
             
            ```
         | 
| 45 44 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
            simple Ruby classes which perform the actual uploads. We instantiate a `Shrine`
         | 
| 48 | 
            -
            with the storage name, and when we call `#upload` Shrine does the following:
         | 
| 45 | 
            +
            Let's see what's going on here:
         | 
| 49 46 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
            * returns a `Shrine::UploadedFile` with relevant data
         | 
| 47 | 
            +
            First we registered the storage we want to use under a name. Storages are plain
         | 
| 48 | 
            +
            Ruby classes which encapsulate file management on a particular service. We can
         | 
| 49 | 
            +
            then instantiate `Shrine` as a wrapper around that storage. A call to `upload`
         | 
| 50 | 
            +
            uploads the given file to the underlying storage.
         | 
| 55 51 |  | 
| 56 | 
            -
            The argument to ` | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
            `# | 
| 60 | 
            -
             | 
| 52 | 
            +
            The argument to `upload` needs to be an IO-like object. So, `File`, `Tempfile`
         | 
| 53 | 
            +
            and `StringIO` are all valid arguments. The object doesn't have to be an actual
         | 
| 54 | 
            +
            IO, though, it's enough that it responds to these 5 methods: `#read(*args)`,
         | 
| 55 | 
            +
            `#size`, `#eof?`, `#rewind` and `#close`. `ActionDispatch::Http::UploadedFile`
         | 
| 56 | 
            +
            is one such object, as well as `Shrine::UploadedFile` itself.
         | 
| 61 57 |  | 
| 62 | 
            -
            The  | 
| 63 | 
            -
             | 
| 58 | 
            +
            The result of uploading is a `Shrine::UploadedFile` object, which represents
         | 
| 59 | 
            +
            the uploaded file on the storage. It is defined solely by its data hash. We can
         | 
| 60 | 
            +
            do a lot with it:
         | 
| 64 61 |  | 
| 65 62 | 
             
            ```rb
         | 
| 66 | 
            -
            uploaded_file.url      #=> " | 
| 63 | 
            +
            uploaded_file.url      #=> "uploads/938kjsdf932.mp4"
         | 
| 67 64 | 
             
            uploaded_file.metadata #=> {...}
         | 
| 68 65 | 
             
            uploaded_file.read     #=> "..."
         | 
| 69 66 | 
             
            uploaded_file.exists?  #=> true
         | 
| @@ -72,16 +69,15 @@ uploaded_file.delete | |
| 72 69 | 
             
            # ...
         | 
| 73 70 | 
             
            ```
         | 
| 74 71 |  | 
| 75 | 
            -
            To read about the metadata that is stored with the uploaded file, see the
         | 
| 76 | 
            -
            [metadata](#metadata) section.
         | 
| 77 | 
            -
             | 
| 78 72 | 
             
            ## Attachment
         | 
| 79 73 |  | 
| 80 | 
            -
            In web applications | 
| 81 | 
            -
            treat them as "attachments" to records  | 
| 82 | 
            -
             | 
| 74 | 
            +
            In web applications we usually want work with files on a higher level. We want
         | 
| 75 | 
            +
            to treat them as "attachments" to records, by persisting their information to a
         | 
| 76 | 
            +
            database column and tying their lifecycle to the record. For this Shrine offers
         | 
| 77 | 
            +
            a higher-level attachment interface.
         | 
| 83 78 |  | 
| 84 | 
            -
             | 
| 79 | 
            +
            First we need to register temporary and permanent storage which will be used
         | 
| 80 | 
            +
            internally:
         | 
| 85 81 |  | 
| 86 82 | 
             
            ```rb
         | 
| 87 83 | 
             
            require "shrine"
         | 
| @@ -93,88 +89,90 @@ Shrine.storages = { | |
| 93 89 | 
             
            }
         | 
| 94 90 | 
             
            ```
         | 
| 95 91 |  | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 92 | 
            +
            The `:cache` and `:store` are only special in terms that they will be used
         | 
| 93 | 
            +
            automatically (but that can be changed with the default_storage plugin). Next,
         | 
| 94 | 
            +
            we create an uploader class specific to the type of attachment we want, so that
         | 
| 95 | 
            +
            later we can have different uploading logic for different attachment types.
         | 
| 99 96 |  | 
| 100 97 | 
             
            ```rb
         | 
| 101 98 | 
             
            class ImageUploader < Shrine
         | 
| 102 | 
            -
              # your logic for uploading  | 
| 99 | 
            +
              # your logic for uploading images
         | 
| 103 100 | 
             
            end
         | 
| 104 101 | 
             
            ```
         | 
| 105 102 |  | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 103 | 
            +
            Finally, to add an attachment to a model, we generate a named "attachment"
         | 
| 104 | 
            +
            module using the uploader and include it:
         | 
| 108 105 |  | 
| 109 106 | 
             
            ```rb
         | 
| 110 | 
            -
            class  | 
| 111 | 
            -
              include ImageUploader[: | 
| 107 | 
            +
            class Photo
         | 
| 108 | 
            +
              include ImageUploader[:image] # requires "image_data" attribute
         | 
| 112 109 | 
             
            end
         | 
| 113 110 | 
             
            ```
         | 
| 114 111 |  | 
| 115 | 
            -
            Now our model has gained special methods for attaching  | 
| 112 | 
            +
            Now our model has gained special methods for attaching files:
         | 
| 116 113 |  | 
| 117 114 | 
             
            ```rb
         | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 115 | 
            +
            photo = Photo.new
         | 
| 116 | 
            +
            photo.image = File.open("nature.jpg") # uploads the file to cache
         | 
| 117 | 
            +
            photo.image      #=> #<Shrine::UploadedFile>
         | 
| 118 | 
            +
            photo.image_url  #=> "/uploads/cache/9260ea09d8effd.jpg"
         | 
| 119 | 
            +
            photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
         | 
| 123 120 | 
             
            ```
         | 
| 124 121 |  | 
| 125 | 
            -
            The attachment module has added `# | 
| 126 | 
            -
            methods to our  | 
| 122 | 
            +
            The attachment module has added `#image`, `#image=` and `#image_url`
         | 
| 123 | 
            +
            methods to our `Photo`, using regular module inclusion.
         | 
| 127 124 |  | 
| 128 125 | 
             
            ```rb
         | 
| 129 | 
            -
            Shrine[: | 
| 130 | 
            -
            Shrine[: | 
| 131 | 
            -
            Shrine[: | 
| 126 | 
            +
            Shrine[:image] #=> #<Shrine::Attachment(image)>
         | 
| 127 | 
            +
            Shrine[:image].is_a?(Module) #=> true
         | 
| 128 | 
            +
            Shrine[:image].instance_methods #=> [:image=, :image, :image_url, :image_attacher]
         | 
| 132 129 |  | 
| 133 130 | 
             
            Shrine[:document] #=> #<Shrine::Attachment(document)>
         | 
| 134 131 | 
             
            Shrine[:document].instance_methods #=> [:document=, :document, :document_url, :document_attacher]
         | 
| 135 132 |  | 
| 136 | 
            -
            #  | 
| 137 | 
            -
            Shrine.attachment(: | 
| 133 | 
            +
            # Expanded forms
         | 
| 134 | 
            +
            Shrine.attachment(:image)
         | 
| 138 135 | 
             
            Shrine::Attachment.new(:document)
         | 
| 139 136 | 
             
            ```
         | 
| 140 137 |  | 
| 141 | 
            -
            * `# | 
| 142 | 
            -
            * `# | 
| 143 | 
            -
            * `# | 
| 138 | 
            +
            * `#image=` – caches the file and saves JSON data into `image_data`
         | 
| 139 | 
            +
            * `#image` – returns a `Shrine::UploadedFile` based on data from `image_data`
         | 
| 140 | 
            +
            * `#image_url` – calls `image.url` if attachment is present, otherwise returns nil.
         | 
| 144 141 |  | 
| 145 | 
            -
            This is how you would typically create the form for a `@ | 
| 142 | 
            +
            This is how you would typically create the form for a `@photo`:
         | 
| 146 143 |  | 
| 147 144 | 
             
            ```erb
         | 
| 148 | 
            -
            <form action="/ | 
| 149 | 
            -
              <input name=" | 
| 150 | 
            -
              <input name=" | 
| 145 | 
            +
            <form action="/photos" method="post" enctype="multipart/form-data">
         | 
| 146 | 
            +
              <input name="photo[image]" type="hidden" value="<%= @photo.image_data %>">
         | 
| 147 | 
            +
              <input name="photo[image]" type="file">
         | 
| 151 148 | 
             
            </form>
         | 
| 152 149 | 
             
            ```
         | 
| 153 150 |  | 
| 154 151 | 
             
            The "file" field is for file upload, while the "hidden" field is to make the
         | 
| 155 152 | 
             
            file persist in case of validation errors, and for direct uploads. This code
         | 
| 156 | 
            -
            works because `# | 
| 153 | 
            +
            works because `#image=` also accepts an already cached file via its JSON
         | 
| 157 154 | 
             
            representation:
         | 
| 158 155 |  | 
| 159 156 | 
             
            ```rb
         | 
| 160 | 
            -
             | 
| 157 | 
            +
            photo.image = '{"id":"9jsdf02kd", "storage":"cache", "metadata": {...}}'
         | 
| 161 158 | 
             
            ```
         | 
| 162 159 |  | 
| 163 160 | 
             
            ### ORM
         | 
| 164 161 |  | 
| 165 | 
            -
             | 
| 166 | 
            -
             | 
| 167 | 
            -
             | 
| 162 | 
            +
            Even though you can use Shrine's attachment interface with plain Ruby objects,
         | 
| 163 | 
            +
            it's much more common to use it with an ORM. Shrine ships with plugins for
         | 
| 164 | 
            +
            Sequel and ActiveRecord ORMs. It uses the `<attachment>_data` column for
         | 
| 165 | 
            +
            storing data for uploaded files, so you'll need to add it in a migration.
         | 
| 168 166 |  | 
| 169 167 | 
             
            ```rb
         | 
| 170 | 
            -
            add_column : | 
| 168 | 
            +
            add_column :movies, :video_data, :text # or a JSON column
         | 
| 171 169 | 
             
            ```
         | 
| 172 170 | 
             
            ```rb
         | 
| 173 171 | 
             
            Shrine.plugin :sequel # or :activerecord
         | 
| 174 172 | 
             
            ```
         | 
| 175 173 | 
             
            ```rb
         | 
| 176 | 
            -
            class  | 
| 177 | 
            -
              include  | 
| 174 | 
            +
            class Movie < Sequel::Model
         | 
| 175 | 
            +
              include VideoUploader[:video]
         | 
| 178 176 | 
             
            end
         | 
| 179 177 | 
             
            ```
         | 
| 180 178 |  | 
| @@ -182,77 +180,61 @@ In addition to getters and setters, the ORM plugins add the appropriate | |
| 182 180 | 
             
            callbacks:
         | 
| 183 181 |  | 
| 184 182 | 
             
            ```rb
         | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
            user.save
         | 
| 188 | 
            -
            user.avatar.storage_key #=> "store"
         | 
| 189 | 
            -
            user.destroy
         | 
| 190 | 
            -
            user.avatar.exists? #=> false
         | 
| 191 | 
            -
            ```
         | 
| 192 | 
            -
             | 
| 193 | 
            -
            *NOTE: The record will first be saved with the cached attachment, and afterwards
         | 
| 194 | 
            -
            (in an "after commit" hook) updated with the stored attachment. This is done so
         | 
| 195 | 
            -
            that processing/storing isn't performed inside a database transaction. If you're
         | 
| 196 | 
            -
            doing processing, there will be a bried period of time when the record will exist
         | 
| 197 | 
            -
            with an unprocessed attachment, so you may need to account for that.*
         | 
| 198 | 
            -
             | 
| 199 | 
            -
            ## Direct uploads
         | 
| 183 | 
            +
            movie.video = File.open("video.mp4")
         | 
| 184 | 
            +
            movie.video_url #=> "/uploads/cache/0sdfllasfi842.mp4"
         | 
| 200 185 |  | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
            file the moment the user selects it (e.g. using the [jQuery-File-Upload] JS
         | 
| 204 | 
            -
            library), which gives a nice experience to the user.
         | 
| 186 | 
            +
            movie.save
         | 
| 187 | 
            +
            movie.video_url #=> "/uploads/store/l02kladf8jlda.mp4"
         | 
| 205 188 |  | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
            ```
         | 
| 209 | 
            -
            ```rb
         | 
| 210 | 
            -
            Rails.application.routes.draw do
         | 
| 211 | 
            -
              mount ImageUploader::UploadEndpoint => "/attachments/images"
         | 
| 212 | 
            -
            end
         | 
| 213 | 
            -
            ```
         | 
| 214 | 
            -
            ```js
         | 
| 215 | 
            -
            $('[type="file"]').fileupload({
         | 
| 216 | 
            -
              url:       '/attachments/images/cache/upload',
         | 
| 217 | 
            -
              paramName: 'file',
         | 
| 218 | 
            -
              add:       function(e, data) { /* Disable the submit button */ },
         | 
| 219 | 
            -
              progress:  function(e, data) { /* Add a nice progress bar */ },
         | 
| 220 | 
            -
              done:      function(e, data) { /* Fill in the hidden field with the result */ }
         | 
| 221 | 
            -
            });
         | 
| 189 | 
            +
            movie.destroy
         | 
| 190 | 
            +
            movie.video.exists? #=> false
         | 
| 222 191 | 
             
            ```
         | 
| 223 192 |  | 
| 224 | 
            -
             | 
| 225 | 
            -
             | 
| 226 | 
            -
             | 
| 193 | 
            +
            First the raw file is cached to temporary storage on assignment, then on saving
         | 
| 194 | 
            +
            the cached file is uploaded to permanent storage. Destroying the record
         | 
| 195 | 
            +
            destroys the attachment.
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            *NOTE: The record will first be saved with the cached attachment, and
         | 
| 198 | 
            +
            afterwards (in an "after commit" hook) updated with the stored attachment. This
         | 
| 199 | 
            +
            is done so that processing/storing isn't performed inside a database
         | 
| 200 | 
            +
            transaction. If you're doing processing, there will be a period of time when
         | 
| 201 | 
            +
            the record will be saved with an unprocessed attachment, so you may need to
         | 
| 202 | 
            +
            account for that.*
         | 
| 227 203 |  | 
| 228 204 | 
             
            ## Processing
         | 
| 229 205 |  | 
| 230 206 | 
             
            Whenever a file is uploaded, `Shrine#process` is called, and this is where
         | 
| 231 207 | 
             
            you're expected to define your processing.
         | 
| 232 208 |  | 
| 209 | 
            +
            ```rb
         | 
| 210 | 
            +
            class ImageUploader < Shrine
         | 
| 211 | 
            +
              def process(io, context)
         | 
| 212 | 
            +
                # ...
         | 
| 213 | 
            +
              end
         | 
| 214 | 
            +
            end
         | 
| 215 | 
            +
            ```
         | 
| 216 | 
            +
             | 
| 217 | 
            +
            Shrine's uploaders are stateless; the `#process` method is simply a function
         | 
| 218 | 
            +
            which takes an input `io` and returns processed file(s) as output. Since it's
         | 
| 219 | 
            +
            called for each upload, attaching the file will call it twice, first when
         | 
| 220 | 
            +
            raw file is cached to temporary storage on assignment, then when cached file
         | 
| 221 | 
            +
            is uploaded to permanent storage on saving. We usually want to process in the
         | 
| 222 | 
            +
            latter phase (after file validations):
         | 
| 223 | 
            +
             | 
| 233 224 | 
             
            ```rb
         | 
| 234 225 | 
             
            class ImageUploader < Shrine
         | 
| 235 226 | 
             
              def process(io, context)
         | 
| 236 227 | 
             
                if context[:phase] == :store
         | 
| 237 | 
            -
                  #  | 
| 228 | 
            +
                  # ...
         | 
| 238 229 | 
             
                end
         | 
| 239 230 | 
             
              end
         | 
| 240 231 | 
             
            end
         | 
| 241 232 | 
             
            ```
         | 
| 242 233 |  | 
| 243 | 
            -
            The `io` is the file being uploaded, and `context` we'll leave for later. You
         | 
| 244 | 
            -
            may be wondering why we need this conditional. Well, when an attachment is
         | 
| 245 | 
            -
            assigned and saved, an "upload" actually happens two times. First the file is
         | 
| 246 | 
            -
            "uploaded" to cache on assignment, and then the cached file is reuploaded to
         | 
| 247 | 
            -
            store on save. You could theoretically do processing in both phases, depending
         | 
| 248 | 
            -
            on your preferences (although it's generally not recommended to process on
         | 
| 249 | 
            -
            caching, because it happens before file validations; use the `recache` plugin
         | 
| 250 | 
            -
            instead).
         | 
| 251 | 
            -
             | 
| 252 234 | 
             
            Ok, now how do we do the actual processing? Well, Shrine actually doesn't ship
         | 
| 253 235 | 
             
            with any file processing functionality, because that is a generic problem that
         | 
| 254 | 
            -
            belongs in  | 
| 255 | 
            -
            created the [image_processing] gem which you can use with Shrine:
         | 
| 236 | 
            +
            belongs in separate libraries. If the type of files you're uploading are
         | 
| 237 | 
            +
            images, I created the [image_processing] gem which you can use with Shrine:
         | 
| 256 238 |  | 
| 257 239 | 
             
            ```rb
         | 
| 258 240 | 
             
            require "image_processing/mini_magick"
         | 
| @@ -268,20 +250,14 @@ class ImageUploader < Shrine | |
| 268 250 | 
             
            end
         | 
| 269 251 | 
             
            ```
         | 
| 270 252 |  | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
            store. The cached file is an instance of `Shrine::UploadedFile`, but for
         | 
| 274 | 
            -
            processing we need to work with actual files, so we first need to download it.
         | 
| 275 | 
            -
             | 
| 276 | 
            -
            In general, processing works in a way that if `#process` returns a file, Shrine
         | 
| 277 | 
            -
            continues storing that file, otherwise if nil is returned, Shrine continues
         | 
| 278 | 
            -
            storing the original file.
         | 
| 253 | 
            +
            Since here `io` is a cached `Shrine::UploadedFile`, we need to download it to
         | 
| 254 | 
            +
            a file, as image_processing only accepts real files.
         | 
| 279 255 |  | 
| 280 256 | 
             
            ### Versions
         | 
| 281 257 |  | 
| 282 258 | 
             
            If you're uploading images, often you'll want to store various thumbnails
         | 
| 283 | 
            -
            alongside your original image.  | 
| 284 | 
            -
             | 
| 259 | 
            +
            alongside your original image. You can do that by loading the versions plugin,
         | 
| 260 | 
            +
            and in `#process` simply returning a Hash of versions:
         | 
| 285 261 |  | 
| 286 262 | 
             
            ```rb
         | 
| 287 263 | 
             
            require "image_processing/mini_magick"
         | 
| @@ -302,25 +278,24 @@ class ImageUploader < Shrine | |
| 302 278 | 
             
            end
         | 
| 303 279 | 
             
            ```
         | 
| 304 280 |  | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
            after upload.
         | 
| 281 | 
            +
            Being able to define processing on instance level provides a lot of flexibility,
         | 
| 282 | 
            +
            allowing things like choosing the order or adding parallelization. It is
         | 
| 283 | 
            +
            recommended to use the delete_raw plugin for automatically deleting processed
         | 
| 284 | 
            +
            files after uploading.
         | 
| 310 285 |  | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 286 | 
            +
            The attachment getter will simply return the processed attachment as a Hash of
         | 
| 287 | 
            +
            versions:
         | 
| 313 288 |  | 
| 314 289 | 
             
            ```rb
         | 
| 315 | 
            -
             | 
| 290 | 
            +
            photo.image.class #=> Hash
         | 
| 316 291 |  | 
| 317 292 | 
             
            # With the store_dimensions plugin
         | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 293 | 
            +
            photo.image[:large].width  #=> 700
         | 
| 294 | 
            +
            photo.image[:medium].width #=> 500
         | 
| 295 | 
            +
            photo.image[:small].width  #=> 300
         | 
| 321 296 |  | 
| 322 297 | 
             
            # The plugin expands this method to accept version names.
         | 
| 323 | 
            -
             | 
| 298 | 
            +
            photo.image_url(:large) #=> "..."
         | 
| 324 299 | 
             
            ```
         | 
| 325 300 |  | 
| 326 301 | 
             
            ## Context
         | 
| @@ -337,28 +312,28 @@ class ImageUploader < Shrine | |
| 337 312 | 
             
            end
         | 
| 338 313 | 
             
            ```
         | 
| 339 314 | 
             
            ```rb
         | 
| 340 | 
            -
             | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 315 | 
            +
            photo = Photo.new
         | 
| 316 | 
            +
            photo.image = File.open("image.jpg") # "cache"
         | 
| 317 | 
            +
            photo.save                           # "store"
         | 
| 343 318 | 
             
            ```
         | 
| 344 319 | 
             
            ```
         | 
| 345 | 
            -
            {:name=>: | 
| 346 | 
            -
            {:name=>: | 
| 320 | 
            +
            {:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:cache}
         | 
| 321 | 
            +
            {:name=>:image, :record=>#<Photo:0x007fe1627f1138>, :phase=>:store}
         | 
| 347 322 | 
             
            ```
         | 
| 348 323 |  | 
| 349 | 
            -
            The `:name` is the name of the attachment, in this case " | 
| 350 | 
            -
            is the model instance, in this case instance of ` | 
| 351 | 
            -
             | 
| 352 | 
            -
             | 
| 324 | 
            +
            The `:name` is the name of the attachment, in this case "image". The `:record`
         | 
| 325 | 
            +
            is the model instance, in this case instance of `Photo`. Lastly, the `:phase`
         | 
| 326 | 
            +
            is a symbol which indicates the purpose of the upload (by default there are
         | 
| 327 | 
            +
            only `:cache` and `:store`, but some plugins add more of them).
         | 
| 353 328 |  | 
| 354 | 
            -
            Context is  | 
| 355 | 
            -
             | 
| 356 | 
            -
             | 
| 329 | 
            +
            Context is useful for doing conditional processing and validation, since we
         | 
| 330 | 
            +
            have access to the record and attachment name, and it is also used by some
         | 
| 331 | 
            +
            plugins internally.
         | 
| 357 332 |  | 
| 358 333 | 
             
            ## Validations
         | 
| 359 334 |  | 
| 360 | 
            -
            Validations are registered by calling ` | 
| 361 | 
            -
             | 
| 335 | 
            +
            Validations are registered by calling `Attacher.validate`, and are best done
         | 
| 336 | 
            +
            with the validation_helpers plugin:
         | 
| 362 337 |  | 
| 363 338 | 
             
            ```rb
         | 
| 364 339 | 
             
            class DocumentUploader < Shrine
         | 
| @@ -366,7 +341,7 @@ class DocumentUploader < Shrine | |
| 366 341 |  | 
| 367 342 | 
             
              Attacher.validate do
         | 
| 368 343 | 
             
                # Evaluated inside an instance of Shrine::Attacher.
         | 
| 369 | 
            -
                if record. | 
| 344 | 
            +
                if record.resume?
         | 
| 370 345 | 
             
                  validate_max_size 10*1024*1024, message: "is too large (max is 10 MB)"
         | 
| 371 346 | 
             
                  validate_mime_type_inclusion ["application/pdf"]
         | 
| 372 347 | 
             
                end
         | 
| @@ -375,72 +350,53 @@ end | |
| 375 350 | 
             
            ```
         | 
| 376 351 |  | 
| 377 352 | 
             
            ```rb
         | 
| 378 | 
            -
             | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
| 381 | 
            -
             | 
| 353 | 
            +
            document = Document.new(resume: true)
         | 
| 354 | 
            +
            document.file = File.open("resume.pdf")
         | 
| 355 | 
            +
            document.valid? #=> false
         | 
| 356 | 
            +
            document.errors.to_hash #=> {file: ["is too large (max is 2 MB)"]}
         | 
| 382 357 | 
             
            ```
         | 
| 383 358 |  | 
| 384 359 | 
             
            ## Metadata
         | 
| 385 360 |  | 
| 386 | 
            -
             | 
| 361 | 
            +
            Shrine automatically extracts and stores general file metadata:
         | 
| 387 362 |  | 
| 388 363 | 
             
            ```rb
         | 
| 389 | 
            -
             | 
| 390 | 
            -
             | 
| 391 | 
            -
             | 
| 392 | 
            -
             | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 396 | 
            -
                #   "size"      => 345993,
         | 
| 397 | 
            -
                # }
         | 
| 364 | 
            +
            photo = Photo.create(image: image)
         | 
| 365 | 
            +
            photo.image.metadata #=>
         | 
| 366 | 
            +
            # {
         | 
| 367 | 
            +
            #   "filename"  => "nature.jpg",
         | 
| 368 | 
            +
            #   "mime_type" => "image/jpeg",
         | 
| 369 | 
            +
            #   "size"      => 345993,
         | 
| 370 | 
            +
            # }
         | 
| 398 371 |  | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 401 | 
            -
             | 
| 402 | 
            -
             | 
| 403 | 
            -
            end
         | 
| 372 | 
            +
            photo.image.original_filename #=> "nature.jpg"
         | 
| 373 | 
            +
            photo.image.extension         #=> "jpg"
         | 
| 374 | 
            +
            photo.image.mime_type         #=> "image/jpeg"
         | 
| 375 | 
            +
            photo.image.size              #=> 345993
         | 
| 404 376 | 
             
            ```
         | 
| 405 377 |  | 
| 406 378 | 
             
            ### MIME type
         | 
| 407 379 |  | 
| 408 380 | 
             
            By default, "mime_type" is inherited from `#content_type` of the uploaded file,
         | 
| 409 | 
            -
            which  | 
| 410 | 
            -
            based on the extension | 
| 411 | 
            -
             | 
| 412 | 
            -
            file.
         | 
| 381 | 
            +
            which is set from the "Content-Type" request header, which is determined by the
         | 
| 382 | 
            +
            browser solely based on the file extension. This means that by default Shrine's
         | 
| 383 | 
            +
            "mime_type" is *not* guaranteed to hold the actual MIME type of the file.
         | 
| 413 384 |  | 
| 414 | 
            -
            To help with that Shrine provides the  | 
| 385 | 
            +
            To help with that Shrine provides the determine_mime_type plugin, which by
         | 
| 415 386 | 
             
            default uses the UNIX [file] utility to determine the actual MIME type:
         | 
| 416 387 |  | 
| 417 388 | 
             
            ```rb
         | 
| 418 389 | 
             
            Shrine.plugin :determine_mime_type
         | 
| 419 390 | 
             
            ```
         | 
| 420 391 | 
             
            ```rb
         | 
| 421 | 
            -
             | 
| 422 | 
            -
             | 
| 423 | 
            -
             | 
| 424 | 
            -
             | 
| 425 | 
            -
            ### Dimensions
         | 
| 426 | 
            -
             | 
| 427 | 
            -
            If you're uploading images and you want to store dimensions, you can use the
         | 
| 428 | 
            -
            `store_dimensions` plugin which extracts dimensions using the [fastimage] gem.
         | 
| 429 | 
            -
             | 
| 430 | 
            -
            ```rb
         | 
| 431 | 
            -
            ImageUploader.plugin :store_dimensions
         | 
| 432 | 
            -
            ```
         | 
| 433 | 
            -
            ```rb
         | 
| 434 | 
            -
            user = User.create(avatar: File.open("image.jpg"))
         | 
| 435 | 
            -
            user.avatar.width  #=> 400
         | 
| 436 | 
            -
            user.avatar.height #=> 500
         | 
| 392 | 
            +
            File.write("image.jpg", "<?php ... ?>") # PHP file with a .jpg extension
         | 
| 393 | 
            +
            photo = Photo.create(image: File.open("image.jpg"))
         | 
| 394 | 
            +
            photo.image.mime_type #=> "text/x-php"
         | 
| 437 395 | 
             
            ```
         | 
| 438 396 |  | 
| 439 | 
            -
            The fastimage gem has built-in protection against [image bombs].
         | 
| 440 | 
            -
             | 
| 441 397 | 
             
            ### Custom metadata
         | 
| 442 398 |  | 
| 443 | 
            -
            You can also extract and store custom metadata | 
| 399 | 
            +
            You can also extract and store custom metadata by overriding
         | 
| 444 400 | 
             
            `Shrine#extract_metadata`:
         | 
| 445 401 |  | 
| 446 402 | 
             
            ```rb
         | 
| @@ -453,49 +409,55 @@ class ImageUploader < Shrine | |
| 453 409 | 
             
            end
         | 
| 454 410 | 
             
            ```
         | 
| 455 411 |  | 
| 456 | 
            -
            Note that  | 
| 457 | 
            -
            rewind the file afterwards.
         | 
| 412 | 
            +
            Note that you should always rewind the `io` after reading from it.
         | 
| 458 413 |  | 
| 459 414 | 
             
            ## Locations
         | 
| 460 415 |  | 
| 461 | 
            -
             | 
| 462 | 
            -
             | 
| 416 | 
            +
            Before Shrine uploads a file, it generates a random location for it. By
         | 
| 417 | 
            +
            default the hierarchy is flat, all files are stored in the root of the storage.
         | 
| 418 | 
            +
            If you want that each attachment has its own directory, you can load the
         | 
| 419 | 
            +
            pretty_location plugin:
         | 
| 463 420 |  | 
| 464 421 | 
             
            ```rb
         | 
| 465 422 | 
             
            Shrine.plugin :pretty_location
         | 
| 466 423 | 
             
            ```
         | 
| 467 424 | 
             
            ```rb
         | 
| 468 | 
            -
             | 
| 469 | 
            -
             | 
| 425 | 
            +
            photo = Photo.create(image: File.open("nature.jpg"))
         | 
| 426 | 
            +
            photo.image.id #=> "photo/34/image/34krtreds2df.jpg"
         | 
| 470 427 | 
             
            ```
         | 
| 471 428 |  | 
| 472 | 
            -
            If you want to generate your own | 
| 429 | 
            +
            If you want to generate locations on your own, simply override
         | 
| 473 430 | 
             
            `Shrine#generate_location`:
         | 
| 474 431 |  | 
| 475 432 | 
             
            ```rb
         | 
| 476 433 | 
             
            class ImageUploader < Shrine
         | 
| 477 434 | 
             
              def generate_location(io, context)
         | 
| 478 | 
            -
                 | 
| 435 | 
            +
                if context[:record]
         | 
| 436 | 
            +
                  "#{context[:record].class}/#{super}"
         | 
| 437 | 
            +
                else
         | 
| 438 | 
            +
                  super
         | 
| 439 | 
            +
                end
         | 
| 479 440 | 
             
              end
         | 
| 480 441 | 
             
            end
         | 
| 481 442 | 
             
            ```
         | 
| 482 443 |  | 
| 483 | 
            -
            Note that there should always be a random component in the location,  | 
| 484 | 
            -
             | 
| 485 | 
            -
             | 
| 444 | 
            +
            Note that there should always be a random component in the location, for dirty
         | 
| 445 | 
            +
            tracking to be detected properly (you can use `Shrine#generate_uid`). Inside
         | 
| 446 | 
            +
            `#generate_location` you can access the extracted metadata through
         | 
| 486 447 | 
             
            `context[:metadata]`.
         | 
| 487 448 |  | 
| 488 449 | 
             
            When using the uploader directly, it's possible to bypass `#generate_location`
         | 
| 489 | 
            -
            by passing  | 
| 450 | 
            +
            by passing a `:location`:
         | 
| 490 451 |  | 
| 491 452 | 
             
            ```rb
         | 
| 492 | 
            -
             | 
| 493 | 
            -
             | 
| 453 | 
            +
            uploader = Shrine.new(:store)
         | 
| 454 | 
            +
            file = File.open("nature.jpg")
         | 
| 455 | 
            +
            uploader.upload(file, location: "some/specific/location.jpg")
         | 
| 494 456 | 
             
            ```
         | 
| 495 457 |  | 
| 496 458 | 
             
            ## Storage
         | 
| 497 459 |  | 
| 498 | 
            -
            Other than [FileSystem], Shrine also ships with [S3] storage:
         | 
| 460 | 
            +
            Other than [FileSystem], Shrine also ships with Amazon [S3] storage:
         | 
| 499 461 |  | 
| 500 462 | 
             
            ```rb
         | 
| 501 463 | 
             
            gem "aws-sdk", "~> 2.1"
         | 
| @@ -507,42 +469,71 @@ Shrine.storages[:store] = Shrine::Storage::S3.new( | |
| 507 469 | 
             
              access_key_id:     "<ACCESS_KEY_ID>",      # "xyz"
         | 
| 508 470 | 
             
              secret_access_key: "<SECRET_ACCESS_KEY>",  # "abc"
         | 
| 509 471 | 
             
              region:            "<REGION>",             # "eu-west-1"
         | 
| 510 | 
            -
              bucket:            "<BUCKET>",             # "my- | 
| 472 | 
            +
              bucket:            "<BUCKET>",             # "my-bucket"
         | 
| 511 473 | 
             
            )
         | 
| 512 474 | 
             
            ```
         | 
| 513 475 |  | 
| 514 476 | 
             
            ```rb
         | 
| 515 | 
            -
             | 
| 516 | 
            -
             | 
| 517 | 
            -
             | 
| 518 | 
            -
             | 
| 477 | 
            +
            movie = Movie.new(video: File.open("video.mp4"))
         | 
| 478 | 
            +
            movie.video_url #=> "/uploads/cache/j4k343ui12ls9.jpg"
         | 
| 479 | 
            +
            movie.save
         | 
| 480 | 
            +
            movie.video_url #=> "https://my-bucket.s3-eu-west-1.amazonaws.com/0943sf8gfk13.mp4"
         | 
| 519 481 | 
             
            ```
         | 
| 520 482 |  | 
| 521 | 
            -
            If you're using S3 both for cache and store,  | 
| 522 | 
            -
             | 
| 523 | 
            -
             | 
| 524 | 
            -
            versions are deleted with a single HTTP request.
         | 
| 483 | 
            +
            If you're using S3 both for cache and store, uploading a cached file to store
         | 
| 484 | 
            +
            will simply do an S3 COPY request instead of downloading and reuploading the
         | 
| 485 | 
            +
            file. Also, the versions plugin takes advantage of S3's MULTI DELETE
         | 
| 486 | 
            +
            capabilities, so versions are deleted with a single HTTP request.
         | 
| 525 487 |  | 
| 526 488 | 
             
            See the full documentation for [FileSystem] and [S3] storages. There are also
         | 
| 527 489 | 
             
            many other Shrine storages available, see the [Plugins & Storages] section.
         | 
| 528 490 |  | 
| 529 | 
            -
             | 
| 491 | 
            +
            ## Upload options
         | 
| 530 492 |  | 
| 531 | 
            -
             | 
| 532 | 
            -
             | 
| 533 | 
            -
            and for FileSystem you can put something like this in your Rake task:
         | 
| 493 | 
            +
            Many storages accept additional upload options, which you can pass via the
         | 
| 494 | 
            +
            upload_options plugin, or manually when uploading:
         | 
| 534 495 |  | 
| 535 496 | 
             
            ```rb
         | 
| 536 | 
            -
             | 
| 537 | 
            -
             | 
| 497 | 
            +
            uploader = Shrine.new(:store)
         | 
| 498 | 
            +
            uploader.upload(file, upload_options: {acl: "private"})
         | 
| 499 | 
            +
            ```
         | 
| 500 | 
            +
             | 
| 501 | 
            +
            ## Direct uploads
         | 
| 502 | 
            +
             | 
| 503 | 
            +
            Shrine comes with a direct_upload plugin which provides a [Roda] endpoint that
         | 
| 504 | 
            +
            accepts file uploads. This allows you to asynchronously start caching the file
         | 
| 505 | 
            +
            the moment the user selects it via AJAX (e.g. using the [jQuery-File-Upload] JS
         | 
| 506 | 
            +
            library).
         | 
| 507 | 
            +
             | 
| 508 | 
            +
            ```rb
         | 
| 509 | 
            +
            Shrine.plugin :direct_upload # Provides a Roda endpoint
         | 
| 510 | 
            +
            ```
         | 
| 511 | 
            +
            ```rb
         | 
| 512 | 
            +
            Rails.application.routes.draw do
         | 
| 513 | 
            +
              mount VideoUploader::UploadEndpoint => "/attachments/videos"
         | 
| 514 | 
            +
            end
         | 
| 515 | 
            +
            ```
         | 
| 516 | 
            +
            ```js
         | 
| 517 | 
            +
            $('[type="file"]').fileupload({
         | 
| 518 | 
            +
              url:       '/attachments/videos/cache/upload',
         | 
| 519 | 
            +
              paramName: 'file',
         | 
| 520 | 
            +
              add:       function(e, data) { /* Disable the submit button */ },
         | 
| 521 | 
            +
              progress:  function(e, data) { /* Add a nice progress bar */ },
         | 
| 522 | 
            +
              done:      function(e, data) { /* Fill in the hidden field with the result */ }
         | 
| 523 | 
            +
            });
         | 
| 538 524 | 
             
            ```
         | 
| 539 525 |  | 
| 526 | 
            +
            The plugin also provides a route that can be used for doing direct S3 uploads.
         | 
| 527 | 
            +
            See the documentation of the plugin for more details, as well as the
         | 
| 528 | 
            +
            [Roda](https://github.com/janko-m/shrine-example)/[Rails](https://github.com/erikdahlstrand/shrine-rails-example)
         | 
| 529 | 
            +
            example app which demonstrates multiple uploads directly to S3.
         | 
| 530 | 
            +
             | 
| 540 531 | 
             
            ## Background jobs
         | 
| 541 532 |  | 
| 542 | 
            -
            Shrine is the first  | 
| 543 | 
            -
             | 
| 544 | 
            -
            and good user experience, and Shrine provides a  | 
| 545 | 
            -
            makes it really easy to plug in your backgrounding library:
         | 
| 533 | 
            +
            Shrine is the first file upload library designed for backgrounding support.
         | 
| 534 | 
            +
            Moving phases of managing attachments to background jobs is essential for
         | 
| 535 | 
            +
            scaling and good user experience, and Shrine provides a backgrounding plugin
         | 
| 536 | 
            +
            which makes it really easy to plug in your favourite backgrounding library:
         | 
| 546 537 |  | 
| 547 538 | 
             
            ```rb
         | 
| 548 539 | 
             
            Shrine.plugin :backgrounding
         | 
| @@ -567,8 +558,8 @@ end | |
| 567 558 | 
             
            ```
         | 
| 568 559 |  | 
| 569 560 | 
             
            The above puts all promoting (moving to store) and deleting of files into a
         | 
| 570 | 
            -
            background Sidekiq job. Obviously instead of Sidekiq you can  | 
| 571 | 
            -
             | 
| 561 | 
            +
            background Sidekiq job. Obviously instead of Sidekiq you can use any other
         | 
| 562 | 
            +
            backgrounding library.
         | 
| 572 563 |  | 
| 573 564 | 
             
            The main advantages of Shrine's backgrounding support over other file upload
         | 
| 574 565 | 
             
            libraries are:
         | 
| @@ -583,14 +574,25 @@ libraries are: | |
| 583 574 | 
             
            * **Generality** – The above solution will automatically work for all uploaders,
         | 
| 584 575 | 
             
              types of files and models.
         | 
| 585 576 | 
             
            * **Safety** – All of Shrine's code has been designed to take delayed storing
         | 
| 586 | 
            -
              into account,  | 
| 577 | 
            +
              into account, and concurrent requests are handled well.
         | 
| 578 | 
            +
             | 
| 579 | 
            +
            ## Clearing cache
         | 
| 580 | 
            +
             | 
| 581 | 
            +
            You will want to periodically clean your temporary storage. Amazon S3 provides
         | 
| 582 | 
            +
            [a built-in solution](http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html),
         | 
| 583 | 
            +
            and for FileSystem you can put something like this in your Rake task:
         | 
| 584 | 
            +
             | 
| 585 | 
            +
            ```rb
         | 
| 586 | 
            +
            file_system = Shrine.storages[:cache]
         | 
| 587 | 
            +
            file_system.clear!(older_than: Time.now - 7*24*60*60) # delete files older than 1 week
         | 
| 588 | 
            +
            ```
         | 
| 587 589 |  | 
| 588 590 | 
             
            ## Plugins
         | 
| 589 591 |  | 
| 590 592 | 
             
            Shrine comes with a small core which provides only the essential functionality,
         | 
| 591 | 
            -
            and  | 
| 592 | 
            -
            exactly how much Shrine does for you. Shrine itself [ships with over | 
| 593 | 
            -
            plugins], most of which I  | 
| 593 | 
            +
            and any additional features are available via plugins. This way you can choose
         | 
| 594 | 
            +
            exactly what and how much Shrine does for you. Shrine itself [ships with over
         | 
| 595 | 
            +
            35 plugins], most of which I didn't cover here.
         | 
| 594 596 |  | 
| 595 597 | 
             
            The plugin system respects inheritance, so you can choose which plugins will
         | 
| 596 598 | 
             
            be applied to which uploaders:
         |