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.

@@ -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.to_s) %>
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
@@ -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. Depending on the magnitude and the nature of the change,
7
- you can take different steps on doing that.
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 a specific version. After
12
- you change your processing code, this is how you would regenerate a specific
13
- version (in Sequel):
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
- file = some_processing(avatar[:thumb].download)
21
- avatar.merge(thumb: avatar[:thumb].replace(file))
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
- In a similar way you would add a new version or remove an existing one.
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. You would typically use a "base" version to regenerate
32
- the other versions from:
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.update(avatar: user.avatar[:original])
99
+ if user.avatar && user.avatar_store.uploaded?(user.avatar)
100
+ user.update(avatar: user.avatar[:original])
101
+ end
37
102
  end
38
103
  ```