shrine 3.6.0 → 3.7.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/doc/changing_derivatives.md +2 -1
  4. data/doc/changing_location.md +17 -5
  5. data/doc/getting_started.md +4 -2
  6. data/doc/plugins/derivation_endpoint.md +2 -1
  7. data/doc/plugins/derivatives.md +2 -1
  8. data/doc/plugins/download_endpoint.md +16 -4
  9. data/doc/plugins/refresh_metadata.md +20 -0
  10. data/doc/plugins/signature.md +8 -6
  11. data/doc/processing.md +5 -3
  12. data/doc/release_notes/3.7.0.md +75 -0
  13. data/lib/shrine/attacher.rb +21 -21
  14. data/lib/shrine/attachment.rb +2 -2
  15. data/lib/shrine/plugins/_urlsafe_serialization.rb +4 -4
  16. data/lib/shrine/plugins/add_metadata.rb +2 -4
  17. data/lib/shrine/plugins/atomic_helpers.rb +7 -7
  18. data/lib/shrine/plugins/backgrounding.rb +9 -9
  19. data/lib/shrine/plugins/column.rb +6 -4
  20. data/lib/shrine/plugins/default_url.rb +4 -4
  21. data/lib/shrine/plugins/delete_raw.rb +2 -2
  22. data/lib/shrine/plugins/derivation_endpoint.rb +25 -25
  23. data/lib/shrine/plugins/derivatives.rb +5 -1
  24. data/lib/shrine/plugins/download_endpoint.rb +62 -10
  25. data/lib/shrine/plugins/entity.rb +7 -7
  26. data/lib/shrine/plugins/infer_extension.rb +1 -1
  27. data/lib/shrine/plugins/instrumentation.rb +8 -8
  28. data/lib/shrine/plugins/mirroring.rb +10 -10
  29. data/lib/shrine/plugins/model.rb +9 -9
  30. data/lib/shrine/plugins/presign_endpoint.rb +4 -4
  31. data/lib/shrine/plugins/pretty_location.rb +2 -2
  32. data/lib/shrine/plugins/processing.rb +3 -3
  33. data/lib/shrine/plugins/rack_file.rb +2 -2
  34. data/lib/shrine/plugins/rack_response.rb +4 -4
  35. data/lib/shrine/plugins/refresh_metadata.rb +6 -6
  36. data/lib/shrine/plugins/remote_url.rb +3 -3
  37. data/lib/shrine/plugins/restore_cached_data.rb +3 -3
  38. data/lib/shrine/plugins/signature.rb +2 -2
  39. data/lib/shrine/plugins/store_dimensions.rb +2 -2
  40. data/lib/shrine/plugins/upload_endpoint.rb +4 -4
  41. data/lib/shrine/plugins/upload_options.rb +1 -1
  42. data/lib/shrine/plugins/validation.rb +8 -8
  43. data/lib/shrine/plugins/versions.rb +10 -10
  44. data/lib/shrine/plugins.rb +6 -14
  45. data/lib/shrine/storage/file_system.rb +4 -17
  46. data/lib/shrine/storage/linter.rb +8 -8
  47. data/lib/shrine/storage/memory.rb +1 -3
  48. data/lib/shrine/storage/s3.rb +45 -37
  49. data/lib/shrine/uploaded_file.rb +20 -18
  50. data/lib/shrine/version.rb +1 -1
  51. data/lib/shrine.rb +18 -18
  52. data/shrine.gemspec +6 -6
  53. metadata +15 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a5fadb4e660b7638e1d17b81ecb1761e8dd5faa675f15a3b30de0c549bafd653
4
- data.tar.gz: a9ee826b483ab474f55ac66bc35e95a4cb8496cada12ce7595091210670f1f38
3
+ metadata.gz: 01c1404cab9882d16a6795c41467591b4083cbef76b5d42b91626a4306b33584
4
+ data.tar.gz: bec484b8594573db2056eabeda97ae5c22fb170a35f984802cd200085611e556
5
5
  SHA512:
