shrine 3.0.0.beta2 → 3.0.0.beta3

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

Potentially problematic release.


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

Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/README.md +100 -106
  4. data/doc/advantages.md +90 -88
  5. data/doc/attacher.md +322 -152
  6. data/doc/carrierwave.md +105 -113
  7. data/doc/changing_derivatives.md +308 -0
  8. data/doc/changing_location.md +92 -21
  9. data/doc/changing_storage.md +107 -0
  10. data/doc/creating_plugins.md +1 -1
  11. data/doc/design.md +8 -9
  12. data/doc/direct_s3.md +3 -2
  13. data/doc/metadata.md +97 -78
  14. data/doc/multiple_files.md +3 -3
  15. data/doc/paperclip.md +89 -88
  16. data/doc/plugins/activerecord.md +3 -12
  17. data/doc/plugins/backgrounding.md +126 -100
  18. data/doc/plugins/derivation_endpoint.md +4 -5
  19. data/doc/plugins/derivatives.md +63 -32
  20. data/doc/plugins/download_endpoint.md +54 -1
  21. data/doc/plugins/entity.md +1 -0
  22. data/doc/plugins/form_assign.md +53 -0
  23. data/doc/plugins/mirroring.md +37 -16
  24. data/doc/plugins/multi_cache.md +22 -0
  25. data/doc/plugins/presign_endpoint.md +1 -1
  26. data/doc/plugins/remote_url.md +19 -4
  27. data/doc/plugins/validation.md +83 -0
  28. data/doc/processing.md +149 -133
  29. data/doc/refile.md +68 -63
  30. data/doc/release_notes/3.0.0.md +835 -0
  31. data/doc/securing_uploads.md +56 -36
  32. data/doc/storage/s3.md +2 -2
  33. data/doc/testing.md +104 -120
  34. data/doc/upgrading_to_3.md +538 -0
  35. data/doc/validation.md +48 -87
  36. data/lib/shrine.rb +7 -4
  37. data/lib/shrine/attacher.rb +16 -6
  38. data/lib/shrine/plugins/activerecord.rb +33 -14
  39. data/lib/shrine/plugins/atomic_helpers.rb +1 -1
  40. data/lib/shrine/plugins/backgrounding.rb +23 -89
  41. data/lib/shrine/plugins/data_uri.rb +13 -2
  42. data/lib/shrine/plugins/derivation_endpoint.rb +7 -11
  43. data/lib/shrine/plugins/derivatives.rb +44 -20
  44. data/lib/shrine/plugins/download_endpoint.rb +26 -0
  45. data/lib/shrine/plugins/form_assign.rb +6 -3
  46. data/lib/shrine/plugins/keep_files.rb +2 -2
  47. data/lib/shrine/plugins/mirroring.rb +62 -22
  48. data/lib/shrine/plugins/model.rb +2 -2
  49. data/lib/shrine/plugins/multi_cache.rb +27 -0
  50. data/lib/shrine/plugins/remote_url.rb +25 -10
  51. data/lib/shrine/plugins/remove_invalid.rb +1 -1
  52. data/lib/shrine/plugins/sequel.rb +39 -20
  53. data/lib/shrine/plugins/validation.rb +3 -0
  54. data/lib/shrine/storage/s3.rb +16 -1
  55. data/lib/shrine/uploaded_file.rb +1 -0
  56. data/lib/shrine/version.rb +1 -1
  57. data/shrine.gemspec +1 -1
  58. metadata +12 -7
  59. data/doc/migrating_storage.md +0 -76
  60. data/doc/regenerating_versions.md +0 -143
  61. data/lib/shrine/plugins/attacher_options.rb +0 -55
@@ -32,14 +32,14 @@ configured differently depending on the types of files you're uploading:
32
32
 
33
33
  ```rb
34
34
  class ImageUploader < Shrine
35
- add_metadata :exif do |io, context|
35
+ add_metadata :exif do |io|
36
36
  MiniMagick::Image.new(io).exif
37
37
  end
38
38
  end
39
39
  ```
40
40
  ```rb
41
41
  class VideoUploader < Shrine
42
- add_metadata :duration do |io, context|
42
+ add_metadata :duration do |io|
43
43
  FFMPEG::Movie.new(io.path).duration
44
44
  end
45
45
  end
@@ -47,43 +47,35 @@ end
47
47
 
48
48
  ### Processing
49
49
 
50
- Refile implements on-the-fly processing, serving all files through the Rack
51
- endpoint. However, it doesn't offer any abilities for processing on upload.
52
- Shrine, on the other hand, generates URLs to specific storages and offers
53
- processing on upload (like CarrierWave and Paperclip), but doesn't support
54
- on-the-fly processing.
55
-
56
- The reason for this decision is that an image server is a completely separate
57
- responsibility, and it's better to use any of the generic services for
58
- on-the-fly processing. Shrine already has integrations for many such services:
59
- [shrine-cloudinary], [shrine-imgix], and [shrine-uploadcare]. There is even
60
- an open-source solution, [Attache], which you can also use with Shrine.
61
-
62
- This is how you would process multiple versions in Shrine:
50
+ Shrine provides on-the-fly processing via the
51
+ [`derivation_endpoint`][derivation_endpoint] plugin:
63
52
 
53
+ ```rb
54
+ # config/routes.rb (Rails)
55
+ Rails.application.routes.draw do
56
+ # ...
57
+ mount ImageUploader.derivation_endpoint => "/derivations/image"
58
+ end
59
+ ```
64
60
  ```rb
65
61
  require "image_processing/mini_magick"
66
62
 
67
63
  class ImageUploader < Shrine
