shrine 3.4.0 → 3.10.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +96 -0
  3. data/README.md +11 -11
  4. data/doc/advantages.md +1 -1
  5. data/doc/attacher.md +1 -1
  6. data/doc/carrierwave.md +2 -2
  7. data/doc/changing_derivatives.md +2 -1
  8. data/doc/changing_location.md +19 -6
  9. data/doc/design.md +4 -4
  10. data/doc/external/articles.md +16 -16
  11. data/doc/external/extensions.md +1 -1
  12. data/doc/getting_started.md +73 -30
  13. data/doc/multiple_files.md +57 -22
  14. data/doc/plugins/activerecord.md +91 -0
  15. data/doc/plugins/backgrounding.md +30 -4
  16. data/doc/plugins/data_uri.md +15 -0
  17. data/doc/plugins/derivation_endpoint.md +39 -1
  18. data/doc/plugins/derivatives.md +13 -2
  19. data/doc/plugins/download_endpoint.md +16 -4
  20. data/doc/plugins/dynamic_storage.md +15 -0
  21. data/doc/plugins/fallback_storage.md +90 -0
  22. data/doc/plugins/keep_files.md +6 -4
  23. data/doc/plugins/rack_response.md +9 -0
  24. data/doc/plugins/refresh_metadata.md +20 -0
  25. data/doc/plugins/sequel.md +57 -0
  26. data/doc/plugins/signature.md +8 -6
  27. data/doc/plugins/tempfile.md +14 -6
  28. data/doc/plugins/upload_options.md +12 -0
  29. data/doc/plugins/url_options.md +12 -0
  30. data/doc/plugins/validation_helpers.md +1 -1
  31. data/doc/processing.md +5 -3
  32. data/doc/refile.md +3 -3
  33. data/doc/release_notes/2.1.0.md +1 -1
  34. data/doc/release_notes/3.10.0.md +30 -0
  35. data/doc/release_notes/3.5.0.md +63 -0
  36. data/doc/release_notes/3.6.0.md +23 -0
  37. data/doc/release_notes/3.7.0.md +75 -0
  38. data/doc/release_notes/3.7.1.md +31 -0
  39. data/doc/release_notes/3.8.0.md +37 -0
  40. data/doc/release_notes/3.9.0.md +42 -0
  41. data/doc/retrieving_uploads.md +1 -1
  42. data/doc/storage/s3.md +10 -0
  43. data/doc/testing.md +45 -17
  44. data/doc/upgrading_to_3.md +3 -5
  45. data/lib/shrine/attacher.rb +41 -29
  46. data/lib/shrine/attachment.rb +2 -2
  47. data/lib/shrine/plugins/_persistence.rb +1 -1
  48. data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
  49. data/lib/shrine/plugins/activerecord.rb +25 -4
  50. data/lib/shrine/plugins/add_metadata.rb +2 -4
  51. data/lib/shrine/plugins/atomic_helpers.rb +7 -7
  52. data/lib/shrine/plugins/backgrounding.rb +10 -10
  53. data/lib/shrine/plugins/column.rb +7 -5
  54. data/lib/shrine/plugins/data_uri.rb +28 -3
  55. data/lib/shrine/plugins/default_url.rb +4 -4
  56. data/lib/shrine/plugins/delete_raw.rb +2 -2
  57. data/lib/shrine/plugins/derivation_endpoint.rb +65 -59
  58. data/lib/shrine/plugins/derivatives.rb +35 -21
  59. data/lib/shrine/plugins/download_endpoint.rb +72 -11
  60. data/lib/shrine/plugins/entity.rb +15 -9
  61. data/lib/shrine/plugins/fallback_storage.rb +51 -0
  62. data/lib/shrine/plugins/infer_extension.rb +5 -1
  63. data/lib/shrine/plugins/instrumentation.rb +13 -9
  64. data/lib/shrine/plugins/metadata_attributes.rb +1 -1
  65. data/lib/shrine/plugins/mirroring.rb +10 -10
  66. data/lib/shrine/plugins/model.rb +12 -10
  67. data/lib/shrine/plugins/presign_endpoint.rb +13 -10
  68. data/lib/shrine/plugins/pretty_location.rb +2 -2
  69. data/lib/shrine/plugins/processing.rb +3 -3
  70. data/lib/shrine/plugins/rack_file.rb +2 -2
  71. data/lib/shrine/plugins/rack_response.rb +12 -6
  72. data/lib/shrine/plugins/refresh_metadata.rb +6 -6
  73. data/lib/shrine/plugins/remote_url.rb +4 -4
  74. data/lib/shrine/plugins/remove_attachment.rb +2 -0
  75. data/lib/shrine/plugins/remove_invalid.rb +4 -0
  76. data/lib/shrine/plugins/restore_cached_data.rb +3 -3
  77. data/lib/shrine/plugins/sequel.rb +15 -1
  78. data/lib/shrine/plugins/signature.rb +2 -2
  79. data/lib/shrine/plugins/store_dimensions.rb +2 -2
  80. data/lib/shrine/plugins/tempfile.rb +0 -2
  81. data/lib/shrine/plugins/upload_endpoint.rb +7 -5
  82. data/lib/shrine/plugins/upload_options.rb +18 -4
  83. data/lib/shrine/plugins/url_options.rb +17 -3
  84. data/lib/shrine/plugins/validation.rb +8 -8
  85. data/lib/shrine/plugins/validation_helpers.rb +2 -2
  86. data/lib/shrine/plugins/versions.rb +10 -10
  87. data/lib/shrine/plugins.rb +6 -14
  88. data/lib/shrine/storage/file_system.rb +15 -19
  89. data/lib/shrine/storage/linter.rb +8 -8
  90. data/lib/shrine/storage/memory.rb +1 -3
  91. data/lib/shrine/storage/s3.rb +60 -40
  92. data/lib/shrine/uploaded_file.rb +23 -19
  93. data/lib/shrine/version.rb +1 -1
  94. data/lib/shrine.rb +24 -20
  95. data/shrine.gemspec +10 -8
  96. metadata +52 -26
