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/doc/advantages.md
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
title: Advantages of Shrine
|
|
3
|
+
---
|
|
2
4
|
|
|
3
|
-
There are many existing file upload solutions for Ruby out there
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
There are many existing file upload solutions for Ruby out there. This guide
|
|
6
|
+
will attempt to cover some of the main advantages that Shrine offers compared
|
|
7
|
+
to these alternatives.
|
|
8
|
+
|
|
9
|
+
For a more direct comparison with specific file attachment libraries, there are
|
|
10
|
+
more specialized guides for [CarrierWave], [Paperclip], and [Refile] users.
|
|
7
11
|
|
|
8
12
|
## Generality
|
|
9
13
|
|
|
@@ -11,15 +15,15 @@ Many alternative file upload solutions are coupled to either Rails (Active
|
|
|
11
15
|
Storage) or Active Record itself (Paperclip, Dragonfly). This is not ideal, as
|
|
12
16
|
Rails-specific solutions fragment the Ruby community between developers that
|
|
13
17
|
use Rails and developers that don't. There are many great web frameworks
|
|
14
|
-
([Sinatra], [Roda], [Cuba], [Hanami], [Grape]) and
|
|
15
|
-
([Sequel], [ROM], [Hanami::Model]) out there that people use instead of
|
|
16
|
-
|
|
18
|
+
([Sinatra], [Roda], [Cuba], [Hanami], [Grape]) and persistence libraries
|
|
19
|
+
([Sequel], [ROM], [Hanami::Model]) out there that people use instead of Rails
|
|
20
|
+
and Active Record.
|
|
17
21
|
|
|
18
22
|
Shrine, on the other hand, doesn't make any assumptions about which web
|
|
19
|
-
framework or
|
|
20
|
-
on top of [Rack], the Ruby web server interface that powers all
|
|
21
|
-
Ruby web frameworks (including Rails). The integrations for
|
|
22
|
-
provided as plugins.
|
|
23
|
+
framework or persistence library you're using. Any web-specific functionality
|
|
24
|
+
is implemented on top of [Rack], the Ruby web server interface that powers all
|
|
25
|
+
the popular Ruby web frameworks (including Rails). The integrations for
|
|
26
|
+
specific ORMs are provided as plugins.
|
|
23
27
|
|
|
24
28
|
```rb
|
|
25
29
|
# Rack-based plugins
|
|
@@ -34,59 +38,62 @@ Shrine.plugin :rack_file
|
|
|
34
38
|
Shrine.plugin :activerecord
|
|
35
39
|
Shrine.plugin :sequel
|
|
36
40
|
Shrine.plugin :mongoid # https://github.com/shrinerb/shrine-mongoid
|
|
41
|
+
Shrine.plugin :rom # https://github.com/shrinerb/shrine-rom
|
|
37
42
|
Shrine.plugin :hanami # https://github.com/katafrakt/hanami-shrine
|
|
38
43
|
```
|
|
39
44
|
|
|
40
45
|
## Simplicity
|
|
41
46
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
There are no `CarrierWave::Uploader::Base` and `Paperclip::Attachment` [god
|
|
47
|
-
objects], Shrine has several core classes each with clear responsibilities:
|
|
47
|
+
Where some popular file attachment libraries have [god objects]
|
|
48
|
+
(`CarrierWave::Uploader::Base` and `Paperclip::Attachment`), Shrine distributes
|
|
49
|
+
responsibilities across multiple core classes:
|
|
48
50
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
| Class | Description |
|
|
52
|
+
| :---- | :----------- |
|
|
53
|
+
| `Shrine::Storage::*` | Encapsulate file operations for the underlying service |
|
|
54
|
+
| `Shrine` | Wraps uploads and handles loading plugins |
|
|
55
|
+
| `Shrine::UploadedFile` | Represents a file that was uploaded to a storage |
|
|
56
|
+
| `Shrine::Attacher` | Handles attaching files to records |
|
|
57
|
+
| `Shrine::Attachment` | Adds convenience attachment methods to model instances |
|
|
54
58
|
|
|
55
59
|
```rb
|
|
56
|
-
photo.image
|
|
57
|
-
photo.image.storage
|
|
58
|
-
photo.image.uploader
|
|
59
|
-
photo.image_attacher
|
|
60
|
+
photo.image #=> #<Shrine::UploadedFile>
|
|
61
|
+
photo.image.storage #=> #<Shrine::Storage::S3>
|
|
62
|
+
photo.image.uploader #=> #<Shrine>
|
|
63
|
+
photo.image_attacher #=> #<Shrine::Attacher>
|
|
64
|
+
photo.class.ancestors #=> [..., #<Shrine::Attachment(:image)>, ...]
|
|
60
65
|
```
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
The attachment functionality is decoupled from persistence and storage, which
|
|
68
|
+
makes it much easier to reason about. Also, special care was taken to make
|
|
69
|
+
integrating new storages and persistence libraries as easy as possible.
|
|
64
70
|
|
|
65
71
|
## Modularity
|
|
66
72
|
|
|
67
73
|
Shrine uses a [plugin system] that allows you to pick and choose the features
|
|
68
|
-
that you want. Moreover, you'
|
|
69
|
-
|
|
74
|
+
that you want. Moreover, you'll only be loading code for the features you've
|
|
75
|
+
selected, which means that Shrine will generally load much faster than the
|
|
76
|
+
alternatives.
|
|
70
77
|
|
|
71
78
|
```rb
|
|
72
79
|
Shrine.plugin :instrumentation
|
|
73
80
|
|
|
74
|
-
# translates to
|
|
81
|
+
# which translates to
|
|
75
82
|
|
|
76
83
|
require "shrine/plugins/instrumentation"
|
|
77
84
|
Shrine.plugin Shrine::Plugins::Instrumentation
|
|
78
85
|
```
|
|
86
|
+
```rb
|
|
87
|
+
Shrine.method(:instrument).owner #=> Shrine::Plugins::Instrumentation::ClassMethods
|
|
88
|
+
```
|
|
79
89
|
|
|
80
|
-
Shrine
|
|
81
|
-
low
|
|
82
|
-
flow. For example, if you prefer the `Attachment`/`Blob` architecture Active
|
|
83
|
-
Storage provides, you can ditch the Shrine's attachment implementation and use
|
|
84
|
-
uploaders and uploaded files that are decoupled from attachment:
|
|
90
|
+
Shrine recommends a certain type of attachment flow, but it still offers good
|
|
91
|
+
low-level abstractions that give you the flexibility to build your own flow.
|
|
85
92
|
|
|
86
93
|
```rb
|
|
87
94
|
uploaded_file = ImageUploader.upload(image, :store) # metadata extraction, upload location generation
|
|
88
95
|
uploaded_file.id #=> "44ccafc10ce6a4ff22829e8f579ee6b9.jpg"
|
|
89
|
-
|
|
96
|
+
uploaded_file.metadata #=> { ... extracted metadata ... }
|
|
90
97
|
|
|
91
98
|
data = uploaded_file.to_json # serialization
|
|
92
99
|
# ...
|
|
@@ -101,8 +108,8 @@ uploaded_file.delete
|
|
|
101
108
|
|
|
102
109
|
Shrine is very diligent when it comes to dependencies. It has two mandatory
|
|
103
110
|
dependencies – [Down] and [ContentDisposition] – which are loaded only by
|
|
104
|
-
components that need them. Some Shrine plugins require additional
|
|
105
|
-
but you only need to load them if you're using those plugins.
|
|
111
|
+
components that need them. Some Shrine plugins also require additional
|
|
112
|
+
dependencies, but you only need to load them if you're using those plugins.
|
|
106
113
|
|
|
107
114
|
Moreover, Shrine often gives you the ability choose between multiple
|
|
108
115
|
alternative dependencies for doing the same task. For example, the
|
|
@@ -116,72 +123,68 @@ Shrine.plugin :determine_mime_type, analyzer: :marcel
|
|
|
116
123
|
Shrine.plugin :store_dimensions, analyzer: :mini_magick
|
|
117
124
|
```
|
|
118
125
|
|
|
119
|
-
This approach gives you control over your dependencies by allowing you to
|
|
120
|
-
choose the combination that best suit your needs.
|
|
121
|
-
|
|
122
126
|
## Inheritance
|
|
123
127
|
|
|
124
128
|
Shrine is designed to handle any types of files. If you're accepting uploads of
|
|
125
129
|
multiple types of files, such as videos and images, chances are that the logic
|
|
126
|
-
for handling them will
|
|
130
|
+
for handling them will differ:
|
|
127
131
|
|
|
128
132
|
* small images can be processed on-the-fly, but large files should be processed in a background job
|
|
129
|
-
*
|
|
130
|
-
*
|
|
133
|
+
* you might want to store different files to different storage services (images, documents, audios, videos)
|
|
134
|
+
* extracting metadata might require different tools depending on the filetype
|
|
131
135
|
|
|
132
|
-
With Shrine you can create isolated uploaders for each type of file.
|
|
133
|
-
|
|
134
|
-
other plugins
|
|
136
|
+
With Shrine you can create isolated uploaders for each type of file. For
|
|
137
|
+
features you want all uploaders to share, their plugins can be loaded globally,
|
|
138
|
+
while other plugins you can load only for selected uploaders.
|
|
135
139
|
|
|
136
140
|
```rb
|
|
141
|
+
# loaded for all plugins
|
|
137
142
|
Shrine.plugin :activerecord
|
|
138
143
|
Shrine.plugin :instrumentation
|
|
139
144
|
```
|
|
140
145
|
```rb
|
|
141
146
|
class ImageUploader < Shrine
|
|
147
|
+
# loaded only for ImageUploader
|
|
142
148
|
plugin :store_dimensions
|
|
143
149
|
end
|
|
144
150
|
```
|
|
145
151
|
```rb
|
|
146
152
|
class VideoUploader < Shrine
|
|
153
|
+
# loaded only for VideoUploader
|
|
147
154
|
plugin :default_storage, store: :vimeo
|
|
148
155
|
end
|
|
149
156
|
```
|
|
150
157
|
|
|
151
158
|
## Processing
|
|
152
159
|
|
|
153
|
-
Most file attachment libraries
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
160
|
+
Most file attachment libraries allow you to process files either "eagerly"
|
|
161
|
+
(Paperclip, CarrierWave) or "on-the-fly" (Dragonfly, Refile, Active Storage).
|
|
162
|
+
However, each approach is suitable for different requirements. For instance,
|
|
163
|
+
while on-the-fly processing is suitable for fast processing (image thumbnails,
|
|
164
|
+
document previews), longer running processing (video transcoding, raw images)
|
|
165
|
+
should be moved into a background job.
|
|
159
166
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
can choose to either generate a set of pre-defined
|
|
163
|
-
|
|
167
|
+
That's why Shrine supports both [eager][derivatives] and
|
|
168
|
+
[on-the-fly][derivation_endpoint] processing. For example, if you're handling
|
|
169
|
+
image uploads, you can choose to either generate a set of pre-defined
|
|
170
|
+
thumbnails during attachment:
|
|
164
171
|
|
|
165
172
|
```rb
|
|
166
173
|
class ImageUploader < Shrine
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
versions[:small] = pipeline.resize_to_limit!(300, 300)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
versions
|
|
174
|
+
Attacher.derivatives do |original|
|
|
175
|
+
magick = ImageProcessing::MiniMagick.source(original)
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
large: magick.resize_to_limit!(800, 800),
|
|
179
|
+
medium: magick.resize_to_limit!(500, 500),
|
|
180
|
+
small: magick.resize_to_limit!(300, 300),
|
|
181
|
+
}
|
|
179
182
|
end
|
|
180
183
|
end
|
|
181
184
|
```
|
|
182
185
|
```rb
|
|
183
|
-
photo.
|
|
184
|
-
#=> "https://s3.amazonaws.com/path/to/large.jpg"
|
|
186
|
+
photo.image_derivatives! # creates thumbnails
|
|
187
|
+
photo.image_url(:large) #=> "https://s3.amazonaws.com/path/to/large.jpg"
|
|
185
188
|
```
|
|
186
189
|
|
|
187
190
|
or generate thumbnails on-demand:
|
|
@@ -196,82 +199,89 @@ class ImageUploader < Shrine
|
|
|
196
199
|
end
|
|
197
200
|
```
|
|
198
201
|
```rb
|
|
199
|
-
photo.image.derivation_url(:thumbnail,
|
|
202
|
+
photo.image.derivation_url(:thumbnail, 600, 400)
|
|
200
203
|
#=> ".../thumbnail/600/400/eyJpZCI6ImZvbyIsInN0b3JhZ2UiOiJzdG9yZSJ9?signature=..."
|
|
201
204
|
```
|
|
202
205
|
|
|
203
|
-
###
|
|
206
|
+
### ImageMagick
|
|
204
207
|
|
|
205
208
|
Many file attachment libraries, such as CarrierWave, Paperclip, Dragonfly and
|
|
206
|
-
Refile, implement their own image processing macros.
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
Even though the ImageProcessing gem was created for Shrine, it's completely
|
|
210
|
-
generic and can be used standalone, or in any other file upload library (e.g.
|
|
211
|
-
Active Storage uses it now as well). It takes care of many details for you,
|
|
212
|
-
such as [auto orienting] the input image and [sharpening] the thumbnails after
|
|
213
|
-
they are resized.
|
|
209
|
+
Refile, implement their own image processing macros. Rather than building yet
|
|
210
|
+
another in-house implementation, a general purpose **[ImageProcessing]** gem
|
|
211
|
+
was created instead, which works great with Shrine.
|
|
214
212
|
|
|
215
213
|
```rb
|
|
216
|
-
require "image_processing"
|
|
214
|
+
require "image_processing/mini_magick"
|
|
217
215
|
|
|
218
216
|
thumbnail = ImageProcessing::MiniMagick
|
|
219
217
|
.source(image)
|
|
220
218
|
.resize_to_limit(400, 400)
|
|
221
219
|
.call # convert input.jpg -auto-orient -resize 400x400> -sharpen 0x1 output.jpg
|
|
222
220
|
|
|
223
|
-
thumbnail #=> #<Tempfile:/var/folders/.../image_processing20180316-18446-1j247h6.
|
|
221
|
+
thumbnail #=> #<Tempfile:/var/folders/.../image_processing20180316-18446-1j247h6.jpg>
|
|
224
222
|
```
|
|
225
223
|
|
|
226
|
-
|
|
224
|
+
It takes care of many details for you, such as [auto orienting] the input image
|
|
225
|
+
and applying [sharpening] to resized images. It also has support for
|
|
226
|
+
[libvips](#libvips).
|
|
227
227
|
|
|
228
|
-
|
|
229
|
-
libvips is a full-featured image processing library like ImageMagick, with
|
|
230
|
-
impressive performance characteristics – it's often **multiple times faster**
|
|
231
|
-
than ImageMagick and has low memory usage (see [Why is libvips quick]).
|
|
228
|
+
### libvips
|
|
232
229
|
|
|
233
|
-
|
|
234
|
-
|
|
230
|
+
**[libvips]** is a full-featured image processing library like ImageMagick,
|
|
231
|
+
with [great performance characteristics][libvips performance]. It's often
|
|
232
|
+
**multiple times faster** than ImageMagick, and also has lower memory usage.
|
|
233
|
+
For more details, see [Why is libvips quick].
|
|
234
|
+
|
|
235
|
+
The ImageProcessing gem provides libvips support as an alternative
|
|
236
|
+
`ImageProcessing::Vips` backend, sharing the same API as the
|
|
237
|
+
`ImageProcessing::MiniMagick` backend.
|
|
235
238
|
|
|
236
239
|
```rb
|
|
237
|
-
require "image_processing/mini_magick"
|
|
238
240
|
require "image_processing/vips"
|
|
239
|
-
require "open-uri"
|
|
240
|
-
|
|
241
|
-
original = open("https://upload.wikimedia.org/wikipedia/commons/3/36/Hopetoun_falls.jpg")
|
|
242
241
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
#=> 0.2s (5x faster)
|
|
242
|
+
# this now generates the thumbnail using libvips
|
|
243
|
+
ImageProcessing::Vips
|
|
244
|
+
.source(image)
|
|
245
|
+
.resize_to_limit!(400, 400)
|
|
248
246
|
```
|
|
249
247
|
|
|
250
248
|
### Other processors
|
|
251
249
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
In contrast to most file attachment libraries, file processing in Shrine is
|
|
251
|
+
just a functional transformation, where you receive the source file on the
|
|
252
|
+
input and return processed files on the output. This makes it easier to use
|
|
253
|
+
custom processing tools and encourages building generic processors that can be
|
|
254
|
+
reused outside of Shrine.
|
|
255
255
|
|
|
256
|
-
|
|
257
|
-
[image_optim] gem to perform additional image optimizations:
|
|
256
|
+
Here is an example of transcoding videos using the [streamio-ffmpeg] gem:
|
|
258
257
|
|
|
258
|
+
```rb
|
|
259
|
+
# Gemfile
|
|
260
|
+
gem "streamio-ffmpeg"
|
|
261
|
+
```
|
|
259
262
|
```rb
|
|
260
263
|
class VideoUploader < Shrine
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
.resize_to_limit!(width, height)
|
|
264
|
+
Attacher.derivatives do |original|
|
|
265
|
+
transcoded = Tempfile.new ["transcoded", ".mp4"]
|
|
266
|
+
screenshot = Tempfile.new ["screenshot", ".jpg"]
|
|
265
267
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
+
movie = FFMPEG::Movie.new(original.path)
|
|
269
|
+
movie.transcode(transcoded.path)
|
|
270
|
+
movie.screenshot(screenshot.path)
|
|
268
271
|
|
|
269
|
-
|
|
272
|
+
{ transcoded: transcoded, screenshot: screenshot }
|
|
270
273
|
end
|
|
271
274
|
end
|
|
272
275
|
```
|
|
276
|
+
```rb
|
|
277
|
+
movie.video_derivatives! # create derivatives
|
|
278
|
+
|
|
279
|
+
movie.video #=> #<Shrine::UploadedFile id="5a5cd0.mov" ...>
|
|
280
|
+
movie.video(:transcoded) #=> #<Shrine::UploadedFile id="7481d6.mp4" ...>
|
|
281
|
+
movie.video(:screenshot) #=> #<Shrine::UploadedFile id="8f3136.jpg" ...>
|
|
282
|
+
```
|
|
273
283
|
|
|
274
|
-
## Metadata
|
|
284
|
+
## Metadata
|
|
275
285
|
|
|
276
286
|
Shrine automatically [extracts metadata][metadata] from each uploaded file,
|
|
277
287
|
including derivatives like image thumbnails, and saves them into the database
|
|
@@ -279,6 +289,17 @@ column. In addition to filename, filesize, and MIME type that are extracted by
|
|
|
279
289
|
default, you can also extract [image dimensions][store_dimensions], or your own
|
|
280
290
|
[custom metadata][add_metadata].
|
|
281
291
|
|
|
292
|
+
```rb
|
|
293
|
+
class ImageUploader < Shrine
|
|
294
|
+
plugin :determine_mime_type # mime_type
|
|
295
|
+
plugin :store_dimensions # width & height
|
|
296
|
+
|
|
297
|
+
add_metadata :resolution do |io|
|
|
298
|
+
image = MiniMagick::Image.new(io.path)
|
|
299
|
+
image.resolution
|
|
300
|
+
end
|
|
301
|
+
end
|
|
302
|
+
```
|
|
282
303
|
```rb
|
|
283
304
|
photo.image.metadata #=>
|
|
284
305
|
# {
|
|
@@ -287,23 +308,30 @@ photo.image.metadata #=>
|
|
|
287
308
|
# "mime_type" => "image/jpeg",
|
|
288
309
|
# "width" => 600,
|
|
289
310
|
# "height" => 400,
|
|
311
|
+
# "resolution" => [72, 72],
|
|
290
312
|
# ...
|
|
291
313
|
# }
|
|
292
314
|
```
|
|
293
315
|
|
|
294
|
-
|
|
295
|
-
|
|
316
|
+
## Validation
|
|
317
|
+
|
|
318
|
+
For file validations there are [built-in validators][validation_helpers], but
|
|
319
|
+
you can also just use plain Ruby code:
|
|
296
320
|
|
|
297
321
|
```rb
|
|
298
|
-
class
|
|
322
|
+
class ImageUploader < Shrine
|
|
323
|
+
plugin :validation_helpers
|
|
324
|
+
|
|
299
325
|
Attacher.validate do
|
|
300
|
-
# validation macros
|
|
301
326
|
validate_max_size 10*1024*1024
|
|
302
|
-
|
|
327
|
+
validate_extension %w[jpg jpeg png webp]
|
|
303
328
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
329
|
+
if validate_mime_type %W[image/jpeg image/png image/webp]
|
|
330
|
+
validate_max_dimensions [5000, 5000]
|
|
331
|
+
|
|
332
|
+
unless ImageProcessing::MiniMagick.valid_image?(file.download.path)
|
|
333
|
+
error << "seems to be corrupted"
|
|
334
|
+
end
|
|
307
335
|
end
|
|
308
336
|
end
|
|
309
337
|
end
|
|
@@ -311,69 +339,70 @@ end
|
|
|
311
339
|
|
|
312
340
|
## Backgrounding
|
|
313
341
|
|
|
314
|
-
In most file upload solutions background processing was an
|
|
315
|
-
resulted in complex implementations. Shrine
|
|
316
|
-
feature in mind from day one. It is supported
|
|
317
|
-
[`backgrounding`][backgrounding] plugin and can be used with [any
|
|
318
|
-
library][backgrounding libraries].
|
|
342
|
+
In most file upload solutions, support for background processing was an
|
|
343
|
+
afterthought, which resulted in complex and unreliable implementations. Shrine
|
|
344
|
+
was designed with backgrounding feature in mind from day one. It is supported
|
|
345
|
+
via the [`backgrounding`][backgrounding] plugin and can be used with [any
|
|
346
|
+
backgrounding library][backgrounding libraries].
|
|
347
|
+
|
|
348
|
+
```rb
|
|
349
|
+
Shrine.plugin :backgrounding
|
|
350
|
+
Shrine::Attacher.promote_block do
|
|
351
|
+
PromoteJob.perform_async(self.class.name, record.class.name, record.id, name, file_data)
|
|
352
|
+
end
|
|
353
|
+
```
|
|
354
|
+
```rb
|
|
355
|
+
class PromoteJob
|
|
356
|
+
include Sidekiq::Worker
|
|
357
|
+
|
|
358
|
+
def perform(attacher_class, record_class, record_id, name, file_data)
|
|
359
|
+
attacher_class = Object.const_get(attacher_class)
|
|
360
|
+
record = Object.const_get(record_class).find(record_id) # if using Active Record
|
|
361
|
+
|
|
362
|
+
attacher = attacher_class.retrieve(model: record, name: name, file: file_data)
|
|
363
|
+
attacher.create_derivatives # perform processing
|
|
364
|
+
attacher.atomic_promote
|
|
365
|
+
rescue Shrine::AttachmentChanged, ActiveRecord::RecordNotFound
|
|
366
|
+
# attachment changes are detected for concurrency safety
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
With Shrine, there is no need for a separate boolean column that indicates the
|
|
372
|
+
processing status. Processed file data is stored into the attachment database
|
|
373
|
+
column, which allows you to easily check whether a file has been processed.
|
|
374
|
+
|
|
375
|
+
```rb
|
|
376
|
+
photo = Photo.create(image: file) # background job is kicked off
|
|
377
|
+
|
|
378
|
+
photo.image(:large) #=> nil (thumbnails are still being processed)
|
|
379
|
+
# ... sometime later ...
|
|
380
|
+
photo.image(:large) #=> #<Shrine::UploadedFile> (processing has finished)
|
|
381
|
+
```
|
|
319
382
|
|
|
320
383
|
## Direct Uploads
|
|
321
384
|
|
|
322
|
-
Shrine
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
So, if you're expecting large file uploads, you can use Uppy as a [JavaScript
|
|
345
|
-
client][Uppy Tus] and have it upload to [Ruby server][tus-ruby-server], then
|
|
346
|
-
attach uploaded files using the handy [Shrine integration][shrine-tus]. Shrine
|
|
347
|
-
handles uploads and downloads in a streaming fashion, so you can expect low
|
|
348
|
-
memory usage.
|
|
349
|
-
|
|
350
|
-
Alternatively, you can have [resumable multipart uploads directly to
|
|
351
|
-
S3][uppy-s3_multipart].
|
|
352
|
-
|
|
353
|
-
## Security
|
|
354
|
-
|
|
355
|
-
It's [important][OWASP] to care about security when handling file uploads, and
|
|
356
|
-
Shrine bakes in many good practices. For starters, it uses a separate
|
|
357
|
-
"temporary" storage for direct uploads, making it easy to periodically clear
|
|
358
|
-
uploads that didn't end up being attached and difficult for the attacker to
|
|
359
|
-
flood the main storage.
|
|
360
|
-
|
|
361
|
-
File processing and upload to permanent storage is done outside of a database
|
|
362
|
-
transaction, and only after the file has been successfully validated. The
|
|
363
|
-
`determine_mime_type` plugin determines MIME type from the file content (rather
|
|
364
|
-
than relying on the `Content-Type` request header), preventing exploits like
|
|
365
|
-
[ImageTragick].
|
|
366
|
-
|
|
367
|
-
The `remote_url` plugin requires specifying a `:max_size` option, which limits
|
|
368
|
-
the maximum allowed size of the remote file. The [Down] gem which the
|
|
369
|
-
`remote_url` plugin uses will [terminate the download early][Down max size]
|
|
370
|
-
when it realizes it's too large.
|
|
371
|
-
|
|
372
|
-
[Paperclip]: https://github.com/thoughtbot/paperclip
|
|
373
|
-
[CarrierWave]: https://github.com/carrierwaveuploader/carrierwave
|
|
374
|
-
[Dragonfly]: http://markevans.github.io/dragonfly/
|
|
375
|
-
[Refile]: https://github.com/refile/refile
|
|
376
|
-
[Active Storage]: https://github.com/rails/rails/tree/master/activestorage#active-storage
|
|
385
|
+
For client side uploads, Shrine adopts **[Uppy]**, a modern JavaScript file
|
|
386
|
+
upload library. This gives the developer a lot more power in customizing the
|
|
387
|
+
user experience compared to a custom JavaScript solution implemented by Refile
|
|
388
|
+
and Active Storage.
|
|
389
|
+
|
|
390
|
+
Uppy supports direct uploads to [AWS S3][Uppy AwsS3] or to a [custom
|
|
391
|
+
endpoint][Uppy XHRUpload]. It also supports **resumable** uploads, either
|
|
392
|
+
[directly to S3][Uppy AwsS3Multipart] or via the [tus protocol][tus]. For the
|
|
393
|
+
UI you can choose from various components, ranging from a simple [status
|
|
394
|
+
bar][Uppy StatusBar] to a full-featured [dashboard][Uppy Dashboard].
|
|
395
|
+
|
|
396
|
+
Shrine provides server side components for each type of upload. They are built
|
|
397
|
+
on top of Rack, so that they can be used with any Ruby web framework.
|
|
398
|
+
|
|
399
|
+
| Uppy | Shrine |
|
|
400
|
+
| :--- | :----- |
|
|
401
|
+
| [XHRUpload][Uppy XHRUpload] | [`upload_endpoint`][upload_endpoint] |
|
|
402
|
+
| [AwsS3][Uppy AwsS3] | [`presign_endpoint`][presign_endpoint] |
|
|
403
|
+
| [AwsS3Multipart][Uppy AwsS3Multipart] | [`uppy-s3_multipart`][uppy-s3_multipart] |
|
|
404
|
+
| [Tus][Uppy Tus] | [`tus-ruby-server`][tus-ruby-server] |
|
|
405
|
+
|
|
377
406
|
[Rack]: https://rack.github.io
|
|
378
407
|
[Sinatra]: http://sinatrarb.com
|
|
379
408
|
[Roda]: http://roda.jeremyevans.net
|
|
@@ -396,37 +425,35 @@ when it realizes it's too large.
|
|
|
396
425
|
[MiniMagick]: https://github.com/minimagick/minimagick
|
|
397
426
|
[ruby-vips]: https://github.com/libvips/ruby-vips
|
|
398
427
|
[god objects]: https://en.wikipedia.org/wiki/God_object
|
|
399
|
-
[ImageMagick]: https://www.imagemagick.org
|
|
400
|
-
[refile-mini_magick]: https://github.com/refile/refile-mini_magick
|
|
401
428
|
[ImageProcessing]: https://github.com/janko/image_processing
|
|
402
429
|
[auto orienting]: https://www.imagemagick.org/script/command-line-options.php#auto-orient
|
|
403
430
|
[sharpening]: https://photography.tutsplus.com/tutorials/what-is-image-sharpening--cms-26627
|
|
404
431
|
[libvips]: http://libvips.github.io/libvips/
|
|
405
432
|
[Why is libvips quick]: https://github.com/libvips/libvips/wiki/Why-is-libvips-quick
|
|
406
|
-
[metadata]: /
|
|
407
|
-
[store_dimensions]: /
|
|
408
|
-
[add_metadata]: /
|
|
409
|
-
[validation]: /
|
|
410
|
-
[upload_endpoint]: /
|
|
411
|
-
[presign_endpoint]: /
|
|
433
|
+
[metadata]: https://shrinerb.com/docs/metadata
|
|
434
|
+
[store_dimensions]: https://shrinerb.com/docs/plugins/store_dimensions
|
|
435
|
+
[add_metadata]: https://shrinerb.com/docs/plugins/add_metadata
|
|
436
|
+
[validation]: https://shrinerb.com/docs/validation
|
|
437
|
+
[upload_endpoint]: https://shrinerb.com/docs/plugins/upload_endpoint
|
|
438
|
+
[presign_endpoint]: https://shrinerb.com/docs/plugins/presign_endpoint
|
|
412
439
|
[Uppy]: https://uppy.io
|
|
413
|
-
[Uppy XHRUpload]: https://uppy.io/docs/
|
|
440
|
+
[Uppy XHRUpload]: https://uppy.io/docs/xhr-upload/
|
|
414
441
|
[Uppy AwsS3]: https://uppy.io/docs/aws-s3/
|
|
415
442
|
[Uppy Tus]: https://uppy.io/docs/tus/
|
|
443
|
+
[Uppy AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
|
|
444
|
+
[tus]: https://tus.io
|
|
416
445
|
[Uppy StatusBar]: https://uppy.io/examples/statusbar/
|
|
417
446
|
[Uppy Dashboard]: https://uppy.io/examples/dashboard/
|
|
418
|
-
[backgrounding]: /doc/plugins/backgrounding.md#readme
|
|
419
|
-
[backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
|
|
420
|
-
[Down streaming]: https://github.com/janko/down#streaming
|
|
421
|
-
[Transloadit]: https://transloadit.com
|
|
422
|
-
[tus]: https://tus.io
|
|
423
|
-
[tus implementations]: https://tus.io/implementations.html
|
|
424
447
|
[tus-ruby-server]: https://github.com/janko/tus-ruby-server
|
|
425
|
-
[shrine-tus]: https://github.com/shrinerb/shrine-tus
|
|
426
|
-
[ImageTragick]: https://imagetragick.com
|
|
427
448
|
[uppy-s3_multipart]: https://github.com/janko/uppy-s3_multipart
|
|
428
|
-
[
|
|
429
|
-
[
|
|
430
|
-
[
|
|
431
|
-
[
|
|
432
|
-
[
|
|
449
|
+
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
|
450
|
+
[backgrounding libraries]: https://github.com/shrinerb/shrine/wiki/Backgrounding-Libraries
|
|
451
|
+
[Down streaming]: https://github.com/janko/down#streaming
|
|
452
|
+
[validation_helpers]: https://shrinerb.com/docs/plugins/validation_helpers
|
|
453
|
+
[derivatives]: https://shrinerb.com/docs/plugins/derivatives
|
|
454
|
+
[derivation_endpoint]: https://shrinerb.com/docs/plugins/derivation_endpoint
|
|
455
|
+
[libvips performance]: https://github.com/libvips/libvips/wiki/Speed-and-memory-use#results
|
|
456
|
+
[streamio-ffmpeg]: https://github.com/streamio/streamio-ffmpeg
|
|
457
|
+
[CarrierWave]: https://shrinerb.com/docs/carrierwave
|
|
458
|
+
[Paperclip]: https://shrinerb.com/docs/paperclip
|
|
459
|
+
[Refile]: https://shrinerb.com/docs/refile
|