shrine 2.19.4 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +485 -43
- data/LICENSE.txt +1 -1
- data/README.md +81 -977
- data/doc/advantages.md +231 -204
- data/doc/attacher.md +304 -153
- data/doc/carrierwave.md +297 -226
- data/doc/changing_derivatives.md +308 -0
- data/doc/changing_location.md +102 -21
- data/doc/changing_storage.md +110 -0
- data/doc/creating_persistence_plugins.md +132 -0
- data/doc/creating_plugins.md +43 -23
- data/doc/creating_storages.md +19 -5
- data/doc/design.md +147 -97
- data/doc/direct_s3.md +38 -28
- data/doc/external/articles.md +63 -0
- data/doc/external/extensions.md +53 -0
- data/doc/external/misc.md +32 -0
- data/doc/getting_started.md +1115 -0
- data/doc/metadata.md +190 -109
- data/doc/multiple_files.md +62 -34
- data/doc/paperclip.md +384 -262
- data/doc/plugins/activerecord.md +177 -46
- data/doc/plugins/add_metadata.md +139 -38
- data/doc/plugins/atomic_helpers.md +217 -0
- data/doc/plugins/backgrounding.md +156 -98
- data/doc/plugins/cached_attachment_data.md +7 -5
- data/doc/plugins/column.md +121 -0
- data/doc/plugins/data_uri.md +23 -22
- data/doc/plugins/default_storage.md +36 -10
- data/doc/plugins/default_url.md +30 -13
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +162 -101
- data/doc/plugins/derivatives.md +829 -0
- data/doc/plugins/determine_mime_type.md +4 -2
- data/doc/plugins/download_endpoint.md +64 -8
- data/doc/plugins/dynamic_storage.md +5 -3
- data/doc/plugins/entity.md +263 -0
- data/doc/plugins/form_assign.md +55 -0
- data/doc/plugins/included.md +31 -8
- data/doc/plugins/infer_extension.md +21 -10
- data/doc/plugins/instrumentation.md +38 -16
- data/doc/plugins/keep_files.md +14 -17
- data/doc/plugins/metadata_attributes.md +42 -13
- data/doc/plugins/mirroring.md +118 -0
- data/doc/plugins/model.md +210 -0
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +24 -0
- data/doc/plugins/persistence.md +101 -0
- data/doc/plugins/presign_endpoint.md +9 -4
- data/doc/plugins/pretty_location.md +16 -3
- data/doc/plugins/processing.md +4 -2
- data/doc/plugins/rack_file.md +8 -2
- data/doc/plugins/rack_response.md +6 -2
- data/doc/plugins/recache.md +4 -2
- data/doc/plugins/refresh_metadata.md +49 -9
- data/doc/plugins/remote_url.md +84 -47
- data/doc/plugins/remove_attachment.md +27 -6
- data/doc/plugins/remove_invalid.md +21 -6
- data/doc/plugins/restore_cached_data.md +11 -3
- data/doc/plugins/sequel.md +159 -35
- data/doc/plugins/signature.md +16 -5
- data/doc/plugins/store_dimensions.md +14 -2
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/type_predicates.md +96 -0
- data/doc/plugins/upload_endpoint.md +13 -13
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/{default_url_options.md → url_options.md} +9 -7
- data/doc/plugins/validation.md +97 -0
- data/doc/plugins/validation_helpers.md +16 -13
- data/doc/plugins/versions.md +15 -19
- data/doc/processing.md +438 -221
- data/doc/refile.md +185 -167
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +6 -2
- data/doc/release_notes/1.2.0.md +4 -0
- data/doc/release_notes/1.3.0.md +4 -0
- data/doc/release_notes/1.4.0.md +4 -0
- data/doc/release_notes/1.4.1.md +4 -0
- data/doc/release_notes/1.4.2.md +4 -0
- data/doc/release_notes/2.0.0.md +4 -0
- data/doc/release_notes/2.0.1.md +4 -0
- data/doc/release_notes/2.1.0.md +4 -0
- data/doc/release_notes/2.1.1.md +4 -0
- data/doc/release_notes/2.10.0.md +4 -0
- data/doc/release_notes/2.10.1.md +4 -0
- data/doc/release_notes/2.11.0.md +4 -0
- data/doc/release_notes/2.12.0.md +4 -0
- data/doc/release_notes/2.13.0.md +4 -0
- data/doc/release_notes/2.14.0.md +5 -1
- data/doc/release_notes/2.15.0.md +11 -7
- data/doc/release_notes/2.16.0.md +4 -0
- data/doc/release_notes/2.17.0.md +4 -0
- data/doc/release_notes/2.18.0.md +4 -0
- data/doc/release_notes/2.19.0.md +6 -3
- data/doc/release_notes/2.2.0.md +4 -0
- data/doc/release_notes/2.3.0.md +4 -0
- data/doc/release_notes/2.3.1.md +4 -0
- data/doc/release_notes/2.4.0.md +4 -0
- data/doc/release_notes/2.4.1.md +4 -0
- data/doc/release_notes/2.5.0.md +4 -0
- data/doc/release_notes/2.6.0.md +4 -0
- data/doc/release_notes/2.6.1.md +4 -0
- data/doc/release_notes/2.7.0.md +4 -0
- data/doc/release_notes/2.8.0.md +4 -0
- data/doc/release_notes/2.9.0.md +4 -0
- data/doc/release_notes/3.0.0.md +981 -0
- data/doc/release_notes/3.0.1.md +22 -0
- data/doc/release_notes/3.1.0.md +73 -0
- data/doc/release_notes/3.2.0.md +96 -0
- data/doc/release_notes/3.2.1.md +31 -0
- data/doc/release_notes/3.2.2.md +14 -0
- data/doc/release_notes/3.3.0.md +105 -0
- data/doc/release_notes/3.4.0.md +35 -0
- data/doc/retrieving_uploads.md +4 -1
- data/doc/securing_uploads.md +60 -37
- data/doc/storage/file_system.md +20 -3
- data/doc/storage/memory.md +19 -0
- data/doc/storage/s3.md +117 -83
- data/doc/testing.md +124 -144
- data/doc/upgrading_to_3.md +710 -0
- data/doc/validation.md +54 -90
- data/lib/shrine/attacher.rb +287 -171
- data/lib/shrine/attachment.rb +13 -46
- data/lib/shrine/plugins/_persistence.rb +93 -0
- data/lib/shrine/plugins/activerecord.rb +77 -34
- data/lib/shrine/plugins/add_metadata.rb +25 -17
- data/lib/shrine/plugins/atomic_helpers.rb +119 -0
- data/lib/shrine/plugins/backgrounding.rb +77 -113
- data/lib/shrine/plugins/cached_attachment_data.rb +6 -15
- data/lib/shrine/plugins/column.rb +102 -0
- data/lib/shrine/plugins/data_uri.rb +38 -36
- data/lib/shrine/plugins/default_storage.rb +45 -15
- data/lib/shrine/plugins/default_url.rb +12 -24
- data/lib/shrine/plugins/default_url_options.rb +3 -30
- data/lib/shrine/plugins/delete_raw.rb +10 -16
- data/lib/shrine/plugins/derivation_endpoint.rb +89 -134
- data/lib/shrine/plugins/derivatives.rb +637 -0
- data/lib/shrine/plugins/determine_mime_type.rb +9 -21
- data/lib/shrine/plugins/download_endpoint.rb +109 -133
- data/lib/shrine/plugins/dynamic_storage.rb +5 -11
- data/lib/shrine/plugins/entity.rb +152 -0
- data/lib/shrine/plugins/form_assign.rb +108 -0
- data/lib/shrine/plugins/included.rb +6 -6
- data/lib/shrine/plugins/infer_extension.rb +13 -20
- data/lib/shrine/plugins/instrumentation.rb +54 -42
- data/lib/shrine/plugins/keep_files.rb +3 -15
- data/lib/shrine/plugins/metadata_attributes.rb +28 -19
- data/lib/shrine/plugins/mirroring.rb +142 -0
- data/lib/shrine/plugins/model.rb +158 -0
- data/lib/shrine/plugins/module_include.rb +3 -3
- data/lib/shrine/plugins/multi_cache.rb +27 -0
- data/lib/shrine/plugins/presign_endpoint.rb +18 -22
- data/lib/shrine/plugins/pretty_location.rb +15 -9
- data/lib/shrine/plugins/processing.rb +22 -9
- data/lib/shrine/plugins/rack_file.rb +2 -42
- data/lib/shrine/plugins/rack_response.rb +15 -10
- data/lib/shrine/plugins/recache.rb +6 -5
- data/lib/shrine/plugins/refresh_metadata.rb +13 -11
- data/lib/shrine/plugins/remote_url.rb +49 -49
- data/lib/shrine/plugins/remove_attachment.rb +10 -6
- data/lib/shrine/plugins/remove_invalid.rb +19 -8
- data/lib/shrine/plugins/restore_cached_data.rb +13 -7
- data/lib/shrine/plugins/sequel.rb +86 -36
- data/lib/shrine/plugins/signature.rb +10 -16
- data/lib/shrine/plugins/store_dimensions.rb +35 -40
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/type_predicates.rb +113 -0
- data/lib/shrine/plugins/upload_endpoint.rb +25 -23
- data/lib/shrine/plugins/upload_options.rb +14 -15
- data/lib/shrine/plugins/url_options.rb +31 -0
- data/lib/shrine/plugins/validation.rb +80 -0
- data/lib/shrine/plugins/validation_helpers.rb +34 -57
- data/lib/shrine/plugins/versions.rb +107 -87
- data/lib/shrine/plugins.rb +22 -0
- data/lib/shrine/storage/file_system.rb +46 -64
- data/lib/shrine/storage/linter.rb +42 -7
- data/lib/shrine/storage/memory.rb +49 -0
- data/lib/shrine/storage/s3.rb +154 -158
- data/lib/shrine/uploaded_file.rb +28 -30
- data/lib/shrine/version.rb +3 -3
- data/lib/shrine.rb +86 -149
- data/shrine.gemspec +9 -10
- metadata +79 -83
- data/doc/migrating_storage.md +0 -76
- data/doc/plugins/backup.md +0 -31
- data/doc/plugins/copy.md +0 -24
- data/doc/plugins/delete_promoted.md +0 -12
- data/doc/plugins/direct_upload.md +0 -172
- data/doc/plugins/hooks.md +0 -58
- data/doc/plugins/logging.md +0 -42
- data/doc/plugins/migration_helpers.md +0 -60
- data/doc/plugins/moving.md +0 -19
- data/doc/plugins/multi_delete.md +0 -20
- data/doc/plugins/parallelize.md +0 -16
- data/doc/plugins/parsed_json.md +0 -23
- data/doc/regenerating_versions.md +0 -143
- data/lib/shrine/plugins/background_helpers.rb +0 -5
- data/lib/shrine/plugins/backup.rb +0 -90
- data/lib/shrine/plugins/copy.rb +0 -50
- data/lib/shrine/plugins/delete_promoted.rb +0 -20
- data/lib/shrine/plugins/direct_upload.rb +0 -217
- data/lib/shrine/plugins/hooks.rb +0 -90
- data/lib/shrine/plugins/logging.rb +0 -142
- data/lib/shrine/plugins/migration_helpers.rb +0 -70
- data/lib/shrine/plugins/moving.rb +0 -57
- data/lib/shrine/plugins/multi_delete.rb +0 -32
- data/lib/shrine/plugins/parallelize.rb +0 -78
- data/lib/shrine/plugins/parsed_json.rb +0 -29
data/doc/creating_plugins.md
CHANGED
@@ -1,6 +1,13 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
id: creating-plugins
|
3
|
+
title: Writing a Plugin
|
4
|
+
---
|
5
|
+
|
6
|
+
Shrine has a lot of plugins built-in, but you can use Shrine's plugin system to
|
7
|
+
create your own.
|
8
|
+
|
9
|
+
## Definition
|
2
10
|
|
3
|
-
Shrine has a lot of plugins built-in, but you can also easily create your own.
|
4
11
|
Simply put, a plugin is a module:
|
5
12
|
|
6
13
|
```rb
|
@@ -11,9 +18,9 @@ end
|
|
11
18
|
Shrine.plugin MyPlugin
|
12
19
|
```
|
13
20
|
|
14
|
-
If you would like to load plugins with a symbol
|
15
|
-
that ship with Shrine, you need to put the plugin in
|
16
|
-
`shrine/plugins/my_plugin.rb` in
|
21
|
+
If you would like to load plugins with a symbol (like you already do with
|
22
|
+
plugins that ship with Shrine), you need to put the plugin in
|
23
|
+
`shrine/plugins/my_plugin.rb` in your load path and register it:
|
17
24
|
|
18
25
|
```rb
|
19
26
|
# shrine/plugins/my_plugin.rb
|
@@ -31,6 +38,8 @@ end
|
|
31
38
|
Shrine.plugin :my_plugin
|
32
39
|
```
|
33
40
|
|
41
|
+
## Methods
|
42
|
+
|
34
43
|
The way to make plugins actually extend Shrine's core classes is by defining
|
35
44
|
special modules inside the plugin. Here's a list of all "special" modules:
|
36
45
|
|
@@ -51,7 +60,7 @@ uploading:
|
|
51
60
|
```rb
|
52
61
|
module MyPlugin
|
53
62
|
module InstanceMethods
|
54
|
-
def upload(io,
|
63
|
+
def upload(io, **options)
|
55
64
|
time = Time.now
|
56
65
|
result = super
|
57
66
|
duration = Time.now - time
|
@@ -61,40 +70,51 @@ module MyPlugin
|
|
61
70
|
end
|
62
71
|
```
|
63
72
|
|
64
|
-
Notice that we can call `super` to get the original behaviour.
|
65
|
-
these modules, you can also make your plugin configurable:
|
73
|
+
Notice that we can call `super` to get the original behaviour.
|
66
74
|
|
67
|
-
|
68
|
-
Shrine.plugin :my_plugin, foo: "bar"
|
69
|
-
```
|
75
|
+
## Configuration
|
70
76
|
|
71
|
-
You
|
72
|
-
|
73
|
-
|
74
|
-
methods.
|
77
|
+
You'll likely want to make your plugin configurable. You can do that by
|
78
|
+
overriding the `.configure` class method and storing received options into
|
79
|
+
`Shrine.opts`:
|
75
80
|
|
76
81
|
```rb
|
77
82
|
module MyPlugin
|
78
|
-
def self.configure(uploader,
|
79
|
-
uploader
|
80
|
-
uploader.opts[:
|
83
|
+
def self.configure(uploader, **opts)
|
84
|
+
uploader.opts[:my_plugin] ||= {}
|
85
|
+
uploader.opts[:my_plugin].merge!(opts)
|
81
86
|
end
|
82
87
|
|
83
88
|
module InstanceMethods
|
84
|
-
def
|
85
|
-
opts[:
|
89
|
+
def upload(io, **options)
|
90
|
+
opts[:my_plugin] #=> { ... }
|
91
|
+
# ...
|
86
92
|
end
|
87
93
|
end
|
88
94
|
end
|
89
95
|
```
|
90
96
|
|
97
|
+
Users can now pass these configuration options when loading your plugin:
|
98
|
+
|
99
|
+
```rb
|
100
|
+
Shrine.plugin :my_plugin, foo: "bar"
|
101
|
+
```
|
102
|
+
|
103
|
+
## Dependencies
|
104
|
+
|
91
105
|
If your plugin depends on other plugins, you can load them inside of
|
92
|
-
`.load_dependencies
|
106
|
+
`.load_dependencies`:
|
93
107
|
|
94
108
|
```rb
|
95
109
|
module MyPlugin
|
96
|
-
def self.load_dependencies(uploader,
|
97
|
-
uploader.plugin :
|
110
|
+
def self.load_dependencies(uploader, **opts)
|
111
|
+
uploader.plugin :derivatives # depends on the derivatives plugin
|
98
112
|
end
|
99
113
|
end
|
100
114
|
```
|
115
|
+
|
116
|
+
The dependencies will get loaded before your plugin, allowing you to override
|
117
|
+
methods of your dependencies in your method modules.
|
118
|
+
|
119
|
+
The same configuration options passed to `.configure` are passed to
|
120
|
+
`.load_dependencies` as well.
|
data/doc/creating_storages.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
id: creating-storages
|
3
|
+
title: Writing a Storage
|
4
|
+
---
|
2
5
|
|
3
6
|
Shrine ships with the FileSystem and S3 storages, but it's also easy to create
|
4
7
|
your own. A storage is a class which needs to implement `#upload`, `#url`,
|
@@ -122,6 +125,8 @@ The storage can support additional options to customize how the file will be
|
|
122
125
|
opened, `Shrine::UploadedFile#open` and `Shrine::UploadedFile#download` will
|
123
126
|
forward any given options to `#open`.
|
124
127
|
|
128
|
+
When file is not found, `Shrine::FileNotFound` exception should be raised.
|
129
|
+
|
125
130
|
## Url
|
126
131
|
|
127
132
|
The `#url` storage method is called by `Shrine::UploadedFile#url`, it accepts a
|
@@ -204,15 +209,24 @@ The storage can support additional options to customize how the presign will be
|
|
204
209
|
generated, those can be forwarded via the `:presign_options` option on the
|
205
210
|
`presign_endpoint` plugin.
|
206
211
|
|
207
|
-
## Clear
|
212
|
+
## Delete Prefixed and Clear
|
213
|
+
|
214
|
+
There are two methods that are not currently used by shrine, but which it's good
|
215
|
+
for storages to provide to allow client code to delete files from storage. If
|
216
|
+
storages provide these conventional methods, then clients can delete files using
|
217
|
+
consistent API for any storage.
|
208
218
|
|
209
|
-
|
210
|
-
|
211
|
-
|
219
|
+
`#clear!` deletes all files from storage, and `#delete_prefixed` will delete all
|
220
|
+
files in a given directory/prefix/path. While not strictly required for shrine storage
|
221
|
+
service functionality, storages should usually implement if possible.
|
212
222
|
|
213
223
|
```rb
|
214
224
|
class MyStorage
|
215
225
|
# ...
|
226
|
+
def delete_prefixed(prefix_path)
|
227
|
+
# deletes all files under the supplied argument prefix
|
228
|
+
end
|
229
|
+
|
216
230
|
def clear!
|
217
231
|
# deletes all files in the storage
|
218
232
|
end
|
data/doc/design.md
CHANGED
@@ -1,21 +1,26 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
title: The Design of Shrine
|
3
|
+
---
|
2
4
|
|
3
|
-
*If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
|
5
|
+
*If you want an in-depth walkthrough through the Shrine codebase, see [Notes on
|
6
|
+
study of shrine implementation] article by Jonathan Rochkind.*
|
4
7
|
|
5
|
-
There are five main types of
|
8
|
+
There are five main types of classes that you deal with in Shrine:
|
6
9
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
| Class | Description |
|
11
|
+
| :---- | :---------- |
|
12
|
+
| [`Shrine::Storage::*`](#Storage) | Manages files on a particular storage service |
|
13
|
+
| [`Shrine`](#Shrine) | Wraps uploads and handles loading plugins |
|
14
|
+
| [`Shrine::UploadedFile`](#shrineuploadedfile) | Represents a file uploaded to a storage |
|
15
|
+
| [`Shrine::Attacher`](#shrineattacher) | Handles file attachment logic |
|
16
|
+
| [`Shrine::Attachment`](#shrineattachment) | Provides convenience model attachment interface |
|
12
17
|
|
13
18
|
## Storage
|
14
19
|
|
15
20
|
On the lowest level we have a storage. A storage class encapsulates file
|
16
21
|
management logic on a particular service. It is what actually performs uploads,
|
17
22
|
generation of URLs, deletions and similar. By convention it is namespaced under
|
18
|
-
`Shrine::Storage
|
23
|
+
`Shrine::Storage::*`.
|
19
24
|
|
20
25
|
```rb
|
21
26
|
filesystem = Shrine::Storage::FileSystem.new("uploads")
|
@@ -24,7 +29,7 @@ filesystem.url("foo") #=> "uploads/foo"
|
|
24
29
|
filesystem.delete("foo")
|
25
30
|
```
|
26
31
|
|
27
|
-
A storage is a PORO which
|
32
|
+
A storage is a PORO which implements the following interface:
|
28
33
|
|
29
34
|
```rb
|
30
35
|
class Shrine
|
@@ -34,7 +39,7 @@ class Shrine
|
|
34
39
|
# uploads `io` to the location `id`
|
35
40
|
end
|
36
41
|
|
37
|
-
def open(id)
|
42
|
+
def open(id, **options)
|
38
43
|
# returns the remote file as an IO-like object
|
39
44
|
end
|
40
45
|
|
@@ -46,7 +51,7 @@ class Shrine
|
|
46
51
|
# deletes the file from the storage
|
47
52
|
end
|
48
53
|
|
49
|
-
def url(id, options
|
54
|
+
def url(id, **options)
|
50
55
|
# URL to the remote file, accepts options for customizing the URL
|
51
56
|
end
|
52
57
|
end
|
@@ -54,24 +59,24 @@ class Shrine
|
|
54
59
|
end
|
55
60
|
```
|
56
61
|
|
57
|
-
Storages are typically not used directly, but through `Shrine
|
62
|
+
Storages are typically not used directly, but through [`Shrine`](#shrine) and
|
63
|
+
[`Shrine::UploadedFile`](#shrine-uploadedfile) classes.
|
58
64
|
|
59
65
|
## `Shrine`
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
name:
|
67
|
+
The `Shrine` class (also called an "uploader") primarily provides a wrapper
|
68
|
+
method around `Storage#upload`. First, the storage needs to be registered under
|
69
|
+
a name:
|
64
70
|
|
65
71
|
```rb
|
66
|
-
Shrine.storages[:
|
72
|
+
Shrine.storages[:disk] = Shrine::Storage::FileSystem.new("uploads")
|
67
73
|
```
|
68
74
|
|
69
|
-
Now we can
|
75
|
+
Now we can upload files to the registered storage:
|
70
76
|
|
71
77
|
```rb
|
72
|
-
|
73
|
-
uploaded_file =
|
74
|
-
uploaded_file #=> #<Shrine::UploadedFile>
|
78
|
+
uploaded_file = Shrine.upload(file, :disk)
|
79
|
+
uploaded_file #=> #<Shrine::UploadedFile storage=:disk id="6a9fb596cc554efb" ...>
|
75
80
|
```
|
76
81
|
|
77
82
|
The argument to `Shrine#upload` must be an IO-like object. The method does the
|
@@ -83,63 +88,97 @@ following:
|
|
83
88
|
* closes the file
|
84
89
|
* creates a `Shrine::UploadedFile` from the data
|
85
90
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
plugin
|
91
|
+
### Plugins
|
92
|
+
|
93
|
+
The `Shrine` class is also used for loading plugins, which provide additional
|
94
|
+
functionality by extending core classes.
|
95
|
+
|
96
|
+
```rb
|
97
|
+
Shrine.plugin :derivatives
|
98
|
+
|
99
|
+
Shrine::UploadedFile.ancestors #=> [..., Shrine::Plugins::Derivatives::FileMethods, Shrine::UploadedFile::InstanceMethods, ...]
|
100
|
+
Shrine::Attacher.ancestors #=> [..., Shrine::Plugins::Derivatives::AttacherMethods, Shrine::Attacher::InstanceMethods, ...]
|
101
|
+
Shrine::Attachment.ancestors #=> [..., Shrine::Plugins::Derivatives::AttachmentMethods, Shrine::Attachment::InstanceMethods, ...]
|
102
|
+
```
|
103
|
+
|
104
|
+
The plugins store their configuration in `Shrine.opts`:
|
105
|
+
|
106
|
+
```rb
|
107
|
+
Shrine.plugin :derivation_endpoint, secret_key: "foo"
|
108
|
+
Shrine.plugin :default_storage, store: :other_store
|
109
|
+
Shrine.plugin :activerecord
|
110
|
+
|
111
|
+
Shrine.opts #=>
|
112
|
+
# { derivation_endpoint: { options: { secret_key: "foo" }, derivations: {} },
|
113
|
+
# default_storage: { store: :other_store },
|
114
|
+
# column: { serializer: Shrine::Plugins::Column::JsonSerializer },
|
115
|
+
# model: { cache: true },
|
116
|
+
# activerecord: { callbacks: true, validations: true } }
|
117
|
+
```
|
118
|
+
|
119
|
+
Each `Shrine` subclass has its own copy of the core classes, storages and
|
120
|
+
options, which makes it possible to customize attachment logic per uploader.
|
121
|
+
|
122
|
+
```rb
|
123
|
+
MyUploader = Class.new(Shrine)
|
124
|
+
MyUploader::UploadedFile.superclass #=> Shrine::UploadedFile
|
125
|
+
MyUploader::Attacher.superclass #=> Shrine::Attacher
|
126
|
+
MyUploader::Attachment.superclass #=> Shrine::Attachment
|
127
|
+
```
|
128
|
+
|
129
|
+
See [Creating a New Plugin] guide and the [Plugin system of Sequel and Roda]
|
130
|
+
article for more details on the design of Shrine's plugin system.
|
93
131
|
|
94
132
|
## `Shrine::UploadedFile`
|
95
133
|
|
96
|
-
`Shrine::UploadedFile` represents a file that was uploaded to a
|
97
|
-
|
98
|
-
|
134
|
+
A `Shrine::UploadedFile` object represents a file that was uploaded to a
|
135
|
+
storage, containing upload location, storage, and any metadata extracted during
|
136
|
+
the upload.
|
99
137
|
|
100
138
|
```rb
|
101
|
-
uploaded_file
|
102
|
-
|
139
|
+
uploaded_file #=> #<Shrine::UploadedFile id="949sdjg834.jpg" storage=:store metadata={...}>
|
140
|
+
|
141
|
+
uploaded_file.id #=> "949sdjg834.jpg"
|
142
|
+
uploaded_file.storage_key #=> :store
|
143
|
+
uploaded_file.storage #=> #<Shrine::Storage::S3>
|
144
|
+
uploaded_file.metadata #=> {...}
|
145
|
+
```
|
146
|
+
|
147
|
+
It has convenience methods for accessing metadata:
|
148
|
+
|
149
|
+
```rb
|
150
|
+
uploaded_file.metadata #=>
|
103
151
|
# {
|
104
|
-
# "
|
105
|
-
# "
|
106
|
-
# "
|
107
|
-
# "filename" => "resume.pdf",
|
108
|
-
# "mime_type" => "application/pdf",
|
109
|
-
# "size" => 983294,
|
110
|
-
# },
|
152
|
+
# "filename" => "matrix.mp4",
|
153
|
+
# "mime_type" => "video/mp4",
|
154
|
+
# "size" => 345993,
|
111
155
|
# }
|
156
|
+
|
157
|
+
uploaded_file.original_filename #=> "matrix.mp4"
|
158
|
+
uploaded_file.extension #=> "mp4"
|
159
|
+
uploaded_file.mime_type #=> "video/mp4"
|
160
|
+
uploaded_file.size #=> 345993
|
112
161
|
```
|
113
162
|
|
114
|
-
|
115
|
-
some metadata: original filename, MIME type and filesize. The
|
116
|
-
`Shrine::UploadedFile` object has handy methods which use this data:
|
163
|
+
It also has methods that delegate to the storage:
|
117
164
|
|
118
165
|
```rb
|
119
|
-
|
120
|
-
uploaded_file.
|
121
|
-
uploaded_file.
|
122
|
-
uploaded_file.
|
123
|
-
|
124
|
-
|
125
|
-
# storage methods
|
126
|
-
uploaded_file.url
|
127
|
-
uploaded_file.exists?
|
128
|
-
uploaded_file.open
|
129
|
-
uploaded_file.download
|
130
|
-
uploaded_file.delete
|
131
|
-
# ...
|
166
|
+
uploaded_file.url #=> "https://my-bucket.s3.amazonaws.com/949sdjg834.jpg"
|
167
|
+
uploaded_file.open { |io| ... } # opens the uploaded file stream
|
168
|
+
uploaded_file.download { |file| ... } # downloads the uploaded file to disk
|
169
|
+
uploaded_file.stream(destination) # streams uploaded content into a writable destination
|
170
|
+
uploaded_file.exists? #=> true
|
171
|
+
uploaded_file.delete # deletes the uploaded file from the storage
|
132
172
|
```
|
133
173
|
|
134
|
-
A `Shrine::UploadedFile` is itself an IO-like object (
|
135
|
-
|
174
|
+
A `Shrine::UploadedFile` is itself an IO-like object (built on top of
|
175
|
+
`Storage#open`), so it can be passed to `Shrine#upload` as well.
|
136
176
|
|
137
177
|
## `Shrine::Attacher`
|
138
178
|
|
139
179
|
We usually want to treat uploaded files as *attachments* to records, saving
|
140
|
-
their data into a database column. This is
|
141
|
-
|
142
|
-
`Shrine::UploadedFile` objects internally.
|
180
|
+
their data into a database column. This is done by `Shrine::Attacher`, which
|
181
|
+
internally uses `Shrine` and `Shrine::UploadedFile` classes.
|
143
182
|
|
144
183
|
The attaching process requires a temporary and a permanent storage to be
|
145
184
|
registered (by default that's `:cache` and `:store`):
|
@@ -151,71 +190,82 @@ Shrine.storages = {
|
|
151
190
|
}
|
152
191
|
```
|
153
192
|
|
154
|
-
A `Shrine::Attacher`
|
155
|
-
|
193
|
+
A `Shrine::Attacher` can be initialized standalone and handle the common
|
194
|
+
attachment flow, which includes dirty tracking (promoting cached file to
|
195
|
+
permanent storage, deleting previously attached file), validation, processing,
|
196
|
+
serialization etc.
|
156
197
|
|
157
198
|
```rb
|
158
|
-
attacher = Shrine::Attacher.new
|
199
|
+
attacher = Shrine::Attacher.new
|
159
200
|
|
160
|
-
|
161
|
-
|
162
|
-
attacher.
|
201
|
+
# ... user uploads a file ...
|
202
|
+
|
203
|
+
attacher.assign(io) # uploads to temporary storage
|
204
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:cache ...>
|
205
|
+
|
206
|
+
# ... handle file validations ...
|
163
207
|
|
164
|
-
attacher.
|
165
|
-
attacher.
|
166
|
-
attacher.record.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
|
208
|
+
attacher.finalize # uploads to permanent storage
|
209
|
+
attacher.file #=> #<Shrine::UploadedFile storage=:store ...>
|
167
210
|
```
|
168
211
|
|
169
|
-
|
170
|
-
|
171
|
-
permanent storage. Behind the scenes a cached `Shrine::UploadedFile` is given
|
172
|
-
to `Shrine#upload`, which works because `Shrine::UploadedFile` is an IO-like
|
173
|
-
object. After both caching and promoting the data hash of the uploaded file is
|
174
|
-
assigned to the record's column as JSON.
|
212
|
+
It can also be initialized with a model instance to handle serialization into a
|
213
|
+
model attribute:
|
175
214
|
|
176
|
-
|
215
|
+
```rb
|
216
|
+
attacher = Shrine::Attacher.from_model(photo, :image)
|
217
|
+
|
218
|
+
attacher.assign(file)
|
219
|
+
photo.image_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
|
220
|
+
|
221
|
+
attacher.finalize
|
222
|
+
photo.image_data #=> "{\"storage\":\"store\",\"id\":\"ksdf02lr9sf3la.jpg\",\"metadata\":{...}}"
|
223
|
+
```
|
224
|
+
|
225
|
+
For more details, see the [Using Attacher] guide and
|
226
|
+
[`entity`][entity]/[`model`][model] plugins.
|
177
227
|
|
178
228
|
## `Shrine::Attachment`
|
179
229
|
|
180
|
-
`Shrine::Attachment`
|
181
|
-
`Shrine::
|
182
|
-
|
183
|
-
means that an instance of `Shrine::Attachment` is a module:
|
230
|
+
A `Shrine::Attachment` module provides a convenience model interface around the
|
231
|
+
`Shrine::Attacher` object. The `Shrine::Attachment` class is a subclass of
|
232
|
+
`Module`, which means that an instance of `Shrine::Attachment` is a module:
|
184
233
|
|
185
234
|
```rb
|
186
235
|
Shrine::Attachment.new(:image).is_a?(Module) #=> true
|
187
|
-
Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher]
|
236
|
+
Shrine::Attachment.new(:image).instance_methods #=> [:image=, :image, :image_url, :image_attacher, ...]
|
188
237
|
|
189
238
|
# equivalents
|
190
239
|
Shrine::Attachment.new(:image)
|
240
|
+
Shrine::Attachment[:image]
|
191
241
|
Shrine::Attachment(:image)
|
192
|
-
Shrine[:image]
|
193
242
|
```
|
194
243
|
|
195
|
-
We can include this module
|
244
|
+
We can include this module into a model:
|
196
245
|
|
197
246
|
```rb
|
198
|
-
|
199
|
-
include Shrine::Attachment.new(:image)
|
200
|
-
end
|
247
|
+
Photo.include Shrine::Attachment(:image)
|
201
248
|
```
|
202
249
|
```rb
|
203
|
-
photo.image = file
|
204
|
-
photo.image
|
205
|
-
photo.image_url
|
250
|
+
photo.image = file # shorthand for `photo.image_attacher.assign(file)`
|
251
|
+
photo.image # shorthand for `photo.image_attacher.get`
|
252
|
+
photo.image_url # shorthand for `photo.image_attacher.url`
|
206
253
|
|
207
|
-
photo.image_attacher #=> #<Shrine::Attacher
|
254
|
+
photo.image_attacher #=> #<Shrine::Attacher @cache_key=:cache @store_key=:store ...>
|
208
255
|
```
|
209
256
|
|
210
|
-
When
|
211
|
-
automatically:
|
257
|
+
When a persistence plugin is loaded ([`activerecord`][activerecord],
|
258
|
+
[`sequel`][sequel]), the `Shrine::Attachment` module also automatically:
|
212
259
|
|
213
260
|
* syncs Shrine's validation errors with the record
|
214
261
|
* triggers promoting after record is saved
|
215
|
-
* deletes the uploaded file if attachment was replaced
|
216
|
-
destroyed
|
262
|
+
* deletes the uploaded file if attachment was replaced or the record destroyed
|
217
263
|
|
218
|
-
[Using Attacher]: /
|
264
|
+
[Using Attacher]: https://shrinerb.com/docs/attacher
|
219
265
|
[Notes on study of shrine implementation]: https://bibwild.wordpress.com/2018/09/12/notes-on-study-of-shrine-implementation/
|
220
|
-
[Creating a New Plugin]: /
|
266
|
+
[Creating a New Plugin]: https://shrinerb.com/docs/creating-plugins
|
221
267
|
[Plugin system of Sequel and Roda]: https://twin.github.io/the-plugin-system-of-sequel-and-roda/
|
268
|
+
[entity]: https://shrinerb.com/docs/plugins/entity
|
269
|
+
[model]: https://shrinerb.com/docs/plugins/model
|
270
|
+
[activerecord]: https://shrinerb.com/docs/plugins/activerecord
|
271
|
+
[sequel]: https://shrinerb.com/docs/plugins/sequel
|
data/doc/direct_s3.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
id: direct-s3
|
3
|
+
title: Direct Uploads to S3
|
4
|
+
---
|
2
5
|
|
3
6
|
Shrine gives you the ability to upload files directly to Amazon S3 (or any
|
4
7
|
other storage service that accepts direct uploads). Uploading directly to a
|
@@ -23,12 +26,14 @@ storage service is beneficial for several reasons:
|
|
23
26
|
request-response lifecycle might not be able to finish before the request
|
24
27
|
times out.
|
25
28
|
|
29
|
+
## Storage
|
30
|
+
|
26
31
|
To start, let's set both temporary and permanent storage to S3, with the
|
27
32
|
temporary storage uploading to the `cache/` prefix:
|
28
33
|
|
29
34
|
```rb
|
30
35
|
# Gemfile
|
31
|
-
gem "shrine", "~>
|
36
|
+
gem "shrine", "~> 3.0"
|
32
37
|
gem "aws-sdk-s3", "~> 1.14"
|
33
38
|
```
|
34
39
|
```rb
|
@@ -71,6 +76,7 @@ If you're using [Uppy], this is the recommended CORS configuration for the
|
|
71
76
|
<AllowedHeader>x-amz-content-sha256</AllowedHeader>
|
72
77
|
<AllowedHeader>content-type</AllowedHeader>
|
73
78
|
<AllowedHeader>content-disposition</AllowedHeader>
|
79
|
+
<ExposeHeader>ETag</ExposeHeader>
|
74
80
|
</CORSRule>
|
75
81
|
<CORSRule>
|
76
82
|
<AllowedOrigin>*</AllowedOrigin>
|
@@ -80,6 +86,31 @@ If you're using [Uppy], this is the recommended CORS configuration for the
|
|
80
86
|
</CORSConfiguration>
|
81
87
|
```
|
82
88
|
|
89
|
+
Or in JSON format:
|
90
|
+
|
91
|
+
```json
|
92
|
+
[
|
93
|
+
{
|
94
|
+
"AllowedOrigins": [ "https://my-app.com" ],
|
95
|
+
"AllowedMethods": [ "GET", "POST", "PUT" ],
|
96
|
+
"MaxAgeSeconds": 3000,
|
97
|
+
"AllowedHeaders": [
|
98
|
+
"Authorization",
|
99
|
+
"x-amz-date",
|
100
|
+
"x-amz-content-sha256",
|
101
|
+
"Content-Type",
|
102
|
+
"Content-Disposition"
|
103
|
+
],
|
104
|
+
"ExposeHeaders": [ "ETag" ]
|
105
|
+
},
|
106
|
+
{
|
107
|
+
"AllowedOrigins": [ "*" ],
|
108
|
+
"AllowedMethods": [ "GET" ],
|
109
|
+
"MaxAgeSeconds": 3000
|
110
|
+
}
|
111
|
+
]
|
112
|
+
```
|
113
|
+
|
83
114
|
Replace `https://my-app.com` with the URL to your app (in development you can
|
84
115
|
set this to `*`). Once you've hit "Save", it may take some time for the
|
85
116
|
new CORS settings to be applied.
|
@@ -247,28 +278,6 @@ additional HTTP requests.
|
|
247
278
|
See [this section][metadata direct uploads] or the rationale and instructions
|
248
279
|
on how to opt in.
|
249
280
|
|
250
|
-
## Object data
|
251
|
-
|
252
|
-
When the cached S3 object is copied to permanent storage, the destination S3
|
253
|
-
object will by default inherit any object data that was assigned to the cached
|
254
|
-
object via presign parameters. However, S3 will by default also ignore any new
|
255
|
-
object parameters that are given to the copy request.
|
256
|
-
|
257
|
-
Whether object data will be copied or replaced depends on the value of the
|
258
|
-
`:metadata_directive` parameter:
|
259
|
-
|
260
|
-
* `"COPY"` - destination object will inherit source object data and any new data will be ignored (default)
|
261
|
-
* `"REPLACE"` - destination object will not inherit any of the source object data and will accept new data
|
262
|
-
|
263
|
-
You can use the `upload_options` plugin to change the `:metadata_directive`
|
264
|
-
option when S3 objects are copied:
|
265
|
-
|
266
|
-
```rb
|
267
|
-
plugin :upload_options, store: -> (io, context) do
|
268
|
-
{ metadata_directive: "REPLACE" } if io.is_a?(Shrine::UploadedFile)
|
269
|
-
end
|
270
|
-
```
|
271
|
-
|
272
281
|
## Clearing cache
|
273
282
|
|
274
283
|
Directly uploaded files won't automatically be deleted from your temporary
|
@@ -330,8 +339,9 @@ backgrounding library to perform the job with a delay:
|
|
330
339
|
```rb
|
331
340
|
Shrine.plugin :backgrounding
|
332
341
|
|
333
|
-
Shrine::Attacher.
|
334
|
-
|
342
|
+
Shrine::Attacher.promote_block do
|
343
|
+
# tells a Sidekiq worker to perform in 3 seconds
|
344
|
+
PromoteJob.perform_in(3, self.class.name, record.class.name, record.id, name, file_data)
|
335
345
|
end
|
336
346
|
```
|
337
347
|
|
@@ -396,6 +406,6 @@ setup] guide.
|
|
396
406
|
[lifecycle Console]: http://docs.aws.amazon.com/AmazonS3/latest/UG/lifecycle-configuration-bucket-no-versioning.html
|
397
407
|
[lifecycle API]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_bucket_lifecycle_configuration-instance_method
|
398
408
|
[Minio]: https://minio.io
|
399
|
-
[minio setup]: /
|
400
|
-
[metadata direct uploads]: /
|
409
|
+
[minio setup]: https://shrinerb.com/docs/testing#minio
|
410
|
+
[metadata direct uploads]: https://shrinerb.com/docs/metadata#direct-uploads
|
401
411
|
[content_disposition]: https://github.com/shrinerb/content_disposition
|