shrine 1.0.0 → 1.1.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.

Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +101 -149
  3. data/doc/carrierwave.md +12 -16
  4. data/doc/changing_location.md +50 -0
  5. data/doc/creating_plugins.md +2 -2
  6. data/doc/creating_storages.md +70 -9
  7. data/doc/direct_s3.md +132 -61
  8. data/doc/migrating_storage.md +12 -10
  9. data/doc/paperclip.md +12 -17
  10. data/doc/refile.md +338 -0
  11. data/doc/regenerating_versions.md +75 -11
  12. data/doc/securing_uploads.md +172 -0
  13. data/lib/shrine.rb +21 -16
  14. data/lib/shrine/plugins/activerecord.rb +2 -2
  15. data/lib/shrine/plugins/background_helpers.rb +2 -148
  16. data/lib/shrine/plugins/backgrounding.rb +148 -0
  17. data/lib/shrine/plugins/backup.rb +88 -0
  18. data/lib/shrine/plugins/data_uri.rb +25 -4
  19. data/lib/shrine/plugins/default_url.rb +37 -0
  20. data/lib/shrine/plugins/delete_uploaded.rb +40 -0
  21. data/lib/shrine/plugins/determine_mime_type.rb +4 -2
  22. data/lib/shrine/plugins/direct_upload.rb +107 -62
  23. data/lib/shrine/plugins/download_endpoint.rb +157 -0
  24. data/lib/shrine/plugins/hooks.rb +19 -5
  25. data/lib/shrine/plugins/keep_location.rb +43 -0
  26. data/lib/shrine/plugins/moving.rb +11 -10
  27. data/lib/shrine/plugins/parallelize.rb +1 -5
  28. data/lib/shrine/plugins/parsed_json.rb +7 -1
  29. data/lib/shrine/plugins/pretty_location.rb +6 -0
  30. data/lib/shrine/plugins/rack_file.rb +7 -1
  31. data/lib/shrine/plugins/remove_invalid.rb +22 -0
  32. data/lib/shrine/plugins/sequel.rb +2 -2
  33. data/lib/shrine/plugins/upload_options.rb +41 -0
  34. data/lib/shrine/plugins/versions.rb +9 -7
  35. data/lib/shrine/storage/file_system.rb +46 -30
  36. data/lib/shrine/storage/linter.rb +48 -25
  37. data/lib/shrine/storage/s3.rb +89 -22
  38. data/lib/shrine/version.rb +1 -1
  39. data/shrine.gemspec +3 -3
  40. metadata +16 -5
data/doc/paperclip.md CHANGED
@@ -22,10 +22,6 @@ class ImageUploader < Shrine
22
22
  def process(io, context)
23
23
  # processing
24
24
  end
25
-
26
- def default_url(context)
27
- # default URL
28
- end
29
25
  end
30
26
  ```
31
27
 
@@ -37,8 +33,8 @@ you can instantiate uploaders with a specific storage:
37
33
  require "shrine/storage/file_system"
38
34
 
39
35
  Shrine.storages = {
40
- cache: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/cache"),
41
- store: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/store"),
36
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
37
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
42
38
  }
43
39
  ```
44
40
  ```rb
@@ -74,9 +70,9 @@ end
74
70
  #### Regenerating versions
75
71
 
76
72
  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.
73
+ very individual and depends on what versions you want regenerated, what ORM are
74
+ you using, how many records there are in your database etc. The [Regenerating
75
+ versions] guide provides some useful tips on this task.
80
76
 
81
77
  ### Logging
82
78
 
@@ -208,8 +204,8 @@ which you have to register:
208
204
  require "shrine/storage/file_system"
209
205
 
210
206
  Shrine.storages = {
211
- cache: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/cache"),
212
- store: Shrine::Storage::FileSystem.new("public", subdirectory: "uploads/store"),
207
+ cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
208
+ store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"),
213
209
  }
214
210
  ```
215
211
 
@@ -222,13 +218,12 @@ As explained in the "Processing" section, processing is done by overriding the
222
218
 
223
219
  #### `:default_url`
224
220
 
225
- In Shrine you achieve default URLs by defining the instance method on the
226
- uploader:
221
+ For default URLs you can use the `default_url` plugin:
227
222
 
228
223
  ```rb
229
224
  class ImageUploader < Shrine
230
- def default_url(context)
231
- "/placeholders/missing-image.png"
225
+ plugin :default_url do |context|
226
+ "/attachments/#{context[:name]}/default.jpg"
232
227
  end
233
228
  end
234
229
  ```
