shrine 3.0.0.rc → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of shrine might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -66
- data/README.md +39 -1061
- data/doc/advantages.md +151 -148
- data/doc/attacher.md +12 -30
- data/doc/carrierwave.md +150 -115
- data/doc/changing_derivatives.md +5 -11
- data/doc/changing_location.md +8 -5
- data/doc/changing_storage.md +5 -2
- data/doc/creating_persistence_plugins.md +9 -6
- data/doc/creating_plugins.md +42 -22
- data/doc/creating_storages.md +4 -1
- data/doc/design.md +7 -5
- data/doc/direct_s3.md +9 -4
- data/doc/external/articles.md +50 -0
- data/doc/external/extensions.md +46 -0
- data/doc/external/misc.md +17 -0
- data/doc/getting_started.md +1038 -0
- data/doc/metadata.md +5 -3
- data/doc/multiple_files.md +55 -29
- data/doc/paperclip.md +206 -163
- data/doc/plugins/activerecord.md +26 -6
- data/doc/plugins/add_metadata.md +4 -2
- data/doc/plugins/atomic_helpers.md +4 -2
- data/doc/plugins/backgrounding.md +83 -44
- data/doc/plugins/cached_attachment_data.md +4 -2
- data/doc/plugins/column.md +4 -2
- data/doc/plugins/data_uri.md +10 -6
- data/doc/plugins/default_storage.md +5 -3
- data/doc/plugins/default_url.md +4 -2
- data/doc/plugins/delete_raw.md +4 -2
- data/doc/plugins/derivation_endpoint.md +63 -39
- data/doc/plugins/derivatives.md +13 -50
- data/doc/plugins/determine_mime_type.md +6 -4
- data/doc/plugins/download_endpoint.md +6 -3
- data/doc/plugins/dynamic_storage.md +4 -2
- data/doc/plugins/entity.md +6 -4
- data/doc/plugins/form_assign.md +4 -2
- data/doc/plugins/included.md +4 -2
- data/doc/plugins/infer_extension.md +6 -4
- data/doc/plugins/instrumentation.md +5 -3
- data/doc/plugins/keep_files.md +9 -2
- data/doc/plugins/metadata_attributes.md +5 -3
- data/doc/plugins/mirroring.md +4 -2
- data/doc/plugins/model.md +6 -4
- data/doc/plugins/module_include.md +4 -2
- data/doc/plugins/multi_cache.md +4 -2
- data/doc/plugins/persistence.md +5 -3
- data/doc/plugins/presign_endpoint.md +6 -2
- data/doc/plugins/pretty_location.md +5 -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 +5 -3
- data/doc/plugins/remote_url.md +26 -5
- data/doc/plugins/remove_attachment.md +4 -2
- data/doc/plugins/remove_invalid.md +10 -2
- data/doc/plugins/restore_cached_data.md +9 -3
- data/doc/plugins/sequel.md +26 -6
- data/doc/plugins/signature.md +6 -4
- data/doc/plugins/store_dimensions.md +6 -4
- data/doc/plugins/tempfile.md +4 -2
- data/doc/plugins/upload_endpoint.md +6 -2
- data/doc/plugins/upload_options.md +6 -4
- data/doc/plugins/url_options.md +4 -2
- data/doc/plugins/validation.md +7 -3
- data/doc/plugins/validation_helpers.md +13 -10
- data/doc/plugins/versions.md +4 -8
- data/doc/processing.md +27 -9
- data/doc/refile.md +119 -127
- data/doc/release_notes/1.0.0.md +4 -0
- data/doc/release_notes/1.1.0.md +4 -0
- 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 +10 -6
- 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 +7 -4
- 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 +120 -38
- data/doc/retrieving_uploads.md +4 -1
- data/doc/securing_uploads.md +4 -1
- data/doc/storage/file_system.md +12 -4
- data/doc/storage/s3.md +4 -2
- data/doc/testing.md +27 -41
- data/doc/upgrading_to_3.md +105 -26
- data/doc/validation.md +8 -6
- data/lib/shrine/attacher.rb +2 -2
- data/lib/shrine/attachment.rb +7 -10
- data/lib/shrine/plugins/activerecord.rb +10 -10
- data/lib/shrine/plugins/add_metadata.rb +1 -3
- data/lib/shrine/plugins/atomic_helpers.rb +6 -8
- data/lib/shrine/plugins/backgrounding.rb +4 -6
- data/lib/shrine/plugins/cached_attachment_data.rb +1 -3
- data/lib/shrine/plugins/column.rb +2 -4
- data/lib/shrine/plugins/data_uri.rb +1 -3
- data/lib/shrine/plugins/default_storage.rb +1 -3
- data/lib/shrine/plugins/default_url.rb +1 -3
- data/lib/shrine/plugins/delete_raw.rb +1 -3
- data/lib/shrine/plugins/derivation_endpoint.rb +3 -4
- data/lib/shrine/plugins/derivatives.rb +2 -4
- data/lib/shrine/plugins/determine_mime_type.rb +1 -3
- data/lib/shrine/plugins/download_endpoint.rb +1 -3
- data/lib/shrine/plugins/dynamic_storage.rb +1 -3
- data/lib/shrine/plugins/entity.rb +25 -9
- data/lib/shrine/plugins/form_assign.rb +1 -3
- data/lib/shrine/plugins/included.rb +1 -3
- data/lib/shrine/plugins/infer_extension.rb +1 -3
- data/lib/shrine/plugins/instrumentation.rb +1 -3
- data/lib/shrine/plugins/keep_files.rb +1 -3
- data/lib/shrine/plugins/metadata_attributes.rb +1 -3
- data/lib/shrine/plugins/mirroring.rb +2 -1
- data/lib/shrine/plugins/model.rb +2 -4
- data/lib/shrine/plugins/module_include.rb +1 -3
- data/lib/shrine/plugins/multi_cache.rb +3 -3
- data/lib/shrine/plugins/presign_endpoint.rb +1 -3
- data/lib/shrine/plugins/pretty_location.rb +1 -3
- data/lib/shrine/plugins/processing.rb +1 -3
- data/lib/shrine/plugins/rack_file.rb +1 -3
- data/lib/shrine/plugins/rack_response.rb +1 -3
- data/lib/shrine/plugins/recache.rb +1 -3
- data/lib/shrine/plugins/refresh_metadata.rb +1 -3
- data/lib/shrine/plugins/remote_url.rb +1 -3
- data/lib/shrine/plugins/remove_attachment.rb +1 -3
- data/lib/shrine/plugins/remove_invalid.rb +1 -3
- data/lib/shrine/plugins/restore_cached_data.rb +1 -3
- data/lib/shrine/plugins/sequel.rb +10 -12
- data/lib/shrine/plugins/signature.rb +1 -3
- data/lib/shrine/plugins/store_dimensions.rb +1 -3
- data/lib/shrine/plugins/tempfile.rb +1 -3
- data/lib/shrine/plugins/upload_endpoint.rb +1 -3
- data/lib/shrine/plugins/upload_options.rb +1 -3
- data/lib/shrine/plugins/url_options.rb +1 -3
- data/lib/shrine/plugins/validation.rb +1 -3
- data/lib/shrine/plugins/validation_helpers.rb +1 -3
- data/lib/shrine/plugins/versions.rb +1 -3
- data/lib/shrine/storage/file_system.rb +1 -1
- data/lib/shrine/storage/linter.rb +1 -1
- data/lib/shrine/storage/memory.rb +2 -1
- data/lib/shrine/storage/s3.rb +3 -3
- data/lib/shrine/version.rb +1 -1
- metadata +8 -4
data/doc/metadata.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
title: Extracting Metadata
|
3
|
+
---
|
2
4
|
|
3
5
|
Before a file is uploaded, Shrine automatically extracts metadata from it, and
|
4
6
|
stores them in the `Shrine::UploadedFile` object.
|
@@ -386,5 +388,5 @@ end
|
|
386
388
|
[MiniMagick]: https://github.com/minimagick/minimagick
|
387
389
|
[ruby-vips]: https://github.com/libvips/ruby-vips
|
388
390
|
[tus server]: https://github.com/janko/tus-ruby-server
|
389
|
-
[determine_mime_type]: /
|
390
|
-
[backgrounding]: /
|
391
|
+
[determine_mime_type]: https://shrinerb.com/docs/plugins/determine_mime_type
|
392
|
+
[backgrounding]: https://shrinerb.com/docs/plugins/backgrounding
|
data/doc/multiple_files.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
id: multiple-files
|
3
|
+
title: Multiple Files
|
4
|
+
---
|
2
5
|
|
3
6
|
There are times when you want to allow users to attach multiple files to a
|
4
7
|
single resource, like an album having many photos or a playlist having many
|
@@ -11,7 +14,7 @@ relationship with the main table, and files will be attached on the records in
|
|
11
14
|
the new table. That way each record from the main table can implicitly have
|
12
15
|
multiple attachments through the associated records.
|
13
16
|
|
14
|
-
```
|
17
|
+
```plaintext
|
15
18
|
album1
|
16
19
|
photo1
|
17
20
|
- attachment1
|
@@ -64,8 +67,9 @@ files (or attachments) table will be the photos table.
|
|
64
67
|
Let's create a table for the main resource and attachments, and add a foreign
|
65
68
|
key in the attachment table for the main table:
|
66
69
|
|
70
|
+
<!--DOCUSAURUS_CODE_TABS-->
|
71
|
+
<!--Sequel-->
|
67
72
|
```rb
|
68
|
-
# with Sequel:
|
69
73
|
Sequel.migration do
|
70
74
|
change do
|
71
75
|
create_table :albums do
|
@@ -80,8 +84,9 @@ Sequel.migration do
|
|
80
84
|
end
|
81
85
|
end
|
82
86
|
end
|
83
|
-
|
84
|
-
|
87
|
+
```
|
88
|
+
<!--ActiveRecord-->
|
89
|
+
```rb
|
85
90
|
class CreateAlbumsAndPhotos < ActiveRecord::Migration
|
86
91
|
def change
|
87
92
|
create_table :albums do |t|
|
@@ -97,21 +102,25 @@ class CreateAlbumsAndPhotos < ActiveRecord::Migration
|
|
97
102
|
end
|
98
103
|
end
|
99
104
|
```
|
105
|
+
<!--END_DOCUSAURUS_CODE_TABS-->
|
100
106
|
|
101
107
|
In the Photo model, create a Shrine attachment attribute named `image`
|
102
108
|
(`:image` matches the `_data` column prefix above):
|
103
109
|
|
110
|
+
<!--DOCUSAURUS_CODE_TABS-->
|
111
|
+
<!--Sequel-->
|
104
112
|
```rb
|
105
|
-
# with Sequel:
|
106
113
|
class Photo < Sequel::Model
|
107
114
|
include ImageUploader::Attachment(:image)
|
108
115
|
end
|
109
|
-
|
110
|
-
|
116
|
+
```
|
117
|
+
<!--ActiveRecord-->
|
118
|
+
```rb
|
111
119
|
class Photo < ActiveRecord::Base
|
112
120
|
include ImageUploader::Attachment(:image)
|
113
121
|
end
|
114
122
|
```
|
123
|
+
<!--END_DOCUSAURUS_CODE_TABS-->
|
115
124
|
|
116
125
|
### 2. Declare nested attributes
|
117
126
|
|
@@ -120,8 +129,9 @@ Using nested attributes is the easiest way to implement any dynamic
|
|
120
129
|
relationship to the photos table, and allow it to directly accept attributes
|
121
130
|
for the associated photo records by enabling nested attributes:
|
122
131
|
|
132
|
+
<!--DOCUSAURUS_CODE_TABS-->
|
133
|
+
<!--Sequel-->
|
123
134
|
```rb
|
124
|
-
# with Sequel:
|
125
135
|
class Album < Sequel::Model
|
126
136
|
one_to_many :photos
|
127
137
|
plugin :association_dependencies, photos: :destroy # destroy photos when album is destroyed
|
@@ -129,13 +139,23 @@ class Album < Sequel::Model
|
|
129
139
|
plugin :nested_attributes
|
130
140
|
nested_attributes :photos, destroy: true
|
131
141
|
end
|
132
|
-
|
133
|
-
|
142
|
+
```
|
143
|
+
<!--ActiveRecord-->
|
144
|
+
```rb
|
134
145
|
class Album < ActiveRecord::Base
|
135
146
|
has_many :photos, dependent: :destroy
|
136
147
|
accepts_nested_attributes_for :photos, allow_destroy: true
|
137
148
|
end
|
138
149
|
```
|
150
|
+
<!--Mongoid-->
|
151
|
+
```rb
|
152
|
+
class Album
|
153
|
+
include Mongoid::Document
|
154
|
+
embeds_many :photos
|
155
|
+
accepts_nested_attributes_for :photos
|
156
|
+
end
|
157
|
+
```
|
158
|
+
<!--END_DOCUSAURUS_CODE_TABS-->
|
139
159
|
|
140
160
|
Documentation on nested attributes:
|
141
161
|
|
@@ -152,20 +172,9 @@ already created photos, so that the same form can be used for updating the
|
|
152
172
|
album/photos as well (they will be submitted under the
|
153
173
|
`album[photos_attributes]` parameter).
|
154
174
|
|
175
|
+
<!--DOCUSAURUS_CODE_TABS-->
|
176
|
+
<!--Rails form builder-->
|
155
177
|
```rb
|
156
|
-
# with Forme:
|
157
|
-
form @album, action: "/photos", enctype: "multipart/form-data" do |f|
|
158
|
-
f.input :title
|
159
|
-
f.subform :photos do # adds new `album[photos_attributes]` parameter
|
160
|
-
f.input :image, type: :hidden, value: f.obj.cached_image_data
|
161
|
-
f.input :image, type: :file
|
162
|
-
f.input :_delete, type: :checkbox unless f.obj.new?
|
163
|
-
end
|
164
|
-
f.input "files[]", type: :file, attr: { multiple: true }, obj: nil
|
165
|
-
f.button "Create"
|
166
|
-
end
|
167
|
-
|
168
|
-
# with Rails form builder:
|
169
178
|
form_for @album, html: { enctype: "multipart/form-data" } do |f|
|
170
179
|
f.text_field :title
|
171
180
|
f.fields_for :photos do |p| # adds new `album[photos_attributes]` parameter
|
@@ -177,6 +186,20 @@ form_for @album, html: { enctype: "multipart/form-data" } do |f|
|
|
177
186
|
f.submit "Create"
|
178
187
|
end
|
179
188
|
```
|
189
|
+
<!--Forme-->
|
190
|
+
```rb
|
191
|
+
form @album, action: "/photos", enctype: "multipart/form-data" do |f|
|
192
|
+
f.input :title
|
193
|
+
f.subform :photos do # adds new `album[photos_attributes]` parameter
|
194
|
+
f.input :image, type: :hidden, value: f.obj.cached_image_data
|
195
|
+
f.input :image, type: :file
|
196
|
+
f.input :_delete, type: :checkbox unless f.obj.new?
|
197
|
+
end
|
198
|
+
f.input "files[]", type: :file, attr: { multiple: true }, obj: nil
|
199
|
+
f.button "Create"
|
200
|
+
end
|
201
|
+
```
|
202
|
+
<!--END_DOCUSAURUS_CODE_TABS-->
|
180
203
|
|
181
204
|
In your controller you should still be able to assign all the attributes to the
|
182
205
|
album, just remember to whitelist the new parameter for the nested attributes,
|
@@ -261,18 +284,21 @@ class ImageUploader < Shrine
|
|
261
284
|
end
|
262
285
|
end
|
263
286
|
```
|
287
|
+
<!--DOCUSAURUS_CODE_TABS-->
|
288
|
+
<!--Sequel-->
|
264
289
|
```rb
|
265
|
-
# with Sequel:
|
266
290
|
class Album < Sequel::Model
|
267
291
|
# ... (nested_attributes already enables validating associated photos) ...
|
268
292
|
end
|
269
|
-
|
270
|
-
|
293
|
+
```
|
294
|
+
<!--ActiveRecord-->
|
295
|
+
```rb
|
271
296
|
class Album < ActiveRecord::Base
|
272
297
|
# ...
|
273
298
|
validates_associated :photos
|
274
299
|
end
|
275
300
|
```
|
301
|
+
<!--END_DOCUSAURUS_CODE_TABS-->
|
276
302
|
|
277
303
|
Note that by default only metadata set on the client side will be available for
|
278
304
|
validations. Shrine will not automatically run metadata extraction for directly
|
@@ -294,8 +320,8 @@ attributes feature gives you for free.
|
|
294
320
|
[`Sequel::Model.nested_attributes`]: http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/NestedAttributes.html
|
295
321
|
[`ActiveRecord::Base.accepts_nested_attributes_for`]: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
|
296
322
|
[`Mongoid::Document.accepts_nested_attributes_for`]: https://docs.mongodb.com/mongoid/master/tutorials/mongoid-nested-attributes/
|
297
|
-
[`upload_endpoint`]: /
|
298
|
-
[`presign_endpoint`]: /
|
323
|
+
[`upload_endpoint`]: https://shrinerb.com/docs/plugins/upload_endpoint
|
324
|
+
[`presign_endpoint`]: https://shrinerb.com/docs/plugins/presign_endpoint
|
299
325
|
[Uppy]: https://uppy.io
|
300
326
|
[direct app uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-App-Uploads
|
301
327
|
[direct S3 uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
|
data/doc/paperclip.md
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
---
|
2
|
+
title: Shrine for Paperclip Users
|
3
|
+
---
|
2
4
|
|
3
5
|
This guide is aimed at helping Paperclip users transition to Shrine, and it
|
4
6
|
consists of three parts:
|
@@ -7,9 +9,52 @@ consists of three parts:
|
|
7
9
|
2. Instructions how to migrate and existing app that uses Paperclip to Shrine
|
8
10
|
3. Extensive reference of Paperclip's interface with Shrine equivalents
|
9
11
|
|
10
|
-
##
|
12
|
+
## Overview
|
11
13
|
|
12
|
-
|
14
|
+
### Uploader
|
15
|
+
|
16
|
+
In Paperclip, the attachment logic is configured directly inside Active Record
|
17
|
+
models:
|
18
|
+
|
19
|
+
```rb
|
20
|
+
class Photo < ActiveRecord::Base
|
21
|
+
has_attached_file :image,
|
22
|
+
preserve_files: true,
|
23
|
+
default_url: "/images/:style/missing.png"
|
24
|
+
|
25
|
+
validated_attachment_content_type :image, content_type: "image/jpeg"
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
Shrine takes a more object-oriented approach, by encapsulating attachment logic
|
30
|
+
in "uploader" classes:
|
31
|
+
|
32
|
+
```rb
|
33
|
+
class ImageUploader < Shrine
|
34
|
+
plugin :keep_files
|
35
|
+
plugin :default_url
|
36
|
+
plugin :validation_helpers
|
37
|
+
|
38
|
+
Attacher.default_url do |derivative: nil, **|
|
39
|
+
"/images/#{derivative}/missing.png" if derivative
|
40
|
+
end
|
41
|
+
|
42
|
+
Attacher.validate do
|
43
|
+
validate_mime_type %w[image/jpeg]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
```
|
47
|
+
```rb
|
48
|
+
class Photo < ActiveRecord::Base
|
49
|
+
include ImageUploader::Attachment(:image)
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Storage
|
54
|
+
|
55
|
+
Paperclip storage is configured together with other attachment options. Also,
|
56
|
+
the storage implementations themselves are mixed into the attachment class,
|
57
|
+
which couples them to the attachment flow.
|
13
58
|
|
14
59
|
```rb
|
15
60
|
class Photo < ActiveRecord::Base
|
@@ -24,7 +69,8 @@ class Photo < ActiveRecord::Base
|
|
24
69
|
end
|
25
70
|
```
|
26
71
|
|
27
|
-
|
72
|
+
Shrine storage objects are configured separately and are decoupled from
|
73
|
+
attachment:
|
28
74
|
|
29
75
|
```rb
|
30
76
|
Shrine.storages[:store] = Shrine::Storage::S3.new(
|
@@ -35,10 +81,8 @@ Shrine.storages[:store] = Shrine::Storage::S3.new(
|
|
35
81
|
)
|
36
82
|
```
|
37
83
|
|
38
|
-
|
39
|
-
uploaded files in case of validation errors
|
40
|
-
implemented in a safe way. Shrine uses separate "temporary" and "permanent"
|
41
|
-
storage for attaching files:
|
84
|
+
Shrine also has a concept of "temporary" storage, which enables retaining
|
85
|
+
uploaded files in case of validation errors and [direct uploads].
|
42
86
|
|
43
87
|
```rb
|
44
88
|
Shrine.storages = {
|
@@ -47,43 +91,79 @@ Shrine.storages = {
|
|
47
91
|
}
|
48
92
|
```
|
49
93
|
|
50
|
-
|
94
|
+
### Persistence
|
51
95
|
|
52
|
-
|
53
|
-
|
54
|
-
|
96
|
+
When using Paperclip, the attached file data will be persisted into several
|
97
|
+
columns:
|
98
|
+
|
99
|
+
* `<name>_file_name`
|
100
|
+
* `<name>_content_type`
|
101
|
+
* `<name>_file_size`
|
102
|
+
* `<name>_updated_at`
|
103
|
+
* `<name>_created_at` (optional)
|
104
|
+
* `<name>_fingerprint` (optional)
|
105
|
+
|
106
|
+
In contrast, Shrine uses a single `<name>_data` column to store data in JSON
|
107
|
+
format:
|
55
108
|
|
56
109
|
```rb
|
57
|
-
|
58
|
-
|
59
|
-
|
110
|
+
{
|
111
|
+
"id": "path/to/image.jpg",
|
112
|
+
"storage": "store",
|
113
|
+
"metadata": {
|
114
|
+
"filename": "nature.jpg",
|
115
|
+
"size": 4739472,
|
116
|
+
"mime_type": "image/jpeg"
|
117
|
+
}
|
118
|
+
}
|
60
119
|
```
|
61
|
-
|
62
120
|
```rb
|
63
|
-
|
64
|
-
|
65
|
-
|
121
|
+
photo.image.id #=> "path/to/image.jpg"
|
122
|
+
photo.image.storage_key #=> :store
|
123
|
+
photo.image.metadata #=> { "filename" => "...", "size" => ..., "mime_type" => "..." }
|
66
124
|
|
67
|
-
|
68
|
-
|
69
|
-
|
125
|
+
photo.image.original_filename #=> "nature.jpg"
|
126
|
+
photo.image.size #=> 4739472
|
127
|
+
photo.image.mime_type #=> "image/jpeg"
|
70
128
|
```
|
71
129
|
|
72
|
-
|
73
|
-
|
130
|
+
This column can be queried if it's made a JSON column. Alternatively, you can
|
131
|
+
use the [`metadata_attributes`][metadata_attributes] plugin to save metadata
|
132
|
+
into separate columns.
|
133
|
+
|
134
|
+
#### ORM
|
135
|
+
|
136
|
+
While Paperclip works only with Active Record, Shrine is designed to integrate
|
137
|
+
with any persistence library (there are integrations for [Active
|
138
|
+
Record][activerecord], [Sequel][sequel], [ROM][rom], [Hanami][hanami] and
|
139
|
+
[Hanami][mongoid]), and can also be used standalone:
|
74
140
|
|
75
141
|
```rb
|
76
|
-
|
77
|
-
|
78
|
-
|
142
|
+
attacher = ImageUploader::Attacher.new
|
143
|
+
attacher.attach File.open("nature.jpg")
|
144
|
+
attacher.file #=> #<Shrine::UploadedFile @id="f4ba5bdbf366ef0b.jpg" ...>
|
145
|
+
attacher.url #=> "https://my-bucket.s3.amazonaws.com/f4ba5bdbf366ef0b.jpg"
|
146
|
+
attacher.data #=> { "id" => "f4ba5bdbf366ef0b.jpg", "storage" => "store", "metadata" => { ... } }
|
79
147
|
```
|
80
148
|
|
81
|
-
|
149
|
+
#### Location
|
150
|
+
|
151
|
+
Paperclip persists only the filename of the uploaded file, and recalculates the
|
152
|
+
full location dynamically based on location configuration. This can be
|
153
|
+
dangerous, because if some component of the location happens to change, all
|
154
|
+
existing links might become invalid.
|
82
155
|
|
83
|
-
|
84
|
-
|
85
|
-
|
156
|
+
To avoid this, Shrine persists the full location on attachment, and uses it
|
157
|
+
when generating file URL. So, even if you change how file locations are
|
158
|
+
generated, existing files that are on old locations will still remain
|
159
|
+
accessible.
|
160
|
+
|
161
|
+
### Processing
|
86
162
|
|
163
|
+
In Shrine, processing is defined and performed on the instance level, which
|
164
|
+
gives you more control. You're also not coupled to ImageMagick, e.g. you can
|
165
|
+
use [libvips] instead (both integrations are provided by the [image_processing]
|
166
|
+
gem).
|
87
167
|
|
88
168
|
```rb
|
89
169
|
class Photo < ActiveRecord::Base
|
@@ -114,25 +194,51 @@ class ImageUploader < Shrine
|
|
114
194
|
end
|
115
195
|
```
|
116
196
|
|
197
|
+
Shrine is agnostic as to how you're performing your processing, so you can
|
198
|
+
easily use any other processing tools. You can also combine different
|
199
|
+
processors for different versions.
|
200
|
+
|
201
|
+
#### Retrieving versions
|
202
|
+
|
203
|
+
When retrieving versions, Paperclip returns a list of declared styles which
|
204
|
+
may or may not have been generated. In contrast, Shrine persists data of
|
205
|
+
uploaded processed files into the database (including any extracted metadata),
|
206
|
+
which then becomes the source of truth on which versions have been generated.
|
207
|
+
|
208
|
+
```rb
|
209
|
+
photo.image #=> #<Shrine::UploadedFile @id="original.jpg" ...>
|
210
|
+
photo.image_derivatives #=> {}
|
211
|
+
|
212
|
+
photo.image_derivatives! # triggers processing
|
213
|
+
photo.image_derivatives #=>
|
214
|
+
# {
|
215
|
+
# large: #<Shrine::UploadedFile @id="large.jpg" @metadata={"size"=>873232, ...} ...>,
|
216
|
+
# medium: #<Shrine::UploadedFile @id="medium.jpg" @metadata={"size"=>94823, ...} ...>,
|
217
|
+
# small: #<Shrine::UploadedFile @id="small.jpg" @metadata={"size"=>37322, ...} ...>,
|
218
|
+
# }
|
219
|
+
```
|
220
|
+
|
117
221
|
#### Reprocessing versions
|
118
222
|
|
119
223
|
Shrine doesn't have a built-in way of regenerating versions, because that has
|
120
|
-
to be written and optimized differently depending on
|
121
|
-
|
122
|
-
|
123
|
-
|
224
|
+
to be written and optimized differently depending on what versions have changed
|
225
|
+
which persistence library you're using, how many records there are in the table
|
226
|
+
etc.
|
227
|
+
|
228
|
+
However, there is an extensive guide for [Managing Derivatives], which provides
|
229
|
+
instructions on how to make these changes safely and with zero downtime.
|
124
230
|
|
125
|
-
###
|
231
|
+
### Validation
|
126
232
|
|
127
|
-
|
128
|
-
|
233
|
+
File validation in Shrine is also instance-level, which allows using
|
234
|
+
conditionals:
|
129
235
|
|
130
236
|
```rb
|
131
237
|
class Photo < ActiveRecord::Base
|
132
238
|
has_attached_file :image
|
133
239
|
validates_attachment :image,
|
134
|
-
|
135
|
-
|
240
|
+
size: { in: 0..10.megabytes },
|
241
|
+
content_type: { content_type: %w[image/jpeg image/png image/webp] }
|
136
242
|
end
|
137
243
|
```
|
138
244
|
|
@@ -141,114 +247,53 @@ class ImageUploader < Shrine
|
|
141
247
|
plugin :validation_helpers
|
142
248
|
|
143
249
|
Attacher.validate do
|
144
|
-
validate_mime_type %w[image/jpeg image/gif image/png]
|
145
250
|
validate_max_size 10*1024*1024
|
251
|
+
|
252
|
+
if validate_mime_type %w[image/jpeg image/png image/webp]
|
253
|
+
validate_max_dimensions [5000, 5000]
|
254
|
+
end
|
146
255
|
end
|
147
256
|
end
|
148
257
|
```
|
149
258
|
|
150
|
-
####
|
151
|
-
|
152
|
-
Paperclip detects MIME type spoofing, in the way that it extracts the MIME type
|
153
|
-
from file contents using the `file` command and MimeMagic, compares it to the
|
154
|
-
value that the `mime-types` gem determined from file extension, and raises a
|
155
|
-
validation error if these two values mismatch.
|
156
|
-
|
157
|
-
However, this turned out to be very problematic, leading to a lot of valid
|
158
|
-
files being classified as "spoofed", because of the differences of MIME
|
159
|
-
type databases between the `mime-types` gem, `file` command, and MimeMagic.
|
160
|
-
|
161
|
-
Shrine takes a different approach here. By default it will extract MIME
|
162
|
-
type from file extension, but it has a plugin for determining MIME type from
|
163
|
-
file contents, which by default uses the `file` command:
|
164
|
-
|
165
|
-
```rb
|
166
|
-
Shrine.plugin :determine_mime_type
|
167
|
-
```
|
168
|
-
|
169
|
-
However, it doesn't try to compare this value with the one from file extension,
|
170
|
-
it just means that now this value will be used for your MIME type validations.
|
171
|
-
With this approach you can still prevent malicious files from being attached,
|
172
|
-
but without the possibility of false negatives.
|
173
|
-
|
174
|
-
### Logging
|
175
|
-
|
176
|
-
In Paperclip you enable logging by setting `Paperclip.options[:log] = true`,
|
177
|
-
however, this only logs ImageMagick commands. Shrine has full logging support,
|
178
|
-
which measures processing, uploading and deleting individually:
|
179
|
-
|
180
|
-
```rb
|
181
|
-
Shrine.plugin :instrumentation
|
182
|
-
```
|
183
|
-
```
|
184
|
-
Metadata (32ms) – {:storage=>:store, :io=>StringIO, :uploader=>Shrine}
|
185
|
-
Upload (1523ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :io=>StringIO, :upload_options=>{}, :uploader=>Shrine}
|
186
|
-
Exists (755ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
|
187
|
-
Download (1002ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :download_options=>{}, :uploader=>Shrine}
|
188
|
-
Delete (700ms) – {:storage=>:store, :location=>"ed0e30ddec8b97813f2c1f4cfd1700b4", :uploader=>Shrine}
|
189
|
-
```
|
190
|
-
|
191
|
-
## Attachments
|
259
|
+
#### Custom metadata
|
192
260
|
|
193
|
-
|
194
|
-
designed to be completely generic and integrate with any ORM. It ships with
|
195
|
-
plugins for ActiveRecord and Sequel:
|
261
|
+
With Shrine you can also extract and validate any custom metadata:
|
196
262
|
|
197
263
|
```rb
|
198
|
-
|
199
|
-
|
200
|
-
|
264
|
+
class VideoUploader < Shrine
|
265
|
+
plugin :add_metadata
|
266
|
+
plugin :validation
|
201
267
|
|
202
|
-
|
203
|
-
|
204
|
-
|
268
|
+
add_metadata :duration do |io|
|
269
|
+
FFMPEG::Movie.new(io.path).duration
|
270
|
+
end
|
205
271
|
|
206
|
-
|
207
|
-
|
208
|
-
|
272
|
+
Attacher.validate do
|
273
|
+
if file.duration > 5*60*60
|
274
|
+
errors << "must not be longer than 5 hours"
|
275
|
+
end
|
276
|
+
end
|
209
277
|
end
|
210
278
|
```
|
211
279
|
|
212
|
-
|
280
|
+
#### MIME type spoofing
|
213
281
|
|
214
|
-
|
215
|
-
|
216
|
-
|
282
|
+
Paperclip attempts to detect MIME type spoofing, which turned out to be
|
283
|
+
unreliable due to differences in MIME type databases between different ruby
|
284
|
+
libraries.
|
217
285
|
|
218
|
-
|
219
|
-
|
220
|
-
# {
|
221
|
-
# "storage" => "store",
|
222
|
-
# "id" => "photo/1/image/0d9o8dk42.png",
|
223
|
-
# "metadata" => {
|
224
|
-
# "filename" => "nature.png",
|
225
|
-
# "size" => 49349138,
|
226
|
-
# "mime_type" => "image/png"
|
227
|
-
# }
|
228
|
-
# }
|
286
|
+
Shrine on the other hand simply allows you to determine MIME type from file
|
287
|
+
content, which you can then validate.
|
229
288
|
|
230
|
-
|
231
|
-
|
232
|
-
photo.image.mime_type #=> "image/png"
|
289
|
+
```rb
|
290
|
+
Shrine.plugin :determine_mime_type, analyzer: :marcel
|
233
291
|
```
|
234
|
-
|
235
|
-
Unlike Paperclip, Shrine will store this information for each processed
|
236
|
-
version, making them first-class citizens:
|
237
|
-
|
238
292
|
```rb
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
photo.image(:thumb) #=> #<Shrine::UploadedFile>
|
243
|
-
photo.image(:thumb).width #=> 300
|
293
|
+
file = uploader.upload StringIO.new("<?php ... ?>")
|
294
|
+
file.mime_type #=> "application/x-php"
|
244
295
|
```
|
245
296
|
|
246
|
-
Also, since Paperclip stores only the filename, it has to recalculate the full
|
247
|
-
location each time it wants to generate the URL. That makes it really difficult
|
248
|
-
to move files to a new location, because changing how the location is generated
|
249
|
-
will now cause incorrect URLs to be generated for all existing files. Shrine
|
250
|
-
calculates the whole location only once and saves it to the column.
|
251
|
-
|
252
297
|
## Migrating from Paperclip
|
253
298
|
|
254
299
|
You have an existing app using Paperclip and you want to transfer it to Shrine.
|
@@ -476,10 +521,18 @@ Shrine has this functionality in the `determine_mime_type` plugin.
|
|
476
521
|
This section explains the equivalent of Paperclip attachment's methods, in
|
477
522
|
Shrine this is an instance of `Shrine::UploadedFile`.
|
478
523
|
|
479
|
-
#### `#url
|
524
|
+
#### `#url`
|
480
525
|
|
481
|
-
|
482
|
-
|
526
|
+
In Shrine you can generate URLs with `#<name>_url`:
|
527
|
+
|
528
|
+
```rb
|
529
|
+
photo.image_url #=> "https://example.com/path/to/original.jpg"
|
530
|
+
photo.image_url(:large) #=> "https://example.com/path/to/large.jpg"
|
531
|
+
```
|
532
|
+
|
533
|
+
#### `#styles`
|
534
|
+
|
535
|
+
In Shrine you can use `#<name>_derivatives` to retrieve a list of versions:
|
483
536
|
|
484
537
|
```rb
|
485
538
|
photo.image_derivatives #=>
|
@@ -489,9 +542,9 @@ photo.image_derivatives #=>
|
|
489
542
|
# large: #<Shrine::UploadedFile>,
|
490
543
|
# }
|
491
544
|
|
492
|
-
photo.
|
545
|
+
photo.image_derivatives[:small] #=> #<Shrine::UploadedFile>
|
493
546
|
# or
|
494
|
-
photo.
|
547
|
+
photo.image(:small) #=> #<Shrine::UploadedFile>
|
495
548
|
```
|
496
549
|
|
497
550
|
#### `#path`
|
@@ -510,7 +563,7 @@ guide provides some useful tips on how to do this.
|
|
510
563
|
|
511
564
|
### `Paperclip::Storage::S3`
|
512
565
|
|
513
|
-
The built-in [`Shrine::Storage::S3`] storage is a direct replacement for
|
566
|
+
The built-in [`Shrine::Storage::S3`][S3] storage is a direct replacement for
|
514
567
|
`Paperclip::Storage::S3`.
|
515
568
|
|
516
569
|
#### `:s3_credentials`, `:s3_region`, `:bucket`
|
@@ -527,36 +580,21 @@ Shrine::Storage::S3.new(
|
|
527
580
|
)
|
528
581
|
```
|
529
582
|
|
530
|
-
#### `:s3_headers`
|
583
|
+
#### `:s3_headers`, `:s3_permissions`, `:s3_metadata`
|
531
584
|
|
532
|
-
|
585
|
+
These can be configured via the `:upload_options` option:
|
533
586
|
|
534
587
|
```rb
|
535
|
-
Shrine::Storage::S3.new(
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
```rb
|
545
|
-
Shrine::Storage::S3.new(upload_options: { acl: "private" }, **options)
|
546
|
-
```
|
547
|
-
|
548
|
-
You can use the `upload_options` plugin to set upload options dynamically.
|
549
|
-
|
550
|
-
#### `:s3_metadata`
|
551
|
-
|
552
|
-
The object metadata can be configured with the `:metadata` upload option:
|
553
|
-
|
554
|
-
```rb
|
555
|
-
Shrine::Storage::S3.new(upload_options: { metadata: { "key" => "value" } }, **options)
|
588
|
+
Shrine::Storage::S3.new(
|
589
|
+
upload_options: {
|
590
|
+
content_disposition: "attachment", # headers
|
591
|
+
acl: "private", # permissions
|
592
|
+
metadata: { "key" => "value" }, # metadata
|
593
|
+
},
|
594
|
+
**options
|
595
|
+
)
|
556
596
|
```
|
557
597
|
|
558
|
-
You can use the `upload_options` plugin to set upload options dynamically.
|
559
|
-
|
560
598
|
#### `:s3_protocol`, `:s3_host_alias`, `:s3_host_name`
|
561
599
|
|
562
600
|
The `#url` method accepts a `:host` option for specifying a CDN host. You can
|
@@ -580,8 +618,13 @@ s3.upload(io, "object/destination/path")
|
|
580
618
|
The Shrine storage has no replacement for the `:url` Paperclip option, and it
|
581
619
|
isn't needed.
|
582
620
|
|
583
|
-
[
|
584
|
-
[
|
585
|
-
[
|
586
|
-
[`Shrine::Storage::S3`]: /doc/storage/s3.md#readme
|
621
|
+
[Managing Derivatives]: https://shrinerb.com/docs/changing-derivatives
|
622
|
+
[direct uploads]: https://shrinerb.com/docs/getting-started#direct-uploads
|
623
|
+
[S3]: https://shrinerb.com/docs/storage/s3
|
587
624
|
[image_processing]: https://github.com/janko/image_processing
|
625
|
+
[libvips]: http://libvips.github.io/libvips/
|
626
|
+
[activerecord]: https://shrinerb.com/docs/plugins/activerecord
|
627
|
+
[sequel]: https://shrinerb.com/docs/plugins/sequel
|
628
|
+
[rom]: https://github.com/shrinerb/shrine-rom
|
629
|
+
[hanami]: https://github.com/katafrakt/hanami-shrine
|
630
|
+
[mongoid]: https://github.com/shrinerb/shrine-mongoid
|