shrine 3.0.1 → 3.3.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.
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