shrine 3.0.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -0
  3. data/LICENSE.txt +1 -1
  4. data/README.md +15 -5
  5. data/doc/advantages.md +33 -16
  6. data/doc/attacher.md +2 -2
  7. data/doc/carrierwave.md +78 -34
  8. data/doc/changing_derivatives.md +39 -39
  9. data/doc/design.md +134 -85
  10. data/doc/direct_s3.md +1 -0
  11. data/doc/external/articles.md +57 -45
  12. data/doc/external/extensions.md +41 -35
  13. data/doc/external/misc.md +23 -8
  14. data/doc/getting_started.md +177 -112
  15. data/doc/metadata.md +79 -43
  16. data/doc/multiple_files.md +6 -4
  17. data/doc/paperclip.md +119 -42
  18. data/doc/plugins/activerecord.md +1 -1
  19. data/doc/plugins/add_metadata.md +112 -35
  20. data/doc/plugins/atomic_helpers.md +41 -3
  21. data/doc/plugins/backgrounding.md +12 -2
  22. data/doc/plugins/column.md +36 -7
  23. data/doc/plugins/data_uri.md +2 -2
  24. data/doc/plugins/default_url.md +6 -3
  25. data/doc/plugins/derivation_endpoint.md +26 -28
  26. data/doc/plugins/derivatives.md +238 -171
  27. data/doc/plugins/determine_mime_type.md +2 -2
  28. data/doc/plugins/download_endpoint.md +5 -5
  29. data/doc/plugins/dynamic_storage.md +1 -1
  30. data/doc/plugins/form_assign.md +5 -5
  31. data/doc/plugins/included.md +25 -5
  32. data/doc/plugins/infer_extension.md +11 -2
  33. data/doc/plugins/instrumentation.md +1 -1
  34. data/doc/plugins/metadata_attributes.md +22 -10
  35. data/doc/plugins/mirroring.md +1 -1
  36. data/doc/plugins/persistence.md +11 -1
  37. data/doc/plugins/refresh_metadata.md +5 -4
  38. data/doc/plugins/remote_url.md +8 -3
  39. data/doc/plugins/remove_invalid.md +9 -1
  40. data/doc/plugins/signature.md +11 -2
  41. data/doc/plugins/store_dimensions.md +12 -2
  42. data/doc/plugins/type_predicates.md +96 -0
  43. data/doc/plugins/upload_endpoint.md +7 -11
  44. data/doc/plugins/upload_options.md +1 -1
  45. data/doc/plugins/url_options.md +4 -4
  46. data/doc/plugins/validation.md +14 -4
  47. data/doc/plugins/validation_helpers.md +3 -3
  48. data/doc/plugins/versions.md +7 -7
  49. data/doc/processing.md +290 -127
  50. data/doc/refile.md +39 -18
  51. data/doc/release_notes/2.19.0.md +1 -1
  52. data/doc/release_notes/2.8.0.md +1 -1
  53. data/doc/release_notes/3.0.0.md +1 -1
  54. data/doc/release_notes/3.0.1.md +4 -0
  55. data/doc/release_notes/3.1.0.md +73 -0
  56. data/doc/release_notes/3.2.0.md +96 -0
  57. data/doc/release_notes/3.2.1.md +31 -0
  58. data/doc/release_notes/3.2.2.md +14 -0
  59. data/doc/release_notes/3.3.0.md +105 -0
  60. data/doc/securing_uploads.md +3 -3
  61. data/doc/storage/file_system.md +1 -1
  62. data/doc/storage/memory.md +19 -0
  63. data/doc/storage/s3.md +105 -82
  64. data/doc/testing.md +2 -2
  65. data/doc/upgrading_to_3.md +97 -49
  66. data/doc/validation.md +3 -2
  67. data/lib/shrine.rb +8 -8
  68. data/lib/shrine/attacher.rb +24 -14
  69. data/lib/shrine/attachment.rb +5 -5
  70. data/lib/shrine/plugins.rb +22 -0
  71. data/lib/shrine/plugins/activerecord.rb +1 -1
  72. data/lib/shrine/plugins/add_metadata.rb +18 -7
  73. data/lib/shrine/plugins/backgrounding.rb +2 -2
  74. data/lib/shrine/plugins/default_storage.rb +6 -6
  75. data/lib/shrine/plugins/default_url.rb +1 -1
  76. data/lib/shrine/plugins/derivation_endpoint.rb +12 -7
  77. data/lib/shrine/plugins/derivatives.rb +61 -29
  78. data/lib/shrine/plugins/determine_mime_type.rb +3 -3
  79. data/lib/shrine/plugins/entity.rb +6 -6
  80. data/lib/shrine/plugins/mirroring.rb +8 -8
  81. data/lib/shrine/plugins/model.rb +3 -3
  82. data/lib/shrine/plugins/presign_endpoint.rb +16 -4
  83. data/lib/shrine/plugins/pretty_location.rb +1 -1
  84. data/lib/shrine/plugins/processing.rb +1 -1
  85. data/lib/shrine/plugins/refresh_metadata.rb +2 -2
  86. data/lib/shrine/plugins/remote_url.rb +3 -3
  87. data/lib/shrine/plugins/remove_attachment.rb +5 -0
  88. data/lib/shrine/plugins/remove_invalid.rb +10 -5
  89. data/lib/shrine/plugins/sequel.rb +1 -1
  90. data/lib/shrine/plugins/signature.rb +7 -6
  91. data/lib/shrine/plugins/store_dimensions.rb +22 -11
  92. data/lib/shrine/plugins/type_predicates.rb +113 -0
  93. data/lib/shrine/plugins/upload_endpoint.rb +10 -5
  94. data/lib/shrine/plugins/upload_options.rb +2 -2
  95. data/lib/shrine/plugins/url_options.rb +2 -2
  96. data/lib/shrine/plugins/validation.rb +9 -7
  97. data/lib/shrine/storage/linter.rb +4 -4
  98. data/lib/shrine/storage/memory.rb +5 -3
  99. data/lib/shrine/storage/s3.rb +117 -38
  100. data/lib/shrine/uploaded_file.rb +0 -1
  101. data/lib/shrine/version.rb +2 -2
  102. data/shrine.gemspec +7 -8
  103. metadata +25 -31
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  id: securing-uploads
3
- title: Securing uploads
3
+ title: Securing Uploads
4
4
  ---