@@ -2,10 +2,11 @@
2
2
  title: Keep Files
3
3
  ---
4
4
 
5
- The [`keep_files`][keep_files] plugin prevents file deletion when the attacher
6
- is about to destroy currently attached or previously attached file. This
7
- functionality is useful when implementing soft deletes, versioning, or in
8
- general any scenario where you need to track history.
5
+ The [`keep_files`][keep_files] plugin prevents the attached file (and any of
6
+ its [derivatives]) from being deleted when the attachment would normally be
7
+ destroyed, which happens when the attachment is removed/replaced, or when the
8
+ record is deleted. This functionality is useful when implementing soft deletes,
9
+ versioning, or in general any scenario where you need to keep history.
9
10
 
10
11
  ```rb
11
12
  plugin :keep_files
@@ -17,3 +18,4 @@ photo.image.exists? #=> true
17
18
  ```
18
19
 
19
20
  [keep_files]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/keep_files.rb
21
+ [derivatives]: https://shrinerb.com/docs/plugins/derivatives
@@ -95,6 +95,15 @@ headers["Content-Range"] #=> "bytes 100-200/1000"
95
95
  body # partial content
96
96
  ```
97
97
 
98
+ ## ETag
99
+
100
+ A custom ETag value can be provided via the `:etag` option:
101
+
102
+ ```rb
103
+ response = uploaded_file.to_rack_response(etag: "my-custom-etag")
104
+ response[1]["ETag"] #=> "my-custom-etag"
105
+ ```
106
+
98
107
  ## Download options
99
108
 
100
109
  The `#to_rack_response` method will automatically open the `UploadedFile` if it
@@ -69,5 +69,25 @@ Any options passed in will be forwarded to metadata extraction:
69
69
  uploaded_file.refresh_metadata!(foo: "bar") # passes `{ foo: "bar" }` options to metadata extraction
70
70
  ```
71
71
 
72
+ ## Replacing Metadata
73
+
74
+ By default the `#refresh_metadata!` method will merge the results into any existing metadata.
75
+
76
+ ```rb
77
+ uploaded_file.metadata["custom"] = "custom value"
78
+ uploaded_file.refresh_metadata!
79
+ uploaded_file.metadata
80
+ # returns {"filename"=>"example.jpg", "size"=>1024, "mime_type"=>"image/jpeg", "custom"=>"custom value"}
81
+ ```
82
+
83
+ Passing `replace: true` will instead fully overwrite the existing metadata with the new metadata.
84
+
85
+ ```rb
86
+ uploaded_file.metadata["custom"] = "custom value"
87
+ uploaded_file.refresh_metadata!(replace: true)
88
+ uploaded_file.metadata
89
+ # returns {"filename"=>"example.jpg", "size"=>1024, "mime_type"=>"image/jpeg"}
90
+ ```
91
+
72
92
  [refresh_metadata]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/refresh_metadata.rb
73
93
  [model]: https://shrinerb.com/docs/plugins/model
@@ -100,6 +100,63 @@ set `:hooks` to `false`:
100
100
  plugin :sequel, hooks: false
