shrine 2.19.4 → 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 +485 -43
- data/LICENSE.txt +1 -1
- data/README.md +81 -977
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +102 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1115 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +62 -34
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +162 -101
- data/doc/plugins/derivatives.md +829 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +14 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +185 -167
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +4 -0
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- 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/retrieving_uploads.md +4 -1
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +117 -83
- data/doc/testing.md +124 -144
- data/doc/upgrading_to_3.md +710 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +287 -171
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
- data/lib/shrine/plugins/derivatives.rb +637 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +109 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +152 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +13 -20
- data/lib/shrine/plugins/instrumentation.rb +54 -42
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +158 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +18 -22
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +15 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +10 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +25 -23
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +34 -57
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +154 -158
- data/lib/shrine/uploaded_file.rb +28 -30
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +86 -149
- data/shrine.gemspec +9 -10
- metadata +79 -83
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
data/README.md
CHANGED
@@ -1,67 +1,44 @@
|
|
1
1
|
# [Shrine]
|
2
2
|
|
3
|
-
|
3
|
+
<img src="https://shrinerb.com/img/logo.png" width="100" alt="Shrine logo: a red paperclip" align="right" />
|
4
|
+
|
5
|
+
Shrine is a toolkit for handling file attachments in Ruby applications. Some highlights:
|
4
6
|
|
5
7
|
* **Modular design** – the [plugin system] allows you to load only the functionality you need
|
6
8
|
* **Memory friendly** – streaming uploads and [downloads][Retrieving Uploads] make it work great with large files
|
7
|
-
* **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and
|
8
|
-
* **
|
9
|
-
* **Flexible processing** – generate thumbnails [
|
9
|
+
* **Cloud storage** – store files on [disk][FileSystem], [AWS S3][S3], [Google Cloud][GCS], [Cloudinary] and others
|
10
|
+
* **Persistence integrations** – works with [Sequel], [ActiveRecord], [ROM], [Hanami] and [Mongoid] and others
|
11
|
+
* **Flexible processing** – generate thumbnails [eagerly] or [on-the-fly] using [ImageMagick] or [libvips]
|
10
12
|
* **Metadata validation** – [validate files][validation] based on [extracted metadata][metadata]
|
11
13
|
* **Direct uploads** – upload asynchronously [to your app][simple upload] or [to the cloud][presigned upload] using [Uppy]
|
12
14
|
* **Resumable uploads** – make large file uploads [resumable][resumable upload] on [S3][uppy-s3_multipart] or [tus][tus-ruby-server]
|
13
15
|
* **Background jobs** – built-in support for [background processing][backgrounding] that supports [any backgrounding library][Backgrounding Libraries]
|
14
16
|
|
15
|
-
If you're curious how it compares to other file attachment libraries, see the
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
| Resource | URL |
|
20
|
-
| :---------------- | :----------------------------------------------------------------------------------------- |
|
21
|
-
| Website | [shrinerb.com](https://shrinerb.com) |
|
22
|
-
| Demo code | [Roda][roda demo] / [Rails][rails demo] |
|
23
|
-
| Source | [github.com/shrinerb/shrine](https://github.com/shrinerb/shrine) |
|
24
|
-
| Wiki | [github.com/shrinerb/shrine/wiki](https://github.com/shrinerb/shrine/wiki) |
|
25
|
-
| Bugs | [github.com/shrinerb/shrine/issues](https://github.com/shrinerb/shrine/issues) |
|
26
|
-
| Help & Discussion | [groups.google.com/group/ruby-shrine](https://groups.google.com/forum/#!forum/ruby-shrine) |
|
17
|
+
If you're curious how it compares to other file attachment libraries, see the
|
18
|
+
[Advantages of Shrine]. Otherwise, follow along with the **[Getting Started
|
19
|
+
guide]**.
|
27
20
|
|
28
|
-
##
|
21
|
+
## Links
|
29
22
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
* [Attachment](#attachment)
|
37
|
-
* [Attacher](#attacher)
|
38
|
-
* [Plugin system](#plugin-system)
|
39
|
-
* [Metadata](#metadata)
|
40
|
-
* [MIME type](#mime-type)
|
41
|
-
* [Other metadata](#other-metadata)
|
42
|
-
* [Processing](#processing)
|
43
|
-
* [Processing on upload](#processing-on-upload)
|
44
|
-
* [Processing on-the-fly](#processing-on-the-fly)
|
45
|
-
* [Validation](#validation)
|
46
|
-
* [Location](#location)
|
47
|
-
* [Direct uploads](#direct-uploads)
|
48
|
-
- [Simple direct upload](#simple-direct-upload)
|
49
|
-
- [Presigned direct upload](#presigned-direct-upload)
|
50
|
-
- [Resumable direct upload](#resumable-direct-upload)
|
51
|
-
* [Backgrounding](#backgrounding)
|
52
|
-
* [Clearing cache](#clearing-cache)
|
53
|
-
* [Logging](#logging)
|
23
|
+
| Resource | URL |
|
24
|
+
| :---------------- | :----------------------------------------------------------------------------- |
|
25
|
+
| Website & Documentation | [shrinerb.com](https://shrinerb.com) |
|
26
|
+
| Demo code | [Roda][roda demo] / [Rails][rails demo] |
|
27
|
+
| Wiki | [github.com/shrinerb/shrine/wiki](https://github.com/shrinerb/shrine/wiki) |
|
28
|
+
| Help & Discussion | [discourse.shrinerb.com](https://discourse.shrinerb.com) |
|
54
29
|
|
55
|
-
##
|
30
|
+
## Setup
|
56
31
|
|
57
|
-
Add
|
58
|
-
loads the ORM plugin:
|
32
|
+
Add the gem to your Gemfile:
|
59
33
|
|
60
34
|
```rb
|
61
35
|
# Gemfile
|
62
|
-
gem "shrine", "~>
|
36
|
+
gem "shrine", "~> 3.0"
|
63
37
|
```
|
64
38
|
|
39
|
+
Then add `config/initializers/shrine.rb` which sets up the storage and loads
|
40
|
+
ORM integration:
|
41
|
+
|
65
42
|
```rb
|
66
43
|
require "shrine"
|
67
44
|
require "shrine/storage/file_system"
|
@@ -71,97 +48,51 @@ Shrine.storages = {
|
|
71
48
|
store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"), # permanent
|
72
49
|
}
|
73
50
|
|
74
|
-
Shrine.plugin :
|
75
|
-
Shrine.plugin :cached_attachment_data #
|
76
|
-
Shrine.plugin :restore_cached_data
|
77
|
-
Shrine.plugin :rack_file # for non-Rails apps
|
51
|
+
Shrine.plugin :activerecord # loads Active Record integration
|
52
|
+
Shrine.plugin :cached_attachment_data # enables retaining cached file across form redisplays
|
53
|
+
Shrine.plugin :restore_cached_data # extracts metadata for assigned cached files
|
78
54
|
```
|
79
55
|
|
80
|
-
Next
|
81
|
-
|
82
|
-
will use to store all information about the attachment:
|
56
|
+
Next, add the `<name>_data` column to the table you want to attach files to. For
|
57
|
+
an "image" attachment on a `photos` table this would be an `image_data` column:
|
83
58
|
|
84
|
-
```rb
|
85
|
-
Sequel.migration do
|
86
|
-
change do
|
87
|
-
add_column :photos, :image_data, :text
|
88
|
-
end
|
89
|
-
end
|
90
59
|
```
|
91
|
-
|
92
|
-
In Rails with Active Record the migration would look similar:
|
93
|
-
|
94
|
-
```sh
|
95
|
-
$ rails generate migration add_image_data_to_photos image_data:text
|
96
|
-
```
|
97
|
-
```rb
|
98
|
-
class AddImageDataToPhotos < ActiveRecord::Migration
|
99
|
-
def change
|
100
|
-
add_column :photos, :image_data, :text
|
101
|
-
end
|
102
|
-
end
|
60
|
+
$ rails generate migration add_image_data_to_photos image_data:text # or :jsonb
|
103
61
|
```
|
62
|
+
If using `jsonb` consider adding a [gin index] for fast key-value pair searchability within `image_data`.
|
104
63
|
|
105
|
-
Now
|
106
|
-
|
107
|
-
your model. If you do not care about adding plugins or additional processing,
|
108
|
-
you can use `Shrine::Attachment`.
|
64
|
+
Now create an uploader class (which you can put in `app/uploaders`) and
|
65
|
+
register the attachment on your model:
|
109
66
|
|
110
67
|
```rb
|
111
68
|
class ImageUploader < Shrine
|
112
69
|
# plugins and uploading logic
|
113
70
|
end
|
114
71
|
```
|
115
|
-
|
116
72
|
```rb
|
117
|
-
class Photo <
|
118
|
-
include ImageUploader::Attachment
|
73
|
+
class Photo < ActiveRecord::Base
|
74
|
+
include ImageUploader::Attachment(:image) # adds an `image` virtual attribute
|
119
75
|
end
|
120
76
|
```
|
121
77
|
|
122
|
-
|
123
|
-
|
124
|
-
uploaded file in case of validation errors and for potential [direct uploads].
|
78
|
+
In our views let's now add form fields for our attachment attribute that will
|
79
|
+
allow users to upload files:
|
125
80
|
|
126
|
-
```
|
127
|
-
|
128
|
-
|
129
|
-
f.
|
130
|
-
f.
|
131
|
-
|
132
|
-
end
|
133
|
-
```
|
134
|
-
```rb
|
135
|
-
# with Simple Form:
|
136
|
-
simple_form_for @photo do |f|
|
137
|
-
f.input :image, as: :hidden, input_html: { value: @photo.cached_image_data }
|
138
|
-
f.input :image, as: :file
|
139
|
-
f.button :submit
|
140
|
-
end
|
141
|
-
```
|
142
|
-
```rb
|
143
|
-
# with Forme:
|
144
|
-
form @photo, action: "/photos", enctype: "multipart/form-data" do |f|
|
145
|
-
f.input :image, type: :hidden, value: @photo.cached_image_data
|
146
|
-
f.input :image, type: :file
|
147
|
-
f.button "Create"
|
148
|
-
end
|
81
|
+
```erb
|
82
|
+
<%= form_for @photo do |f| %>
|
83
|
+
<%= f.hidden_field :image, value: @photo.cached_image_data %>
|
84
|
+
<%= f.file_field :image %>
|
85
|
+
<%= f.submit %>
|
86
|
+
<% end %>
|
149
87
|
```
|
150
88
|
|
151
|
-
|
152
|
-
|
153
|
-
Also notice the `enctype="multipart/form-data"` HTML attribute, which is
|
154
|
-
required for submitting files through the form (the Rails form builder
|
155
|
-
will automatically generate this for you).
|
156
|
-
|
157
|
-
When the form is submitted, in your router/controller you can assign the file
|
158
|
-
from request params to the attachment attribute on the model.
|
89
|
+
When the form is submitted, in your controller you can assign the file from
|
90
|
+
request params to the attachment attribute on the model:
|
159
91
|
|
160
92
|
```rb
|
161
|
-
# In Rails:
|
162
93
|
class PhotosController < ApplicationController
|
163
94
|
def create
|
164
|
-
Photo.create(photo_params)
|
95
|
+
Photo.create(photo_params) # attaches the uploaded file
|
165
96
|
# ...
|
166
97
|
end
|
167
98
|
|
@@ -172,773 +103,15 @@ class PhotosController < ApplicationController
|
|
172
103
|
end
|
173
104
|
end
|
174
105
|
```
|
175
|
-
```rb
|
176
|
-
# In Sinatra:
|
177
|
-
post "/photos" do
|
178
|
-
Photo.create(params[:photo])
|
179
|
-
# ...
|
180
|
-
end
|
181
|
-
```
|
182
106
|
|
183
|
-
Once a file is uploaded and attached to the record, you can retrieve
|
184
|
-
|
107
|
+
Once a file is uploaded and attached to the record, you can retrieve the file
|
108
|
+
URL and display it on the page:
|
185
109
|
|
186
110
|
```erb
|
187
|
-
<!-- In Rails: -->
|
188
111
|
<%= image_tag @photo.image_url %>
|
189
112
|
```
|
190
|
-
```erb
|
191
|
-
<!-- In HTML: -->
|
192
|
-
<img src="<%= @photo.image_url %>" />
|
193
|
-
```
|
194
|
-
|
195
|
-
## Storage
|
196
|
-
|
197
|
-
A "storage" in Shrine is an object that encapsulates communication with a
|
198
|
-
specific storage service, by implementing a common public interface. Storage
|
199
|
-
instances are registered under an identifier in `Shrine.storages`, so that they
|
200
|
-
can later be used by [uploaders][uploader].
|
201
|
-
|
202
|
-
Previously we've shown the [FileSystem] storage which saves files to disk, but
|
203
|
-
Shrine also ships with [S3] storage which stores files on [AWS S3] (or any
|
204
|
-
S3-compatible service such as [DigitalOcean Spaces] or [MinIO]).
|
205
|
-
|
206
|
-
```rb
|
207
|
-
# Gemfile
|
208
|
-
gem "aws-sdk-s3", "~> 1.14" # for AWS S3 storage
|
209
|
-
```
|
210
|
-
```rb
|
211
|
-
require "shrine/storage/s3"
|
212
|
-
|
213
|
-
s3_options = {
|
214
|
-
bucket: "<YOUR BUCKET>", # required
|
215
|
-
access_key_id: "<YOUR ACCESS KEY ID>",
|
216
|
-
secret_access_key: "<YOUR SECRET ACCESS KEY>",
|
217
|
-
region: "<YOUR REGION>",
|
218
|
-
}
|
219
|
-
|
220
|
-
Shrine.storages = {
|
221
|
-
cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
|
222
|
-
store: Shrine::Storage::S3.new(**s3_options),
|
223
|
-
}
|
224
|
-
```
|
225
113
|
|
226
|
-
|
227
|
-
suitable for [direct uploads][Direct Uploads to S3]. The `:cache` and
|
228
|
-
`:store` names are special only in terms that the [attacher] will automatically
|
229
|
-
pick them up, you can also register more storage objects under different names.
|
230
|
-
|
231
|
-
See the [FileSystem] and [S3] storage docs for more details. There are [many
|
232
|
-
more Shrine storages][external] provided by external gems, and you can also
|
233
|
-
[create your own storage][Creating Storages].
|
234
|
-
|
235
|
-
## Uploader
|
236
|
-
|
237
|
-
Uploaders are subclasses of `Shrine`, and they wrap the actual upload to the
|
238
|
-
storage. They perform common tasks around upload that aren't related to a
|
239
|
-
particular storage.
|
240
|
-
|
241
|
-
```rb
|
242
|
-
class MyUploader < Shrine
|
243
|
-
# image attachent logic
|
244
|
-
end
|
245
|
-
```
|
246
|
-
|
247
|
-
It's common to create an uploader for each type of file that you want to handle
|
248
|
-
(`ImageUploader`, `VideoUploader`, `AudioUploader` etc), but really you can
|
249
|
-
organize them in any way you like.
|
250
|
-
|
251
|
-
### Uploading
|
252
|
-
|
253
|
-
The main method of the uploader is `#upload`, which takes an [IO-like
|
254
|
-
object][io abstraction] and a storage identifier on the input, and returns a
|
255
|
-
representation of the [uploaded file] on the output.
|
256
|
-
|
257
|
-
```rb
|
258
|
-
MyUploader.upload(file, :store) #=> #<Shrine::UploadedFile>
|
259
|
-
```
|
260
|
-
|
261
|
-
Internally this instantiates the uploader with the storage and calls `#upload`
|
262
|
-
on it:
|
263
|
-
|
264
|
-
```rb
|
265
|
-
uploader = MyUploader.new(:store)
|
266
|
-
uploader.upload(file) #=> #<Shrine::UploadedFile>
|
267
|
-
```
|
268
|
-
|
269
|
-
Some of the tasks performed by `#upload` include:
|
270
|
-
|
271
|
-
* any defined [file processing][on upload]
|
272
|
-
* extracting [metadata]
|
273
|
-
* generating [location]
|
274
|
-
* uploading (this is where the [storage] is called)
|
275
|
-
* closing the uploaded file
|
276
|
-
|
277
|
-
The second argument is a `context` hash which is forwarded to places like
|
278
|
-
metadata extraction and location generation, but it has a few special options:
|
279
|
-
|
280
|
-
```rb
|
281
|
-
uploader.upload(io, metadata: { "foo" => "bar" }) # add metadata
|
282
|
-
uploader.upload(io, location: "path/to/file") # specify custom location
|
283
|
-
uploader.upload(io, upload_options: { acl: "public-read" }) # add options to Storage#upload
|
284
|
-
```
|
285
|
-
|
286
|
-
### IO abstraction
|
287
|
-
|
288
|
-
Shrine is able to upload any IO-like object that implement methods [`#read`],
|
289
|
-
[`#rewind`], [`#eof?`] and [`#close`] whose behaviour matches the [`IO`] class.
|
290
|
-
This includes built-in IO and IO-like objects like File, Tempfile and StringIO.
|
291
|
-
|
292
|
-
When a file is uploaded to a Rails app, in request params it will be
|
293
|
-
represented by an `ActionDispatch::Http::UploadedFile` object, which is also an
|
294
|
-
IO-like object accepted by Shrine. In other Rack applications the uploaded file
|
295
|
-
will be represented as a Hash, but it can be converted into an IO-like object
|
296
|
-
with the [`rack_file`][rack_file plugin] plugin.
|
297
|
-
|
298
|
-
Here are some examples of various IO-like objects that can be uploaded:
|
299
|
-
|
300
|
-
```rb
|
301
|
-
uploader.upload File.open("/path/to/file", binmode: true) # upload from disk
|
302
|
-
uploader.upload StringIO.new("file content") # upload from memory
|
303
|
-
uploader.upload ActionDispatch::Http::UploadedFile.new(...) # upload from Rails controller
|
304
|
-
uploader.upload Shrine.rack_file({ tempfile: tempfile }) # upload from Rack controller
|
305
|
-
uploader.upload Rack::Test::UploadedFile.new(...) # upload from rack-test
|
306
|
-
uploader.upload Down.open("https://example.org/file") # upload from internet
|
307
|
-
uploader.upload Shrine::UploadedFile.new(...) # upload from Shrine storage
|
308
|
-
```
|
309
|
-
|
310
|
-
## Uploaded file
|
311
|
-
|
312
|
-
The `Shrine::UploadedFile` object represents the file that was uploaded to a
|
313
|
-
storage, and it's what's returned from `Shrine#upload` or when retrieving a
|
314
|
-
record [attachment]. It contains the following information:
|
315
|
-
|
316
|
-
| Key | Description |
|
317
|
-
| :------- | :---------- |
|
318
|
-
| `id` | location of the file on the storage |
|
319
|
-
| `storage` | identifier of the storage the file was uploaded to |
|
320
|
-
| `metadata` | file [metadata] that was extracted before upload |
|
321
|
-
|
322
|
-
```rb
|
323
|
-
uploaded_file = uploader.upload(file)
|
324
|
-
uploaded_file.data #=> {"id"=>"949sdjg834.jpg","storage"=>"store","metadata"=>{...}}
|
325
|
-
|
326
|
-
uploaded_file.id #=> "949sdjg834.jpg"
|
327
|
-
uploaded_file.storage #=> #<Shrine::Storage::S3>
|
328
|
-
uploaded_file.metadata #=> {...}
|
329
|
-
```
|
330
|
-
|
331
|
-
It comes with many convenient methods that delegate to the storage:
|
332
|
-
|
333
|
-
```rb
|
334
|
-
uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
|
335
|
-
uploaded_file.open { |io| ... } # opens the uploaded file stream
|
336
|
-
uploaded_file.download { |file| ... } # downloads the uploaded file to disk
|
337
|
-
uploaded_file.stream(destination) # streams uploaded content into a writable destination
|
338
|
-
uploaded_file.exists? #=> true
|
339
|
-
uploaded_file.delete # deletes the uploaded file from the storage
|
340
|
-
```
|
341
|
-
|
342
|
-
It also implements the IO-like interface that conforms to Shrine's [IO
|
343
|
-
abstraction][io abstraction], which allows it to be uploaded again to other
|
344
|
-
storages.
|
345
|
-
|
346
|
-
```rb
|
347
|
-
uploaded_file.read # returns content of the uploaded file
|
348
|
-
uploaded_file.eof? # returns true if the whole IO was read
|
349
|
-
uploaded_file.rewind # rewinds the IO
|
350
|
-
uploaded_file.close # closes the IO
|
351
|
-
```
|
352
|
-
|
353
|
-
For more details, see the [Retrieving Uploads] guide and
|
354
|
-
[`Shrine::UploadedFile`] API docs.
|
355
|
-
|
356
|
-
## Attachment
|
357
|
-
|
358
|
-
Storage objects, uploaders, and uploaded file objects are Shrine's foundational
|
359
|
-
components. To help you actually attach uploaded files to database records in
|
360
|
-
your application, Shrine comes with a high-level attachment interface built on
|
361
|
-
top of these components.
|
362
|
-
|
363
|
-
There are plugins for hooking into most database libraries, and in case of
|
364
|
-
ActiveRecord and Sequel the plugin will automatically tie the attached files to
|
365
|
-
records' lifecycles. But you can also use Shrine just with plain old Ruby
|
366
|
-
objects.
|
367
|
-
|
368
|
-
```rb
|
369
|
-
Shrine.plugin :sequel # :activerecord
|
370
|
-
```
|
371
|
-
|
372
|
-
```rb
|
373
|
-
class Photo < Sequel::Model # ActiveRecord::Base
|
374
|
-
include ImageUploader::Attachment.new(:image) #
|
375
|
-
include ImageUploader::Attachment(:image) # these are all equivalent
|
376
|
-
include ImageUploader[:image] #
|
377
|
-
end
|
378
|
-
```
|
379
|
-
|
380
|
-
You can choose whichever of these syntaxes you prefer. Either of these
|
381
|
-
will create a `Shrine::Attachment` module with attachment methods for the
|
382
|
-
specified attribute, which then get added to your model when you include it:
|
383
|
-
|
384
|
-
| Method | Description |
|
385
|
-
| :----- | :---------- |
|
386
|
-
| `#image=` | uploads the file to temporary storage and serializes the result into `image_data` |
|
387
|
-
| `#image` | returns [`Shrine::UploadedFile`][uploaded file] instantiated from `image_data` |
|
388
|
-
| `#image_url` | calls `url` on the attachment if it's present, otherwise returns nil |
|
389
|
-
| `#image_attacher` | returns instance of [`Shrine::Attacher`][attacher] which handles the attaching |
|
390
|
-
|
391
|
-
The ORM plugin that we loaded adds appropriate callbacks. For example, saving
|
392
|
-
the record uploads the attachment to permanent storage, while deleting the
|
393
|
-
record deletes the attachment.
|
394
|
-
|
395
|
-
```rb
|
396
|
-
# no file is attached
|
397
|
-
photo.image #=> nil
|
398
|
-
|
399
|
-
# the assigned file is cached to temporary storage and written to `image_data` column
|
400
|
-
photo.image = File.open("waterfall.jpg")
|
401
|
-
photo.image #=> #<Shrine::UploadedFile @data={...}>
|
402
|
-
photo.image_url #=> "/uploads/cache/0sdfllasfi842.jpg"
|
403
|
-
photo.image_data #=> '{"id":"0sdfllasfi842.jpg","storage":"cache","metadata":{...}}'
|
404
|
-
|
405
|
-
# the cached file is promoted to permanent storage and saved to `image_data` column
|
406
|
-
photo.save
|
407
|
-
photo.image #=> #<Shrine::UploadedFile @data={...}>
|
408
|
-
photo.image_url #=> "/uploads/store/l02kladf8jlda.jpg"
|
409
|
-
photo.image_data #=> '{"id":"l02kladf8jlda.jpg","storage":"store","metadata":{...}}'
|
410
|
-
|
411
|
-
# the attached file is deleted with the record
|
412
|
-
photo.destroy
|
413
|
-
photo.image.exists? #=> false
|
414
|
-
```
|
415
|
-
|
416
|
-
If there is already a file attached and a new file is attached, the previous
|
417
|
-
attachment will get deleted when the record gets saved.
|
418
|
-
|
419
|
-
```rb
|
420
|
-
photo.update(image: new_file) # changes the attachment and deletes previous
|
421
|
-
photo.update(image: nil) # removes the attachment and deletes previous
|
422
|
-
```
|
423
|
-
|
424
|
-
## Attacher
|
425
|
-
|
426
|
-
The model attachment attributes and callbacks added by `Shrine::Attachment`
|
427
|
-
just delegate the behaviour to their underlying `Shrine::Attacher` object.
|
428
|
-
|
429
|
-
```rb
|
430
|
-
photo.image_attacher #=> #<Shrine::Attacher>
|
431
|
-
```
|
432
|
-
|
433
|
-
The `Shrine::Attacher` object can be instantiated and used directly:
|
434
|
-
|
435
|
-
```rb
|
436
|
-
attacher = ImageUploader::Attacher.new(photo, :image)
|
437
|
-
|
438
|
-
attacher.assign(file) # equivalent to `photo.image = file`
|
439
|
-
attacher.get # equivalent to `photo.image`
|
440
|
-
attacher.url # equivalent to `photo.image_url`
|
441
|
-
```
|
442
|
-
|
443
|
-
The attacher is what drives attaching files to model instances; you can use it
|
444
|
-
as a more explicit alternative to models' attachment interface, or simply when
|
445
|
-
you need something that's not available through the attachment methods.
|
446
|
-
|
447
|
-
You can do things such as change the temporary and permanent storage the
|
448
|
-
attacher uses, or upload files directly to permanent storage. See the [Using
|
449
|
-
Attacher] guide for more details.
|
450
|
-
|
451
|
-
## Plugin system
|
452
|
-
|
453
|
-
By default Shrine comes with a small core which provides only the essential
|
454
|
-
functionality. All additional features are available via [plugins], which also
|
455
|
-
ship with Shrine. This way you can choose exactly what and how much Shrine does
|
456
|
-
for you, and you load the code only for features that you use.
|
457
|
-
|
458
|
-
```rb
|
459
|
-
Shrine.plugin :instrumentation # adds instrumentation
|
460
|
-
```
|
461
|
-
|
462
|
-
Plugins add behaviour by extending Shrine core classes via module inclusion, and
|
463
|
-
many of them also accept configuration options. The plugin system respects
|
464
|
-
inheritance, so you can choose to load a plugin globally or per uploader.
|
465
|
-
|
466
|
-
```rb
|
467
|
-
class ImageUploader < Shrine
|
468
|
-
plugin :store_dimensions # extract image dimensions only for this uploader and its descendants
|
469
|
-
end
|
470
|
-
```
|
471
|
-
|
472
|
-
If you want to extend Shrine functionality with custom behaviour, you can also
|
473
|
-
[create your own plugin][Creating Plugins].
|
474
|
-
|
475
|
-
## Metadata
|
476
|
-
|
477
|
-
Shrine automatically extracts some basic file metadata and saves them to the
|
478
|
-
`Shrine::UploadedFile`. You can access them through the `#metadata` hash or via
|
479
|
-
metadata methods:
|
480
|
-
|
481
|
-
```rb
|
482
|
-
uploaded_file.metadata #=>
|
483
|
-
# {
|
484
|
-
# "filename" => "matrix.mp4",
|
485
|
-
# "mime_type" => "video/mp4",
|
486
|
-
# "size" => 345993,
|
487
|
-
# }
|
488
|
-
|
489
|
-
uploaded_file.original_filename #=> "matrix.mp4"
|
490
|
-
uploaded_file.extension #=> "mp4"
|
491
|
-
uploaded_file.mime_type #=> "video/mp4"
|
492
|
-
uploaded_file.size #=> 345993
|
493
|
-
```
|
494
|
-
|
495
|
-
### MIME type
|
496
|
-
|
497
|
-
By default, `mime_type` metadata will be set from the `#content_type` attribute
|
498
|
-
of the uploaded file (if it exists), which is generally not secure and will
|
499
|
-
trigger a warning. You can load the [`determine_mime_type`][determine_mime_type
|
500
|
-
plugin] plugin to have MIME type extracted from file *content* instead.
|
501
|
-
|
502
|
-
```rb
|
503
|
-
# Gemfile
|
504
|
-
gem "marcel", "~> 0.3"
|
505
|
-
```
|
506
|
-
```rb
|
507
|
-
Shrine.plugin :determine_mime_type, analyzer: :marcel
|
508
|
-
```
|
509
|
-
```rb
|
510
|
-
photo = Photo.create(image: StringIO.new("<?php ... ?>"))
|
511
|
-
photo.image.mime_type #=> "application/x-php"
|
512
|
-
```
|
513
|
-
|
514
|
-
### Other metadata
|
515
|
-
|
516
|
-
In addition to basic metadata, you can also extract [image
|
517
|
-
dimensions][store_dimensions plugin], calculate [signatures][signature plugin],
|
518
|
-
and in general extract any [custom metadata][add_metadata plugin]. Check out
|
519
|
-
the [Extracting Metadata] guide for more details.
|
520
|
-
|
521
|
-
## Processing
|
522
|
-
|
523
|
-
Shrine allows you to process attached files either "on upload" or
|
524
|
-
"on-the-fly". For example, if your app is accepting image uploads, you can
|
525
|
-
generate a pre-defined set of of thumbnails as soon as the image is attached to
|
526
|
-
a record ("on upload"), or you can generate necessary thumbnails dynamically as
|
527
|
-
they're needed ("on-the-fly").
|
528
|
-
|
529
|
-
For image processing it's recommended to use the **[ImageProcessing]** gem,
|
530
|
-
which is a high-level wrapper for processing with [ImageMagick] (via
|
531
|
-
[MiniMagick]) or [libvips] (via [ruby-vips]).
|
532
|
-
|
533
|
-
### Processing on upload
|
534
|
-
|
535
|
-
For processing "on upload", you can intercept when a cached file is being
|
536
|
-
uploaded to permanent storage, and perform any file processing you might want.
|
537
|
-
The [`processing`][processing plugin] plugin provides the promotion hook, while
|
538
|
-
the [`versions`][versions plugin] plugin enables handling a hash of versions.
|
539
|
-
|
540
|
-
```sh
|
541
|
-
$ brew install imagemagick
|
542
|
-
```
|
543
|
-
```rb
|
544
|
-
# Gemfile
|
545
|
-
gem "image_processing", "~> 1.2"
|
546
|
-
```
|
547
|
-
```rb
|
548
|
-
require "image_processing/mini_magick"
|
549
|
-
|
550
|
-
class ImageUploader < Shrine
|
551
|
-
plugin :processing # allows hooking into promoting
|
552
|
-
plugin :versions # enable Shrine to handle a hash of files
|
553
|
-
plugin :delete_raw # delete processed files after uploading
|
554
|
-
|
555
|
-
process(:store) do |io, context|
|
556
|
-
versions = { original: io } # retain original
|
557
|
-
|
558
|
-
io.download do |original|
|
559
|
-
pipeline = ImageProcessing::MiniMagick.source(original)
|
560
|
-
|
561
|
-
versions[:large] = pipeline.resize_to_limit!(800, 800)
|
562
|
-
versions[:medium] = pipeline.resize_to_limit!(500, 500)
|
563
|
-
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
564
|
-
end
|
565
|
-
|
566
|
-
versions # return the hash of processed files
|
567
|
-
end
|
568
|
-
end
|
569
|
-
```
|
570
|
-
|
571
|
-
After the files are uploaded, their data is saved into the `<attachment>_data`
|
572
|
-
column, and the attachment getter will read them as a Hash of
|
573
|
-
[`Shrine::UploadedFile`][uploaded file] objects.
|
574
|
-
|
575
|
-
```rb
|
576
|
-
photo = Photo.create(image: file) # processing is triggered
|
577
|
-
photo.image #=>
|
578
|
-
# {
|
579
|
-
# :original => #<Shrine::UploadedFile @data={"id"=>"9sd84.jpg", ...}>,
|
580
|
-
# :large => #<Shrine::UploadedFile @data={"id"=>"lg043.jpg", ...}>,
|
581
|
-
# :medium => #<Shrine::UploadedFile @data={"id"=>"kd9fk.jpg", ...}>,
|
582
|
-
# :small => #<Shrine::UploadedFile @data={"id"=>"932fl.jpg", ...}>,
|
583
|
-
# }
|
584
|
-
|
585
|
-
photo.image[:medium] #=> #<Shrine::UploadedFile>
|
586
|
-
photo.image[:medium].url #=> "/uploads/store/lg043.jpg"
|
587
|
-
photo.image[:medium].size #=> 5825949
|
588
|
-
photo.image[:medium].mime_type #=> "image/jpeg"
|
589
|
-
|
590
|
-
photo.image_url(:large) # returns version URL with fallbacks in case version is missing
|
591
|
-
```
|
592
|
-
|
593
|
-
By default processing is executed synchronously, but you can choose to delay it
|
594
|
-
into a [background job][backgrounding]. You can also do any other type of file
|
595
|
-
processing you want, see the [File Processing] guide for more details.
|
596
|
-
|
597
|
-
### Processing on-the-fly
|
598
|
-
|
599
|
-
On-the-fly processing is provided by the
|
600
|
-
[`derivation_endpoint`][derivation_endpoint plugin] plugin. It comes with a
|
601
|
-
[mountable][Mounting Endpoints] Rack app which applies processing on request
|
602
|
-
and returns processed files.
|
603
|
-
|
604
|
-
To set it up, we mount the Rack app in our router on a chosen path prefix,
|
605
|
-
configure the plugin with a secret key and that path prefix, and define
|
606
|
-
processing we want to perform:
|
607
|
-
|
608
|
-
```sh
|
609
|
-
$ brew install imagemagick
|
610
|
-
```
|
611
|
-
```rb
|
612
|
-
# Gemfile
|
613
|
-
gem "image_processing", "~> 1.2"
|
614
|
-
```
|
615
|
-
```rb
|
616
|
-
# config/routes.rb (Rails)
|
617
|
-
Rails.application.routes.draw do
|
618
|
-
# ...
|
619
|
-
mount ImageUploader.derivation_endpoint => "/derivations/image"
|
620
|
-
end
|
621
|
-
```
|
622
|
-
```rb
|
623
|
-
require "image_processing/mini_magick"
|
624
|
-
|
625
|
-
class ImageUploader < Shrine
|
626
|
-
plugin :derivation_endpoint,
|
627
|
-
secret_key: "<YOUR SECRET KEY>",
|
628
|
-
prefix: "derivations/image" # needs to match the mount point in routes
|
629
|
-
|
630
|
-
derivation :thumbnail do |file, width, height|
|
631
|
-
ImageProcessing::MiniMagick
|
632
|
-
.source(file)
|
633
|
-
.resize_to_limit!(width.to_i, height.to_i)
|
634
|
-
end
|
635
|
-
end
|
636
|
-
```
|
637
|
-
|
638
|
-
Now we can generate URLs from attached files that will perform the desired
|
639
|
-
processing:
|
640
|
-
|
641
|
-
```rb
|
642
|
-
photo.image.derivation_url(:thumbnail, "600", "400")
|
643
|
-
#=> "/derivations/image/thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
|
644
|
-
```
|
645
|
-
|
646
|
-
The on-the-fly processing feature is highly customizable, see the
|
647
|
-
[`derivation_endpoint`][derivation_endpoint plugin] plugin documentation for
|
648
|
-
more details.
|
649
|
-
|
650
|
-
## Validation
|
651
|
-
|
652
|
-
Shrine can perform file validations for files assigned to the model, with
|
653
|
-
[`validation_helpers`][validation_helpers plugin] plugin providing some common
|
654
|
-
validation methods:
|
655
|
-
|
656
|
-
```rb
|
657
|
-
class DocumentUploader < Shrine
|
658
|
-
plugin :validation_helpers
|
659
|
-
|
660
|
-
Attacher.validate do
|
661
|
-
validate_max_size 5*1024*1024, message: "is too large (max is 5 MB)"
|
662
|
-
validate_mime_type_inclusion %w[application/pdf]
|
663
|
-
end
|
664
|
-
end
|
665
|
-
```
|
666
|
-
|
667
|
-
```rb
|
668
|
-
user = User.new
|
669
|
-
user.cv = File.open("cv.pdf", "rb")
|
670
|
-
user.valid? #=> false
|
671
|
-
user.errors.to_hash #=> {:cv=>["is too large (max is 5 MB)"]}
|
672
|
-
```
|
673
|
-
|
674
|
-
For more details, see the [File Validation] guide and
|
675
|
-
[`validation_helpers`][validation_helpers plugin] plugin docs.
|
676
|
-
|
677
|
-
## Location
|
678
|
-
|
679
|
-
Shrine automatically generated random locations before uploading files. By
|
680
|
-
default the hierarchy is flat, meaning all files are stored in the root
|
681
|
-
directory of the storage. The [`pretty_location`][pretty_location plugin]
|
682
|
-
plugin provides a good default hierarchy, but you can also override
|
683
|
-
`#generate_location` with a custom implementation:
|
684
|
-
|
685
|
-
```rb
|
686
|
-
class ImageUploader < Shrine
|
687
|
-
def generate_location(io, context)
|
688
|
-
type = context[:record].class.name.downcase if context[:record]
|
689
|
-
style = context[:version] == :original ? "originals" : "thumbs" if context[:version]
|
690
|
-
name = super # the default unique identifier
|
691
|
-
|
692
|
-
[type, style, name].compact.join("/")
|
693
|
-
end
|
694
|
-
end
|
695
|
-
```
|
696
|
-
```
|
697
|
-
uploads/
|
698
|
-
photos/
|
699
|
-
originals/
|
700
|
-
la98lda74j3g.jpg
|
701
|
-
thumbs/
|
702
|
-
95kd8kafg80a.jpg
|
703
|
-
ka8agiaf9gk4.jpg
|
704
|
-
```
|
705
|
-
|
706
|
-
Note that there should always be a random component in the location, so that
|
707
|
-
the ORM dirty tracking is detected properly. Inside `#generate_location` you
|
708
|
-
can also access the extracted metadata through `context[:metadata]`.
|
709
|
-
|
710
|
-
## Direct uploads
|
711
|
-
|
712
|
-
To improve the user experience, it's recommended to upload files asynchronously
|
713
|
-
as soon as the user selects them. The direct uploads would go to temporary
|
714
|
-
storage, just like in the synchronous flow. Then, instead of attaching a raw
|
715
|
-
file to your model, you assign the cached file JSON data.
|
716
|
-
|
717
|
-
```rb
|
718
|
-
# in the regular synchronous flow
|
719
|
-
photo.image = file
|
720
|
-
|
721
|
-
# in the direct upload flow
|
722
|
-
photo.image = '{"id":"...","storage":"cache","metadata":{...}}'
|
723
|
-
```
|
724
|
-
|
725
|
-
On the client side it's highly recommended to use **[Uppy]** :dog:, a very
|
726
|
-
flexible modern JavaScript file upload library that happens to integrate nicely
|
727
|
-
with Shrine.
|
728
|
-
|
729
|
-
### Simple direct upload
|
730
|
-
|
731
|
-
The simplest approach is to upload directly to an endpoint in your app, which
|
732
|
-
forwards uploads to the specified storage. The
|
733
|
-
[`upload_endpoint`][upload_endpoint plugin] Shrine plugin provides a
|
734
|
-
[mountable][Mounting Endpoints] Rack app that implements this endpoint:
|
735
|
-
|
736
|
-
```rb
|
737
|
-
Shrine.plugin :upload_endpoint
|
738
|
-
```
|
739
|
-
```rb
|
740
|
-
# config/routes.rb (Rails)
|
741
|
-
Rails.application.routes.draw do
|
742
|
-
# ...
|
743
|
-
mount ImageUploader.upload_endpoint(:cache) => "/images/upload" # POST /images/upload
|
744
|
-
end
|
745
|
-
```
|
746
|
-
|
747
|
-
Then you can configure Uppy's [XHR Upload][uppy xhr-upload] plugin to upload to
|
748
|
-
this endpoint. See [this walkthrough][Adding Direct App Uploads] for adding
|
749
|
-
simple direct uploads from scratch, it includes a complete JavaScript example
|
750
|
-
(there is also the [Roda][roda demo] / [Rails][rails demo] demo app).
|
751
|
-
|
752
|
-
### Presigned direct upload
|
753
|
-
|
754
|
-
For better performance, you can also upload files directly to your cloud
|
755
|
-
storage service (AWS S3, Google Cloud Storage etc). For this, your temporary
|
756
|
-
storage needs to be your cloud service:
|
757
|
-
|
758
|
-
```rb
|
759
|
-
require "shrine/storage/s3"
|
760
|
-
|
761
|
-
Shrine.storages = {
|
762
|
-
cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
|
763
|
-
store: Shrine::Storage::S3.new(**s3_options)
|
764
|
-
}
|
765
|
-
```
|
766
|
-
|
767
|
-
In this flow, the client needs to first fetch upload parameters from the
|
768
|
-
server, and then use these parameters for the upload to the cloud service.
|
769
|
-
The [`presign_endpoint`][presign_endpoint plugin] Shrine plugin provides a
|
770
|
-
[mountable][Mounting Endpoints] Rack app that generates upload parameters:
|
771
|
-
|
772
|
-
```rb
|
773
|
-
Shrine.plugin :presign_endpoint
|
774
|
-
```
|
775
|
-
```rb
|
776
|
-
# config/routes.rb (Rails)
|
777
|
-
Rails.application.routes.draw do
|
778
|
-
# ...
|
779
|
-
mount Shrine.presign_endpoint(:cache) => "/s3/params" # GET /s3/params
|
780
|
-
end
|
781
|
-
```
|
782
|
-
|
783
|
-
Then you can configure Uppy's [AWS S3][uppy aws-s3] plugin to fetch params from
|
784
|
-
your endpoint before uploading to S3. See [this walkthrough][Adding Direct S3
|
785
|
-
Uploads] for adding direct uploads to S3 from scratch, it includes a complete
|
786
|
-
JavaScript example (there is also the [Roda][roda demo] / [Rails][rails demo]
|
787
|
-
demo). See also the [Direct Uploads to S3] guide for more details.
|
788
|
-
|
789
|
-
### Resumable direct upload
|
790
|
-
|
791
|
-
If your app is accepting large uploads, you can improve resilience by making
|
792
|
-
the uploads **resumable**. This can significantly improve experience for users
|
793
|
-
on slow and flaky internet connections.
|
794
|
-
|
795
|
-
#### Uppy S3 Multipart
|
796
|
-
|
797
|
-
You can achieve resumable uploads directly to S3 with the [AWS S3
|
798
|
-
Multipart][uppy aws-s3-multipart] Uppy plugin, accompanied with
|
799
|
-
`uppy_s3_multipart` Shrine plugin provided by the [uppy-s3_multipart] gem.
|
800
|
-
|
801
|
-
```rb
|
802
|
-
# Gemfile
|
803
|
-
gem "uppy-s3_multipart", "~> 0.3"
|
804
|
-
```
|
805
|
-
```rb
|
806
|
-
Shrine.plugin :uppy_s3_multipart
|
807
|
-
```
|
808
|
-
```rb
|
809
|
-
# config/routes.rb (Rails)
|
810
|
-
Rails.application.routes.draw do
|
811
|
-
# ...
|
812
|
-
mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
|
813
|
-
end
|
814
|
-
```
|
815
|
-
|
816
|
-
See the [uppy-s3_multipart] docs for more details.
|
817
|
-
|
818
|
-
#### Tus protocol
|
819
|
-
|
820
|
-
If you want a more generic approach, you can build your resumable uploads on
|
821
|
-
**[tus]** – an open resumable upload protocol. On the server side you can use
|
822
|
-
the [tus-ruby-server] gem, on the client side Uppy's [Tus][uppy tus] plugin,
|
823
|
-
and the [shrine-tus] gem for the glue.
|
824
|
-
|
825
|
-
```rb
|
826
|
-
# Gemfile
|
827
|
-
gem "tus-server", "~> 2.0"
|
828
|
-
gem "shrine-tus", "~> 1.2"
|
829
|
-
```
|
830
|
-
```rb
|
831
|
-
require "shrine/storage/tus"
|
832
|
-
|
833
|
-
Shrine.storages = {
|
834
|
-
cache: Shrine::Storage::Tus.new, # tus server acts as temporary storage
|
835
|
-
store: ..., # your permanent storage
|
836
|
-
}
|
837
|
-
```
|
838
|
-
```rb
|
839
|
-
# config/routes.rb (Rails)
|
840
|
-
Rails.application.routes.draw do
|
841
|
-
# ...
|
842
|
-
mount Tus::Server => "/files"
|
843
|
-
end
|
844
|
-
```
|
845
|
-
|
846
|
-
See [this walkthrough][Adding Resumable Uploads] for adding tus-powered
|
847
|
-
resumable uploads from scratch, it includes a complete JavaScript example
|
848
|
-
(there is also a [demo app][resumable demo]). See also [shrine-tus] and
|
849
|
-
[tus-ruby-server] docs for more details.
|
850
|
-
|
851
|
-
## Backgrounding
|
852
|
-
|
853
|
-
Shrine allows you to put file deletion and promotion of cached files to
|
854
|
-
permanent storage into a background job. The [`backgrounding`][backgrounding
|
855
|
-
plugin] plugin provides hooks for plugging in your [favourite backgrounding
|
856
|
-
library][Backgrounding Libraries].
|
857
|
-
|
858
|
-
```rb
|
859
|
-
Shrine.plugin :backgrounding
|
860
|
-
Shrine::Attacher.promote { |data| PromoteJob.perform_async(data) }
|
861
|
-
Shrine::Attacher.delete { |data| DeleteJob.perform_async(data) }
|
862
|
-
```
|
863
|
-
```rb
|
864
|
-
class PromoteJob
|
865
|
-
include Sidekiq::Worker
|
866
|
-
def perform(data)
|
867
|
-
Shrine::Attacher.promote(data)
|
868
|
-
end
|
869
|
-
end
|
870
|
-
```
|
871
|
-
```rb
|
872
|
-
class DeleteJob
|
873
|
-
include Sidekiq::Worker
|
874
|
-
def perform(data)
|
875
|
-
Shrine::Attacher.delete(data)
|
876
|
-
end
|
877
|
-
end
|
878
|
-
```
|
879
|
-
|
880
|
-
## Clearing cache
|
881
|
-
|
882
|
-
Shrine doesn't automatically delete files uploaded to temporary storage, instead
|
883
|
-
you should set up a separate recurring task that will automatically delete old
|
884
|
-
cached files.
|
885
|
-
|
886
|
-
Most Shrine storage classes come with a `#clear!` method, which you can call in
|
887
|
-
a recurring script. For FileSystem and S3 storage it would look like this:
|
888
|
-
|
889
|
-
```rb
|
890
|
-
# FileSystem storage
|
891
|
-
file_system = Shrine.storages[:cache]
|
892
|
-
file_system.clear! { |path| path.mtime < Time.now - 7*24*60*60 } # delete files older than 1 week
|
893
|
-
```
|
894
|
-
```rb
|
895
|
-
# S3 storage
|
896
|
-
s3 = Shrine.storages[:cache]
|
897
|
-
s3.clear! { |object| object.last_modified < Time.now - 7*24*60*60 } # delete files older than 1 week
|
898
|
-
```
|
899
|
-
|
900
|
-
## Logging
|
901
|
-
|
902
|
-
The [`instrumentation`][instrumentation plugin] plugin sends and logs events for
|
903
|
-
important operations:
|
904
|
-
|
905
|
-
```rb
|
906
|
-
Shrine.plugin :instrumentation, notifications: ActiveSupport::Notifications
|
907
|
-
|
908
|
-
uploaded_file = Shrine.upload(io, :store)
|
909
|
-
uploaded_file.exists?
|
910
|
-
uploaded_file.download
|
911
|
-
uploaded_file.delete
|
912
|
-
```
|
913
|
-
```
|
914
|
-
Metadata (32ms) – {:storage=>:store, :io=>StringIO, :uploader=>Shrine}
|
915
|
-
Upload (1523ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :io=>StringIO, :upload_options=>{}, :uploader=>Shrine}
|
916
|
-
Exists (755ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
|
917
|
-
Download (1002ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :download_options=>{}, :uploader=>Shrine}
|
918
|
-
Delete (700ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
|
919
|
-
```
|
920
|
-
|
921
|
-
Some plugins add their own instrumentation as well when they detect that the
|
922
|
-
`instrumentation` plugin has been loaded. For that to work, the
|
923
|
-
`instrumentation` plugin needs to be loaded *before* any of these plugins.
|
924
|
-
|
925
|
-
| Plugin | Instrumentation |
|
926
|
-
| :----- | :-------------- |
|
927
|
-
| `derivation_endpoint` | instruments file processing |
|
928
|
-
| `determine_mime_type` | instruments analyzing MIME type |
|
929
|
-
| `store_dimensions` | instruments extracting image dimensions |
|
930
|
-
| `signature` | instruments calculating signature |
|
931
|
-
| `infer_extension` | instruments inferring extension |
|
932
|
-
| `remote_url` | instruments remote URL downloading |
|
933
|
-
| `data_uri` | instruments data URI parsing |
|
934
|
-
|
935
|
-
For instrumentation, warnings, and other logging, Shrine uses its internal
|
936
|
-
logger. You can tell Shrine to use a different logger, for example if you're
|
937
|
-
using Rails:
|
938
|
-
|
939
|
-
```rb
|
940
|
-
Shrine.logger = Rails.logger
|
941
|
-
```
|
114
|
+
See the **[Getting Started guide]** for further documentation.
|
942
115
|
|
943
116
|
## Inspiration
|
944
117
|
|
@@ -955,6 +128,10 @@ system.
|
|
955
128
|
* Refile
|
956
129
|
* Active Storage
|
957
130
|
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
Please refer to the [contributing page][Contributing].
|
134
|
+
|
958
135
|
## Code of Conduct
|
959
136
|
|
960
137
|
Everyone interacting in the Shrine project’s codebases, issue trackers, and
|
@@ -964,112 +141,39 @@ mailing lists is expected to follow the [Shrine code of conduct][CoC].
|
|
964
141
|
|
965
142
|
The gem is available as open source under the terms of the [MIT License].
|
966
143
|
|
967
|
-
|
968
|
-
[Advantages of Shrine]: /
|
969
|
-
[
|
970
|
-
[
|
971
|
-
[
|
972
|
-
[
|
973
|
-
[File Processing]: /doc/processing.md#readme
|
974
|
-
[File Validation]: /doc/validation.md#readme
|
975
|
-
[Retrieving Uploads]: /doc/retrieving_uploads.md#readme
|
976
|
-
[Using Attacher]: /doc/attacher.md#readme
|
977
|
-
[FileSystem]: /doc/storage/file_system.md#readme
|
978
|
-
[S3]: /doc/storage/s3.md#readme
|
979
|
-
[`Shrine::UploadedFile`]: https://shrinerb.com/rdoc/classes/Shrine/UploadedFile/InstanceMethods.html
|
980
|
-
|
981
|
-
<!-- Sections -->
|
982
|
-
[attacher]: #attacher
|
983
|
-
[attachment]: #attachment
|
984
|
-
[backgrounding]: #backgrounding
|
985
|
-
[direct uploads]: #direct-uploads
|
986
|
-
[io abstraction]: #io-abstraction
|
987
|
-
[location]: #location
|
988
|
-
[metadata]: #metadata
|
989
|
-
[on upload]: #processing-on-upload
|
990
|
-
[on-the-fly]: #processing-on-the-fly
|
991
|
-
[plugin system]: #plugin-system
|
992
|
-
[simple upload]: #simple-direct-upload
|
993
|
-
[presigned upload]: #presigned-direct-upload
|
994
|
-
[resumable upload]: #resumable-direct-upload
|
995
|
-
[storage]: #storage
|
996
|
-
[uploaded file]: #uploaded-file
|
997
|
-
[uploading]: #uploading
|
998
|
-
[uploader]: #uploader
|
999
|
-
[validation]: #validation
|
1000
|
-
|
1001
|
-
<!-- Wikis -->
|
1002
|
-
[Adding Direct App Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
|
1003
|
-
[Adding Resumable Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Resumable-Uploads
|
1004
|
-
[Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
|
1005
|
-
[Backgrounding Libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
|
1006
|
-
[Mounting Endpoints]: https://github.com/shrinerb/shrine/wiki/Mounting-Endpoints
|
1007
|
-
|
1008
|
-
<!-- Storage & Destinations -->
|
1009
|
-
[AWS S3]: https://aws.amazon.com/s3/
|
1010
|
-
[MinIO]: https://min.io/
|
1011
|
-
[DigitalOcean Spaces]: https://www.digitalocean.com/products/spaces/
|
1012
|
-
[Cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
144
|
+
[Shrine]: https://shrinerb.com
|
145
|
+
[Advantages of Shrine]: https://shrinerb.com/docs/advantages
|
146
|
+
[plugin system]: https://shrinerb.com/docs/getting-started#plugin-system
|
147
|
+
[Retrieving Uploads]: https://shrinerb.com/docs/retrieving-uploads
|
148
|
+
[FileSystem]: https://shrinerb.com/docs/storage/file-system
|
149
|
+
[S3]: https://shrinerb.com/docs/storage/s3
|
1013
150
|
[GCS]: https://github.com/renchap/shrine-google_cloud_storage
|
151
|
+
[Cloudinary]: https://github.com/shrinerb/shrine-cloudinary
|
152
|
+
[Sequel]: https://shrinerb.com/docs/plugins/sequel
|
153
|
+
[ActiveRecord]: https://shrinerb.com/docs/plugins/activerecord
|
154
|
+
[ROM]: https://github.com/shrinerb/shrine-rom
|
155
|
+
[Hanami]: https://github.com/katafrakt/hanami-shrine
|
156
|
+
[Mongoid]: https://github.com/shrinerb/shrine-mongoid
|
157
|
+
[eagerly]: https://shrinerb.com/docs/getting-started#eager-processing
|
158
|
+
[on-the-fly]: https://shrinerb.com/docs/getting-started#on-the-fly-processing
|
159
|
+
[ImageMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
|
160
|
+
[libvips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
|
161
|
+
[validation]: https://shrinerb.com/docs/validation
|
162
|
+
[metadata]: https://shrinerb.com/docs/metadata
|
163
|
+
[simple upload]: https://shrinerb.com/docs/getting-started#simple-direct-upload
|
164
|
+
[presigned upload]: https://shrinerb.com/docs/getting-started#presigned-direct-upload
|
165
|
+
[resumable upload]: https://shrinerb.com/docs/getting-started#resumable-direct-upload
|
166
|
+
[Uppy]: https://uppy.io/
|
1014
167
|
[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
|
1015
168
|
[tus-ruby-server]: https://github.com/janko/tus-ruby-server
|
1016
|
-
|
1017
|
-
|
1018
|
-
[
|
1019
|
-
[
|
1020
|
-
[tus]: https://tus.io
|
1021
|
-
[uppy aws-s3-multipart]: https://uppy.io/docs/aws-s3-multipart/
|
1022
|
-
[uppy aws-s3]: https://uppy.io/docs/aws-s3/
|
1023
|
-
[uppy tus]: https://uppy.io/docs/tus/
|
1024
|
-
[uppy xhr-upload]: https://uppy.io/docs/xhr-upload/
|
1025
|
-
|
1026
|
-
<!-- Processing -->
|
1027
|
-
[ImageMagick]: https://imagemagick.org/
|
1028
|
-
[MiniMagick]: https://github.com/minimagick/minimagick
|
1029
|
-
[ruby-vips]: https://github.com/libvips/ruby-vips
|
1030
|
-
[ImageProcessing]: https://github.com/janko/image_processing
|
1031
|
-
[ImageProcessing::MiniMagick]: https://github.com/janko/image_processing/blob/master/doc/minimagick.md#readme
|
1032
|
-
[ImageProcessing::Vips]: https://github.com/janko/image_processing/blob/master/doc/vips.md#readme
|
1033
|
-
[libvips]: http://libvips.github.io/libvips/
|
1034
|
-
[`file`]: http://linux.die.net/man/1/file
|
1035
|
-
|
1036
|
-
<!-- Plugins -->
|
1037
|
-
[activerecord plugin]: /doc/plugins/activerecord.md#readme
|
1038
|
-
[add_metadata plugin]: /doc/plugins/add_metadata.md#readme
|
1039
|
-
[backgrounding plugin]: /doc/plugins/backgrounding.md#readme
|
1040
|
-
[derivation_endpoint plugin]: /doc/plugins/derivation_endpoint.md#readme
|
1041
|
-
[determine_mime_type plugin]: /doc/plugins/determine_mime_type.md#readme
|
1042
|
-
[instrumentation plugin]: /doc/plugins/instrumentation.md#readme
|
1043
|
-
[hanami plugin]: https://github.com/katafrakt/hanami-shrine
|
1044
|
-
[mongoid plugin]: https://github.com/shrinerb/shrine-mongoid
|
1045
|
-
[presign_endpoint plugin]: /doc/plugins/presign_endpoint.md#readme
|
1046
|
-
[pretty_location plugin]: /doc/plugins/pretty_location.md#readme
|
1047
|
-
[processing plugin]: /doc/plugins/processing.md#readme
|
1048
|
-
[rack_file plugin]: /doc/plugins/rack_file.md#readme
|
1049
|
-
[sequel plugin]: /doc/plugins/sequel.md#readme
|
1050
|
-
[signature plugin]: /doc/plugins/signature.md#readme
|
1051
|
-
[store_dimensions plugin]: /doc/plugins/store_dimensions.md#readme
|
1052
|
-
[upload_endpoint plugin]: /doc/plugins/upload_endpoint.md#readme
|
1053
|
-
[validation_helpers plugin]: /doc/plugins/validation_helpers.md#readme
|
1054
|
-
[versions plugin]: /doc/plugins/versions.md#readme
|
1055
|
-
|
1056
|
-
<!-- Demos -->
|
169
|
+
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
170
|
+
[Backgrounding Libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
|
171
|
+
[Getting Started guide]: https://shrinerb.com/docs/getting-started
|
172
|
+
[roda demo]: /demo
|
1057
173
|
[rails demo]: https://github.com/erikdahlstrand/shrine-rails-example
|
1058
|
-
[roda demo]: https://github.com/shrinerb/shrine/tree/master/demo
|
1059
|
-
[resumable demo]: https://github.com/shrinerb/shrine-tus-demo
|
1060
|
-
|
1061
|
-
<!-- Misc -->
|
1062
|
-
[`#read`]: https://ruby-doc.org/core/IO.html#method-i-read
|
1063
|
-
[`#eof?`]: https://ruby-doc.org/core/IO.html#method-i-eof
|
1064
|
-
[`#rewind`]: https://ruby-doc.org/core/IO.html#method-i-rewind
|
1065
|
-
[`#close`]: https://ruby-doc.org/core/IO.html#method-i-close
|
1066
|
-
[`IO`]: https://ruby-doc.org/core/IO.html
|
1067
174
|
[Refile]: https://github.com/refile/refile
|
1068
175
|
[Roda]: https://github.com/jeremyevans/roda
|
1069
|
-
|
1070
|
-
|
1071
|
-
[
|
1072
|
-
[
|
1073
|
-
[plugins]: https://shrinerb.com/#plugins
|
1074
|
-
[CoC]: CODE_OF_CONDUCT.md
|
1075
|
-
[MIT License]: http://opensource.org/licenses/MIT
|
176
|
+
[CoC]: /CODE_OF_CONDUCT.md
|
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
|