5
5
 
6
6
  Shrine does a lot to make your file uploads secure, but there are still a lot
@@ -63,7 +63,7 @@ in the `:max_size` option to reject files that are larger than the specified
63
63
  limit:
64
64
 
65
65
  ```rb
66
- plugin :upload_endpoint, max_size: 100*1024*1024 # 20 MB
66
+ plugin :upload_endpoint, max_size: 100*1024*1024 # 100 MB
67
67
  ```
68
68
 
69
69
  If you're doing direct uploads to Amazon S3 using the `presign_endpoint`
@@ -151,7 +151,7 @@ processing:
151
151
  ```rb
152
152
  class ImageUploader < Shrine
153
153
  # ...
154
- Attacher.derivatives_processor do |original|
154
+ Attacher.derivatives do |original|
155
155
  width, height = Shrine.dimensions(original)
156
156
 
157
157
  fail ImageBombError if width > 5000 || height > 5000
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  id: file-system
3
- title: Shrine::Storage::FileSystem
3
+ title: File System
4
4
  ---
5
5
 
6
6
  The FileSystem storage handles uploads to the filesystem, and it is most
@@ -0,0 +1,19 @@
1
+ ---
2
+ title: Memory
3
+ ---
4
+
5
+ The Memory storage stores uploaded files in memory, which is suitable for
6
+ testing.
7
+
8
+ ```rb
9
+ Shrine.storages[:store] = Shrine::Storage::Memory.new
10
+ ```
11
+
12
+ By default, each storage instance uses a new Hash object for storing files,
13
+ but you can pass your own:
14
+
15
+ ```rb
16
+ my_store = Hash.new
17
+
18
+ Shrine.storages[:store] = Shrine::Storage::Memory.new(my_store)
19
+ ```
@@ -1,31 +1,41 @@
1
1
  ---
2
- title: Shrine::Storage::S3
2
+ title: AWS S3
3
3
  ---
4
4
 
5
- The S3 storage handles uploads to Amazon S3 service, using the [aws-sdk-s3]
5
+ The S3 storage handles uploads to [AWS S3] service (or any s3-compatible
6
+ service such as [DigitalOcean Spaces] or [MinIO]). It requires the [aws-sdk-s3]
6
7
  gem:
7
8
 
8
9
  ```rb