101
101
  ```
102
102
 
103
+ #### Duplicating records
104
+
105
+ Since a record being created can't yet have a confirmed attachment of its own
106
+ to safely replace, Shrine never deletes the previous file when the attachment
107
+ changes as part of *creating* a record, only when *updating* one.
108
+
109
+ Note that Sequel's `#dup`/`#clone`, unlike Active Record's, don't reset the
110
+ primary key or persistence state — a duplicated record still refers to the
111
+ *same* row, so saving it just updates that row rather than inserting a new
112
+ one. There's only ever one row here, so there's nothing to protect:
113
+
114
+ ```rb
115
+ photo = Photo.create(image: file)
116
+ photo2 = photo.dup # `photo2` refers to the same row as `photo`
117
+
118
+ photo2.update(image: new_file)
119
+ photo.image.exists? #=> false (the row now has `new_file`, so this is expected)
120
+ ```
121
+
122
+ The common way to duplicate a record in Sequel is instead to construct a new
123
+ one from the original's values, which *does* produce a genuinely new,
124
+ unpersisted row, and so *is* protected:
125
+
126
+ ```rb
127
+ photo2 = Photo.new(photo.values.except(:id))
128
+
129
+ photo2.update(image: new_file)
130
+ photo.image.exists? #=> true (not affected)
131
+ ```
132
+
133
+ Keep in mind this only protects against *replacing* the attachment on create.
134
+ As long as `photo` and `photo2` continue to reference the same underlying
135
+ file (i.e. `photo2` is saved without ever changing its attachment),
136
+ destroying either record will still delete the file the other one
137
+ references, since Shrine has no way of knowing the file is shared:
138
+
139
+ ```rb
140
+ photo2.save # still references the same file as `photo`
141
+
142
+ photo2.destroy
143
+ photo.image.exists? #=> false
144
+ ```
145
+
146
+ If you want `photo2` to have its own independent copy of the file from the
147
+ start, so that destroying either record is also safe, upload a new copy
148
+ explicitly:
149
+
150
+ ```rb
151
+ photo2 = Photo.new(photo.values.reject { |k, _| k == :id })
152
+ photo2.image_attacher.set(nil)
153
+ photo2.image_attacher.attach(photo.image, storage: photo.image.storage_key)
154
+ photo2.save
155
+
156
+ photo2.destroy # no longer affects `photo`
157
+ photo.image.exists? #=> true
158
+ ```
159
+
103
160
  ### Validations
104
161
 
105
162
  If you're using the [`validation`][validation] plugin, the attachment module
@@ -78,15 +78,17 @@ plugin :signature
78
78
  Calculating signature will trigger a `signature.shrine` event with the
79
79
  following payload:
80
80
 
81
- | Key | Description |
82
- | :-- | :---- |
83
- | `:io` | The IO object |
84
- | `:uploader` | The uploader class that sent the event |
81
+ | Key | Description |
82
+ | :-- | :---- |
83
+ | `:algorithm` | The hashing algorithm used |
84
+ | `:format` | The encoding format |
85
+ | `:io` | The IO object |
86
+ | `:uploader` | The uploader class that sent the event |
85
87
 
86
88
  A default log subscriber is added as well which logs these events:
87
89
 
88
90
  ```
89
- MIME Type (33ms) – {:io=>StringIO, :uploader=>Shrine}
91
+ Signature (1ms) – {io: StringIO, algorithm: :md5, format: :hex, uploader: Shrine}
90
92
  ```
91
93
 
92
94
  You can also use your own log subscriber:
@@ -97,7 +99,7 @@ plugin :signature, log_subscriber: -> (event) {
97
99
  }
98
100
  ```
99
101
  ```
100
- {"name":"signature","duration":24,"io":"#<StringIO:0x00007fb7c5b08b80>","uploader":"Shrine"}
102
+ {"name":"signature","duration":24,"io":"#<StringIO:0x00007fb7c5b08b80>","algorithm":"sha512","format":"hex","uploader":"Shrine"}
101
103
  ```
102
104
 
103
105
  Or disable logging altogether:
@@ -9,14 +9,22 @@ uploaded file on disk.
9
9
  Shrine.plugin :tempfile
10
10
  ```
11
11
 
