shrine 3.1.0 → 3.4.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +11 -4
- data/doc/advantages.md +4 -4
- data/doc/attacher.md +2 -2
- data/doc/carrierwave.md +24 -12
- data/doc/changing_derivatives.md +1 -1
- data/doc/changing_location.md +6 -5
- data/doc/design.md +134 -85
- data/doc/direct_s3.md +26 -0
- data/doc/external/articles.md +57 -45
- data/doc/external/extensions.md +41 -35
- data/doc/external/misc.md +23 -8
- data/doc/getting_started.md +156 -85
- data/doc/metadata.md +80 -44
- data/doc/multiple_files.md +1 -1
- data/doc/paperclip.md +28 -9
- data/doc/plugins/add_metadata.md +112 -35
- data/doc/plugins/atomic_helpers.md +41 -3
- data/doc/plugins/backgrounding.md +12 -2
- data/doc/plugins/column.md +36 -7
- data/doc/plugins/default_url.md +6 -3
- data/doc/plugins/derivatives.md +83 -44
- data/doc/plugins/download_endpoint.md +5 -5
- data/doc/plugins/dynamic_storage.md +1 -1
- data/doc/plugins/entity.md +12 -4
- data/doc/plugins/form_assign.md +5 -5
- data/doc/plugins/included.md +25 -5
- data/doc/plugins/infer_extension.md +9 -0
- data/doc/plugins/instrumentation.md +1 -1
- data/doc/plugins/metadata_attributes.md +1 -0
- data/doc/plugins/mirroring.md +1 -1
- data/doc/plugins/model.md +8 -3
- data/doc/plugins/persistence.md +10 -1
- data/doc/plugins/remote_url.md +6 -1
- data/doc/plugins/remove_invalid.md +9 -1
- data/doc/plugins/sequel.md +1 -1
- data/doc/plugins/store_dimensions.md +10 -0
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +1 -1
- data/doc/plugins/upload_options.md +1 -1
- data/doc/plugins/url_options.md +4 -4
- data/doc/plugins/validation.md +14 -4
- data/doc/plugins/versions.md +7 -7
- data/doc/processing.md +287 -123
- data/doc/refile.md +9 -9
- data/doc/release_notes/2.8.0.md +1 -1
- data/doc/release_notes/3.0.0.md +1 -1
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/securing_uploads.md +2 -2
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +104 -77
- data/doc/testing.md +12 -2
- data/doc/upgrading_to_3.md +99 -53
- data/lib/shrine.rb +9 -8
- data/lib/shrine/attacher.rb +20 -10
- data/lib/shrine/attachment.rb +2 -2
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/plugins/activerecord.rb +3 -3
- data/lib/shrine/plugins/add_metadata.rb +20 -5
- data/lib/shrine/plugins/backgrounding.rb +2 -2
- data/lib/shrine/plugins/default_url.rb +1 -1
- data/lib/shrine/plugins/derivation_endpoint.rb +13 -8
- data/lib/shrine/plugins/derivatives.rb +59 -30
- data/lib/shrine/plugins/determine_mime_type.rb +5 -3
- data/lib/shrine/plugins/entity.rb +12 -11
- data/lib/shrine/plugins/instrumentation.rb +12 -18
- data/lib/shrine/plugins/mirroring.rb +8 -8
- data/lib/shrine/plugins/model.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +16 -4
- data/lib/shrine/plugins/pretty_location.rb +1 -1
- data/lib/shrine/plugins/processing.rb +1 -1
- data/lib/shrine/plugins/refresh_metadata.rb +2 -2
- data/lib/shrine/plugins/remote_url.rb +3 -3
- data/lib/shrine/plugins/remove_attachment.rb +5 -0
- data/lib/shrine/plugins/remove_invalid.rb +10 -5
- data/lib/shrine/plugins/sequel.rb +1 -1
- data/lib/shrine/plugins/store_dimensions.rb +4 -2
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +10 -5
- data/lib/shrine/plugins/upload_options.rb +2 -2
- data/lib/shrine/plugins/url_options.rb +2 -2
- data/lib/shrine/plugins/validation.rb +9 -7
- data/lib/shrine/storage/linter.rb +4 -4
- data/lib/shrine/storage/memory.rb +5 -3
- data/lib/shrine/storage/s3.rb +117 -38
- data/lib/shrine/version.rb +1 -1
- data/shrine.gemspec +8 -8
- metadata +42 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c012576836f3a56efa91be436999395123342c4650f76f152ef64808ce82ddb
|
4
|
+
data.tar.gz: 5be0e63b923ba28f838b4f692758af3eb975c937c4b63453acc629aab0240eac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: da5eb6be96c3cacb0575e124b4238c038709d44f33085a5bfb4ae6cb1702d079ba0c7f1ca775828812c6d8cacd1b75aa5979c564dbe0e1ea52fea0036c0f577e
|
7
|
+
data.tar.gz: fe12c86f5d826581e4ecb1b5e1951ca1330c8d6d288d19001badd79dab646013a2e57faf51d6cf7369669515636dda3058bf42a2128ea04934f7b611a53e97dd
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,85 @@
|
|
1
|
+
## 3.4.0 (2021-06-14)
|
2
|
+
|
3
|
+
* `base` – Fix passing options to `Shrine.Attachment` on Ruby 3.0 (@lucianghinda)
|
4
|
+
|
5
|
+
* `determine_mime_type` – Return correct `image/svg+xml` MIME type for SVGs with `:fastimage` analyzer (@Bandes)
|
6
|
+
|
7
|
+
* `activerecord` – Fix keyword argument warning when adding errors with options (@janko)
|
8
|
+
|
9
|
+
* `entity` – Make `Attacher#read` method public (@janko)
|
10
|
+
|
11
|
+
* `entity` – Reset attachment dirty tracking in `Attacher#reload` (@janko)
|
12
|
+
|
13
|
+
* `activerecord` – Don't load the attacher on `ActiveRecord::Base#reload` if it hasn't yet been initialized (@janko)
|
14
|
+
|
15
|
+
* `sequel` – Don't load the attacher on `Sequel::Model#reload` if it hasn't yet been initialized (@janko)
|
16
|
+
|
17
|
+
## 3.3.0 (2020-10-04)
|
18
|
+
|
19
|
+
* `s3` - Support new `Aws::S3::EncryptionV2::Client` for client-side encryption (@janko)
|
20
|
+
|
21
|
+
* `derivation_endpoint` – Reduce possibility of timing attacks when comparing signatures (@esparta)
|
22
|
+
|
23
|
+
* `derivatives` – Avoid downloading the attached file when calling default no-op processor (@janko)
|
24
|
+
|
25
|
+
* `derivatives` – Add `:download` processor setting for skipping downloading source file (@jrochkind, @janko)
|
26
|
+
|
27
|
+
* `derivatives` – Copy non-file source IO objects into local file before passing them to the processor (@jrochkind)
|
28
|
+
|
29
|
+
* `sequel` – Call `Attacher#reload` in `Sequel::Model#reload`, which keeps rest of attacher state (@janko, @jrochkind)
|
30
|
+
|
31
|
+
* `activerecord` – Call `Attacher#reload` in `ActiveRecord::Base#reload`, which keeps rest of attacher state (@janko, @jrochkind)
|
32
|
+
|
33
|
+
* `add_metadata` – Add `:skip_nil` option for excluding metadata keys whose values are nil (@renchap)
|
34
|
+
|
35
|
+
* `store_dimensions` – Add `:auto_extraction` option for disabling automatically extracting dimensions on upload (@renchap)
|
36
|
+
|
37
|
+
* `mirroring` – Forward original upload options when mirroring upload (@corneverbruggen)
|
38
|
+
|
39
|
+
* `derivation_endpoint` – Apply `version` URL option in derivation endpoint (@janko)
|
40
|
+
|
41
|
+
* `remove_attachment` – Delete removed file if a new file was attached right after removal (@janko)
|
42
|
+
|
43
|
+
* `upload_endpoint` – Fix `Shrine.upload_response` not working in a Rails controller (@pldavid2)
|
44
|
+
|
45
|
+
* `presign_endpoint` – Add `OPTIONS` route that newer versions of Uppy check (@janko)
|
46
|
+
|
47
|
+
* `derivatives` – Add `:create_on_promote` option for auto-creating derivatives on promotion (@janko)
|
48
|
+
|
49
|
+
* `s3` – Add back support for client-side encryption (@janko)
|
50
|
+
|
51
|
+
* `memory` – Ensure `Memory#open` returns content in original encoding (@jrochkind)
|
52
|
+
|
53
|
+
## 3.2.2 (2020-08-05)
|
54
|
+
|
55
|
+
* `s3` – Fix `S3#open` not working on aws-sdk-core 3.104 and above (@janko)
|
56
|
+
|
57
|
+
## 3.2.1 (2020-01-12)
|
58
|
+
|
59
|
+
* `derivation_endpoint` – Use `Rack::Files` constant on Rack >= 2.1 (@janko)
|
60
|
+
|
61
|
+
* Fix Ruby 2.7 warnings regarding separation of positional and keyword arguments (@janko)
|
62
|
+
|
63
|
+
* `s3` – Make `S3#open` handle empty S3 objects (@janko)
|
64
|
+
|
65
|
+
## 3.2.0 (2019-12-17) [[release notes]](https://shrinerb.com/docs/release_notes/3.2.0)
|
66
|
+
|
67
|
+
* `validation` – Run validation on `Attacher#attach` & `Attacher#attach_cached` instead of `Attacher#change` (@janko)
|
68
|
+
|
69
|
+
* `remove_invalid` – Activate also when `Attacher#validate` is run manually (@janko)
|
70
|
+
|
71
|
+
* `remove_invalid` – Fix incompatibility with `derivatives` plugin (@janko)
|
72
|
+
|
73
|
+
* `type_predicates` – Add new plugin with convenient `UploadedFile` predicate methods based on MIME type (@janko)
|
74
|
+
|
75
|
+
* `core` – Allow assigning back current attached file data (@janko)
|
76
|
+
|
77
|
+
* `derivatives` – Fix `:derivative` value inconsistency when derivatives are being promoted (@janko)
|
78
|
+
|
79
|
+
* `add_metadata` – Add `#add_metadata` method for adding metadata to uploaded files (@janko)
|
80
|
+
|
81
|
+
* `derivatives` – Add `:io` and `:attacher` values to instrumentation event payload (@janko)
|
82
|
+
|
1
83
|
## 3.1.0 (2019-11-15) [[release notes]](https://shrinerb.com/docs/release_notes/3.1.0)
|
2
84
|
|
3
85
|
* `default_storage` – Coerce storage key to symbol in `Attacher#cache_key` & `Attacher#store_key` (@janko)
|
data/README.md
CHANGED
@@ -8,7 +8,7 @@ Shrine is a toolkit for handling file attachments in Ruby applications. Some hig
|
|
8
8
|
* **Memory friendly** – streaming uploads and [downloads][Retrieving Uploads] make it work great with large files
|
9
9
|
* **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and others
|
10
10
|
* **Persistence integrations** – works with [Sequel], [ActiveRecord], [ROM], [Hanami] and [Mongoid] and others
|
11
|
-
* **Flexible processing** – generate thumbnails [
|
11
|
+
* **Flexible processing** – generate thumbnails [eagerly] or [on-the-fly] using [ImageMagick] or [libvips]
|
12
12
|
* **Metadata validation** – [validate files][validation] based on [extracted metadata][metadata]
|
13
13
|
* **Direct uploads** – upload asynchronously [to your app][simple upload] or [to the cloud][presigned upload] using [Uppy]
|
14
14
|
* **Resumable uploads** – make large file uploads [resumable][resumable upload] on [S3][uppy-s3_multipart] or [tus][tus-ruby-server]
|
@@ -57,8 +57,9 @@ Next, add the `<name>_data` column to the table you want to attach files to. For
|
|
57
57
|
an "image" attachment on a `photos` table this would be an `image_data` column:
|
58
58
|
|
59
59
|
```
|
60
|
-
$ rails generate migration add_image_data_to_photos image_data:text
|
60
|
+
$ rails generate migration add_image_data_to_photos image_data:text # or :jsonb
|
61
61
|
```
|
62
|
+
If using `jsonb` consider adding a [gin index] for fast key-value pair searchability within `image_data`.
|
62
63
|
|
63
64
|
Now create an uploader class (which you can put in `app/uploaders`) and
|
64
65
|
register the attachment on your model:
|
@@ -127,6 +128,10 @@ system.
|
|
127
128
|
* Refile
|
128
129
|
* Active Storage
|
129
130
|
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
Please refer to the [contributing page][Contributing].
|
134
|
+
|
130
135
|
## Code of Conduct
|
131
136
|
|
132
137
|
Everyone interacting in the Shrine project’s codebases, issue trackers, and
|
@@ -149,8 +154,8 @@ The gem is available as open source under the terms of the [MIT License].
|
|
149
154
|
[ROM]: https://github.com/shrinerb/shrine-rom
|
150
155
|
[Hanami]: https://github.com/katafrakt/hanami-shrine
|
151
156
|
[Mongoid]: https://github.com/shrinerb/shrine-mongoid
|
152
|
-
[
|
153
|
-
[on-the-fly]: https://shrinerb.com/docs/getting-started#
|
157
|
+
[eagerly]: https://shrinerb.com/docs/getting-started#eager-processing
|
158
|
+
[on-the-fly]: https://shrinerb.com/docs/getting-started#on-the-fly-processing
|
154
159
|
[ImageMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
|
155
160
|
[libvips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
|
156
161
|
[validation]: https://shrinerb.com/docs/validation
|
@@ -170,3 +175,5 @@ The gem is available as open source under the terms of the [MIT License].
|
|
170
175
|
[Roda]: https://github.com/jeremyevans/roda
|
171
176
|
[CoC]: /CODE_OF_CONDUCT.md
|
172
177
|
[MIT License]: /LICENSE.txt
|
178
|
+
[Contributing]: https://github.com/shrinerb/shrine/blob/master/CONTRIBUTING.md
|
179
|
+
[gin index]: https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING
|
data/doc/advantages.md
CHANGED
@@ -93,7 +93,7 @@ low-level abstractions that give you the flexibility to build your own flow.
|
|
93
93
|
```rb
|
94
94
|
uploaded_file = ImageUploader.upload(image, :store) # metadata extraction, upload location generation
|
95
95
|
uploaded_file.id #=> "44ccafc10ce6a4ff22829e8f579ee6b9.jpg"
|
96
|
-
|
96
|
+
uploaded_file.metadata #=> { ... extracted metadata ... }
|
97
97
|
|
98
98
|
data = uploaded_file.to_json # serialization
|
99
99
|
# ...
|
@@ -157,14 +157,14 @@ end
|
|
157
157
|
|
158
158
|
## Processing
|
159
159
|
|
160
|
-
Most file attachment libraries
|
161
|
-
(Paperclip, CarrierWave) or on-the-fly (Dragonfly, Refile, Active Storage).
|
160
|
+
Most file attachment libraries allow you to process files either "eagerly"
|
161
|
+
(Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active Storage).
|
162
162
|
However, each approach is suitable for different requirements. For instance,
|
163
163
|
while on-the-fly processing is suitable for fast processing (image thumbnails,
|
164
164
|
document previews), longer running processing (video transcoding, raw images)
|
165
165
|
should be moved into a background job.
|
166
166
|
|
167
|
-
That's why Shrine supports both [
|
167
|
+
That's why Shrine supports both [eager][derivatives] and
|
168
168
|
[on-the-fly][derivation_endpoint] processing. For example, if you're handling
|
169
169
|
image uploads, you can choose to either generate a set of pre-defined
|
170
170
|
thumbnails during attachment:
|
data/doc/attacher.md
CHANGED
@@ -130,8 +130,8 @@ attacher.attach_cached(file)
|
|
130
130
|
|
131
131
|
# sets cached file
|
132
132
|
attacher.attach_cached('{"id":"asdf.jpg","storage":"cache","metadata":{...}}')
|
133
|
-
attacher.attach_cached("id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... })
|
134
|
-
attacher.attach_cached(id: "asdf.jpg", storage: "cache", metadata: { ... })
|
133
|
+
attacher.attach_cached({ "id" => "asdf.jpg", "storage" => "cache", "metadata" => { ... } })
|
134
|
+
attacher.attach_cached({ id: "asdf.jpg", storage: "cache", metadata: { ... } })
|
135
135
|
|
136
136
|
# unsets attached file
|
137
137
|
attacher.attach_cached(nil)
|
data/doc/carrierwave.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
---
|
2
|
-
title:
|
2
|
+
title: Upgrading from CarrierWave
|
3
3
|
---
|
4
4
|
|
5
5
|
This guide is aimed at helping CarrierWave users transition to Shrine, and it
|
@@ -322,19 +322,22 @@ module CarrierwaveShrineSynchronization
|
|
322
322
|
|
323
323
|
private
|
324
324
|
|
325
|
-
# If you'll be using `:prefix` on your Shrine storage, make sure to
|
326
|
-
# subtract it from the path assigned as `:id`.
|
327
325
|
def shrine_file(uploader)
|
328
326
|
name = uploader.mounted_as
|
329
327
|
filename = read_attribute(name)
|
330
|
-
|
328
|
+
location = uploader.store_path(filename)
|
329
|
+
location = location.sub(%r{^#{storage.prefix}/}, "") if storage.prefix
|
331
330
|
|
332
331
|
Shrine.uploaded_file(
|
333
332
|
storage: :store,
|
334
|
-
id:
|
333
|
+
id: location,
|
335
334
|
metadata: { "filename" => filename },
|
336
335
|
)
|
337
336
|
end
|
337
|
+
|
338
|
+
def storage
|
339
|
+
Shrine.storages[:store]
|
340
|
+
end
|
338
341
|
end
|
339
342
|
```
|
340
343
|
```rb
|
@@ -476,16 +479,25 @@ photo.image.original_filename #=> "avatar.jpg"
|
|
476
479
|
|
477
480
|
#### `#store_dir`, `#cache_dir`
|
478
481
|
|
479
|
-
Shrine here provides a `#generate_location` method
|
480
|
-
storages:
|
482
|
+
Shrine here provides a single `#generate_location` method that's triggered for
|
483
|
+
all storages:
|
481
484
|
|
482
485
|
```rb
|
483
486
|
class ImageUploader < Shrine
|
484
|
-
def generate_location(io, record: nil, **)
|
485
|
-
|
487
|
+
def generate_location(io, record: nil, name: nil, **)
|
488
|
+
[ storage_key,
|
489
|
+
record && record.class.name.underscore,
|
490
|
+
record && record.id,
|
491
|
+
super,
|
492
|
+
io.original_filename ].compact.join("/")
|
486
493
|
end
|
487
494
|
end
|
488
495
|
```
|
496
|
+
```
|
497
|
+
cache/user/123/2feff8c724e7ce17/nature.jpg
|
498
|
+
store/user/456/7f99669fde1e01fc/kitten.jpg
|
499
|
+
...
|
500
|
+
```
|
489
501
|
|
490
502
|
You might also want to use the `pretty_location` plugin for automatically
|
491
503
|
generating an organized folder structure.
|
@@ -498,8 +510,8 @@ For default URLs you can use the `default_url` plugin:
|
|
498
510
|
class ImageUploader < Shrine
|
499
511
|
plugin :default_url
|
500
512
|
|
501
|
-
Attacher.default_url do |
|
502
|
-
"/
|
513
|
+
Attacher.default_url do |derivative: nil, **|
|
514
|
+
"/fallbacks/#{derivative || "original"}.jpg"
|
503
515
|
end
|
504
516
|
end
|
505
517
|
```
|
@@ -654,7 +666,7 @@ shows what are Shrine's equivalents.
|
|
654
666
|
|
655
667
|
#### `root`, `base_path`, `permissions`, `directory_permissions`
|
656
668
|
|
657
|
-
In Shrine these are configured on the FileSystem storage directly.
|
669
|
+
In Shrine these are configured on the `FileSystem` storage directly.
|
658
670
|
|
659
671
|
#### `storage`, `storage_engines`
|
660
672
|
|
data/doc/changing_derivatives.md
CHANGED
data/doc/changing_location.md
CHANGED
@@ -53,16 +53,17 @@ Photo.find_each do |photo|
|
|
53
53
|
next unless attacher.stored? # move only attachments uploaded to permanent storage
|
54
54
|
|
55
55
|
old_attacher = attacher.dup
|
56
|
+
current_file = old_attacher.file
|
56
57
|
|
57
58
|
attacher.set attacher.upload(attacher.file) # reupload file
|
58
59
|
attacher.set_derivatives attacher.upload_derivatives(attacher.derivatives) # reupload derivatives if you have derivatives
|
59
60
|
|
60
61
|
begin
|
61
|
-
attacher.atomic_persist
|
62
|
-
old_attacher.destroy_attached
|
63
|
-
rescue Shrine::AttachmentChanged,
|
64
|
-
ActiveRecord::RecordNotFound
|
65
|
-
attacher.destroy_attached
|
62
|
+
attacher.atomic_persist(current_file) # persist changes if attachment has not changed in the meantime
|
63
|
+
old_attacher.destroy_attached # delete files on old location
|
64
|
+
rescue Shrine::AttachmentChanged, # attachment has changed during reuploading
|
65
|
+
ActiveRecord::RecordNotFound # record has been deleted during reuploading
|
66
|
+
attacher.destroy_attached # delete now orphaned files
|
66
67
|
end
|
67
68
|
end
|
68
69
|
```
|
data/doc/design.md
CHANGED
@@ -2,22 +2,25 @@
|
|
2
2
|
title: The Design of Shrine
|
3
3
|
---
|
4
4
|
|
5
|
-
*If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
|
5
|
+
*If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
|
6
|
+
study of shrine implementation] article by Jonathan Rochkind.*
|
6
7
|
|
7
|
-
There are five main types of
|
8
|
+
There are five main types of classes that you deal with in Shrine:
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
| Class | Description |
|
11
|
+
| :---- | :---------- |
|
12
|
+
| [`Shrine::Storage::*`](#Storage) | Manages files on a particular storage service |
|
13
|
+
| [`Shrine`](#Shrine) | Wraps uploads and handles loading plugins |
|
14
|
+
| [`Shrine::UploadedFile`](#shrineuploadedfile) | Represents a file uploaded to a storage |
|
15
|
+
| [`Shrine::Attacher`](#shrineattacher) | Handles file attachment logic |
|
16
|
+
| [`Shrine::Attachment`](#shrineattachment) | Provides convenience model attachment interface |
|
14
17
|
|
15
18
|
## Storage
|
16
19
|
|
17
20
|
On the lowest level we have a storage. A storage class encapsulates file
|
18
21
|
management logic on a particular service. It is what actually performs uploads,
|
19
22
|
generation of URLs, deletions and similar. By convention it is namespaced under
|
20
|
-
`Shrine::Storage
|
23
|
+
`Shrine::Storage::*`.
|
21
24
|
|
22
25
|
```rb
|
23
26
|
filesystem = Shrine::Storage::FileSystem.new("uploads")
|
@@ -26,7 +29,7 @@ filesystem.url("foo") #=> "uploads/foo"
|
|
26
29
|
filesystem.delete("foo")
|
27
30
|
```
|
28
31
|
|
29
|
-
A storage is a PORO which
|
32
|
+
A storage is a PORO which implements the following interface:
|
30
33
|
|
31
34
|
```rb
|
32
35
|
class Shrine
|
@@ -56,13 +59,14 @@ class Shrine
|
|
56
59
|
end
|
57
60
|
```
|
58
61
|
|
59
|
-
Storages are typically not used directly, but through `Shrine
|
62
|
+
Storages are typically not used directly, but through [`Shrine`](#shrine) and
|
63
|
+
[`Shrine::UploadedFile`](#shrine-uploadedfile) classes.
|
60
64
|
|
61
65
|
## `Shrine`
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
name:
|
67
|
+
The `Shrine` class (also called an "uploader") primarily provides a wrapper
|
68
|
+
method around `Storage#upload`. First, the storage needs to be registered under
|
69
|
+
a name:
|
66
70
|
|
67
71
|
```rb
|
68
72
|
Shrine.storages[:disk] = Shrine::Storage::FileSystem.new("uploads")
|
@@ -72,7 +76,7 @@ Now we can upload files to the registered storage:
|
|
72
76
|
|
73
77
|
```rb
|
74
78
|
uploaded_file = Shrine.upload(file, :disk)
|
75
|
-
uploaded_file #=> #<Shrine::UploadedFile
|
79
|
+
uploaded_file #=> #<Shrine::UploadedFile storage=:disk id="6a9fb596cc554efb" ...>
|
76
80
|
```
|
77
81
|
|
78
82
|
The argument to `Shrine#upload` must be an IO-like object. The method does the
|
@@ -84,63 +88,97 @@ following:
|
|
84
88
|
* closes the file
|
85
89
|
* creates a `Shrine::UploadedFile` from the data
|
86
90
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
plugin
|
91
|
+
### Plugins
|
92
|
+
|
93
|
+
The `Shrine` class is also used for loading plugins, which provide additional
|
94
|
+
functionality by extending core classes.
|
95
|
+
|
96
|
+
```rb
|
97
|
+
Shrine.plugin :derivatives
|
98
|
+
|
99
|
+
Shrine::UploadedFile.ancestors #=> [..., Shrine::Plugins::Derivatives::FileMethods, Shrine::UploadedFile::InstanceMethods, ...]
|
100
|
+
Shrine::Attacher.ancestors #=> [..., Shrine::Plugins::Derivatives::AttacherMethods, Shrine::Attacher::InstanceMethods, ...]
|
101
|
+
Shrine::Attachment.ancestors #=> [..., Shrine::Plugins::Derivatives::AttachmentMethods, Shrine::Attachment::InstanceMethods, ...]
|
102
|
+
```
|
103
|
+
|
104
|
+
The plugins store their configuration in `Shrine.opts`:
|
105
|
+
|
106
|
+
```rb
|
107
|
+
Shrine.plugin :derivation_endpoint, secret_key: "foo"
|
108
|
+
Shrine.plugin :default_storage, store: :other_store
|
109
|
+
Shrine.plugin :activerecord
|
110
|
+
|
111
|
+
Shrine.opts #=>
|
112
|
+
# { derivation_endpoint: { options: { secret_key: "foo" }, derivations: {} },
|
113
|
+
# default_storage: { store: :other_store },
|
114
|
+
# column: { serializer: Shrine::Plugins::Column::JsonSerializer },
|
115
|
+
# model: { cache: true },
|
116
|
+
# activerecord: { callbacks: true, validations: true } }
|
117
|
+
```
|
118
|
+
|
119
|
+
Each `Shrine` subclass has its own copy of the core classes, storages and
|
120
|
+
options, which makes it possible to customize attachment logic per uploader.
|
121
|
+
|
122
|
+
```rb
|
123
|
+
MyUploader = Class.new(Shrine)
|
124
|
+
MyUploader::UploadedFile.superclass #=> Shrine::UploadedFile
|
125
|
+
MyUploader::Attacher.superclass #=> Shrine::Attacher
|
126
|
+
MyUploader::Attachment.superclass #=> Shrine::Attachment
|
127
|
+
```
|
128
|
+
|
129
|
+
See [Creating a New Plugin] guide and the [Plugin system of Sequel and Roda]
|
130
|
+
article for more details on the design of Shrine's plugin system.
|
94
131
|
|
95
132
|
## `Shrine::UploadedFile`
|
96
133
|
|
97
|
-
`Shrine::UploadedFile` represents a file that was uploaded to a
|
98
|
-
|
99
|
-
|
134
|
+
A `Shrine::UploadedFile` object represents a file that was uploaded to a
|
135
|
+
storage, containing upload location, storage, and any metadata extracted during
|
136
|
+
the upload.
|
100
137
|
|
101
138
|
```rb
|
102
|
-
uploaded_file
|
103
|
-
|
139
|
+
uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
|
140
|
+
|
141
|
+
uploaded_file.id #=> "949sdjg834.jpg"
|
142
|
+
uploaded_file.storage_key #=> :store
|
143
|
+
uploaded_file.storage #=> #<Shrine::Storage::S3>
|
144
|
+
uploaded_file.metadata #=> {...}
|
145
|
+
```
|
146
|
+
|
147
|
+
It has convenience methods for accessing metadata:
|
148
|
+
|
149
|
+
```rb
|
150
|
+
uploaded_file.metadata #=>
|
104
151
|
# {
|
105
|
-
# "
|
106
|
-
# "
|
107
|
-
# "
|
108
|
-
# "filename" => "resume.pdf",
|
109
|
-
# "mime_type" => "application/pdf",
|
110
|
-
# "size" => 983294,
|
111
|
-
# },
|
152
|
+
# "filename" => "matrix.mp4",
|
153
|
+
# "mime_type" => "video/mp4",
|
154
|
+
# "size" => 345993,
|
112
155
|
# }
|
156
|
+
|
157
|
+
uploaded_file.original_filename #=> "matrix.mp4"
|
158
|
+
uploaded_file.extension #=> "mp4"
|
159
|
+
uploaded_file.mime_type #=> "video/mp4"
|
160
|
+
uploaded_file.size #=> 345993
|
113
161
|
```
|
114
162
|
|
115
|
-
|
116
|
-
some metadata: original filename, MIME type and filesize. The
|
117
|
-
`Shrine::UploadedFile` object has handy methods which use this data:
|
163
|
+
It also has methods that delegate to the storage:
|
118
164
|
|
119
165
|
```rb
|
120
|
-
|
121
|
-
uploaded_file.
|
122
|
-
uploaded_file.
|
123
|
-
uploaded_file.
|
124
|
-
|
125
|
-
|
126
|
-
# storage methods
|
127
|
-
uploaded_file.url
|
128
|
-
uploaded_file.exists?
|
129
|
-
uploaded_file.open
|
130
|
-
uploaded_file.download
|
131
|
-
uploaded_file.delete
|
132
|
-
# ...
|
166
|
+
uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
|
167
|
+
uploaded_file.open { |io| ... } # opens the uploaded file stream
|
168
|
+
uploaded_file.download { |file| ... } # downloads the uploaded file to disk
|
169
|
+
uploaded_file.stream(destination) # streams uploaded content into a writable destination
|
170
|
+
uploaded_file.exists? #=> true
|
171
|
+
uploaded_file.delete # deletes the uploaded file from the storage
|
133
172
|
```
|
134
173
|
|
135
|
-
A `Shrine::UploadedFile` is itself an IO-like object (
|
136
|
-
|
174
|
+
A `Shrine::UploadedFile` is itself an IO-like object (built on top of
|
175
|
+
`Storage#open`), so it can be passed to `Shrine#upload` as well.
|
137
176
|
|
138
177
|
## `Shrine::Attacher`
|
139
178
|
|
140
179
|
We usually want to treat uploaded files as *attachments* to records, saving
|
141
|
-
their data into a database column. This is
|
142
|
-
|
143
|
-
`Shrine::UploadedFile` objects internally.
|
180
|
+
their data into a database column. This is done by `Shrine::Attacher`, which
|
181
|
+
internally uses `Shrine` and `Shrine::UploadedFile` classes.
|
144
182
|
|
145
183
|
The attaching process requires a temporary and a permanent storage to be
|
146
184
|
registered (by default that's `:cache` and `:store`):
|
@@ -152,40 +190,50 @@ Shrine.storages = {
|
|
152
190
|
}
|
153
191
|
```
|
154
192
|
|
155
|
-
A `Shrine::Attacher`
|
156
|
-
|
193
|
+
A `Shrine::Attacher` can be initialized standalone and handle the common
|
194
|
+
attachment flow, which includes dirty tracking (promoting cached file to
|
195
|
+
permanent storage, deleting previously attached file), validation, processing,
|
196
|
+
serialization etc.
|
197
|
+
|
198
|
+
```rb
|
199
|
+
attacher = Shrine::Attacher.new
|
200
|
+
|
201
|
+
# ... user uploads a file ...
|
202
|
+
|
203
|
+
attacher.assign(io) # uploads to temporary storage
|
204
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
|
205
|
+
|
206
|
+
# ... handle file validations ...
|
207
|
+
|
208
|
+
attacher.finalize # uploads to permanent storage
|
209
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
|
210
|
+
```
|
211
|
+
|
212
|
+
It can also be initialized with a model instance to handle serialization into a
|
213
|
+
model attribute:
|
157
214
|
|
158
215
|
```rb
|
159
216
|
attacher = Shrine::Attacher.from_model(photo, :image)
|
160
217
|
|
161
218
|
attacher.assign(file)
|
162
|
-
|
163
|
-
attacher.record.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
219
|
+
photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
164
220
|
|
165
221
|
attacher.finalize
|
166
|
-
|
167
|
-
attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
|
222
|
+
photo.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
|
168
223
|
```
|
169
224
|
|
170
|
-
|
171
|
-
|
172
|
-
permanent storage. Behind the scenes a cached `Shrine::UploadedFile` is given
|
173
|
-
to `Shrine#upload`, which works because `Shrine::UploadedFile` is an IO-like
|
174
|
-
object. After both caching and promoting the data hash of the uploaded file is
|
175
|
-
assigned to the record's column as JSON.
|
176
|
-
|
177
|
-
For more details see [Using Attacher].
|
225
|
+
For more details, see the [Using Attacher] guide and
|
226
|
+
[`entity`][entity]/[`model`][model] plugins.
|
178
227
|
|
179
228
|
## `Shrine::Attachment`
|
180
229
|
|
181
|
-
`Shrine::Attachment`
|
182
|
-
`Shrine::
|
183
|
-
|
184
|
-
means that an instance of `Shrine::Attachment` is a module:
|
230
|
+
A `Shrine::Attachment` module provides a convenience model interface around the
|
231
|
+
`Shrine::Attacher` object. The `Shrine::Attachment` class is a subclass of
|
232
|
+
`Module`, which means that an instance of `Shrine::Attachment` is a module:
|
185
233
|
|
186
234
|
```rb
|
187
235
|
Shrine::Attachment.new(:image).is_a?(Module) #=> true
|
188
|
-
Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher]
|
236
|
+
Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher, ...]
|
189
237
|
|
190
238
|
# equivalents
|
191
239
|
Shrine::Attachment.new(:image)
|
@@ -193,30 +241,31 @@ Shrine::Attachment[:image]
|
|
193
241
|
Shrine::Attachment(:image)
|
194
242
|
```
|
195
243
|
|
196
|
-
We can include this module
|
244
|
+
We can include this module into a model:
|
197
245
|
|
198
246
|
```rb
|
199
|
-
|
200
|
-
include Shrine::Attachment(:image)
|
201
|
-
end
|
247
|
+
Photo.include Shrine::Attachment(:image)
|
202
248
|
```
|
203
249
|
```rb
|
204
|
-
photo.image = file
|
205
|
-
photo.image
|
206
|
-
photo.image_url
|
250
|
+
photo.image = file # shorthand for `photo.image_attacher.assign(file)`
|
251
|
+
photo.image # shorthand for `photo.image_attacher.get`
|
252
|
+
photo.image_url # shorthand for `photo.image_attacher.url`
|
207
253
|
|
208
|
-
photo.image_attacher #=> #<Shrine::Attacher
|
254
|
+
photo.image_attacher #=> #<Shrine::Attacher @cache_key=:cache @store_key=:store ...>
|
209
255
|
```
|
210
256
|
|
211
|
-
When a persistence plugin is loaded
|
212
|
-
automatically:
|
257
|
+
When a persistence plugin is loaded ([`activerecord`][activerecord],
|
258
|
+
[`sequel`][sequel]), the `Shrine::Attachment` module also automatically:
|
213
259
|
|
214
260
|
* syncs Shrine's validation errors with the record
|
215
261
|
* triggers promoting after record is saved
|
216
|
-
* deletes the uploaded file if attachment was replaced
|
217
|
-
destroyed
|
262
|
+
* deletes the uploaded file if attachment was replaced or the record destroyed
|
218
263
|
|
219
264
|
[Using Attacher]: https://shrinerb.com/docs/attacher
|
220
265
|
[Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
|
221
266
|
[Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
|
222
267
|
[Plugin system of Sequel and Roda]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
|
268
|
+
[entity]: https://shrinerb.com/docs/plugins/entity
|
269
|
+
[model]: https://shrinerb.com/docs/plugins/model
|
270
|
+
[activerecord]: https://shrinerb.com/docs/plugins/activerecord
|
271
|
+
[sequel]: https://shrinerb.com/docs/plugins/sequel
|