shrine 2.19.3 → 3.6.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 +523 -41
- data/LICENSE.txt +1 -1
- data/README.md +83 -979
- 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 +103 -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 +1156 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +93 -30
- 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 +186 -101
- data/doc/plugins/derivatives.md +839 -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 +16 -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 +188 -170
- 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 +5 -1
- 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/release_notes/3.5.0.md +63 -0
- data/doc/release_notes/3.6.0.md +23 -0
- data/doc/retrieving_uploads.md +5 -2
- 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 +122 -78
- data/doc/testing.md +141 -133
- data/doc/upgrading_to_3.md +708 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +292 -169
- 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 +130 -171
- data/lib/shrine/plugins/derivatives.rb +645 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +118 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +158 -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 +17 -20
- data/lib/shrine/plugins/instrumentation.rb +59 -43
- 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 +160 -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 +27 -28
- 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 +21 -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 +12 -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 +28 -24
- 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 +35 -58
- 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 +173 -160
- data/lib/shrine/uploaded_file.rb +32 -32
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +87 -150
- data/shrine.gemspec +11 -12
- metadata +92 -82
- 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
|
-
|
|
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
|
+
| Discussion forum | [github.com/shrinerb/shrine/discussions](https://github.com/shrinerb/shrine/discussions) |
|
|
29
|
+
| Alternate Discussion forum | [discourse.shrinerb.com](https://discourse.shrinerb.com) |
|
|
54
30
|
|
|
55
|
-
##
|
|
31
|
+
## Setup
|
|
56
32
|
|
|
57
|
-
|
|
58
|
-
loads the ORM plugin:
|
|
33
|
+
Run:
|
|
59
34
|
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
gem "shrine", "~> 2.0"
|
|
35
|
+
```sh
|
|
36
|
+
bundle add shrine
|
|
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
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Next decide how you will name the attachment attribute on your model, and run a
|
|
81
|
-
migration that adds an `<attachment>_data` text or JSON column, which Shrine
|
|
82
|
-
will use to store all information about the attachment:
|
|
83
|
-
|
|
84
|
-
```rb
|
|
85
|
-
Sequel.migration do
|
|
86
|
-
change do
|
|
87
|
-
add_column :photos, :image_data, :text
|
|
88
|
-
end
|
|
89
|
-
end
|
|
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
|
|
90
54
|
```
|
|
91
55
|
|
|
92
|
-
|
|
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:
|
|
93
58
|
|
|
94
|
-
```sh
|
|
95
|
-
$ rails generate migration add_image_data_to_photos image_data:text
|
|
96
59
|
```
|
|
97
|
-
|
|
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, id: nil %>
|
|
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
|
-
|
|
226
|
-
The above example sets up S3 for both temporary and permanent storage, which is
|
|
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
113
|
|
|
818
|
-
|
|
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
|