10
+ # Gemfile
9
11
  gem "aws-sdk-s3", "~> 1.14"
10
12
  ```
11
13
 
12
- It can be initialized by providing the bucket name and credentials:
14
+ ## Initialization
15
+
16
+ The storage is initialized by providing your bucket name, region and
17
+ credentials:
13
18
 
14
19
  ```rb
15
20
  require "shrine/storage/s3"
16
21
 
17
22
  s3 = Shrine::Storage::S3.new(
18
23
  bucket: "my-app", # required
24
+ region: "eu-west-1", # required
19
25
  access_key_id: "abc",
20
26
  secret_access_key: "xyz",
21
- region: "eu-west-1",
22
27
  )
23
28
  ```
24
29
 
25
- The core features of this storage require the following AWS permissions:
26
- `s3:ListBucket`, `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject`. If you
27
- have additional upload options configured such as setting object ACLs, then
28
- additional permissions may be required.
30
+ > The storage requires the following AWS S3 permissions:
31
+ >
32
+ > * `s3:ListBucket` for the bucket resource
33
+ > * `s3:GetObject`, `s3:PutObject`, `s3:PutObjectAcl`, `s3:DeleteObject`,
34
+ > `s3:ListMultipartUploadParts` and `s3:AbortMultipartUpload` for the object
35
+ > resources
36
+
37
+ > The `:access_key_id` and `:secret_access_key` options is just one form of
38
+ > authentication, see the [AWS SDK docs][credentials] for more options.
29
39
 
30
40
  The storage exposes the underlying Aws objects:
31
41
 
@@ -45,14 +55,29 @@ s3.object("key") #=> #<Aws::S3::Object>
45
55
 
46
56
  By default, uploaded S3 objects will have private visibility, meaning they can
47
57
  only be accessed via signed expiring URLs generated using your private S3
48
- credentials. If you would like to generate public URLs, you can tell S3 storage
49
- to make uploads public:
58
+ credentials.
50
59
 
51
60
  ```rb
52
- s3 = Shrine::Storage::S3.new(public: true, **s3_options)
61
+ s3 = Shrine::Storage::S3.new(**s3_options)
62
+ s3.upload(io, "key") # uploads with default "private" ACL
63
+ s3.url("key") # https://my-bucket.s3.amazonaws.com/key?X-Amz-Expires=900&X-Amz-Signature=b22d37c37d...
64
+ ```
53
65
 
66
+ If you would like to generate public URLs, you can tell S3 storage to make
67
+ uploads public:
68
+
69
+ ```rb
70
+ s3 = Shrine::Storage::S3.new(public: true, **s3_options)
54
71
  s3.upload(io, "key") # uploads with "public-read" ACL
55
- s3.url("key") # returns public (unsigned) object URL
72
+ s3.url("key") # https://my-bucket.s3.amazonaws.com/key
73
+ ```
74
+
75
+ If you want to make only *some* uploads public, you can conditionally apply the
76
+ `:acl` upload option and `:public` URL option:
77
+
78
+ ```rb
79
+ Shrine.plugin :upload_options, store: -> (io, **) { { acl: "public-read" } }
80
+ Shrine.plugin :url_options, store: -> (io, **) { { public: true } }
56
81
  ```
57
82
 
58
83
  ## Prefix
@@ -80,13 +105,11 @@ You can also generate upload options per upload with the `upload_options`
80
105
  plugin
81
106
 
82
107
  ```rb
83
- class MyUploader < Shrine
84
- plugin :upload_options, store: -> (io, derivative: nil, **) do
85
- if derivative == :thumb
86
- { acl: "public-read" }
87
- else
88
- { acl: "private" }
89
- end
108
+ Shrine.plugin :upload_options, store: -> (io, derivative: nil, **) do
109
+ if derivative == :thumb
110
+ { acl: "public-read" }
111
+ else
112
+ { acl: "private" }
90
113
  end