68
- plugin :processing
69
- plugin :versions
70
-
71
- process(:store) do |io, context|
72
- versions = { original: io } # retain original
73
-
74
- io.download do |original|
75
- pipeline = ImageProcessing::MiniMagick.source(original)
76
-
77
- versions[:large] = pipeline.resize_to_limit!(800, 800)
78
- versions[:medium] = pipeline.resize_to_limit!(500, 500)
79
- versions[:small] = pipeline.resize_to_limit!(300, 300)
80
- end
81
-
82
- versions # return the hash of processed files
64
+ plugin :derivation_endpoint,
65
+ secret_key: "<YOUR SECRET KEY>",
66
+ prefix: "derivations/image" # needs to match the mount point in routes
67
+
68
+ derivation :thumbnail do |file, width, height|
69
+ ImageProcessing::MiniMagick
70
+ .source(file)
71
+ .resize_to_limit!(width.to_i, height.to_i)
83
72
  end
84
73
  end
85
74
  ```
86
75
 
76
+ Shrine also support processing up front using the [`derivatives`][derivatives]
77
+ plugin.
78
+
87
79
  ### URL
88
80
 
89
81
  While Refile serves all files through the Rack endpoint mounted in your app,
@@ -99,7 +91,7 @@ Refile.attachment_url(@photo, :image) #=> "/attachments/cache/50dfl833lfs0gfh.jp
99
91
 
100
92
  If you're using storage which don't expose files over URL (e.g. a database
101
93
  storage), or you want to secure your downloads, you can also serve files
102
- through your app using the download_endpoint plugin.
94
+ through your app using the [`download_endpoint`][download_endpoint] plugin.
103
95
 
104
96
  ## Attachments
105
97
 
@@ -118,11 +110,10 @@ end
118
110
  ```rb
119
111
  class ImageUploader < Shrine
120
112
  plugin :sequel
121
- plugin :keep_files, destroyed: true
122
113
  end
123
114
 
124
115
  class Photo < Sequel::Model
125
- include ImageUploader::Attachment.new(:image)
116
+ include ImageUploader::Attachment(:image)
126
117
  end
127
118
  ```
128
119
 
@@ -178,16 +169,16 @@ class ImageUploader < Shrine
178
169
  plugin :validation_helpers
179
170
 
180
171
  Attacher.validate do
181
- validate_extension_inclusion %w[jpg jpeg png gif]
182
- validate_mime_type_inclusion %w[image/jpeg image/png image/gif]
183
- validate_max_size 10*1024*1024 unless record.admin?
172
+ validate_extension %w[jpg jpeg png gif]
173
+ validate_mime_type %w[image/jpeg image/png image/gif]
174
+ validate_max_size 10*1024*1024
184
175
  end
185
176
  end
186
177
  ```
187
178
 
188
179
  Refile extracts the MIME type from the file extension, which means it can
189
180
  easily be spoofed (just give a PHP file a `.jpg` extension). Shrine has the
190
- determine_mime_type plugin for determining MIME type from file *content*.
181
+ `determine_mime_type` plugin for determining MIME type from file *content*.
191
182
 
192
183
  ### Multiple uploads
193
184
 
@@ -228,25 +219,39 @@ Afterwards we need to make new uploads write to the `image_data` column. This
228
219
  can be done by including the below module to all models that have Refile
229
220
  attachments:
230
221
 
222
+ ```rb
223
+ require "shrine"
224
+
225
+ Shrine.storages = {
226
+ cache: ...,
227
+ store: ...,
228
+ }
229
+
230
+ Shrine.plugin :model
231
+ ```
231
232
  ```rb
232
233
  module RefileShrineSynchronization
233
234
  def write_shrine_data(name)
234
- if read_attribute("#{name}_id").present?
235
- data = {
236
- storage: :store,
237
- id: send("#{name}_id"),
238
- metadata: {
239
- size: (send("#{name}_size") if respond_to?("#{name}_size")),
240
- filename: (send("#{name}_filename") if respond_to?("#{name}_filename")),
241
- mime_type: (send("#{name}_content_type") if respond_to?("#{name}_content_type")),
242
- }
243
- }
235
+ attacher = Shrine::Attacher.from_model(self, name)
244
236
 
245
- write_attribute(:"#{name}_data", data.to_json)
237
+ if read_attribute("#{name}_id").present?
238
+ attacher.set shrine_file(name)
246
239
  else
247
- write_attribute(:"#{name}_data", nil)
240
+ attacher.set nil
248
241
  end
249
242
  end
243
+
244
+ def shrine_file(name)
245
+ Shrine.uploaded_file(
246
+ storage: :store,
247
+ id: send("#{name}_id"),
248
+ metadata: {
249
+ "size" => (send("#{name}_size") if respond_to?("#{name}_size")),
250
+ "filename" => (send("#{name}_filename") if respond_to?("#{name}_filename")),
251
+ "mime_type" => (send("#{name}_content_type") if respond_to?("#{name}_content_type")),
252
+ }
253
+ )
254
+ end
250
255
  end
251
256
  ```
252
257
  ```rb
@@ -293,8 +298,9 @@ Shrine.storages = {
293
298
 
294
299
  #### `.app`, `.mount_point`, `.automount`
295
300
 
296
- The `upload_endpoint` and `presign_endpoint` plugins provide methods for
297
- generating Rack apps, but you need to mount them explicitly:
301
+ The `upload_endpoint`, `presign_endpoint`, and `derivation_endpoint` plugins
302
+ provide methods for generating Rack apps, but you need to mount them
303
+ explicitly:
298
304
 
299
305
  ```rb
300
306
  # config/routes.rb
@@ -318,10 +324,10 @@ Shrine.logger
318
324
  #### `.processors`, `.processor`
319
325
 