12
- The plugin provides the `UploadedFile#tempfile` method, which when called on an
13
- open uploaded file will return a copy of its content on disk. The first time
14
- the method is called the file content will cached into a temporary file and
15
- returned. On any subsequent method calls the cached temporary file will be
16
- returned directly. The temporary file is deleted when the uploaded file is
17
- closed.
12
+ The plugin provides the `UploadedFile#tempfile` method, which returns a copy
13
+ of the uploaded file's content on disk. The first time the method is called
14
+ the file content will be downloaded into a temporary file and returned. On
15
+ any subsequent method calls the cached temporary file will be returned
16
+ directly. If the uploaded file is currently open, its tempfile is deleted
17
+ when the uploaded file is closed; otherwise it's deleted whenever it becomes
18
+ unreachable and is garbage collected (so it's still recommended to close the
19
+ uploaded file when you're done with it, to have the tempfile cleaned up
20
+ deterministically).
18
21
 
19
22
  ```rb
23
+ uploaded_file.tempfile #=> #<Tempfile:...> (file is downloaded and cached)
24
+ uploaded_file.tempfile #=> #<Tempfile:...> (cache is returned)
25
+
26
+ # OR
27
+
20
28
  uploaded_file.open do
21
29
  # ...
22
30
  uploaded_file.tempfile #=> #<Tempfile:...> (file is cached)
@@ -29,4 +29,16 @@ the uploader.
29
29
  uploader.upload(file, upload_options: { acl: "public-read" })
30
30
  ```
31
31
 
32
+ If storage keys are generated dynamically (e.g. via the
33
+ [`dynamic_storage`][dynamic_storage] plugin), it's not possible to list every
34
+ storage key upfront. In that case you can use a `Regexp` instead, which will
35
+ be matched against the storage key:
36
+
37
+ ```rb
38
+ plugin :upload_options, /_store\z/ => { acl: "private" }
39
+ ```
40
+
41
+ An exact storage key match always takes precedence over a `Regexp` match.
42
+
32
43
  [upload_options]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/upload_options.rb
44
+ [dynamic_storage]: https://shrinerb.com/docs/plugins/dynamic_storage
@@ -24,4 +24,16 @@ In both cases the default options are merged with options passed to
24
24
  `UploadedFile#url`, and the latter will always have precedence over default
25
25
  options.
26
26
 
27
+ If storage keys are generated dynamically (e.g. via the
28
+ [`dynamic_storage`][dynamic_storage] plugin), it's not possible to list every
29
+ storage key upfront. In that case you can use a `Regexp` instead, which will
30
+ be matched against the storage key:
31
+
32
+ ```rb
33
+ plugin :url_options, /_store\z/ => { expires_in: 24*60*60 }
34
+ ```
35
+
36
+ An exact storage key match always takes precedence over a `Regexp` match.
37
+
27
38
  [url_options]: https://github.com/shrinerb/shrine/blob/master/lib/shrine/plugins/url_options.rb
39
+ [dynamic_storage]: https://shrinerb.com/docs/plugins/dynamic_storage
@@ -10,7 +10,7 @@ plugin :validation_helpers
10
10
 
11
11
  Attacher.validate do
12
12
  validate_mime_type %w[image/jpeg image/png image/webp]
13
- validate_max_size 5*1024*1024
13
+ validate_max_size 5*1024*1024 # bytes
14
14
  # ...
15
15
  end
16
16
  ```
data/doc/processing.md CHANGED
@@ -18,7 +18,8 @@ $ brew install imagemagick
18
18
  ```
19
19
  ```rb
20
20
  # Gemfile
21
- gem "image_processing", "~> 1.8"
21
+ gem "image_processing", "~> 2.0"
22
+ gem "mini_magick", "~> 5.0"
22
23
  ```
23
24
  ```rb
24
25
  require "image_processing/mini_magick"
@@ -405,7 +406,7 @@ Shrine integration, the ImageProcessing gem that we saw earlier is a completely
405
406
  generic gem.
406
407
 
407
408
  To demonstrate, here is an example of transcoding videos using
408
- [streamio-ffmpeg]:
409
+ [streamio-ffmpeg][streamio-ffmpeg]:
409
410
 
410
411
  ```rb
411
412
  # Gemfile
@@ -502,7 +503,8 @@ $ brew install vips
502
503
 
503
504
  ```rb
504
505
  # Gemfile
505
- gem "image_processing", "~> 1.8"
506
+ gem "image_processing", "~> 2.0"
507
+ gem "ruby-vips", "~> 2.3"
506
508
  ```
507
509
 
508
510
  ```rb
data/doc/refile.md CHANGED
@@ -458,7 +458,7 @@ Shrine.plugin :cached_attachment_data
458
458
  ```
459
459
  ```rb
460
460
  form_for @user do |form|
461
- form.hidden_field :profile_image, value: @user.cached_profile_image_data
461
+ form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil
462
462
  form.file_field :profile_image
463
463
  end
464
464
  ```