91
114
  end
92
115
  ```
@@ -97,23 +120,9 @@ or when using the uploader directly
97
120
  uploader.upload(file, upload_options: { acl: "private" })
98
121
  ```
99
122
 
100
- Note that, unlike the `:upload_options` storage option, upload options given on
101
- the uploader level won't be forwarded for generating presigns, since presigns
102
- are generated using the storage directly.
103
-
104
- ## URL options
105
-
106
- Other than [`:host`](#url-host) and [`:public`](#public-uploads) URL options,
107
- all additional options are forwarded to [`Aws::S3::Object#presigned_url`].
108
-
109
- ```rb
110
- s3.url(
111
- expires_in: 15,
112
- response_content_disposition: ContentDisposition.attachment("my-filename"),
113
- response_content_type: "foo/bar",
114
- # ...
115
- )
116
- ```
123
+ > Unlike the `:upload_options` storage option, upload options given on
124
+ the uploader level won't be forwarded for generating presigns, since presigns
125
+ are generated using the storage directly.
117
126
 
118
127
  ## URL Host
119
128
 
@@ -133,12 +142,14 @@ s3.url("image.jpg", host: "https://your-s3-host.com/prefix/") # needs to end wit
133
142
  ```
134
143
 
135
144
  To have the `:host` option passed automatically for every URL, use the
136
- `url_options` plugin.
145
+ `url_options` plugin:
137
146
 
138
147
  ```rb
139
148
  plugin :url_options, store: { host: "http://abc123.cloudfront.net" }
140
149
  ```
141
150
 
151
+ ### Signer
152
+
142
153
  If you would like to [serve private content via CloudFront], you need to sign
143
154
  the object URLs with a special signer, such as [`Aws::CloudFront::UrlSigner`]
144
155
  provided by the `aws-sdk-cloudfront` gem. The S3 storage initializer accepts a
@@ -157,13 +168,27 @@ Shrine::Storage::S3.new(signer: signer.method(:signed_url))
157
168
  Shrine::Storage::S3.new(signer: -> (url, **options) { signer.signed_url(url, **options) })
158
169
  ```
159
170
 
171
+ ## URL options
172
+
173
+ Other than `:host` and `:public` URL options, all additional `S3#url` options
174
+ are forwarded to [`Aws::S3::Object#presigned_url`].
175
+
176
+ ```rb
177
+ s3.url(
178
+ expires_in: 15,
179
+ response_content_disposition: ContentDisposition.attachment("my-filename"),
180
+ response_content_type: "foo/bar",
181
+ # ...
182
+ )
183
+ ```
184
+
160
185
  ## Presigns
161
186
 
162
- The `#presign` method can be used for generating paramters for direct uploads
163
- to Amazon S3:
187
+ The `S3#presign` method can be used for generating parameters for direct upload
188
+ to S3:
164
189
 
165
190
  ```rb
166
- s3.presign("/path/to/file") #=>
191
+ s3.presign("key") #=>
167
192
  # {
168
193
  # url: "https://my-bucket.s3.amazonaws.com/...",
169
194
  # fields: { ... }, # blank for PUT presigns
@@ -172,11 +197,25 @@ s3.presign("/path/to/file") #=>
172
197
  # }
173
198
  ```
174
199
 
175
- Additional presign options can be given in three places:
200
+ By default, parameters for a POST upload is generated, but you can also
201
+ generate PUT upload parameters:
202
+
203
+ ```rb
204
+ s3.presign("key", method: :put)
205
+ ```
176
206
 
177
- * in `Storage::S3#presign` by forwarding options
178
- * in `:upload_options` option on this storage
179
- * in `presign_endpoint` plugin through `:presign_options`
207
+ Any additional options are forwarded to [`Aws::S3::Object#presigned_post`]
208
+ (for POST uploads) and [`Aws::S3::Object#presigned_url`] (for PUT uploads).
209
+
210
+ ```rb
211
+ s3.presign("key", method: :put, content_disposition: "attachment; filename=my-file.txt") #=>
212
+ # {
213
+ # url: "https://my-bucket.s3.amazonaws.com/...",
214
+ # fields: {},
215
+ # headers: { "Content-Disposition" => "attachment; filename=my-file.txt" },
216
+ # method :put,
217
+ # }
218
+ ```
180
219
 
181
220
  ## Large files
182
221
 
@@ -184,33 +223,24 @@ The aws-sdk-s3 gem has the ability to automatically use multipart upload/copy
184
223
  for larger files, splitting the file into multiple chunks and uploading/copying
185
224
  them in parallel.
186
225
 
187
- By default any files that are uploaded will use the multipart upload if they're
188
- larger than 15MB, and any files that are copied will use the multipart copy if
189
- they're larger than 150MB, but you can change the thresholds via
190
- `:multipart_threshold`.
191
-
192
- ```rb
193
- thresholds = { upload: 30*1024*1024, copy: 200*1024*1024 }
194
- Shrine::Storage::S3.new(multipart_threshold: thresholds, **s3_options)
195
- ```
196
-
197
- If you want to change how many threads aws-sdk-s3 will use for multipart
198
- upload/copy, you can use the `upload_options` plugin to specify
199
- `:thread_count`.
226
+ By default, multipart upload will be used for files larger than 15MB, and
227
+ multipart copy for files larger than 100MB, but you can change the thresholds
228
+ via `:multipart_threshold`:
200
229
 
201
230
  ```rb
202
- plugin :upload_options, store: -> (io, context) do
203
- { thread_count: 5 }
204
- end
231
+ Shrine::Storage::S3.new(
232
+ multipart_threshold: { upload: 30*1024*1024, copy: 200*1024*1024 },
233
+ **s3_options,
234
+ )
205
235
  ```
206
236
 
207
237
  ## Encryption
208
238
 
209
- The easiest way to use server-side encryption for uploaded S3 objects is to
239
+ The easiest way to use **server-side** encryption for uploaded S3 objects is to
210
240
  configure default encryption for your S3 bucket. Alternatively, you can pass
211
241
  server-side encryption parameters to the API calls.
212
242
 
213
- The `#upload` method accepts `:sse_*` options:
243
+ The `S3#upload` method accepts `:sse_*` options:
214
244
 
215
245
  ```rb
216
246
  s3.upload(io, "key", sse_customer_algorithm: "AES256",
@@ -219,7 +249,7 @@ s3.upload(io, "key", sse_customer_algorithm: "AES256",
219
249
  ssekms_key_id: "key_id")
220
250
  ```
221
251
 
222
- The `#presign` method accepts `:server_side_encryption_*` options for POST
252
+ The `S3#presign` method accepts `:server_side_encryption_*` options for POST
223
253
  presigns, and the same `:sse_*` options as above for PUT presigns.
224
254
 
225
255
  ```rb
@@ -232,24 +262,19 @@ When downloading encrypted S3 objects, the same server-side encryption
232
262
  parameters need to be passed in.
233
263
 
234
264
  ```rb
235
- s3.download("key", sse_customer_algorithm: "AES256",
236
- sse_customer_key: "secret_key",
237
- sse_customer_key_md5: "secret_key_md5")
238
-
239
265
  s3.open("key", sse_customer_algorithm: "AES256",
240
266
  sse_customer_key: "secret_key",
241
267
  sse_customer_key_md5: "secret_key_md5")
242
268
  ```
243
269
 
244
- If you want to use client-side encryption instead, you can instantiate the
245
- storage with an `Aws::S3::Encryption::Client` instance.
270
+ **Client-side** encryption is supported as well:
246
271
 
247
272
  ```rb
248
- client = Aws::S3::Encryption::Client.new(
249
- kms_key_id: "alias/my-key"
250
- )
273
+ encryption_client = Aws::S3::EncryptionV2::Client.new(...)
274
+ s3 = Shrine::Storage::S3.new(client: encryption_client, **other_options)
251
275
 
252
- Shrine::Storage::S3(client: client, bucket: "my-bucket")
276
+ s3.upload(io, "key") # encrypts on upload
277
+ s3.open("key") # decrypts on download
253
278
  ```
254
279
 
255
280
  ## Accelerate endpoint
@@ -283,20 +308,18 @@ Alternatively you can periodically call the `#clear!` method:
283
308
  s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 }