320
326
  ```rb
321
- class MyUploader < Shrine
322
- plugin :processing
327
+ class ImageUploader < Shrine
328
+ plugin :derivatives
323
329
 
324
- process(:store) do |io, context|
330
+ derivation :thumbnail do |file, width, height|
325
331
  # ...
326
332
  end
327
333
  end
@@ -329,7 +335,7 @@ end
329
335
 
330
336
  #### `.types`
331
337
 
332
- In Shrine validations are done by calling `.validate` on the attacher class:
338
+ In Shrine, validations are done by calling `.validate` on the attacher class:
333
339
 
334
340
  ```rb
335
341
  class MyUploader < Shrine
@@ -375,8 +381,8 @@ Shrine's equivalent to calling the attachment is including an attachment module
375
381
  of an uploader:
376
382
 
377
383
  ```rb
378
- class User
379
- include ImageUploader::Attachment.new(:avatar)
384
+ class Photo
385
+ include ImageUploader::Attachment(:image)
380
386
  end
381
387
  ```
382
388
 
@@ -390,8 +396,8 @@ class ImageUploader < Shrine
390
396
  plugin :validation_helpers
391
397
 
392
398
  Attacher.validate do
393
- validate_extension_inclusion %w[jpg jpeg png]
394
- validate_mime_type_inclusion %w[image/jpeg image/png]
399
+ validate_extension %w[jpg jpeg png]
400
+ validate_mime_type %w[image/jpeg image/png]
395
401
  end
396
402
  end
397
403
  ```
@@ -477,12 +483,11 @@ form_for @user do |form|
477
483
  end
478
484
  ```
479
485
 
480
- [shrine-cloudinary]: https://github.com/shrinerb/shrine-cloudinary
481
- [shrine-imgix]: https://github.com/shrinerb/shrine-imgix
482
- [shrine-uploadcare]: https://github.com/shrinerb/shrine-uploadcare
483
- [Attache]: https://github.com/choonkeat/attache
484
486
  [image_processing]: https://github.com/janko/image_processing
485
487
  [Uppy]: https://uppy.io
486
488
  [Direct Uploads to S3]: /doc/direct_s3.md#readme
487
489
  [demo app]: https://github.com/shrinerb/shrine/tree/master/demo
488
490
  [Multiple Files]: /doc/multiple_files.md#readme