@@ -301,8 +296,8 @@ user.avatar.id #=> "users/342/avatar/398543qjfdsf.jpg"
301
296
 
302
297
  #### `#reprocess!`
303
298
 
304
- Shrine doesn't have an equivalent to this, but there is a "[Regenerating
305
- versions]" that should give you some useful guidelines.
299
+ Shrine doesn't have an equivalent to this, but the [Regenerating versions]
300
+ guide provides some useful tips on how to do this.
306
301
 
307
302
  [file]: http://linux.die.net/man/1/file
308
303
  [Regenerating versions]: http://shrinerb.com/rdoc/files/doc/regenerating_versions_md.html
data/doc/refile.md ADDED
@@ -0,0 +1,338 @@
1
+ # Shrine for Refile Users
2
+
3
+ This guide is aimed at helping Refile users transition to Shrine. We will first
4
+ generally mention what are the key differences, and afterwards we will give a
5
+ complete reference of Refile's interface and note what is the equivalent in
6
+ Shrine.
7
+
8
+ ## Uploaders
9
+
10
+ Shrine has the concept of storages very similar to Refile's backends. However,
11
+ while in Refile you usually work with storages directly, in Shrine you use
12
+ *uploaders* which act as wrappers around storages, and they are subclasses of
13
+ `Shrine`:
14
+
15
+ ```rb
16
+ require "shrine/storage/file_system"
17
+ require "shrine/storage/s3"
18
+
19
+ Shrine.storages = {
20
+ cache: Shrine::Storage::FileSystem.new(*args),
21
+ store: Shrine::Storage::S3.new(*args),
22
+ }
23
+ ```
24
+ ```rb
25
+ class ImageUploader < Shrine
26
+ # uploading logic
27
+ end
28
+ ```
29
+
30
+ While in Refile you configure attachments by passing options to `.attachment`,
31
+ in Shrine you define all your uploading logic inside uploaders, and then
32
+ generate an attacment module with that uploader which is included into the
33
+ model:
34
+
35
+ ```rb
36
+ class ImageUploader < Shrine
37
+ plugin :store_dimensions
38
+ plugin :determine_mime_type
39
+ plugin :keep_files, destroyed: true
40
+ end
41
+ ```
42
+ ```rb
43
+ class User
44
+ include ImageUploader[:avatar] # requires "avatar_data" column
45
+ end
46
+ ```
47
+
48
+ Unlike Refile which has just a few options of configuring attachments, Shrine
49
+ has a very rich arsenal of features via plugins, and allows you to share your
50
+ uploading logic between uploaders through inheritance.
51
+
52
+ ### ORMs
53
+
54
+ In Refile you extend the model with an Attachment module specific to the ORM
55
+ you're using. In Shrine you load the appropriate ORM plugin:
56
+
57
+ ```rb
58
+ Shrine.plugin :sequel # or :activerecord
59
+ ```
60
+ ```rb
61
+ class Photo < Sequel::Model
62
+ include ImageUploader[:image]
63
+ end
64
+ ```
65
+
66
+ These integrations work much like Refile; on assignment the file is cached,
67
+ and on saving the record file is moved from cache to store. Shrine doesn't
68
+ provide form helpers for Rails, because it's so easy to do it yourself:
69
+
70
+ ```erb
71
+ <%= form_for @photo do |f| %>
72
+ <%= f.hidden_field :image, value: @photo.image_data %>
73
+ <%= f.file_field :image %>
74
+ <% end %>
75
+ ```
76
+
77
+ ### URLs
78
+
79
+ To get file URLs, in Shrine you just call `#url` on the file:
80
+
81
+ ```rb
82
+ @photo.image.url
83
+ @photo.image_url # returns nil if attachment is missing
84
+ ```
85
+
86
+ If you're using storages which don't expose files over URL, or you want to
87
+ secure your downloads, you can use the `download_endpoint` plugin.
88
+
89
+ ### Metadata
90
+
91
+ While in Refile you're required to have a separate column for each metadata you
92
+ want to save (filename, size, content type), in Shrine all of the metadata are
93
+ stored in a single column (for "avatar" it's `avatar_data` column) as JSON.
94
+
95
+ ```rb
96
+ user.avatar_data #=> "{\"storage\":\"cache\",\"id\":\"9260ea09d8effd.jpg\",\"metadata\":{...}}"
97
+ ```
98
+
99
+ By default Shrine stores "filename", "size" and "mime_type" metadata, but you
100
+ can also store image dimensions by loading the `store_dimensions` plugin.
101
+
102
+ ### Processing
103
+
104
+ One of the key differences between Refile and Shrine is that in Refile you do
105
+ processing on-the-fly (like Dragonfly), while in Shrine you do your processing
106
+ on upload (like CarrierWave and Paperclip). However, there are storages which
107
+ you can use which support on-the-fly processing, like [shrine-cloudinary] or
108
+ [shrine-imgix].
109
+
110
+ In Shrine you do processing by overriding the `#process` method on your
111
+ uploader (for images you can use the [image_processing] gem):
112
+
113
+ ```rb
114
+ require "image_processing/mini_magick"
115
+
116
+ class ImageUploader < Shrine
117
+ include ImageProcessing::MiniMagick
118
+
119
+ def process(io, context)
120
+ case context[:phase]
121
+ when :store
122
+ resize_to_fit!(io.download, 700, 700)
123
+ end
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Validations
129
+
130
+ While in Refile you can do extension, mime type and filesize validation by
131
+ passing options to `.attachment`, in Shrine you do this logic instance level,
132
+ with the help of the `validation_helpers` plugin:
133
+
134
+ ```rb
135
+ class ImageUploader < Shrine
136
+ plugin :validation_helpers
137
+
138
+ Attacher.validate do
139
+ validate_mime_type_inclusion ["image/jpeg", "image/png", "image/gif"]
140
+ end
141
+ end
142
+ ```
143
+
144
+ ### Direct uploads
145
+
146
+ Shrine borrows Refile's idea of direct uploads, and ships with a
147
+ `direct_upload` plugin which provides the endpoint that you can mount:
148
+
149
+ ```rb
150
+ class ImageUploader < Shrine
151
+ plugin :direct_upload
152
+ end
153
+ ```
154
+ ```rb
155
+ # config/routes.rb
156
+ Rails.application.routes.draw do
157
+ mount ImageUploader::UploadEndpoint => "/attachments/images"
158
+ end
159
+ ```
160
+ ```rb
161
+ # POST /attachments/images/cache/avatar
162
+ {
163
+ "id": "43kewit94.jpg",
164
+ "storage": "cache",
165
+ "metadata": {
166
+ "size": 384393,
167
+ "filename": "nature.jpg",
168
+ "mime_type": "image/jpeg"
169
+ }
170
+ }
171
+ ```
172
+
173
+ Unlike Refile, Shrine doesn't ship with a JavaScript script which you can just
174
+ include to make it work. Instead, you're expected to use one of the many
175
+ excellent JavaScript libraries for generic file uploads, for example
176
+ [jQuery-File-Upload].
177
+
178
+ #### Presigned S3 uploads
179
+
180
+ The `direct_upload` plugin also provides an endpoint for getting S3 presigns,
181
+ you just need to pass the `presign: true` option. In the same way as with regular
182
+ direct uploads, you can use a generic JavaScript file upload library. For the
183
+ details read the [Direct Uploads to S3] guide.
184
+
185
+ ### Multiple uploads
186
+
187
+ Shrine doesn't have a built-in solution for accepting multiple uploads, but
188
+ it's actually very easy to do manually, see the [example app] on how you can do
189
+ multiple uploads directly to S3.
190
+
191
+ ## Refile to Shrine direct mapping
192
+
193
+ ### `Refile`
194
+
195
+ #### `.cache`, `.store`, `.backends`
196
+
197
+ Shrine calles these "storages", and it doesn't have special accessors for
198
+ `:cache` and `:store`:
199
+
200
+ ```rb
201
+ Shrine.storages = {
202
+ cache: Shrine::Storage::Foo.new(*args),
203
+ store: Shrine::Storage::Bar.new(*args),
204
+ }
205
+ ```
206
+
207
+ #### `.app`, `.mount_point`, `.automount`
208
+
209
+ The `direct_upload` plugin provides a subset of Refile's app's functionality,
210
+ and you have to mount it in your framework's router:
211
+
212
+ ```rb
213
+ # config/routes.rb
214
+ Rails.application.routes.draw do
215
+ # adds `POST /attachments/images/:storage/:name`
216
+ mount ImageUploader::UploadEndpoint => "/attachments/images"
217
+ end
218
+ ```
219
+
220
+ #### `.allow_uploads_to`
221
+
222
+ ```rb
223
+ Shrine.plugin :direct_upload, storages: [:cache]
224
+ ```
225
+
226
+ #### `.logger`
227
+
228
+ ```rb
229
+ Shrine.plugin :logging
230
+ ```
231
+
232
+ #### `.processors`, `.processor`
233
+
234
+ In Shrine processing is done by overriding the `#process` method in your
235
+ uploader:
236
+
237
+ ```rb
238
+ class MyUploader < Shrine
239
+ def process(io, context)
240
+ # ...
241
+ end
242
+ end
243
+ ```
244
+
245
+ #### `.types`
246
+
247
+ In Shrine validations are done by calling `.validate` on the attacher class:
248
+
249
+ ```rb
250
+ class MyUploader < Shrine
251
+ plugin :validation_helpers
252
+
253
+ Attacher.validate do
254
+ validate_max_size 5*1024*1024
255
+ end
256
+ end
257
+ ```
258
+
259
+ #### `.extract_filename`, `.extract_content_type`
260
+
261
+ In Shrine equivalents are (private) methods `Shrine#extract_filename` and
262
+ `Shrine#extract_mime_type`.
263
+
264
+ #### `.app_url`
265
+
266
+ You should use your framework to generate the URL to your mounted direct
267
+ enpdoint.
268
+
269
+ #### `.attachment_url`, `.file_url`
270
+
271
+ You can call `#url` on the uploaded file, or `#<name>_url` on the model.
272
+ Additionally you can use the `download_endpoint` plugin.
273
+
274
+ #### `.upload_url`, `.attachment_upload_url`, `.presign_url`, `.attachment_presign_url`
275
+
276
+ These should be generated directly by you, it depends on where you've mounted
277
+ the direct endpoint.
278
+
279
+ #### `.host`, `.cdn_host`, `.app_host`, `.allow_downloads_from`, `allow_origin`, `.content_max_age`
280
+
281
+ Not needed since Shrine doesn't offer on-the-fly processing.
282
+
283
+ #### `.secret_key`, `.token`, `.valid_token?`
284
+
285
+ Not needed since Shrine doesn't offer on-the-fly processing.
286
+
287
+ ### `attachment`
288
+
289
+ Shrine's equivalent to calling the attachment is including an attachment module
290
+ of an uploader:
291
+
292
+ ```rb
293
+ class User
294
+ include ImageUploader[:avatar]
295
+ end
296
+ ```
297
+
298
+ #### `:extension`, `:content_type`, `:type`
299
+
300
+ In Shrine validations are done instance-level inside the uploader, most
301
+ commonly with the `validation_helpers` plugin:
302
+
303
+ ```rb
304
+ class ImageUploader < Shrine
305
+ plugin :validation_helpers
306
+
307
+ Attacher.validate do
308
+ validate_extension_inclusion [/jpe?g/, "png"]
309
+ validate_mime_type_inclusion [/image/jpeg/, "image/png"]
310
+ end
311
+ end
312
+ ```
313
+
314
+ #### `:cache`, `:store`
315
+
316
+ Shrine provides a `default_storage` plugin for setting custom storages on the
317
+ uploader:
318
+
319
+ ```rb
320
+ Shrine.storages[:custom_cache] = Shrine::Storage::Foo.new(*args)
321
+ Shrine.storages[:custom_store] = Shrine::Storage::Bar.new(*args)
322
+ ```
323
+ ```rb
324
+ class ImageUploader < Shrine
325
+ plugin :default_storage, cache: :custom_cache, store: :custom_store
326
+ end
327
+ ```
328
+
329
+ #### `:raise_errors`
330
+
331
+ No equivalent currently exists in Shrine.
332
+
333
+ [shrine-cloudinary]: https://github.com/janko-m/shrine-cloudinary
334
+ [shrine-imgix]: https://github.com/janko-m/shrine-imgix
335
+ [image_processing]: https://github.com/janko-m/image_processing
336
+ [jQuery-File-Upload]: https://github.com/blueimp/jQuery-File-Upload
337
+ [Direct Uploads to S3]: http://shrinerb.com/rdoc/files/doc/direct_s3_md.html
338
+ [example app]: https://github.com/janko-m/shrine-example
@@ -1,4 +1,4 @@
1
- # Regenerating versions
1
+ # Reprocessing Versions
2
2
 
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
@@ -6,15 +6,66 @@ addition to changing you processing code, you also need to reprocess the
6
6
  existing attachments. This guide is aimed to help doing this migration with
7
7
  zero downtime and no unused files left in the main storage.
8
8
 
9
- ## Regenerating a specific version
9
+ ## Adding versions
10
+
11
+ Most common scenario is when initially you're not doing any processing, but
12
+ later decide that you want to generate versions. First you need to update your
13
+ code to generate versions, and you also need to change your views to use those
14
+ versions:
15
+
16
+ ```rb
17
+ class ImageUploader < Shrine
18
+ plugin :versions, names: [:original, :thumb]
19
+
20
+ def process(io, context)
21
+ case context[:phase]
22
+ when :store
23
+ thumb = process_thumb(io.download) # replace with actual processing method
24
+ {original: io, thumb: thumb}
25
+ end
26
+ end
27
+ end
28
+ ```
29
+ ```rb
30
+ # In your views add the version name to all <attachment>_url calls.
31
+ user.avatar_url(:thumb)
32
+ ```
33
+
34
+ Note that you should deploy both of these changes at once, because the
35
+ `<attachment>_url` method will fail if there are versions generated but no
36
+ version name was passed in. If a version name was passed in but versions aren't
37
+ generated yet (which will be the case here), it will just return the
38
+ unprocessed file URL.
39
+
40
+ Afterwards you should run a script which reprocesses the versions for existing
41
+ files:
42
+
43
+ ```rb
44
+ Shrine.plugin :migration_helpers # before the model is loaded
45
+ ```
46
+ ```rb
47
+ User.paged_each do |user|
48
+ user.update_avatar do |avatar|
49
+ unless avatar.is_a?(Hash)
50
+ file = some_processing(avatar.download)
51
+ thumb = user.avatar_store.upload(file, {record: user, name: :avatar, version: :thumb})
52
+ {original: avatar, thumb: thumb}
53
+ end
54
+ end
55
+ end
56
+ ```
57
+
58
+ ## Reprocessing a single version
10
59
 
11
60
  The simplest scenario is where you need to regenerate an existing version.
12
61
  First you need to change and deploy your updated processing code, and
13
62
  afterwards you can run a script like this on your production database:
14
63
 
15
64
  ```rb
16
- Shrine.plugin :migration_helpers
65
+ Shrine.plugin :migration_helpers # before the model is loaded
66
+ ```
17
67
 
68
+ ```rb
18
69
  User.paged_each do |user|
19
70
  user.update_avatar do |avatar|
20
71
  thumb = some_processing(avatar[:original].download)
@@ -47,15 +98,17 @@ After you've deployed this change, you should run a script that will generate
47
98
  the new version for all existing records:
48
99
 
49
100
  ```rb
50
- Shrine.plugin :migration_helpers
101
+ Shrine.plugin :migration_helpers # before the model is loaded
102
+ ```
51
103
 
104
+ ```rb
52
105
  User.paged_each do |user|
53
106
  user.update_avatar do |avatar|
54
- unless new = avatar[:new]
107
+ unless avatar[:new]
55
108
  file = some_processing(avatar[:original].download, *args)
56
- new = user.avatar_store.upload(file)
109
+ new = user.avatar_store.upload(file, {record: user, name: :avatar, version: :new})
110
+ avatar.merge(new: new)
57
111
  end
58
- avatar.merge(new: new)
59
112
  end
60
113
  end
61
114
  ```
@@ -72,29 +125,40 @@ not to use the new version, and deploy that code. After you've done that, you
72
125
  can run a script which removes that version:
73
126
 
74
127
  ```rb
75
- Shrine.plugin :migration_helpers
128
+ Shrine.plugin :migration_helpers # before the model is loaded
129
+ ```
130
+
131
+ ```rb
132
+ removed_versions = []
76
133
 
77
134
  User.paged_each do |user|
78
135
  user.update_avatar do |avatar|
79
136
  old_version = avatar.delete(:old_version)
80
- old_version.delete if old_version
137
+ removed_versions << old_version if old_version
81
138
  avatar
82
139
  end
83
140
  end
141
+
142
+ if removed_versions.any?
143
+ uploader = removed_versions.first.uploader
144
+ uploader.delete(removed_versions)
145
+ end
84
146
  ```
85
147
 
86
148
  After the script has finished, you should be able to safely remove the version
87
149
  name from the list.
88
150
 
89
- ## Regenerating all versions
151
+ ## Reprocessing all versions
90
152
 
91
153
  If you made a lot of changes to versions, it might make sense to simply
92
154
  regenerate all versions. After you've deployed the change in processing, you
93
155
  can run a script which updates existing records:
94
156
 
95
157
  ```rb
96
- Shrine.plugin :migration_helpers
158
+ Shrine.plugin :migration_helpers # before the model is loaded
159
+ ```
97
160
 
161
+ ```rb
98
162
  User.paged_each do |user|
99
163
  if user.avatar && user.avatar_store.uploaded?(user.avatar)
100
164
  user.update(avatar: user.avatar[:original])