shrine 0.9.0 → 1.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/README.md +70 -59
- data/doc/carrierwave.md +436 -0
- data/doc/direct_s3.md +20 -1
- data/doc/paperclip.md +308 -0
- data/doc/regenerating_versions.md +76 -11
- data/lib/shrine.rb +42 -46
- data/lib/shrine/plugins/activerecord.rb +12 -6
- data/lib/shrine/plugins/background_helpers.rb +5 -5
- data/lib/shrine/plugins/default_storage.rb +1 -1
- data/lib/shrine/plugins/default_url_options.rb +31 -0
- data/lib/shrine/plugins/determine_mime_type.rb +13 -3
- data/lib/shrine/plugins/direct_upload.rb +55 -106
- data/lib/shrine/plugins/hooks.rb +33 -11
- data/lib/shrine/plugins/keep_files.rb +4 -19
- data/lib/shrine/plugins/logging.rb +16 -11
- data/lib/shrine/plugins/migration_helpers.rb +4 -4
- data/lib/shrine/plugins/module_include.rb +46 -0
- data/lib/shrine/plugins/moving.rb +0 -11
- data/lib/shrine/plugins/multi_delete.rb +3 -12
- data/lib/shrine/plugins/parsed_json.rb +22 -0
- data/lib/shrine/plugins/rack_file.rb +63 -0
- data/lib/shrine/plugins/recache.rb +1 -1
- data/lib/shrine/plugins/restore_cached.rb +1 -2
- data/lib/shrine/plugins/sequel.rb +9 -3
- data/lib/shrine/plugins/validation_helpers.rb +1 -1
- data/lib/shrine/plugins/versions.rb +30 -17
- data/lib/shrine/storage/file_system.rb +62 -17
- data/lib/shrine/storage/linter.rb +8 -3
- data/lib/shrine/storage/s3.rb +84 -20
- data/lib/shrine/version.rb +2 -2
- data/shrine.gemspec +11 -8
- metadata +31 -40
- data/lib/shrine/plugins/delete_invalid.rb +0 -25
data/doc/direct_s3.md
CHANGED
@@ -23,7 +23,7 @@ upfront the fields necessary for direct S3 uploads using
|
|
23
23
|
object, which has `#url` and `#fields`, which you could use like this:
|
24
24
|
|
25
25
|
```erb
|
26
|
-
<% presign = Shrine.storages[:cache].presign(SecureRandom.hex
|
26
|
+
<% presign = Shrine.storages[:cache].presign(SecureRandom.hex) %>
|
27
27
|
|
28
28
|
<form action="<%= presign.url %>" method="post" enctype="multipart/form-data">
|
29
29
|
<input type="file" name="file">
|
@@ -33,6 +33,16 @@ object, which has `#url` and `#fields`, which you could use like this:
|
|
33
33
|
</form>
|
34
34
|
```
|
35
35
|
|
36
|
+
You can also pass additional options to `#presign`:
|
37
|
+
|
38
|
+
```rb
|
39
|
+
Shrine.storages[:cache].presign(SecureRandom.hex,
|
40
|
+
content_length_range: 0..(5*1024*1024), # Limit of 5 MB
|
41
|
+
success_action_redirect: webhook_url, # Tell S3 where to redirect
|
42
|
+
# ...
|
43
|
+
)
|
44
|
+
```
|
45
|
+
|
36
46
|
## Dynamic upload
|
37
47
|
|
38
48
|
If the frontend is separate from the backend, or you want to do multiple file
|
@@ -64,6 +74,15 @@ You can use this data in a similar way as with static upload. See
|
|
64
74
|
the [example app] for how multiple file upload to S3 can be done using
|
65
75
|
[jQuery-File-Upload].
|
66
76
|
|
77
|
+
If you want to pass additional options to `Storage::S3#presign`, you can pass
|
78
|
+
a block to `:presign`:
|
79
|
+
|
80
|
+
```rb
|
81
|
+
plugin :direct_upload, presign: ->(request) do # yields a Roda request object
|
82
|
+
{success_action_redirect: "http://example.com/webhook"}
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
67
86
|
## File hash
|
68
87
|
|
69
88
|
Once you've uploaded the file to S3, you need to create the representation of
|
data/doc/paperclip.md
ADDED
@@ -0,0 +1,308 @@
|
|
1
|
+
# Shrine for Paperclip Users
|
2
|
+
|
3
|
+
This guide is aimed at helping Paperclip users transition to Shrine. We will
|
4
|
+
first generally mention what are the key differences. Afterwards there is a
|
5
|
+
complete reference of Paperclip's interface and what is the equivalent in
|
6
|
+
Shrine.
|
7
|
+
|
8
|
+
## Uploaders
|
9
|
+
|
10
|
+
While in Paperclip you write your uploading logic as a list of options inside
|
11
|
+
your models, in Shrine you instead have "uploader" classes where you put all
|
12
|
+
your uploading logic.
|
13
|
+
|
14
|
+
```rb
|
15
|
+
class ImageUploader < Shrine
|
16
|
+
plugin :validation_helpers
|
17
|
+
|
18
|
+
Attacher.validate do
|
19
|
+
validate_mime_type_inclusion [/^image/]
|
20
|
+
end
|
21
|
+
|
22
|
+
def process(io, context)
|
23
|
+
# processing
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_url(context)
|
27
|
+
# default URL
|
28
|
+
end
|
29
|
+
end
|
30
|
+
```
|
31
|
+
|
32
|
+
Unlike Paperclip, in Shrine you can use these uploaders directly if you have
|
33
|
+
to do some lower-level logic. First you need to register storages, and then
|
34
|
+
you can instantiate uploaders with a specific storage:
|
35
|
+
|
36
|
+
```rb
|
37
|
+
require "shrine/storage/file_system"
|
38
|
+
|
39
|
+
Shrine.storages = {
|
40
|
+
cache: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/cache"),
|
41
|
+
store: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/store"),
|
42
|
+
}
|
43
|
+
```
|
44
|
+
```rb
|
45
|
+
uploader = Shrine.new(:cache)
|
46
|
+
uploaded_file = uploader.upload(File.open("nature.jpg"))
|
47
|
+
uploaded_file.path #=> "/uploads/cache/s9ffdkfd02kd.jpg"
|
48
|
+
uploaded_file.original_filename #=> "nature.jpg"
|
49
|
+
```
|
50
|
+
|
51
|
+
### Processing
|
52
|
+
|
53
|
+
In Shrine you do processing inside the uploader's `#process` method, and unlike
|
54
|
+
Paperclip, the processing is done on instance-level, so you have maximum
|
55
|
+
flexibility. In Shrine you generate versions by simply returning a hash, and
|
56
|
+
also loading the `versions` plugin to make your uploader recognize versions:
|
57
|
+
|
58
|
+
```rb
|
59
|
+
require "image_processing/mini_magick" # part of the "image_processing" gem
|
60
|
+
|
61
|
+
class ImageUploader < Shrine
|
62
|
+
include ImageProcessing::MiniMagick
|
63
|
+
plugin :versions, names: [:original, :thumb]
|
64
|
+
|
65
|
+
def process(io, context)
|
66
|
+
if context[:phase] == :store
|
67
|
+
thumb = resize_to_limit(io.download, 300, 300)
|
68
|
+
{original: io, thumb: thumb}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
#### Regenerating versions
|
75
|
+
|
76
|
+
Shrine doesn't have a built-in way of regenerating versions, because that's
|
77
|
+
very individual and depends on what versions you want regenerated, what ORM
|
78
|
+
are you using, how many records there are in your database etc. But I wrote
|
79
|
+
a "[Regenerating versions]" guide that should give you useful guidelines.
|
80
|
+
|
81
|
+
### Logging
|
82
|
+
|
83
|
+
In Paperclip you enable logging by setting `Paperclip.options[:log] = true`.
|
84
|
+
Shrine also provides logging with the `logging` plugin:
|
85
|
+
|
86
|
+
```rb
|
87
|
+
Shrine.plugin :logging
|
88
|
+
```
|
89
|
+
|
90
|
+
## Attachments
|
91
|
+
|
92
|
+
The uploaders can then integrate with models by generating attachment modules
|
93
|
+
which are included into the models. Shrine ships with plugins for Sequel and
|
94
|
+
ActiveRecord ORMs, so you first have to load the one for your ORM:
|
95
|
+
|
96
|
+
```rb
|
97
|
+
Shrine.plugin :sequel # If you're using Sequel
|
98
|
+
Shrine.plugin :activerecord # If you're using ActiveRecord
|
99
|
+
```
|
100
|
+
|
101
|
+
Now you use your uploaders to generate "attachment modules", which you can then
|
102
|
+
include in your models:
|
103
|
+
|
104
|
+
```rb
|
105
|
+
class User < Sequel::Model
|
106
|
+
include ImageUploader[:avatar] # adds `avatar`, `avatar=` and `avatar_url` methods
|
107
|
+
end
|
108
|
+
```
|
109
|
+
|
110
|
+
Unlike in Paperclip which requires you to have `<attachment>_file_name`,
|
111
|
+
`<attachment>_file_size`, `<attachment>_content_type` and
|
112
|
+
`<attachment>_updated_at` columns, in Shrine you only need to have an
|
113
|
+
`<attachment>_data` text column, and all information will be stored there.
|
114
|
+
|
115
|
+
The attachments use `:store` for storing the files, and `:cache` for caching.
|
116
|
+
The latter is something Paperclip doesn't do, but caching before storing is
|
117
|
+
really great because the file then persists on validation errors, and also in
|
118
|
+
backgrounding you can show the users the cached version before the file is
|
119
|
+
finished storing.
|
120
|
+
|
121
|
+
### Validations
|
122
|
+
|
123
|
+
In Shrine validations are done inside uploader classes, and validation methods
|
124
|
+
are provided by the `validation_helpers` plugin:
|
125
|
+
|
126
|
+
```rb
|
127
|
+
class ImageUploader < Shrine
|
128
|
+
plugin :validation_helpers
|
129
|
+
|
130
|
+
Attacher.validate do
|
131
|
+
validate_max_size 5*1024*1024
|
132
|
+
validate_mime_type_inclusion [/^image/]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
For presence validation you should use the one provided by your ORM:
|
138
|
+
|
139
|
+
```rb
|
140
|
+
class User < Sequel::Model
|
141
|
+
include ImageUploader[:avatar]
|
142
|
+
|
143
|
+
def validate
|
144
|
+
validates_presence [:avatar]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
#### MIME type spoofing
|
150
|
+
|
151
|
+
By default Shrine will extract the MIME type from the `Content-Type` header of
|
152
|
+
the uploaded file, which is solely determined from the file extension, so it's
|
153
|
+
prone to spoofing. Shrine provides the `determine_mime_type` plugin which
|
154
|
+
determines the MIME type from the file *contents* instead:
|
155
|
+
|
156
|
+
```rb
|
157
|
+
Shrine.plugin :determine_mime_type
|
158
|
+
```
|
159
|
+
|
160
|
+
By default the UNIX [file] utility is used, but you can choose other analyzers.
|
161
|
+
Unlike Paperclip, you won't get any errors if the MIME type is "spoofed",
|
162
|
+
instead it's better if you simply validate allowed MIME types.
|
163
|
+
|
164
|
+
### Hooks/Callbacks
|
165
|
+
|
166
|
+
Shrine's `hooks` plugin provides callbacks for Shrine, so to get Paperclip's
|
167
|
+
`(before|after)_post_process`, you can override `#before_process` and
|
168
|
+
`#after_process` methods:
|
169
|
+
|
170
|
+
```rb
|
171
|
+
class ImageUploader < Shrine
|
172
|
+
plugin :hooks
|
173
|
+
|
174
|
+
def before_process(io, context)
|
175
|
+
# ...
|
176
|
+
super
|
177
|
+
end
|
178
|
+
|
179
|
+
def after_process(io, context)
|
180
|
+
super
|
181
|
+
# ...
|
182
|
+
end
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
## Paperclip to Shrine direct mapping
|
187
|
+
|
188
|
+
### `has_attached_file`
|
189
|
+
|
190
|
+
As mentioned above, Shrine's equivalent of `has_attached_file` is including
|
191
|
+
an attachment module:
|
192
|
+
|
193
|
+
```rb
|
194
|
+
class User < Sequel::Model
|
195
|
+
include ImageUploader[:avatar] # adds `avatar`, `avatar=` and `avatar_url` methods
|
196
|
+
end
|
197
|
+
```
|
198
|
+
|
199
|
+
Now we'll list all options that `has_attached_file` accepts, and explain
|
200
|
+
Shrine's equivalents:
|
201
|
+
|
202
|
+
#### `:storage`
|
203
|
+
|
204
|
+
In Shrine attachments will automatically use `:cache` and `:store` storages
|
205
|
+
which you have to register:
|
206
|
+
|
207
|
+
```rb
|
208
|
+
require "shrine/storage/file_system"
|
209
|
+
|
210
|
+
Shrine.storages = {
|
211
|
+
cache: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/cache"),
|
212
|
+
store: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/store"),
|
213
|
+
}
|
214
|
+
```
|
215
|
+
|
216
|
+
You can change that for a specific uploader with the `default_storage` plugin.
|
217
|
+
|
218
|
+
#### `:styles`, `:processors`, `:convert_options`
|
219
|
+
|
220
|
+
As explained in the "Processing" section, processing is done by overriding the
|
221
|
+
`Shrine#process` method.
|
222
|
+
|
223
|
+
#### `:default_url`
|
224
|
+
|
225
|
+
In Shrine you achieve default URLs by defining the instance method on the
|
226
|
+
uploader:
|
227
|
+
|
228
|
+
```rb
|
229
|
+
class ImageUploader < Shrine
|
230
|
+
def default_url(context)
|
231
|
+
"/placeholders/missing-image.png"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
#### `:preserve_files`
|
237
|
+
|
238
|
+
Shrine provides a `keep_files` plugin which allows you to keep files that would
|
239
|
+
otherwise be deleted:
|
240
|
+
|
241
|
+
```rb
|
242
|
+
Shrine.plugin :keep_files, destroyed: true
|
243
|
+
```
|
244
|
+
|
245
|
+
#### `:path`, `:url`, `:interpolator`, `:url_generator`
|
246
|
+
|
247
|
+
Shrine by default stores your files in the same directory, but you can also
|
248
|
+
load the `pretty_location` plugin for nice folder structure:
|
249
|
+
|
250
|
+
```rb
|
251
|
+
Shrine.plugin :pretty_location
|
252
|
+
```
|
253
|
+
|
254
|
+
Alternatively, if you want to generate locations yourself you can override the
|
255
|
+
`#generate_location` method:
|
256
|
+
|
257
|
+
```rb
|
258
|
+
class ImageUploader < Shrine
|
259
|
+
def generate_location(io, context)
|
260
|
+
# ...
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
#### `:validate_media_type`
|
266
|
+
|
267
|
+
Shrine has this functionality in the `determine_mime_type` plugin.
|
268
|
+
|
269
|
+
### `Paperclip::Attachment`
|
270
|
+
|
271
|
+
This section explains the equivalent of Paperclip attachment's methods, in
|
272
|
+
Shrine this is an instance of `Shrine::UploadedFile`.
|
273
|
+
|
274
|
+
#### `#url`, `#styles`
|
275
|
+
|
276
|
+
If you're generating versions in Shrine, the attachment will be a hash of
|
277
|
+
uploaded files:
|
278
|
+
|
279
|
+
```rb
|
280
|
+
user.avatar.class #=> Hash
|
281
|
+
user.avatar #=>
|
282
|
+
# {
|
283
|
+
# small: #<Shrine::UploadedFile>,
|
284
|
+
# medium: #<Shrine::UploadedFile>,
|
285
|
+
# large: #<Shrine::UploadedFile>,
|
286
|
+
# }
|
287
|
+
|
288
|
+
user.avatar[:small].url #=> "..."
|
289
|
+
# or
|
290
|
+
user.avatar_url(:small) #=> "..."
|
291
|
+
```
|
292
|
+
|
293
|
+
#### `#path`
|
294
|
+
|
295
|
+
Shrine doesn't have this because storages are abstract and this would be
|
296
|
+
specific to the filesystem, but the closest is probably `#id`:
|
297
|
+
|
298
|
+
```rb
|
299
|
+
user.avatar.id #=> "users/342/avatar/398543qjfdsf.jpg"
|
300
|
+
```
|
301
|
+
|
302
|
+
#### `#reprocess!`
|
303
|
+
|
304
|
+
Shrine doesn't have an equivalent to this, but there is a "[Regenerating
|
305
|
+
versions]" that should give you some useful guidelines.
|
306
|
+
|
307
|
+
[file]: http://linux.die.net/man/1/file
|
308
|
+
[Regenerating versions]: http://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
|
@@ -3,36 +3,101 @@
|
|
3
3
|
While your app is serving uploads in production, you may realize that you want
|
4
4
|
to change how your attachment's versions are generated. This means that, in
|
5
5
|
addition to changing you processing code, you also need to reprocess the
|
6
|
-
existing attachments.
|
7
|
-
|
6
|
+
existing attachments. This guide is aimed to help doing this migration with
|
7
|
+
zero downtime and no unused files left in the main storage.
|
8
8
|
|
9
9
|
## Regenerating a specific version
|
10
10
|
|
11
|
-
The simplest scenario is where you need to regenerate
|
12
|
-
you change your processing code,
|
13
|
-
|
11
|
+
The simplest scenario is where you need to regenerate an existing version.
|
12
|
+
First you need to change and deploy your updated processing code, and
|
13
|
+
afterwards you can run a script like this on your production database:
|
14
14
|
|
15
15
|
```rb
|
16
16
|
Shrine.plugin :migration_helpers
|
17
17
|
|
18
18
|
User.paged_each do |user|
|
19
19
|
user.update_avatar do |avatar|
|
20
|
-
|
21
|
-
avatar.merge(thumb: avatar[:thumb].replace(
|
20
|
+
thumb = some_processing(avatar[:original].download)
|
21
|
+
avatar.merge(thumb: avatar[:thumb].replace(thumb))
|
22
22
|
end
|
23
23
|
end
|
24
24
|
```
|
25
25
|
|
26
|
-
|
26
|
+
### Adding a new version
|
27
|
+
|
28
|
+
When adding a new version to a production app, first add it to the list and
|
29
|
+
update your processing code to generate it, and deploy it:
|
30
|
+
|
31
|
+
```rb
|
32
|
+
class ImageUploader < Shrine
|
33
|
+
plugin :versions, names: [:small, :medium, :new] # we add the ":new" version
|
34
|
+
|
35
|
+
def process(io, context)
|
36
|
+
case context[:phase]
|
37
|
+
when :store
|
38
|
+
# ...
|
39
|
+
new = some_processing(io.download, *args)
|
40
|
+
{small: small, medium: medium, new: new} # we generate the ":new" version
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
After you've deployed this change, you should run a script that will generate
|
47
|
+
the new version for all existing records:
|
48
|
+
|
49
|
+
```rb
|
50
|
+
Shrine.plugin :migration_helpers
|
51
|
+
|
52
|
+
User.paged_each do |user|
|
53
|
+
user.update_avatar do |avatar|
|
54
|
+
unless new = avatar[:new]
|
55
|
+
file = some_processing(avatar[:original].download, *args)
|
56
|
+
new = user.avatar_store.upload(file)
|
57
|
+
end
|
58
|
+
avatar.merge(new: new)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
```
|
62
|
+
|
63
|
+
After you've run this script on your production database, all records should
|
64
|
+
have the new version, and now you should be able to safely update your app to
|
65
|
+
use it.
|
66
|
+
|
67
|
+
### Removing a version
|
68
|
+
|
69
|
+
Before removing a version, you first need to update your processing to not
|
70
|
+
generate it (but keep the version name in the list), as well as update your app
|
71
|
+
not to use the new version, and deploy that code. After you've done that, you
|
72
|
+
can run a script which removes that version:
|
73
|
+
|
74
|
+
```rb
|
75
|
+
Shrine.plugin :migration_helpers
|
76
|
+
|
77
|
+
User.paged_each do |user|
|
78
|
+
user.update_avatar do |avatar|
|
79
|
+
old_version = avatar.delete(:old_version)
|
80
|
+
old_version.delete if old_version
|
81
|
+
avatar
|
82
|
+
end
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
After the script has finished, you should be able to safely remove the version
|
87
|
+
name from the list.
|
27
88
|
|
28
89
|
## Regenerating all versions
|
29
90
|
|
30
91
|
If you made a lot of changes to versions, it might make sense to simply
|
31
|
-
regenerate all versions.
|
32
|
-
|
92
|
+
regenerate all versions. After you've deployed the change in processing, you
|
93
|
+
can run a script which updates existing records:
|
33
94
|
|
34
95
|
```rb
|
96
|
+
Shrine.plugin :migration_helpers
|
97
|
+
|
35
98
|
User.paged_each do |user|
|
36
|
-
user.
|
99
|
+
if user.avatar && user.avatar_store.uploaded?(user.avatar)
|
100
|
+
user.update(avatar: user.avatar[:original])
|
101
|
+
end
|
37
102
|
end
|
38
103
|
```
|