6
- metadata.gz: 2c0f867b7215189135563524f9e7d8cf28d8be1a5b298bc173bbe1af9fb3ced15f98dd198c8cb13bfa87abdf3eb29566083c6ebc1de0955713da6c165562c41d
7
- data.tar.gz: 47c2f75d9a831fdc1d731086fbeeb5a6885a1845eac375a5ba2ce9b5354654f49931e6b26f2732d98edfa39cbfcf3e587be63f01490910a06a7bad64cea8219e
6
+ metadata.gz: 298e5065ba40638cee54dc4b74be98e466974502f56b5bff956fb2d7cc4d73028ec8a9d79dae7c073306c4512145cfef082ec4beeeb60a0eddef5497bbcf8174
7
+ data.tar.gz: 3413b070afe6315133342ab3faf7c41d97bb9767a3aa13f62bf4ba339246fb4f1b3d1ebc91036b37942b8893c20b652edf4603f2b710bd66647caed6b9cddc48
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## 3.7.0 (2026-05-27)
2
+
3
+ * `download_endpoint` - Add support for expiring URLs (@davidwessman)
4
+
5
+ * `s3` - Use `TransferManager` where available instead of deprecated `upload_steam` (@danieldevlewis)
6
+
7
+ * `column` - Don't attempt to deserialize empty string as JSON (@adam12)
8
+
9
+ * `derivatives` - Add `:keep_derivatives` plugin option to keep existing derivatives when a new file is attached (@fnordfish)
10
+
11
+ * `refresh_metadata` - Add `replace:` keyword argument to `refresh_metadata!` for replacing instead of merging existing metadata (@JacobGalati)
12
+
13
+ * `backgrounding` - Fix options not being forwarded from `promote_cached` to `promote_block` (@4ndypanda)
14
+
15
+ * Fix URI default parser warnings in `UploadedFile` (@adam12)
16
+
17
+ * Drop support for Ruby < 3.2
18
+
1
19
  ## 3.6.0 (2024-04-29)
2
20
 
3
21
  * Add Rack 3 support (@tomasc, @janko)
@@ -36,7 +36,8 @@ derivatives generated). Let's assume you're generating image thumbnails:
36
36
 
37
37
  ```rb
38
38
  # Gemfile
39
- gem "image_processing", "~> 1.8"
39
+ gem "image_processing", "~> 2.0"
40
+ gem "mini_magick", "~> 5.0"
40
41
  ```
41
42
  ```rb
42
43
  require "image_processing/mini_magick"
@@ -3,7 +3,7 @@ id: changing-location
3
3
  title: Migrating File Locations
4
4
  ---
5
5
 
6
- This guide shows how to migrate the location of uploaded files on the same
6
+ This guide shows how to migrate the location of uploaded files on the same
7
7
  storage in production, with zero downtime.
8
8
 
9
9
  Let's assume we have a `Photo` model with an `image` file attachment:
@@ -31,20 +31,32 @@ to work with the previously stored urls because the files have not been migrated
31
31
  ```rb
32
32
  class ImageUploader < Shrine
33
33
  def generate_location(io, **options)
34
- # change location generation
34
+ # change location generation, eg....
35
+ [
36
+ options[:record] && options[:record].class.name.underscore,
37
+ option[:record] && options[:record].id,
38
+ super
39
+ ].compact.join("/")
35
40
  end
36
41
  end
37
42
  ```
38
43
 
39
- We can now deploy this change to production so new file uploads will be stored in
44
+ We can now deploy this change to production so new file uploads will be stored in
40
45
  the new location.
41
46
 
47
+ As seen above, we can call `super` to get the include the default location, which uses ruby
48
+ `SecureRandom.hex` to have a unique immutable storage location. While it isn't
49
+ strictly required to have a unique immutable storage location, it makes many
50
+ things work smoother when different content will get a different storage location,
51
+ and is recommended. One approach is using fixed directory/prefix as above.
52
+
53
+
42
54
  ## 2. Move existing files
43
55
 
44
56
  To move existing files to new location, run the following script. It fetches
45
57
  the photos in batches, downloads the image, and re-uploads it to the new location.
46
- We only need to migrate the files in `:store` storage need to be migrated as the files
47
- in `:cache` storage will be uploaded to the new location on promotion.
58
+ Only the files in `:store` storage need to be migrated as the files in `:cache`
59
+ storage will be uploaded to the new location on promotion.
48
60
 
49
61
  ```rb