491
+ [derivation_endpoint]: /doc/plugins/derivation_endpoint.md#readme
492
+ [download_endpoint]: /doc/plugins/download_endpoint.md#readme
493
+ [derivatives]: /doc/plugins/derivatives.md#readme
@@ -0,0 +1,835 @@
1
+ # Shrine 3.0.0
2
+
3
+ This guide covers all the changes in the 3.0.0 version of Shrine. If you're
4
+ currently using Shrine 2.x, see [Upgrading to Shrine 3.x] for instructions on
5
+ how to upgrade.
6
+
7
+ ## Major features
8
+
9
+ * The new **[`derivatives`][derivatives]** plugin has been added for storing
10
+ additional processed files alongside the main file.
11
+
12
+ ```rb
13
+ Shrine.plugin :derivatives
14
+ ```
15
+ ```rb
16
+ class ImageUploader < Shrine
17
+ Attacher.derivatives_processor do |original|
18
+ magick = ImageProcessing::MiniMagick.source(original)
19
+
20
+ {
21
+ large: magick.resize_to_limit!(800, 800),
22
+ medium: magick.resize_to_limit!(500, 500),
23
+ small: magick.resize_to_limit!(300, 300),
24
+ }
25
+ end
26
+ end
27
+ ```
28
+ ```rb
29
+ photo = Photo.new(photo_params)
30
+ photo.image_derivatives! # creates derivatives
31
+ photo.save
32
+ ```
33
+
34
+ This is a rewrite of the [`versions`][versions] plugin, bringing numerous
35
+ improvements:
36
+
37
+ - processed files are separated from the main file
38
+ - processing is decoupled from promotion
39
+ - ability to add or remove processed files at any point
40
+ - possibility of storing processed files on a separate storage
41
+
42
+ * The [`Shrine::Attacher`][attacher] class has been rewritten and can now be
43
+ used without models:
44
+
45
+ ```rb
46
+ attacher = Shrine::Attacher.new
47
+ attacher.attach(file)
48
+ attacher.file #=> #<Shrine::UploadedFile>
49
+ ```
50
+
51
+ The `Attacher#data`, `Attacher#load_data`, and `Attacher.from_data` methods
52
+ have been added for dumping and loading the attached file:
53
+
54
+ ```rb
55
+ # dump attached file into a serializable Hash
56
+ data = attacher.data #=> { "id" => "abc123.jpg", "storage" => "store", "metadata" => { ... } }
57
+ ```
58
+ ```rb
59
+ # initialize attacher from attached file data...
60
+ attacher = Shrine::Attacher.from_data(data)
61
+ attacher.file #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store @metadata={...}>
62
+
63
+ # ...or load attached file into an existing attacher
64
+ attacher = Shrine::Attacher.new
65
+ attacher.load_data(data)
66
+ attacher.file #=> #<Shrine::UploadedFile>
67
+ ```
68
+
69
+ Several more methods have been added:
70
+
71
+ - `Attacher#attach` – attaches the file directly to permanent storage
72
+ - `Attacher#attach_cached` – extracted from `Attacher#assign`
73
+ - `Attacher#upload` – calls `Shrine#upload`, passing `:record` and `:name` context
74
+ - `Attacher#file` – alias for `Attacher#get`
75
+ - `Attacher#cache_key` – returns temporary storage key (`:cache` by default)
76
+ - `Attacher#store_key` – returns permanent storage key (`:store` by default)
77
+
78
+ * The new [`column`][column] plugin adds the ability to serialize attached file
79
+ data, in format suitable for writing into a database column.
80
+
81
+ ```rb
82
+ Shrine.plugin :column
83
+ ```
84
+ ```rb
85
+ # dump attached file data into a JSON string
86
+ data = attacher.column_data #=> '{"id":"abc123.jpg","storage":"store","metadata":{...}}'
87
+ ```
88
+ ```rb
89
+ # initialize attacher from attached file data...
90
+ attacher = Shrine::Attacher.from_column(data)
91
+ attacher.file #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store @metadata={...}>
92
+
93
+ # ...or load attached file into an existing attacher
94
+ attacher = Shrine::Attacher.new
95
+ attacher.load_column(data)
96
+ attacher.file #=> #<Shrine::UploadedFile>
97
+ ```
98
+
99
+ * The new [`entity`][entity] plugin adds support for immutable structs, which
100
+ are commonly used with ROM, Hanami and dry-rb.
101
+
102
+ ```rb
103
+ Shrine.plugin :entity
104
+ ```
105
+ ```rb
106
+ class Photo < Hanami::Entity
107
+ include Shrine::Attachment(:image)
108
+ end
109
+ ```
110
+ ```rb
111
+ photo = Photo.new(image_data: '{"id":"abc123.jpg","storage":"store","metadata":{...}}')
112
+ photo.image #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:store ...>
113
+ ```
114
+
115
+ * The new [`model`][model] plugin adds support for mutable structs, which is
116
+ used for `activerecord` and `sequel` plugins.
117
+
118
+ ```rb
119
+ Shrine.plugin :model
120
+ ```
121
+ ```rb
122
+ class Photo < Struct.new(:image_data)
123
+ include Shrine::Attachment(:image)
124
+ end
125
+ ```
126
+ ```rb
127
+ photo = Photo.new
128
+ photo.image = file
129
+ photo.image #=> #<Shrine::UploadedFile @id="abc123.jpg" @storage_key=:cache ...>
130
+ photo.image_data #=> #=> '{"id":"abc123.jpg", "storage":"cache", "metadata":{...}}'
131
+ ```
132
+
133
+ * The [`backgrounding`][backgrounding] plugin has been rewritten for more
134
+ flexibility and simplicity. The new usage is much more explicit:
135
+
136
+ ```rb
137
+ Shrine.plugin :backgrounding
138
+ Shrine::Attacher.promote_block { PromoteJob.promote_later(self.class, record, name, file_data) }
139
+ Shrine::Attacher.destroy_block { DestroyJob.promote_later(self.class, data) }
140
+ ```
141
+ ```rb
142
+ class PromoteJob < ActiveJob::Base
143
+ def perform(attacher_class, record, name, file_data)
144
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
145
+ attacher.atomic_promote # promote if attachment hasn't changed
146
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
147
+ # attachment has changed or record has been deleted, nothing to do
148
+ end
149
+ end
150
+ ```
151
+ ```rb
152
+ class DestroyJob < ActiveJob::Base
153
+ def perform(attacher_class, data)
154
+ attacher = attacher_class.from_data(data)
155
+ attacher.destroy
156
+ end
157
+ end
158
+ ```
159
+
160
+ There are several main differences compared to the old implementation:
161
+
162
+ - we are in charge of passing the record to the background job
163
+ - we can access the attacher before promotion
164
+ - we can react to errors that caused promotion to abort
165
+
166
+ We can now also register backgrounding hooks on an attacher instance, allowing
167
+ us to pass additional parameters to the background job:
168
+
169
+ ```rb
170
+ photo = Photo.new(photo_params)
171
+
172
+ photo.image_attacher.promote_block do |attacher|
173
+ PromoteJob.perform_later(
174
+ attacher.class,
175
+ attacher.record,
176
+ attacher.name,
177
+ attacher.file_data,
178
+ current_user.id, # <== parameters from the controller
179
+ )
180
+ end
181
+
182
+ photo.save # will call our instance-level backgrounding hook
183
+ ```
184
+
185
+ * The persistence plugins (`activerecord`, `sequel`) now implement a unified
186
+ [persistence] interface:
187
+
188
+ | Method | Description |
189
+ | :----------------- | :---------- |
190
+ | `Attacher#persist` | persists attachment data |
191
+ | `Attacher#atomic_persist` | persists attachment data if attachment hasn’t changed |
192
+ | `Attacher#atomic_promote` | promotes cached file and atomically persists changes |
193
+
194
+ The "atomic" methods use the new [`atomic_helpers`][atomic_helpers] plugin,
195
+ and are useful for background jobs. For example, this is how we'd use them to
196
+ implement metadata extraction in the background in a concurrency-safe way:
197
+
198
+ ```rb
199
+ MetadataJob.perform_later(
200
+ attacher.class,
201
+ attacher.record,
202
+ attacher.name,
203
+ attacher.file_data,
204
+ )
205
+ ```
206
+ ```rb
207
+ class MetadataJob < ActiveJob::Base
208
+ def perform(attacher_class, record, name, file_data)
209
+ attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
210
+ attacher.refresh_metadata! # extract metadata
211
+ attacher.atomic_persist # persist if attachment hasn't changed
212
+ rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
213
+ # attachment has changed or record has been deleted, nothing to do
214
+ end
215
+ end
216
+ ```
217
+
218
+ ## Other features
219
+
220
+ * The new [`mirroring`][mirroring] plugin has been added for replicating
221
+ uploads and deletes to other storages.
222
+
223
+ ```rb
224
+ Shrine.storages = { cache: ..., store: ..., backup: ... }
225
+
226
+ Shrine.plugin :mirroring, mirror: { store: :backup }
227
+ ```
228
+ ```rb
229
+ file = Shrine.upload(io, :store) # uploads to :store and :backup
230
+ file.delete # deletes from :store and :backup
231
+ ```
232
+
233
+ * The new [`multi_cache`][multi_cache] plugin has been added for allowing an
234
+ attacher to accept files from additional temporary storages.
235
+
236
+ ```rb
237
+ Shrine.storages = { cache: ..., cache_one: ..., cache_two: ..., store: ... }
238
+
239
+ Shrine.plugin :multi_cache, additional_cache: [:cache_one, :cache_two]
240
+ ```
241
+ ```rb
242
+ photo.image = { "id" => "...", "storage" => "cache", "metadata" => { ... } }
243
+ photo.image.storage_key #=> :cache
244
+ # or
245
+ photo.image = { "id" => "...", "storage" => "cache_one", "metadata" => { ... } }
246
+ photo.image.storage_key #=> :cache_one
247
+ # or
248
+ photo.image = { "id" => "...", "storage" => "cache_two", "metadata" => { ... } }
249
+ photo.image.storage_key #=> :cache_two
250
+ ```
251
+
252
+ * The new [`form_assign`][form_assign] plugin has been added for assigning
253
+ files directly from form params.
254
+
255
+ ```rb
256
+ Shrine.plugin :form_assign
257
+ ```
258
+ ```rb
259
+ attacher = photo.image_attacher
260
+ attacher.form_assign("image" => file, "title" => "...", "description" => "...")
261
+ attacher.file #=> #<Shrine::UploadedFile @id="..." @storage_key=:cache ...>
262
+ ```
263
+
264
+ * Model file assignment can now be configured to upload directly to permanent
265
+ storage.
266
+
267
+ ```rb
268
+ Shrine.plugin :model, cache: false
269
+ ```
270
+ ```rb
271
+ photo.image = file
272
+ photo.image.storage_key #=> :store (permanent storage)
273
+ ```
274
+
275
+ * New `Shrine.download_response` method has been added to the
276
+ `download_endpoint` plugin for generating file response from the controller.
277
+
278
+ ```rb
279
+ Rails.application.routes.draw do
280
+ get "/attachments" => "files#download"
281
+ end
282
+ ```
283
+ ```rb
284
+ class FilesController < ApplicationController
285
+ def download
286
+ # ... we can now perform things like authentication here ...
287
+ set_rack_response Shrine.download_response(env)
288
+ end
289
+
290
+ private
291
+
292
+ def set_rack_response((status, headers, body))
293
+ self.status = status
294
+ self.headers.merge!(headers)
295
+ self.response_body = body
296
+ end
297
+ end
298
+ ```
299
+
300
+ * The `Attacher#refresh_metadata!` method has been added to `refresh_metadata`
301
+ plugin. It refreshes metadata and writes new attached file data back into the
302
+ data attribute.
303
+
304
+ ```rb
305
+ attacher.file.refresh_metadata!
306
+ attacher.write
307
+ # can now be shortened to
308
+ attacher.refresh_metadata!
309
+ ```
310
+
311
+ * Including a `Shrine::Attachment` module now defines a `.<name>_attacher`
312
+ class method on the target class.
313
+
314
+ ```rb
315
+ class Photo
316
+ include ImageUploader::Attachment(:image)
317
+
318
+ image_attacher #=> #<ImageUploader::Attacher ...>
319
+ end
320
+ ```
321
+
322
+ * The attachment data serializer is now configurable (by default `JSON`
323
+ standard library is used):
324
+
325
+ ```rb
326
+ require "oj" # https://github.com/ohler55/oj
327
+
328
+ Shrine.plugin :column, serializer: Oj
329
+ ```
330
+
331
+ * It's now possible to pass options to the validate block via the `:validate`
332
+ option:
333
+
334
+ ```rb
335
+ attacher.assign(file, validate: { foo: "bar" })
336
+ ```
337
+ ```rb
338
+ class MyUploader < Shrine
339
+ Attacher.validate do |**options|
340
+ options #=> { foo: "bar" }
341
+ end
342
+ end
343
+ ```
344
+
345
+ * Validation can now be skipped on assignment by passing `validate: false`.
346
+
347
+ ```rb
348
+ attacher.attach(file, validate: false) # skip validation
349
+ ```
350
+
351
+ * Closing the uploaded file can now be prevented by passing `close: false` to
352
+ `Shrine#upload`.
353
+
354
+ * Uploaded file can now be automatically deleted by passing `delete: true` to
355
+ `Shrine#upload`.
356
+
357
+ * New `Attacher#file!` method has been added for retrieving the attached file
358
+ and raising and exception if it doesn't exist.
359
+
360
+ * New `Derivation#opened` method has been added for retrieving an opened
361
+ derivative in `derivation_endpoint` plugin.
362
+
363
+ ## Performance improvements
364
+
365
+ * The attached file is now parsed and loaded from record column only once,
366
+ which can greatly improve performance if the same attached file is being
367
+ accessed multiple times.
368
+
369
+ ```rb
370
+ photo = Photo.find(photo_id)
371
+ photo.image # parses and loads attached file
372
+ photo.image # returns memoized attached file
373
+ ```
374
+
375
+ * The `S3#open` method doesn't perform a `#head_obect` request anymore.
376
+
377
+ * The `derivation_endpoint` plugin doesn't perform a `Storage#exists?` call
378
+ anymore when `:upload` is enabled.
379
+
380
+ * The `download_endpoint` plugin doesn't perform a `Storage#exists?` call
381
+ anymore.
382
+
383
+ ## Other Improvements
384
+
385
+ * The `Attacher#assign` method now accepts cached file data as a Hash.
386
+
387
+ ```rb
388
+ photo.image = { "id" => "...", "storage" => "cache", "metadata" => { ... } }
389
+ ````
390
+
391
+ * An `UploadedFile` object can now be initialized with symbol keys.
392
+
393
+ ```rb
394
+ Shrine.uploaded_file(id: "...", storage: :store, metadata: { ... })
395
+ ```
396
+
397
+ * The temporary storage doesn't need to be defined anymore if it's not used.
398
+
399
+ * The memory storage from the [shrine-memory] gem has been merged into core.
400
+
401
+ ```rb
402
+ # Gemfile
403
+ gem "shrine-memory" # this can be removed
404
+ ```
405
+
406
+ * When copying the S3 object to another location, any specified upload options
407
+ will now be applied.
408
+
409
+ * The `url_options` plugin now allows you to override URL options by deleting
410
+ them.
411
+
412
+ ```rb
413
+ uploaded_file.url(response_content_disposition: "attachment")
414
+ ```
415
+ ```rb
416
+ plugin :url_options, store: -> (io, options) {
417
+ disposition = options.delete(:response_content_disposition, "inline")
418
+
419
+ {
420
+ response_content_disposition: ContentDisposition.format(
421
+ disposition: disposition,
422
+ filename: io.original_filename,
423
+ )
424
+ }
425
+ }
426
+ ```
427
+
428
+ * The `:upload_options` hash passed to the uploader is now merged with any
429
+ options defined with the `upload_options` plugin.
430
+
431
+ * The `default_storage` now evaluates the storage block in context of the
432
+ `Attacher` instance.
433
+
434
+ * New `Attacher.default_cache` and `Attacher.default_store` methods have been
435
+ added to the `default_storage` plugin for declaratively setting default
436
+ storage.
437
+
438
+ ```rb
439
+ Attacher.default_cache { ... }
440
+ Attacher.default_store { ... }
441
+ ```
442
+
443
+ * The `derivation_endpoint` plugin now handles string derivation names.
444
+
445
+ * The `Derivation#upload` method from `derivation_endpoint` plugin now accepts
446
+ additional uploader options
447
+
448
+ * The `Derivation#upload` method from `derivation_endpoint` plugin now accepts
449
+ any IO-like object.
450
+
451
+ * The `instrumentation` plugin now instruments `UploadedFile#open` calls as a
452
+ new `open.shrine` event. `UploadedFile#download` is still instrumented as
453
+ `download.shrine`.
454
+
455
+ * The `upload.shrine` event now has `:metadata` on the top level in
456
+ `instrumentation` plugin.
457
+
458
+ * The width & height validators in `store_dimensions` plugin don't require
459
+ `UploadedFile#width` and `UploadedFile#height` methods to be defined anymore,
460
+ only that the corresponding metadata exists.
461
+
462
+ * Any options passed to `Attacher#attach_cached` are now forwarded to metadata
463
+ extraction when `restore_cached_data` plugin is loaded.
464
+
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
+ * The `infer_extension` plugin now works correctly with `pretty_location`
474
+ plugin when `pretty_location` was loaded after `infer_extension`.
475
+
476
+ * The `derivation_endpoint` plugin doesn't re-open `File` objects returned in
477
+ derivation block anymore.
478
+
479
+ * Any changes to `Shrine.storages` will now be applied to existing `Shrine` and
480
+ `Attacher` instances.
481
+
482
+ * Callback code from `activerecord` and `sequel` plugin has been moved into
483
+ attacher methods, allowing the user to override them.
484
+
485
+ * The `Shrine.opts` hash is now deep-copied on subclassing. This allows plugins
486
+ to freely mutate hashes and arrays in `Shrine.opts`, knowing they won't be
487
+ shared across subclasses.
488
+
489
+ ## Backwards compatibility
490
+
491
+ ### Plugin deprecation and removal
492
+
493
+ * The `backgrounding` plugin has been rewritten and has a new API. While the
494
+ new API works in a similar way, no backwards compatibility has been kept with
495
+ the previous API.
496
+
497
+ * The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
498
+ deprecated in favor of the new `derivatives` plugin.
499
+
500
+ * The `module_include` plugin has been deprecated over overriding core classes
501
+ directly.
502
+
503
+ * The `hooks`, `parallelize`, `parsed_json`, and `delete_promoted` plugins have
504
+ been removed.
505
+
506
+ * The deprecated `copy`, `backup`, `multi_delete`, `moving`, `logging`,
507
+ `direct_upload` `background_helpers`, and `migration_helpers` plugins have
508
+ been removed.
509
+
510
+ * The `default_url_options` plugin has been renamed to `url_options`.
511
+
512
+ ### Attacher API
513
+
514
+ * The `Attacher.new` method now only accepts a hash of options.
515
+
516
+ ```rb
517
+ # this doesn't work anymore
518
+ Shrine::Attacher.new(photo, :image) # ~> ArgumentError: invalid number of arguments
519
+
520
+ # this should be used instead
521
+ Shrine::Attacher.from_model(photo, :image)
522
+ ```
523
+
524
+ * The attacher won't detect direct data attribute changes anymore. If you're
525
+ updating the data attribute and are expecting to see the change applied to
526
+ the attacher, you'll need to call `Attacher#reload`.
527
+
528
+ ```rb
529
+ attacher = photo.image_attacher
530
+ photo.image_data = '...'
531
+ attacher.reload
532
+ ```
533
+
534
+ * The `Attacher#promote` method now only saves the promoted file in memory,
535
+ it doesn't persist the changes.
536
+
537
+ * The `Attacher#_set` and `Attacher#set` methods have been renamed to
538
+ `Attacher#set` and `Attacher#changed`.
539
+
540
+ * The `Attacher#cache!`, `Attacher#store!`, and `Attacher#delete!` methods have
541
+ been removed.
542
+
543
+ * The `Attacher#_promote` and `Attacher#_delete` methods have been removed.
544
+
545
+ * The `Attacher#swap`, `Attacher#update`, `Attacher#read`, `Attacher#write`,
546
+ `Attacher#convert_to_data`, `Attacher#convert_before_write`, and
547
+ `Attache#convert_after_read` methods have been removed.
548
+
549
+ * The `Attacher.validate`, `Attacher#validate` and `Attacher#errors` methods
550
+ have been extracted into the new `validation` plugin.
551
+
552
+ * The `Attacher#data_attribute` method has been renamed to `Attacher#attribute`.
553
+
554
+ * The `Attacher#replace` method has been renamed to
555
+ `Attacher#destroy_previous`.
556
+
557
+ * The `Attacher#attached?` method now returns whether a file is attached,
558
+ regardless of whether it was changed or not.
559
+
560
+ ### Attachment API
561
+
562
+ * The `Shrine::Attachment` module doesn't define any instance methods by itself
563
+ anymore. This has been moved into `entity` and `model` plugins.
564
+
565
+ ### Uploader API
566
+
567
+ * The `:phase` option has been removed.
568
+
569
+ * The `Shrine#process` and `Shrine#processed` methods have been removed.
570
+
571
+ * The `Shrine#store`, `Shrine#_store`, `Shrine#put`, and `Shrine#copy` methods
572
+ have been removed.
573
+
574
+ * The `Shrine#delete`, `Shrine#_delete`, and `Shrine#remove` methods have been
575
+ removed.
576
+
577
+ * The `Shrine#uploaded?` method has been removed.
578
+
579
+ * The `Shrine.uploaded_file` method doesn't yield files anymore by default.
580
+
581
+ * The options for `Shrine#upload` and `Shrine#extract_metadata` are now
582
+ required to have symbol keys.
583
+
584
+ * The `Shrine.uploaded_file` method now raises `ArgumentError` on invalid
585
+ arguments.
586
+
587
+ * The `Shrine#upload` method doesn't rescue exceptions that happen in
588
+ `IO#close` anymore.
589
+
590
+ * The deprecated `Shrine::IO_METHODS` constant has been removed.
591
+
592
+ ### Uploaded File API
593
+
594
+ * The `UploadedFile` method now extracts data from the given hash on
595
+ initialization, it doesn't store the whole hash anymore. This means the
596
+ following potential code won't work anymore:
597
+
598
+ ```rb
599
+ uploaded_file.id #=> "foo"
600
+ uploaded_file.data["id"] = "bar"
601
+ uploaded_file.id #=> "foo"
602
+ ```
603
+
604
+ * The `UploadedFile#storage_key` method now returns a Symbol instead of a
605
+ String.
606
+
607
+ ```rb
608
+ # previous behaviour
609
+ uploaded_file.storage_key #=> "store"
610
+
611
+ # new behaviour
612
+ uploaded_file.storage_key #=> :store
613
+ ```
614
+
615
+ * The `UploadedFile#==` method now requires both uploaded file objects to be
616
+ of the same class.
617
+
618
+ ### Storage API
619
+
620
+ * The `Storage#open` method is now required to accept additional options.
621
+
622
+ ```rb
623
+ # this won't work anymore
624
+ def open(id)
625
+ # ...
626
+ end
627
+
628
+ # this is now required
629
+ def open(id, **options)
630
+ # ...
631
+ end
632
+ ```
633
+
634
+ * The `Storage#open` method is now required to raise `Shrine::FileNotFound`
635
+ exception when the file is missing.
636
+
637
+ ### S3 API
638
+
639
+ * The support for `aws-sdk` 2.x and `aws-sdk-s3` lower than 1.14 has been
640
+ removed.
641
+
642
+ * `S3#open` now raises `Shrine::FileNotFound` exception when S3 object doesn't
643
+ exist on the bucket.
644
+
645
+ * The `S3#upload` method will now override any S3 object data when copying
646
+ from another S3 object. If you were relying on S3 object data being inherited
647
+ on copy, you will need to update your code.
648
+
649
+ * The `:host` option in `S3#initialize` has been removed.
650
+
651
+ * The `:download` option in `S3#url` has been removed.
652
+
653
+ * Specifying `:multipart_threshold` as an integer is not supported anymore.
654
+
655
+ * Non URI-escaped `:content_disposition` and `:response_content_disposition`
656
+ values are not supported anymore.
657
+
658
+ * The `S3#presign` method now returns a hash instead of an object for default
659
+ POST method.
660
+
661
+ * The deprecated `S3#stream`, `S3#download`, and `S3#s3` methods have been
662
+ removed.
663
+
664
+ * The `S3#open` method doesn't include an `:object` in `Down::ChunkedIO#data`
665
+ anymore.
666
+
667
+ ### FileSystem API
668
+
669
+ * `FileSystem#open` now raises `Shrine::FileNotFound` exception when file does
670
+ not exist on the filesystem.
671
+
672
+ * The deprecated `:host` option in `FileSystem#initialize` has been removed.
673
+
674
+ * The deprecated `:older_than` option in `FileSystem#clear!` has been removed.
675
+
676
+ * The deprecated `FileSystem#download` method has been removed.
677
+
678
+ * The `FileSystem#movable?` and `FileSystem#move` methods have been made
679
+ private.
680
+
681
+ * The `FileSystem#open` method doesn't accept a block anymore.
682
+
683
+ ### Plugins API
684
+
685
+ * `remote_url`
686
+
687
+ - A custom downloader is now required to raise
688
+ `Shrine::Plugins::RemoteUrl::DownloadError` in order for the exception to
689
+ be converted into a validation error. `Down::NotFound` and `Down::TooLarge`
690
+ exceptions are converted by default. All other exceptions are propagated.
691
+
692
+ * `upload_endpoint`
693
+
694
+ - The deprecated `Shrine::Plugins::UploadEndpoint::App` constant has been
695
+ removed.
696
+
697
+ - The `:request` option holding the `Rack::Request` object isn't passed to
698
+ the uploader anymore.
699
+
700
+ * `presign_endpoint`
701
+
702
+ - `Storage#presign` results that cannot coerce themselves into a Hash are not
703
+ supported anymore.
704
+
705
+ - The deprecated `Shrine::Plugins::PresignEndpoint::App` constant has been
706
+ removed.
707
+
708
+ * `derivation_endpoint`
709
+
710
+ - The derivation block is now evaluated in context of a `Shrine::Derivation`
711
+ instance.
712
+
713
+ - The derivation block can now return only `File` and `Tempfile` objects.
714
+
715
+ - The `:download_errors` option has been removed, as it's now obsolete.
716
+
717
+ - The `:include_uploaded_file` option has been removed, as it's now obsolete.
718
+
719
+ - The source `UploadedFile` is not passed to the derivation block anymore
720
+ on `download: false`.
721
+
722
+ - The `Derivation#upload` method now closes the uploaded file.
723
+
724
+ - Uploader deleting the uploaded derivative isn't supported anymore (e.g.
725
+ `delete_raw` plugin).
726
+
727
+ - The `derivation.upload` instrumentation event payload now includes only
728
+ `:derivation` key.
729
+
730
+ * `download_endpoint`
731
+
732
+ - Support for legacy `/:storage/:id` URLs has been dropped.
733
+
734
+ - The `:storages` plugin option has been removed.
735
+
736
+ - The `Shrine::Plugins::DownloadEndpoint::App` constant has been removed.
737
+
738
+ * `data_uri`
739
+
740
+ - The deprecated `:filename` plugin option has been removed.
741
+
742
+ - The deprecated `Shrine::Plugins::DataUri::DataFile` constant has been
743
+ removed.
744
+
745
+ * `rack_file`
746
+
747
+ - The deprecated `Shrine::Plugins::RackFile::UploadedFile` constant has been
748
+ removed.
749
+
750
+ - Passing a Rack uploaded file hash to `Shrine#upload` is not supported
751
+ anymore.
752
+
753
+ * `cached_attachment_data`
754
+
755
+ - The `#<name>_cached_data=` model method has been removed.
756
+
757
+ - The `Attacher#read_cached` method has been renamed to
758
+ `Attacher#cached_data`.
759
+
760
+ * `default_url`
761
+
762
+ - Passing a block when loading the plugin is not supported anymore.
763
+
764
+ * `determine_mime_type`
765
+
766
+ - The deprecated `:default` analyzer alias has been removed.
767
+
768
+ - The private `Shrine#mime_type_analyzers` method has been removed.
769
+
770
+ * `store_dimensions`
771
+
772
+ - Failing to extract dimensions now prints out a warning by default.
773
+
774
+ - The private `Shrine#extract_dimensions` and `Shrine#dimensions_analyzers`
775
+ methods have been removed.
776
+
777
+ * `infer_extension`
778
+
779
+ - The `:mini_mime` inferrer is now the default inferrer, which requires the
780
+ `mini_mime` gem.
781
+
782
+ - The private `Shrine#infer_extension` method has been removed.
783
+
784
+ * `validation_helpers`
785
+
786
+ - The width & height validators will now raise an exception if `width` or
787
+ `height` metadata is missing.
788
+
789
+ - Support for regexes has been dropped for MIME type and extension
790
+ validators.
791
+
792
+ * `versions`
793
+
794
+ - The deprecated `:version_names`, `Shrine.version_names` and
795
+ `Shrine.version?` have been removed from `versions` plugin.
796
+
797
+ * `keep_files`
798
+
799
+ - The plugin will now always prevent deletion of both replaced and destroyed
800
+ attachments. The `:replaced` and `:destroyed` options don't have effect
801
+ anymore.
802
+
803
+ * `dynamic_storage`
804
+
805
+ - The `Shrine.dynamic_storages` method has been removed.
806
+
807
+ * `instrumentation`
808
+
809
+ - The `:location`, `:upload_options`, and `:metadata` keys have been removed
810
+ from `:options` in `upload.shrine` event payload.
811
+
812
+ - The `:metadata` key has been removed from `metadata.shrine` event payload.
813
+
814
+ * `default_storage`
815
+
816
+ - Passing `record` & `name` arguments to the storage block has been
817
+ deprecated over evaluating the block in context of the attacher instance.
818
+
819
+ * `sequel`
820
+
821
+ - The `:callbacks` plugin option has been renamed to `:hooks`.
822
+
823
+ [derivatives]: /doc/plugins/derivatives.md#readme
824
+ [versions]: /doc/plugins/versions.md#readme
825
+ [backgrounding]: /doc/plugins/backgrounding.md#readme
826
+ [shrine-memory]: https://github.com/shrinerb/shrine-memory
827
+ [atomic_helpers]: /doc/plugins/atomic_helpers.md#readme
828
+ [attacher]: /doc/attacher.md#readme
829
+ [column]: /doc/plugins/column.md#readme
830
+ [entity]: /doc/plugins/entity.md#readme
831
+ [model]: /doc/plugins/model.md#readme
832
+ [persistence]: /doc/plugins/persistence.md#readme
833
+ [mirroring]: /doc/plugins/mirroring.md#readme
834
+ [form_assign]: /doc/plugins/form_assign.md#readme
835
+ [Upgrading to Shrine 3.x]: /doc/upgrading_to_3.md#readme