284
309
  ```
285
310
 
286
- ## Request Rate and Performance Guidelines
287
-
288
- Amazon S3 automatically scales to high request rates. For example, your
289
- application can achieve at least 3,500 PUT/POST/DELETE and 5,500 GET requests
290
- per second per prefix in a bucket (a prefix is a top-level "directory" in the
291
- bucket). If your app needs to support higher request rates to S3 than that, you
292
- can scale exponentially by using more prefixes.
293
-
311
+ [AWS S3]: https://aws.amazon.com/s3/
312
+ [MinIO]: https://min.io/
313
+ [DigitalOcean Spaces]: https://www.digitalocean.com/products/spaces/
314
+ [aws-sdk-s3]: https://rubygems.org/gems/aws-sdk-s3
294
315
  [uploading]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#put-instance_method
295
316
  [copying]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#copy_from-instance_method
296
317
  [presigning]: http://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
297
318
  [`Aws::S3::Object#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
298
- [aws-sdk-s3]: https://github.com/aws/aws-sdk-ruby/tree/master/gems/aws-sdk-s3
299
319
  [Transfer Acceleration]: http://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration.html
300
320
  [object lifecycle]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
301
321
  [serve private content via CloudFront]: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/PrivateContent.html
302
322
  [`Aws::CloudFront::UrlSigner`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/CloudFront/UrlSigner.html
323
+ [credentials]: https://docs.aws.amazon.com/sdk-for-ruby/v3/developer-guide/setup-config.html
324
+ [`Aws::S3::Object#presigned_post`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_post-instance_method
325
+ [`Aws::S3::Object#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
@@ -119,7 +119,7 @@ module TestData
119
119
  small: uploaded_image,
120
120
  )
121
121
 
122
- attacher.column_data
122
+ attacher.column_data # or attacher.data in case of postgres jsonb column
123
123
  end
124
124
 
125
125
  def uploaded_image
@@ -128,7 +128,7 @@ module TestData
128
128
  # for performance we skip metadata extraction and assign test metadata
129
129
  uploaded_file = Shrine.upload(file, :store, metadata: false)
130
130
  uploaded_file.metadata.merge!(
131
- "size" => file.size,
131
+ "size" => File.size(file.path),
132
132
  "mime_type" => "image/jpeg",
133
133
  "filename" => "test.jpg",
134
134
  )
@@ -283,8 +283,9 @@ attacher.destroy_background # calls destroy block
283
283
  ## Versions
284
284
 
285
285
  The `versions`, `processing`, `recache`, and `delete_raw` plugins have been
286
- deprecated in favour of the new [`derivatives`][derivatives] plugin. Let's
287
- assume you have the following `versions` code:
286
+ deprecated in favour of the new **[`derivatives`][derivatives]** plugin.
287
+
288
+ Let's assume you have the following `versions` configuration:
288
289
 
289
290
  ```rb
290
291
  class ImageUploader < Shrine
@@ -307,27 +308,35 @@ class ImageUploader < Shrine
307
308
  end
308
309
  end
309
310
  ```
311
+
312
+ When an attached file is promoted to permanent storage, the versions would
313
+ automatically get generated:
314
+
310
315
  ```rb
311
316
  photo = Photo.new(photo_params)
312
317
 
313
318
  if photo.valid?
314
- photo.save # automatically calls processing block
319
+ photo.save # generates versions on promotion
315
320
  # ...
316
321
  else
317
322
  # ...
318
323
  end
319
324
  ```
320
325
 
321
- With `derivatives` it becomes this:
326
+ With `derivatives`, the original file is automatically downloaded and retained
327
+ during processing, so the setup is simpler:
322
328
 
323
329
  ```rb
324
- Shrine.plugin :derivatives, versions_compatibility: true # handle versions column format
330
+ Shrine.plugin :derivatives,
331
+ create_on_promote: true, # automatically create derivatives on promotion
332
+ versions_compatibility: true # handle versions column format
325
333
  ```
326
334
  ```rb
327
335
  class ImageUploader < Shrine
328
- Attacher.derivatives_processor do |original|
336
+ Attacher.derivatives do |original|
329
337
  magick = ImageProcessing::MiniMagick.source(original)
330
338
 
339
+ # the :original file should NOT be included anymore
331
340
  {
332
341
  large: magick.resize_to_limit!(800, 800),
333
342
  medium: magick.resize_to_limit!(500, 500),
@@ -340,28 +349,13 @@ end
340
349
  photo = Photo.new(photo_params)
341
350
 
342
351
  if photo.valid?
343
- photo.image_derivatives! if photo.image_changed? # create derivatives
344
- photo.save # automatically calls processing block
352
+ photo.save # creates derivatives on promotion
345
353
  # ...
346
354
  else
347
355
  # ...
348
356
  end
349
357
  ```
350
358
 
351
- If you have multiple places where you need to generate derivatives, and want it
352
- to happen automatically like it did with the `versions` plugin, you can
353
- override `Attacher#promote` to call `Attacher#create_derivatives` before
354
- promotion:
355
-
356
- ```rb
357
- class Shrine::Attacher
358
- def promote(*)
359
- create_derivatives
360
- super
361
- end
362
- end
363
- ```
364
-
365
359
  ### Accessing derivatives
366
360
 
367
361
  The derivative URLs are accessed in the same way as versions:
@@ -370,7 +364,7 @@ The derivative URLs are accessed in the same way as versions:
370
364
  photo.image_url(:small)
371
365
  ```
372
366
 
373
- But the derivatives themselves are accessed differently:
367
+ But the files themselves are accessed differently:
374
368
 
375
369
  ```rb
376
370
  # versions
@@ -424,8 +418,8 @@ database column in different formats:
424
418
 
425
419
  The `:versions_compatibility` flag to the `derivatives` plugin enables it to
426
420
  read the `versions` format, which aids in transition. Once the `derivatives`
427
- plugin has been deployed to production, you can switch existing records to the
428
- new column format:
421
+ plugin has been deployed to production, you can update existing records with
422
+ the new column format:
429
423
 
430
424
  ```rb
431
425
  Photo.find_each do |photo|
@@ -465,11 +459,11 @@ creating another derivatives processor that you will trigger in the controller:
465
459
 
466
460
  ```rb
467
461
  class ImageUploader < Shrine
468
- Attacher.derivatives_processor do |original|
462
+ Attacher.derivatives do |original|
469
463
  # this will be triggered in the background job
470
464
  end
471
465
 
472
- Attacher.derivatives_processor :foreground do |original|
466
+ Attacher.derivatives :foreground do |original|
473
467
  # this will be triggered in the controller
474
468
  end
475
469
  end
@@ -486,48 +480,77 @@ else
486
480
  end
487
481
  ```
488
482
 
489
- #### Parallelize
483
+ ### Default URL
490
484
 
491
- The `parallelize` plugin has been removed. The `derivatives` plugin is
492
- thread-safe, so you can parallelize uploading processed files manually:
485
+ If you were using the `default_url` plugin, the `Attacher.default_url` now
486
+ receives a `:derivative` option:
493
487
 
494
488
  ```rb
495
- # Gemfile
496
- gem "concurrent-ruby"
489
+ Attacher.default_url do |derivative: nil, **|
490
+ "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
491
+ end
497
492
  ```
498
- ```rb
499
- require "concurrent"
500
493
 
501
- derivatives = attacher.process_derivatives
494
+ #### Fallback to original
502
495
 
503
- tasks = derivatives.map do |name, file|
504
- Concurrent::Promises.future(name, file) do |name, file|
505
- attacher.add_derivative(name, file)
506
- end
507
- end
496
+ With the `versions` plugin, a missing version URL would automatically fall back
497
+ to the original file. The `derivatives` plugin has no such fallback, but you
498
+ can configure it manually:
508
499
 
509
- Concurrent::Promises.zip(*tasks).wait!
500
+ ```rb
501
+ Attacher.default_url do |derivative: nil, **|
502
+ file&.url if derivative
503
+ end
510
504
  ```
511
505
 
512
- #### Default URL
506
+ #### Fallback to version
513
507
 
514
- The `derivatives` plugin integrates with the `default_url` plugin:
508
+ The `versions` plugin had the ability to fall back missing version URL to
509
+ another version that already exists. The `derivatives` plugin doesn't have this
510
+ built in, but you can implement it as follows:
515
511
 
516
512
  ```rb
513
+ DERIVATIVE_FALLBACKS = { foo: :bar, ... }
514
+
517
515
  Attacher.default_url do |derivative: nil, **|
518
- "https://my-app.com/fallbacks/#{derivative}.jpg" if derivative
516
+ derivatives[DERIVATIVE_FALLBACKS[derivative]]&.url if derivative
517
+ end
518
+ ```
519
+
520
+ ### Location
521
+
522
+ The `Shrine#generate_location` method will now receive a `:derivative`
523
+ parameter instead of `:version`:
524
+
525
+ ```rb
526
+ class MyUploader < Shrine
527
+ def generate_location(io, derivative: nil, **)
528
+ derivative #=> :large, :medium, :small, ...
529
+ # ...
530
+ end
519
531
  end
520
532
  ```
521
533
 
522
- However, it doesn't implement any other URL fallbacks that the `versions`
523
- plugin has for missing derivatives.
534
+ ### Overwriting original
535
+
536
+ With the `derivatives` plugin, saving processed files separately from the
537
+ original file, so the original file is automatically kept. This means it's not
538
+ possible anymore to overwrite the original file as part of processing.
539
+
540
+ However, **it's highly recommended to always keep the original file**, even if
541
+ you don't plan to use it. That way, if there is ever a need to reprocess
542
+ derivatives, you have the original file to use as a base.
543
+
544
+ That being said, if you still want to overwrite the original file, [this
545
+ thread][overwriting original] has some tips.
524
546
 
525
547
  ## Other
526
548
 
527
549
  ### Processing
528
550
 
529
551
  The `processing` plugin has been deprecated over the new
530
- [`derivatives`][derivatives] plugin. If you were modifying the original file:
552
+ [`derivatives`][derivatives] plugin. If you were previously replacing the
553
+ original file:
531
554
 
532
555
  ```rb
533
556
  class MyUploader < Shrine
@@ -547,7 +570,7 @@ you should now add the processed file as a derivative:
547
570
  class MyUploader < Shrine
548
571
  plugin :derivatives
549
572
 
550
- Attacher.derivatives_processor do |original|
573
+ Attacher.derivatives do |original|
551
574
  magick = ImageProcessing::MiniMagick.source(original)
552
575
 
553
576
  { normalized: magick.resize_to_limit!(1600, 1600) }
@@ -555,6 +578,30 @@ class MyUploader < Shrine
555
578
  end
556
579
  ```
557
580
 
581
+ ### Parallelize
582
+
583
+ The `parallelize` plugin has been removed. With `derivatives` plugin you can
584
+ parallelize uploading processed files manually:
585
+
586
+ ```rb
587
+ # Gemfile
588
+ gem "concurrent-ruby"
589
+ ```
590
+ ```rb
591
+ require "concurrent"
592
+
593
+ attacher = photo.image_attacher
594
+ derivatives = attacher.process_derivatives
595
+
596
+ tasks = derivatives.map do |name, file|
597
+ Concurrent::Promises.future(name, file) do |name, file|
598
+ attacher.add_derivative(name, file)
599
+ end
600
+ end
601
+
602
+ Concurrent::Promises.zip(*tasks).wait!
603
+ ```
604
+
558
605
  ### Logging
559
606
 
560
607
  The `logging` plugin has been removed in favour of the
@@ -602,7 +649,7 @@ attacher.copy(other_attacher)
602
649
  with
603
650
 
604
651
  ```rb
605
- attacher.attach other_attacher.file
652
+ attacher.set attacher.upload(other_attacher.file)
606
653
  attacher.add_derivatives other_attacher.derivatives # if using derivatives
607
654
  ```
608
655
 
@@ -660,3 +707,4 @@ end
660
707
  [derivatives]: https://shrinerb.com/docs/plugins/derivatives
661
708
  [instrumentation]: https://shrinerb.com/docs/plugins/instrumentation
662
709
  [mirroring]: https://shrinerb.com/docs/plugins/mirroring
710
+ [overwriting original]: https://discourse.shrinerb.com/t/keep-original-file-after-processing/50/4