50
62
  Photo.find_each do |photo|
@@ -602,7 +602,8 @@ creation:
602
602
 
603
603
  ```rb
604
604
  # Gemfile
605
- gem "image_processing", "~> 1.8"
605
+ gem "image_processing", "~> 2.0"
606
+ gem "mini_magick", "~> 5.0"
606
607
  ```
607
608
  ```rb
608
609
  Shrine.plugin :derivatives, create_on_promote: true
@@ -656,7 +657,8 @@ processing we want to perform:
656
657
 
657
658
  ```rb
658
659
  # Gemfile
659
- gem "image_processing", "~> 1.8"
660
+ gem "image_processing", "~> 2.0"
661
+ gem "mini_magick", "~> 5.0"
660
662
  ```
661
663
  ```rb
662
664
  # config/initializers/rails.rb (Rails)
@@ -35,7 +35,8 @@ apply to an attached file. For example, we can generate image thumbnails using
35
35
  the [ImageProcessing] gem:
36
36
 
37
37
  ```rb
38
- gem "image_processing", "~> 1.8"
38
+ gem "image_processing", "~> 2.0"
39
+ gem "mini_magick", "~> 5.0"
39
40
  ```
40
41
  ```rb
41
42
  require "image_processing/mini_magick"
@@ -20,7 +20,8 @@ Here is an example of generating image thumbnails:
20
20
 
21
21
  ```rb
22
22
  # Gemfile
23
- gem "image_processing", "~> 1.8"
23
+ gem "image_processing", "~> 2.0"
24
+ gem "mini_magick", "~> 5.0"
24
25
  ```
25
26
  ```rb
26
27
  require "image_processing/mini_magick"
@@ -7,7 +7,7 @@ downloading uploaded files from specified storages. This can be useful when
7
7
  files from your storage isn't accessible over URL (e.g. database storages) or
8
8
  if you want to authenticate your downloads.
9
9
 
10
- ## Global Endpoint
10
+ ## Global Endpoint
11
11
 
12
12
  You can configure the plugin with the path prefix which the endpoint will be
13
13
  mounted on.
@@ -34,6 +34,7 @@ Links to the download endpoint are generated by calling
34
34
  ```rb
35
35
  uploaded_file.download_url #=> "/attachments/eyJpZCI6ImFkdzlyeTM..."
36
36
  ```
37
+
37
38
  ## Endpoint via Uploader
38
39
 
39
40
  You can also configure the plugin in the uploader directly - just make sure to mount it via your Uploader-class.
@@ -52,8 +53,8 @@ Rails.application.routes.draw do
52
53
  end
53
54
  ```
54
55
 
55
- *Hint: For shrine versions 2.x -> ensure that you don't include the plugin
56
- twice (globally and in your uploader class - see #408)*
56
+ _Hint: For shrine versions 2.x -> ensure that you don't include the plugin
57
+ twice (globally and in your uploader class - see #408)_
57
58
 
58
59
  ## Calling from a controller
59
60
 
@@ -69,6 +70,7 @@ Rails.application.routes.draw do
69
70
  get "/attachments/*rest", to: "downloads#image"
70
71
  end
71
72
  ```
73
+
72
74
  ```rb
73
75
  # app/controllers/downloads_controller.rb (Rails)
74
76
  class DownloadsController < ApplicationController
@@ -131,6 +133,16 @@ plugin :download_endpoint, download_options: -> (uploaded_file, request) {
131
133
  }
132
134
  ```
133
135
 
136
+ ## Expiring download urls
137
+
138
+ If you want to have URLs that expire after a certain time, you can use the `:expires_in` and `secret_key` options:
139
+
140
+ ```rb
141
+ plugin :download_endpoint, expires_in: 5 * 60, secret_key: "secret"
142
+ ```
143
+
144
+ this will generate URLs that are signed with a signature valid for 5 minutes.
145
+
134
146
  ## Performance considerations
135
147
 
136
148
  Streaming files through the app might impact the request throughput, depending