@@ -475,7 +475,7 @@ Shrine.plugin :remove_attachment
475
475
  ```
476
476
  ```rb
477
477
  form_for @user do |form|
478
- form.hidden_field :profile_image, value: @user.cached_profile_image_data
478
+ form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil
479
479
  form.file_field :profile_image
480
480
  form.check_box :remove_profile_image
481
481
  end
@@ -491,7 +491,7 @@ Shrine.plugin :remote_url
491
491
  ```
492
492
  ```rb
493
493
  form_for @user do |form|
494
- form.hidden_field :profile_image, value: @user.cached_profile_image_data
494
+ form.hidden_field :profile_image, value: @user.cached_profile_image_data, id: nil
495
495
  form.file_field :profile_image
496
496
  form.text_field :profile_image_remote_url
497
497
  end
@@ -51,7 +51,7 @@ end
51
51
 
52
52
  ```rb
53
53
  form_for @photo do |f|
54
- f.hidden_field :image, value: @photo.cached_image_data
54
+ f.hidden_field :image, value: @photo.cached_image_data, id: nil
55
55
  f.file_filed :image
56
56
  end
57
57
  ```
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Shrine 3.10.0
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * The new `fallback_storage` plugin makes Shrine read from a secondary (fallback) storage when a file is missing in the primary storage. This is useful for staging or development environments that should read files from production without copying them. `UploadedFile#exists?`, `#url` and `#open` (and methods relying on it) fall back to the secondary storage, while uploads and deletes only affect the primary storage.
8
+
9
+ ```rb
10
+ Shrine.storages = {
11
+ cache: Shrine::Storage::S3.new(endpoint: "https://stage.example.com", prefix: "cache"),
12
+ store: Shrine::Storage::S3.new(endpoint: "https://stage.example.com"),
13
+ fallback: Shrine::Storage::S3.new(endpoint: "https://production.example.com"),
14
+ }
15
+
16
+ Shrine.plugin :fallback_storage # looks for the `:fallback` storage by default
17
+ Shrine.plugin :fallback_storage, store: :production_store # custom storage name
18
+ ```
19
+
20
+ * The `data_uri` plugin now accepts a `:max_size` option for limiting the size of the data URI content. The size is calculated from the data URI before the content is decoded, so oversized content never gets loaded into memory. It's exact for base64 content, while for percent-encoded content the encoded length is used, which can overestimate. Content over the limit fails parsing the same way as an invalid data URI.
21
+
22
+ ```rb
23
+ plugin :data_uri, max_size: 10*1024*1024 # 10 MB
24
+ ```
25
+
26
+ * The `data_uri` plugin's `:error_message` proc can now optionally accept the error as a second argument, so the size limit can have its own message.
27
+
28
+ ```rb
29
+ plugin :data_uri, error_message: ->(uri, error) { I18n.t("errors.data_uri.#{error.message}") }
30
+ ```
@@ -0,0 +1,63 @@
1
+ ---
2
+ title: Shrine 3.5.0
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * The website has been migrated to Docusaurus v2. :sparkles:
8
+
9
+ * The `:signer` option has been added to the `derivation_endpoint` plugin, for when you want to use custom URL signing. This is useful when using `:expires_in`, and wanting to have expiring URLs work with CDN caching.
10
+
11
+ ```rb
12
+ require "aws-sdk-cloudfront"
13
+ signer = Aws::CloudFront::UrlSigner.new(key_pair_id: "...", private_key: "...")
14
+
15
+ plugin :derivation_endpoint,
16
+ expires_in: 90,
17
+ signer: -> (url, expires_in:) do
18
+ signer.signed_url(url, expires: Time.now.to_i + expires_in)
19
+ end
20
+ ```
21
+
22
+ * The S3 storage now supports `:max_multipart_parts` option for specifying the maximum number of concurrent parts in which a large file will get uploaded. This number defaults to `10_000`.
23
+
24
+ ```rb
25
+ Shrine::Storage::S3.new(max_multipart_parts: 1000, ...)
26
+ ```
27
+
28
+ * The `:encoding` option can now be passed to `S3#open`, which is applied to downloaded chunks.
29
+
30
+ ```rb
31
+ io = uploaded_file.open(encoding: Encoding::UTF_8)
32
+ csv = CSV.new(io)
33
+ # ...
34
+ ```
35
+
36
+ ## Other improvements
37
+
38
+ * Passing a boolean value to the `#remove_attachment=` setter now works on Ruby 3.2. Previously this would raise an error, because Shrine would try to call `=~` on it, but `Object#=~` method has been removed in Ruby 3.2.
39
+
40
+ * When duplicating a model instance, the duplicated attacher now references the duplicated model instance instead of the original one.
41
+
42
+ * The download endpoint now returns a `400 Bad Request` response when the serialized file component is invalid.
43
+
44
+ * The `derivatives` plugin now supports passing `mutex: false` option to disable usage of a mutex. This makes the `Shrine::Attacher` object marshallable, which should enable using `Marshal.dump` and `Marshal.load` on model instances with attachments. This should be safe unless you're adding derivatives on the same attacher object concurrently.
45
+
46
+ * When loading the `derivatives` plugin with `versions_compatibility: true`, this setting doesn't leak to other uploaders anymore. Previously if other uploaders would load `derivatives` plugin without this option, versions compatibility would still get enabled for them. This change also fixes behavior on JRuby.
47
+
48
+ * When S3 storage copies files, the AWS tag are not inherited anymore. This allows passing the `:tagging` upload option when promoting from temporary to permanent storage, and have it take effect.
49
+
50
+ * The `UploadedFile#url` method doesn't call the obsolete `URI.regexp` method anymore, which should avoid warnings.
51
+
52
+ * The `infer_extension` plugin now defines `infer_extension` instance method (in addition to class method) on the uploader for convenience, so that it can be easily called at the uploader instance level.
53
+
54
+ ```rb
55
+ class MyUploader < Shrine
56
+ plugin :infer_extension
57
+
58
+ def generate_location(io, metadata:, **)
59
+ extension = infer_extension(metadata["mime_type"])
60
+ # ...
61
+ end
62
+ end
63
+ ```
@@ -0,0 +1,23 @@
1
+ ---
2
+ title: Shrine 3.6.0
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * The S3 storage now accepts `:copy_options` when initializing. This can be used for supporting Cloudflare R2 by removing `:tagging_directive` when copying file from temporary to permanent storage.
8
+
9
+ ```rb
10
+ Shrine::Storage::S3.new(bucket: BUCKET, copy_options: {}, **s3_options)
11
+ ```
12
+
13
+ ## Other improvements
14
+
15
+ * Rack 3 is now supported.
16
+
17
+ * When duplicating the attacher, the `Attacher#context` hash is now copied as well, instead of being kept the same between the two attachers.
18
+
19
+ * After `UploadedFile#close` was called, `UploadedFile#opened?` will return `false` and the uploaded file can be implicitly re-opened again.
20
+
21
+ ## Backwards compatibility
22
+
23
+ * Shrine API that is returning a rack response triple will now return headers as an instance of `Rack::Headers` on Rack 3, which is a subclass of `Hash`. This should keep user code that references header names in mixed case working (in addition to lowercase), but could theoretically cause issues for code explicitly requiring headers to be an instance of `Hash`.
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: Shrine 3.7.0
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * The `download_endpoint` plugin now supports expiring URLs. Configure a `secret_key` and optionally a default `expires_in` on the plugin, then pass `expires_in:` when generating a URL. The URL is signed with HMAC-SHA256, and the endpoint will reject requests with an expired or tampered signature.
8
+
9
+ ```rb
10
+ plugin :download_endpoint,
11
+ prefix: "downloads",
12
+ secret_key: "<your-secret-key>",
13
+ expires_in: 5 * 60 # optional default; can be overridden per URL
14
+ ```
15
+
16
+ ```rb
17
+ uploaded_file.download_url # uses the default expires_in
18
+ uploaded_file.download_url(expires_in: 10 * 60) # override per URL
19
+ # => "https://example.com/downloads/<token>?signature=...&expires_at=..."
20
+ ```
21
+
22
+ * The `derivatives` plugin now accepts a `:keep_derivatives` option. When set to `true`, existing derivatives are kept when a new file is attached via `Attacher#change`, instead of being cleared.
23
+
24
+ ```rb
25
+ plugin :derivatives, keep_derivatives: true
26
+ ```
27
+
28
+ ```rb
29
+ attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> }
30
+ attacher.change(new_file)
31
+ attacher.derivatives #=> { thumb: #<Shrine::UploadedFile> } # preserved
32
+ ```
33
+
34
+ * The `refresh_metadata` plugin now accepts a `replace:` keyword argument on `refresh_metadata!`. Passing `replace: true` replaces the file's metadata entirely with the freshly extracted values, instead of merging them. This is useful when you want to remove stale custom metadata keys.
35
+
36
+ ```rb
37
+ uploaded_file.metadata["custom"] = "stale value"
38
+
39
+ uploaded_file.refresh_metadata! # merge (default)
40
+ uploaded_file.metadata["custom"] #=> "stale value" (preserved)
41
+
42
+ uploaded_file.refresh_metadata!(replace: true) # replace
43
+ uploaded_file.metadata["custom"] #=> nil (removed)
44
+ ```
45
+
46
+ ## Other improvements
47
+
48
+ * The `s3` storage now prefers using `TransferManager#upload_stream` over the deprecated `#upload_stream` method on the S3 object, when `TransferManager` is available. This avoids deprecation warnings from newer versions of the AWS SDK.
49
+
50
+ * The `column` plugin no longer attempts to deserialize an empty string as JSON. Previously this would raise a parse error; now the attachment is treated as blank.
51
+
52
+ * The `backgrounding` plugin now correctly forwards keyword arguments passed to `Attacher#promote_cached` into the `promote_block` callback.
53
+
54
+ ```rb
55
+ Shrine::Attacher.promote_block do |attacher:, my_option:, **|
56
+ # my_option was previously not forwarded here
57
+ SomePromoteJob.perform_async(attacher.dump, my_option: my_option)
58
+ end
59
+
60
+ attacher.promote_cached(my_option: "value") # now forwarded correctly
61
+ ```
62
+
63
+ * `UploadedFile` no longer produces URI default parser warnings (`URI::RFC3986_PARSER.make_regexp is obsolete`) when verbose warnings are enabled.
64
+
65
+ ## Backwards compatibility
66
+
67
+ * Support for Ruby versions below 3.2 has been dropped. Ruby >= 3.2 is now required.
68
+
69
+ * ImageProcessing 2.0 made `mini_magick` and `ruby-vips` soft dependencies that are no longer loaded automatically. If you use either gem for image processing, you will need to add it explicitly to your `Gemfile`:
70
+
71
+ ```rb
72
+ gem "mini_magick"
73
+ # or
74
+ gem "ruby-vips"
75
+ ```
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Shrine 3.7.1
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * The `derivation_endpoint` plugin now supports adding a file extension to the derivation URL path via the `:format` option on `UploadedFile#derivation_url`. Some HTTP clients and CDNs use the URL path extension to determine the content type of the response.
8
+
9
+ ```rb
10
+ uploaded_file.derivation_url(:thumbnail, format: "jpg")
11
+ #=> ".../thumbnail/eyJpZCI6ImZvbyIsInN.jpg?signature=..."
12
+ ```
13
+
14
+ The extension is included in the URL signature, so it cannot be tampered with.
15
+
16
+ * The `rack_response` plugin now accepts an `:etag` option for setting a custom `ETag` header, overriding the default one Shrine generates.
17
+
18
+ ```rb
19
+ response = uploaded_file.to_rack_response(etag: "my-custom-etag")
20
+ response[1]["ETag"] #=> "my-custom-etag"
21
+ ```
22
+
23
+ ## Bug fixes
24
+
25
+ * When initializing Shrine with the `activerecord` plugin, under Bootsnap 1.24.5 and Ruby 3.4.5 this would raise an error:
26
+
27
+ ```
28
+ ArgumentError: wrong number of arguments (given 2, expected 1; required keyword: plugin)
29
+ ```
30
+
31
+ The seems to be triggered by a special combination of method signatures, where keyword arguments get converted into positional arguments under Bootsnap. To work around this issue, method signatures of some plugin methods have been updated.
@@ -0,0 +1,37 @@
1
+ ---
2
+ title: Shrine 3.8.0
3
+ ---
4
+
5
+ ## Security
6
+
7
+ * The `file_system` storage now prevents path traversal outside of the storage
8
+ directory. Previously, an id containing `../` sequences (e.g. coming from
9
+ attacker-controlled data) could resolve to a location outside of the
10
+ configured storage directory. Now `Shrine::Error` is raised whenever an id
11
+ would resolve outside of the storage directory:
12
+
13
+ ```rb
14
+ storage = Shrine::Storage::FileSystem.new("uploads")
15
+ storage.open("../../etc/passwd") #~> Shrine::Error
16
+ ```
17
+
18
+ ## New features
19
+
20
+ * The `derivatives` plugin now forwards options passed to `Attacher#promote`
21
+ into `Attacher#upload_derivatives`. This means options such as upload options
22
+ reach the derivatives upload as well, not just the main file.
23
+
24
+ ```rb
25
+ attacher.promote(upload_options: { acl: "public-read" })
26
+ # the same options are now forwarded when uploading derivatives
27
+ ```
28
+
29
+ ## Other improvements
30
+
31
+ * The `s3` storage again uses single-request uploads for smaller files, instead
32
+ of always using multipart uploads. In Shrine 3.7.0, when a `TransferManager`
33
+ was available, it was used for uploads of any size, but `TransferManager`
34
+ uploads via multipart unconditionally, which is inefficient for smaller files.
35
+ Now files at or below the multipart threshold (`:upload`, 15MB by default) are
36
+ uploaded in a single request, and only larger files use multipart upload via
37
+ the `TransferManager` (falling back to the older API when it's not available).
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Shrine 3.9.0
3
+ ---
4
+
5
+ ## New features
6
+
7
+ * `Shrine.find_storage` now raises `Shrine::MissingStorage` (a subclass of `Shrine::Error`) when the storage isn't registered, instead of a generic `Shrine::Error`. This is useful with the `dynamic_storage` plugin, where the resolver might fail to find its dependencies (e.g. a deleted DB record), and lets you rescue that case separately from other errors.
8
+
9
+ ```rb
10
+ begin
11
+ Shrine.find_storage(:store)
12
+ rescue Shrine::MissingStorage
13
+ # handle missing storage specifically
14
+ end
15
+ ```
16
+
17
+ * The `url_options` and `upload_options` plugins now accept a `Regexp` in place of a storage key. This is useful when storage keys are generated dynamically (e.g. via the `dynamic_storage` plugin), since it's not possible to list every storage key upfront. An exact storage key match always takes precedence over a `Regexp` match.
18
+
19
+ ```rb
20
+ plugin :url_options, /_store\z/ => { expires_in: 24*60*60 }
21
+ plugin :upload_options, /_store\z/ => { acl: "private" }
22
+ ```
23
+
24
+ * The `activerecord` plugin now accepts an `:attribute_types` option. When enabled, it uses `type_for_attribute` (instead of just `columns_hash`) to detect whether the data attribute is JSON/JSONB, so Shrine also skips its own serialization for attributes declared via the Active Record Attributes API (not just real database columns). It defaults to `false` for backwards compatibility with apps that might be relying on the previous double-serialization behavior.
25
+
26
+ ```rb
27
+ class Photo < ActiveRecord::Base # `image_data` is a text column
28
+ include ImageUploader::Attachment(:image)
29
+
30
+ attribute :image_data, :json
31
+ end
32
+
33
+ plugin :activerecord, attribute_types: true
34
+ ```
35
+
36
+ ## Bug fixes
37
+
38
+ * The `activerecord` and `sequel` plugins no longer delete the previous attachment when the record being saved was just created. Previously, duplicating a persisted record (e.g. via `#dup`) and immediately assigning a new attachment before saving the duplicate could end up deleting the *original* record's attachment, since both records shared the same underlying file.
39
+
40
+ * The `remove_invalid` plugin now correctly clears/reverts the model attribute after an invalid file is deassigned. Previously the record's raw attribute could be left out of sync with the attacher after validation failed.
41
+
42
+ * `UploadedFile#tempfile` (from the `tempfile` plugin) can now be called without opening the uploaded file first. Previously it raised `Shrine::Error` unless the file was already open; now it downloads and caches the file on demand. If the uploaded file is open, its tempfile is still cleaned up when the file is closed; otherwise it's cleaned up whenever it becomes unreachable and is garbage collected, so it's still recommended to close the uploaded file when you're done with it for deterministic cleanup.
@@ -124,7 +124,7 @@ end # underlying IO object is closed
124
124
  ```
125
125
 
126
126
  `Shrine::UploadedFile#open` will return the result of a given block.
127
- block. We can use that to safely retrieve the whole content of a file, without
127
+ We can use that to safely retrieve the whole content of a file, without
128
128
  leaving any temporary files lying around.
129
129
 
130
130
  ```rb
data/doc/storage/s3.md CHANGED
@@ -124,6 +124,16 @@ uploader.upload(file, upload_options: { acl: "private" })
124
124
  the uploader level won't be forwarded for generating presigns, since presigns
125
125
  are generated using the storage directly.
126
126
 
127
+ ## Copy options
128
+
129
+ If you wish to override options that are passed when copying objects from
130
+ temporary to permanent storage, you can pass `:copy_options`:
131
+
132
+ ```rb
133
+ # Removes default :tagging_directive, which isn't supported by Cloudflare R2
134
+ Shrine::Storage::S3.new(copy_options: {}, **s3_options)
135
+ ```
136
+
127
137
  ## URL Host
128
138
 
129
139
  If you want your S3 object URLs to be generated with a different URL host (e.g.