shrine 3.1.0 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|