@@ -162,7 +174,7 @@ Shrine.download_endpoint(disposition: "attachment")
162
174
  ## Plugin options
163
175
 
164
176
  | Name | Description | Default |
165
- | :-------- | :---------- | :------ |
177
+ | :------------------ | :-------------------------------------------------------------------------------- | :------- |
166
178
  | `:disposition` | Whether browser should render the file `inline` or download it as an `attachment` | `inline` |
167
179
  | `:download_options` | Hash of storage-specific options passed to `Storage#open` | `{}` |
168
180
  | `:host` | URL host that will be added to download URLs | `nil` |
@@ -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
@@ -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:
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
@@ -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
+ ```
@@ -22,8 +22,8 @@ class Shrine
22
22
  #
23
23
  # attacher = Attacher.from_data({ "id" => "...", "storage" => "...", "metadata" => { ... } })
24
24
  # attacher.file #=> #<Shrine::UploadedFile>
25
- def from_data(data, **options)
26
- attacher = new(**options)
25
+ def from_data(data, **)
26
+ attacher = new(**)
27
27
  attacher.load_data(data)
28
28
  attacher
29
29
  end
@@ -47,14 +47,14 @@ class Shrine
47
47
  end
48
48
 
49
49
  # Returns the temporary storage identifier.
50
- def cache_key; @cache.to_sym; end
50
+ def cache_key = @cache.to_sym
51
51
  # Returns the permanent storage identifier.
52
- def store_key; @store.to_sym; end
52
+ def store_key = @store.to_sym
53
53
 
54
54
  # Returns the uploader that is used for the temporary storage.
55
- def cache; shrine_class.new(cache_key); end
55
+ def cache = shrine_class.new(cache_key)
56
56
  # Returns the uploader that is used for the permanent storage.
57
- def store; shrine_class.new(store_key); end
57
+ def store = shrine_class.new(store_key)
58
58
 
59
59
  # Calls #attach_cached, but skips if value is an empty string (this is
60
60
  # useful when the uploaded file comes from form fields). Forwards any
@@ -67,14 +67,14 @@ class Shrine
67
67
  #
68
68
  # # ignores the assignment when a blank string is given
69
69
  # attacher.assign("")
70
- def assign(value, **options)
70
+ def assign(value, **)
71
71
  return if value == "" # skip empty hidden field
72
72
 
73
73
  if value.is_a?(Hash) || value.is_a?(String)
74
74
  return if uploaded_file(value) == file # skip assignment for current file
75
75
  end
76
76
 
77
- attach_cached(value, **options)
77
+ attach_cached(value, **)
78
78
  end
79
79
 
80
80
  # Sets an existing cached file, or uploads an IO object to temporary
@@ -92,11 +92,11 @@ class Shrine
92
92
  #
93
93
  # # sets an existing cached file from Hash data
94
94
  # attacher.attach_cached({ "id" => "...", "storage" => "cache", "metadata" => {} })
95
- def attach_cached(value, **options)
95
+ def attach_cached(value, **)
96
96
  if value.is_a?(String) || value.is_a?(Hash)
97
- change(cached(value, **options))
97
+ change(cached(value, **))
98
98
  else
99
- attach(value, storage: cache_key, action: :cache, **options)
99
+ attach(value, storage: cache_key, action: :cache, **)
100
100
  end
101
101
  end
102
102
 
@@ -113,8 +113,8 @@ class Shrine
113
113
  #
114
114
  # # removes the attachment
115
115
  # attacher.attach(nil)
116
- def attach(io, storage: store_key, **options)
117
- file = upload(io, storage, **options) if io
116
+ def attach(io, storage: store_key, **)
117
+ file = upload(io, storage, **) if io
118
118
 
119
119
  change(file)
120
120
  end
@@ -158,8 +158,8 @@ class Shrine
158
158
  # attacher.cached? #=> true
159
159
  # attacher.promote_cached
160
160
  # attacher.stored? #=> true
161
- def promote_cached(**options)
162
- promote(**options) if promote?
161
+ def promote_cached(**)
162
+ promote(**) if promote?
163
163
  end
164
164
 
165
165
  # Uploads current file to permanent storage and sets the stored file.
@@ -167,8 +167,8 @@ class Shrine
167
167
  # attacher.cached? #=> true
168
168
  # attacher.promote
169
169
  # attacher.stored? #=> true
170
- def promote(storage: store_key, **options)
171
- set upload(file, storage, action: :store, **options)
170
+ def promote(storage: store_key, **)
171
+ set upload(file, storage, action: :store, **)
172
172
  end
173
173
 
174
174
  # Delegates to `Shrine.upload`, passing the #context.
@@ -178,8 +178,8 @@ class Shrine
178
178
  #
179
179
  # # pass additional options for the uploader
180
180
  # attacher.upload(io, :store, metadata: { "foo" => "bar" })
181
- def upload(io, storage = store_key, **options)
182
- shrine_class.upload(io, storage, **context, **options)
181
+ def upload(io, storage = store_key, **)
182
+ shrine_class.upload(io, storage, **context, **)
183
183
  end
184
184
 
185
185
  # If a new file was attached, deletes previously attached file if any.
@@ -249,8 +249,8 @@ class Shrine
249
249
  #
250
250
  # attacher.file = nil
251
251
  # attacher.url #=> nil
252
- def url(**options)
253
- file&.url(**options)
252
+ def url(**)
253
+ file&.url(**)
254
254
  end
255
255
 
256
256
  # Returns whether the attachment has changed.
@@ -22,8 +22,8 @@ class Shrine
22
22
  # Shorthand for `Attachment.new`.
23
23
  #
24
24
  # Shrine::Attachment[:image]
25
- def [](*args, **options)
26
- new(*args, **options)
25
+ def [](*, **)
26
+ new(*, **)
27
27
  end
28
28
  end
29
29
 
@@ -23,8 +23,8 @@ class Shrine
23
23
  end
24
24
 
25
25
  module FileMethods
26
- def urlsafe_dump(**options)
27
- self.class.urlsafe_dump(self, **options)
26
+ def urlsafe_dump(**)
27
+ self.class.urlsafe_dump(self, **)
28
28
  end
29
29
 
30
30
  def urlsafe_data(metadata: [])
@@ -45,8 +45,8 @@ class Shrine
45
45
  end
46
46
 
47
47
  module FileClassMethods
48
- def urlsafe_dump(file, **options)
49
- data = file.urlsafe_data(**options)
48
+ def urlsafe_dump(file, **)
49
+ data = file.urlsafe_data(**)
50
50
 
51
51
  shrine_class.urlsafe_serialize(data)
52
52
  end
@@ -29,11 +29,9 @@ class Shrine
29
29
  end
30
30
 
31
31
  module InstanceMethods
32
- def extract_metadata(io, **options)
32
+ def extract_metadata(io, **)
33
33
  metadata = super
34
-
35
- extract_custom_metadata(io, **options, metadata: metadata)
36
-
34
+ extract_custom_metadata(io, **, metadata:)
37
35
  metadata
38
36
  end
39
37
 
@@ -14,14 +14,14 @@ class Shrine
14
14
  #
15
15
  # Shrine::Attacher.retrieve(model: photo, name: :image, file: file_data)
16
16
  # #=> #<ImageUploader::Attacher>
17
- def retrieve(model: nil, entity: nil, name:, file:, **options)
17
+ def retrieve(model: nil, entity: nil, name:, file:, **)
18
18
  fail ArgumentError, "either :model or :entity is required" unless model || entity
19
19
 
20
20
  record = model || entity
21
21
 
22
- attacher = record.send(:"#{name}_attacher", **options) if record.respond_to?(:"#{name}_attacher")
23
- attacher ||= from_model(record, name, **options) if model
24
- attacher ||= from_entity(record, name, **options) if entity
22
+ attacher = record.send(:"#{name}_attacher", **) if record.respond_to?(:"#{name}_attacher")
23
+ attacher ||= from_model(record, name, **) if model
24
+ attacher ||= from_entity(record, name, **) if entity
25
25
 
26
26
  if attacher.file != attacher.uploaded_file(file)
27
27
  fail Shrine::AttachmentChanged, "attachment has changed"
@@ -43,13 +43,13 @@ class Shrine
43
43
  #
44
44
  # This more convenient to use with concrete persistence plugins, which
45
45
  # provide defaults for reloading and persistence.
46
- def abstract_atomic_promote(reload:, persist:, **options, &block)
46
+ def abstract_atomic_promote(reload:, persist:, **, &block)
47
47
  original_file = file
48
48
 
49
- result = promote(**options)
49
+ result = promote(**)
50
50
 
51
51
  begin
52
- abstract_atomic_persist(original_file, reload: reload, persist: persist, &block)
52
+ abstract_atomic_persist(original_file, reload:, persist:, &block)
53
53
  result
54
54
  rescue Shrine::AttachmentChanged
55
55
  destroy_attached
@@ -67,19 +67,19 @@ class Shrine
67
67
  end
68
68
 
69
69
  # Does a background promote if promote block was registered.
70
- def promote_cached(**options)
70
+ def promote_cached(**)
71
71
  if promote? && promote_block
72
- promote_background
72
+ promote_background(**)
73
73
  else
74
74
  super
75
75
  end
76
76
  end
77
77
 
78
78
  # Calls the registered promote block.
79
- def promote_background(**options)
79
+ def promote_background(**)
80
80
  fail Error, "promote block is not registered" unless promote_block
81
81
 
82
- background_block(promote_block, **options)
82
+ background_block(promote_block, **)
83
83
  end
84
84
 
85
85
  # Does a background destroy if destroy block was registered.
@@ -92,19 +92,19 @@ class Shrine
92
92
  end
93
93
 
94
94
  # Calls the registered destroy block.
95
- def destroy_background(**options)
95
+ def destroy_background(**)
96
96
  fail Error, "destroy block is not registered" unless destroy_block
97
97
 
98
- background_block(destroy_block, **options)
98
+ background_block(destroy_block, **)
99
99
  end
100
100
 
101
101
  private
102
102
 
103
- def background_block(block, **options)
103
+ def background_block(block, **)
104
104
  if block.arity == 1
105
- block.call(self, **options)
105
+ block.call(self, **)
106
106
  else
107
- instance_exec(**options, &block)
107
+ instance_exec(**, &block)
108
108
  end
109
109
  end
110
110
  end
@@ -16,8 +16,8 @@ class Shrine
16
16
  # from a database record column.
17
17
  #
18
18
  # Attacher.from_column('{"id":"...","storage":"...","metadata":{...}}')
19
- def from_column(data, **options)
20
- attacher = new(**options)
19
+ def from_column(data, **)
20
+ attacher = new(**)
21
21
  attacher.load_column(data)
22
22
  attacher
23
23
  end
@@ -28,8 +28,8 @@ class Shrine
28
28
  attr_reader :column_serializer
29
29
 
30
30
  # Allows overriding the default column serializer.
31
- def initialize(column_serializer: shrine_class.opts[:column][:serializer], **options)
32
- super(**options)
31
+ def initialize(column_serializer: shrine_class.opts[:column][:serializer], **)
32
+ super(**)
33
33
  @column_serializer = column_serializer
34
34
  end
35
35
 
@@ -75,6 +75,8 @@ class Shrine
75
75
  # Attacher.deserialize_column(nil)
76
76
  # #=> nil
77
77
  def deserialize_column(data)
78
+ return nil if data == ""
79
+
78
80
  if column_serializer && data && !data.is_a?(Hash)
79
81
  column_serializer.load(data)
80
82
  else
@@ -16,16 +16,16 @@ class Shrine
16
16
  end
17
17
 
18
18
  module AttacherMethods
19
- def url(**options)
20
- super || default_url(**options)
19
+ def url(**)
20
+ super || default_url(**)
21
21
  end
22
22
 
23
23
  private
24
24
 
25
- def default_url(**options)
25
+ def default_url(**)
26
26
  return unless default_url_block
27
27
 
28
- url = instance_exec(**options, &default_url_block)
28
+ url = instance_exec(**, &default_url_block)
29
29
 
30
30
  [*default_url_host, url].join
31
31
  end