shrine 3.0.0.beta2 → 3.0.0.beta3

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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -1
  3. data/README.md +100 -106
  4. data/doc/advantages.md +90 -88
  5. data/doc/attacher.md +322 -152
  6. data/doc/carrierwave.md +105 -113
  7. data/doc/changing_derivatives.md +308 -0
  8. data/doc/changing_location.md +92 -21
  9. data/doc/changing_storage.md +107 -0
  10. data/doc/creating_plugins.md +1 -1
  11. data/doc/design.md +8 -9
  12. data/doc/direct_s3.md +3 -2
  13. data/doc/metadata.md +97 -78
  14. data/doc/multiple_files.md +3 -3
  15. data/doc/paperclip.md +89 -88
  16. data/doc/plugins/activerecord.md +3 -12
  17. data/doc/plugins/backgrounding.md +126 -100
  18. data/doc/plugins/derivation_endpoint.md +4 -5
  19. data/doc/plugins/derivatives.md +63 -32
  20. data/doc/plugins/download_endpoint.md +54 -1
  21. data/doc/plugins/entity.md +1 -0
  22. data/doc/plugins/form_assign.md +53 -0
  23. data/doc/plugins/mirroring.md +37 -16
  24. data/doc/plugins/multi_cache.md +22 -0
  25. data/doc/plugins/presign_endpoint.md +1 -1
  26. data/doc/plugins/remote_url.md +19 -4
  27. data/doc/plugins/validation.md +83 -0
  28. data/doc/processing.md +149 -133
  29. data/doc/refile.md +68 -63
  30. data/doc/release_notes/3.0.0.md +835 -0
  31. data/doc/securing_uploads.md +56 -36
  32. data/doc/storage/s3.md +2 -2
  33. data/doc/testing.md +104 -120
  34. data/doc/upgrading_to_3.md +538 -0
  35. data/doc/validation.md +48 -87
  36. data/lib/shrine.rb +7 -4
  37. data/lib/shrine/attacher.rb +16 -6
  38. data/lib/shrine/plugins/activerecord.rb +33 -14
  39. data/lib/shrine/plugins/atomic_helpers.rb +1 -1
  40. data/lib/shrine/plugins/backgrounding.rb +23 -89
  41. data/lib/shrine/plugins/data_uri.rb +13 -2
  42. data/lib/shrine/plugins/derivation_endpoint.rb +7 -11
  43. data/lib/shrine/plugins/derivatives.rb +44 -20
  44. data/lib/shrine/plugins/download_endpoint.rb +26 -0
  45. data/lib/shrine/plugins/form_assign.rb +6 -3
  46. data/lib/shrine/plugins/keep_files.rb +2 -2
  47. data/lib/shrine/plugins/mirroring.rb +62 -22
  48. data/lib/shrine/plugins/model.rb +2 -2
  49. data/lib/shrine/plugins/multi_cache.rb +27 -0
  50. data/lib/shrine/plugins/remote_url.rb +25 -10
  51. data/lib/shrine/plugins/remove_invalid.rb +1 -1
  52. data/lib/shrine/plugins/sequel.rb +39 -20
  53. data/lib/shrine/plugins/validation.rb +3 -0
  54. data/lib/shrine/storage/s3.rb +16 -1
  55. data/lib/shrine/uploaded_file.rb +1 -0
  56. data/lib/shrine/version.rb +1 -1
  57. data/shrine.gemspec +1 -1
  58. metadata +12 -7
  59. data/doc/migrating_storage.md +0 -76
  60. data/doc/regenerating_versions.md +0 -143
  61. data/lib/shrine/plugins/attacher_options.rb +0 -55
@@ -20,7 +20,7 @@ class Shrine
20
20
  private
21
21
 
22
22
  def revert_change
23
- destroy(background: true)
23
+ destroy
24
24
  set @previous.file
25
25
  remove_instance_variable(:@previous)
26
26
  end
@@ -32,42 +32,26 @@ class Shrine
32
32
  name = @name
33
33
 
34
34
  if shrine_class.opts[:sequel][:validations]
35
- # add validation plugin integration
36
35
  define_method :validate do
37
36
  super()
38
- return unless send(:"#{name}_attacher").respond_to?(:errors)
39
-
40
- send(:"#{name}_attacher").errors.each do |message|
41
- errors.add(name, *message)
42
- end
37
+ send(:"#{name}_attacher").send(:sequel_validate)
43
38
  end
44
39
  end
45
40
 
46
41
  if shrine_class.opts[:sequel][:hooks]
47
42
  define_method :before_save do
48
43
  super()
49
- if send(:"#{name}_attacher").changed?
50
- send(:"#{name}_attacher").save
51
- end
44
+ send(:"#{name}_attacher").send(:sequel_before_save)
52
45
  end
53
46
 
54
47
  define_method :after_save do
55
48
  super()
56
- if send(:"#{name}_attacher").changed?
57
- db.after_commit do
58
- send(:"#{name}_attacher").finalize
59
- send(:"#{name}_attacher").persist
60
- end
61
- end
49
+ send(:"#{name}_attacher").send(:sequel_after_save)
62
50
  end
63
51
 
64
52
  define_method :after_destroy do
65
53
  super()
66
- if send(:"#{name}_attacher").attached?
67
- db.after_commit do
68
- send(:"#{name}_attacher").destroy_attached
69
- end
70
- end
54
+ send(:"#{name}_attacher").send(:sequel_after_destroy)
71
55
  end
72
56
  end
73
57
 
@@ -90,6 +74,41 @@ class Shrine
90
74
  module AttacherMethods
91
75
  private
92
76
 
77
+ # Adds file validation errors to the model. Called on model validation.
78
+ def sequel_validate
79
+ return unless respond_to?(:errors)
80
+
81
+ errors.each do |message|
82
+ record.errors.add(name, *message)
83
+ end
84
+ end
85
+
86
+ # Calls Attacher#save. Called before model save.
87
+ def sequel_before_save
88
+ return unless changed?
89
+
90
+ save
91
+ end
92
+
93
+ # Finalizes attachment and persists changes. Called after model save.
94
+ def sequel_after_save
95
+ return unless changed?
96
+
97
+ record.db.after_commit do
98
+ finalize
99
+ persist
100
+ end
101
+ end
102
+
103
+ # Deletes attached files. Called after model destroy.
104
+ def sequel_after_destroy
105
+ return unless attached?
106
+
107
+ record.db.after_commit do
108
+ destroy_attached
109
+ end
110
+ end
111
+
93
112
  # Saves changes to the model instance, skipping validations. Used by
94
113
  # the _persistence plugin.
95
114
  def sequel_persist
@@ -2,6 +2,9 @@
2
2
 
3
3
  class Shrine
4
4
  module Plugins
5
+ # Documentation lives in [doc/plugins/validation.md] on GitHub.
6
+ #
7
+ # [doc/plugins/validation.md]: https://github.com/shrinerb/shrine/blob/master/doc/plugins/validation.md
5
8
  module Validation
6
9
  module AttacherClassMethods
7
10
  # Block that is executed in context of Shrine::Attacher during
@@ -15,6 +15,9 @@ class Shrine
15
15
  class S3
16
16
  attr_reader :client, :bucket, :prefix, :upload_options, :signer, :public
17
17
 
18
+ MAX_MULTIPART_PARTS = 10_000
19
+ MIN_PART_SIZE = 5*1024*1024
20
+
18
21
  MULTIPART_THRESHOLD = { upload: 15*1024*1024, copy: 100*1024*1024 }
19
22
 
20
23
  # Initializes a storage for uploading to S3. All options are forwarded to
@@ -242,12 +245,24 @@ class Shrine
242
245
  if io.respond_to?(:size) && io.size && io.size <= @multipart_threshold[:upload]
243
246
  object(id).put(body: io, **options)
244
247
  else # multipart upload
245
- object(id).upload_stream(**options) do |write_stream|
248
+ object(id).upload_stream(part_size: part_size(io), **options) do |write_stream|
246
249
  IO.copy_stream(io, write_stream)
247
250
  end
248
251
  end
249
252
  end
250
253
 
254
+ # Determins the part size that should be used when uploading the given IO
255
+ # object via multipart upload.
256
+ def part_size(io)
257
+ return unless io.respond_to?(:size) && io.size
258
+
259
+ if io.size <= MIN_PART_SIZE * MAX_MULTIPART_PARTS # <= 50 GB
260
+ MIN_PART_SIZE
261
+ else # > 50 GB
262
+ (io.size.to_f / MAX_MULTIPART_PARTS).ceil
263
+ end
264
+ end
265
+
251
266
  # Aws::S3::Object#get doesn't allow us to get the content length of the
252
267
  # object before the content is downloaded, so we hack our way around it.
253
268
  def get_object(object, params)
@@ -215,6 +215,7 @@ class Shrine
215
215
  data
216
216
  end
217
217
 
218
+ # Returns serializable hash representation of the uploaded file.
218
219
  def data
219
220
  { "id" => id, "storage" => storage_key.to_s, "metadata" => metadata }
220
221
  end
@@ -9,7 +9,7 @@ class Shrine
9
9
  MAJOR = 3
10
10
  MINOR = 0
11
11
  TINY = 0
12
- PRE = "beta2"
12
+ PRE = "beta3"
13
13
 
14
14
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
15
15
  end
@@ -26,7 +26,7 @@ direct uploads for fully asynchronous user experience.
26
26
  "bug_tracker_uri" => "https://github.com/shrinerb/shrine/issues",
27
27
  "changelog_uri" => "https://github.com/shrinerb/shrine/blob/master/CHANGELOG.md",
28
28
  "documentation_uri" => "https://shrinerb.com",
29
- "mailing_list_uri" => "https://groups.google.com/forum/#!forum/ruby-shrine",
29
+ "mailing_list_uri" => "https://discourse.shrinerb.com",
30
30
  "source_code_uri" => "https://github.com/shrinerb/shrine",
31
31
  }
32
32
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shrine
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta2
4
+ version: 3.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Janko Marohnić
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-09-11 00:00:00.000000000 Z
11
+ date: 2019-09-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: down
@@ -367,14 +367,15 @@ files:
367
367
  - doc/advantages.md
368
368
  - doc/attacher.md
369
369
  - doc/carrierwave.md
370
+ - doc/changing_derivatives.md
370
371
  - doc/changing_location.md
372
+ - doc/changing_storage.md
371
373
  - doc/creating_persistence_plugins.md
372
374
  - doc/creating_plugins.md
373
375
  - doc/creating_storages.md
374
376
  - doc/design.md
375
377
  - doc/direct_s3.md
376
378
  - doc/metadata.md
377
- - doc/migrating_storage.md
378
379
  - doc/multiple_files.md
379
380
  - doc/paperclip.md
380
381
  - doc/plugins/activerecord.md
@@ -393,6 +394,7 @@ files:
393
394
  - doc/plugins/download_endpoint.md
394
395
  - doc/plugins/dynamic_storage.md
395
396
  - doc/plugins/entity.md
397
+ - doc/plugins/form_assign.md
396
398
  - doc/plugins/included.md
397
399
  - doc/plugins/infer_extension.md
398
400
  - doc/plugins/instrumentation.md
@@ -401,6 +403,7 @@ files:
401
403
  - doc/plugins/mirroring.md
402
404
  - doc/plugins/model.md
403
405
  - doc/plugins/module_include.md
406
+ - doc/plugins/multi_cache.md
404
407
  - doc/plugins/persistence.md
405
408
  - doc/plugins/presign_endpoint.md
406
409
  - doc/plugins/pretty_location.md
@@ -420,11 +423,11 @@ files:
420
423
  - doc/plugins/upload_endpoint.md
421
424
  - doc/plugins/upload_options.md
422
425
  - doc/plugins/url_options.md
426
+ - doc/plugins/validation.md
423
427
  - doc/plugins/validation_helpers.md
424
428
  - doc/plugins/versions.md
425
429
  - doc/processing.md
426
430
  - doc/refile.md
427
- - doc/regenerating_versions.md
428
431
  - doc/release_notes/1.0.0.md
429
432
  - doc/release_notes/1.1.0.md
430
433
  - doc/release_notes/1.2.0.md
@@ -458,11 +461,13 @@ files:
458
461
  - doc/release_notes/2.7.0.md
459
462
  - doc/release_notes/2.8.0.md
460
463
  - doc/release_notes/2.9.0.md
464
+ - doc/release_notes/3.0.0.md
461
465
  - doc/retrieving_uploads.md
462
466
  - doc/securing_uploads.md
463
467
  - doc/storage/file_system.md
464
468
  - doc/storage/s3.md
465
469
  - doc/testing.md
470
+ - doc/upgrading_to_3.md
466
471
  - doc/validation.md
467
472
  - lib/shrine.rb
468
473
  - lib/shrine/attacher.rb
@@ -473,7 +478,6 @@ files:
473
478
  - lib/shrine/plugins/activerecord.rb
474
479
  - lib/shrine/plugins/add_metadata.rb
475
480
  - lib/shrine/plugins/atomic_helpers.rb
476
- - lib/shrine/plugins/attacher_options.rb
477
481
  - lib/shrine/plugins/backgrounding.rb
478
482
  - lib/shrine/plugins/cached_attachment_data.rb
479
483
  - lib/shrine/plugins/column.rb
@@ -497,6 +501,7 @@ files:
497
501
  - lib/shrine/plugins/mirroring.rb
498
502
  - lib/shrine/plugins/model.rb
499
503
  - lib/shrine/plugins/module_include.rb
504
+ - lib/shrine/plugins/multi_cache.rb
500
505
  - lib/shrine/plugins/presign_endpoint.rb
501
506
  - lib/shrine/plugins/pretty_location.rb
502
507
  - lib/shrine/plugins/processing.rb
@@ -532,7 +537,7 @@ metadata:
532
537
  bug_tracker_uri: https://github.com/shrinerb/shrine/issues
533
538
  changelog_uri: https://github.com/shrinerb/shrine/blob/master/CHANGELOG.md
534
539
  documentation_uri: https://shrinerb.com
535
- mailing_list_uri: https://groups.google.com/forum/#!forum/ruby-shrine
540
+ mailing_list_uri: https://discourse.shrinerb.com
536
541
  source_code_uri: https://github.com/shrinerb/shrine
537
542
  post_install_message:
538
543
  rdoc_options: []
@@ -549,7 +554,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
549
554
  - !ruby/object:Gem::Version
550
555
  version: 1.3.1
551
556
  requirements: []
552
- rubygems_version: 3.0.1
557
+ rubygems_version: 3.0.3
553
558
  signing_key:
554
559
  specification_version: 4
555
560
  summary: Toolkit for file attachments in Ruby applications
@@ -1,76 +0,0 @@
1
- # Migrating to Different Storage
2
-
3
- While your application is live in production and performing uploads, it may
4
- happen that you decide you want to change your storage (the `:store`). Shrine
5
- by design allows you to do that easily, with zero downtime, by deploying the
6
- change in 2 phases.
7
-
8
- ## Phase 1: Changing the storage
9
-
10
- The first stage, add the desired storage to your registry, and make it your
11
- current store (let's say that you're migrating from FileSystem to S3):
12
-
13
- ```rb
14
- Shrine.storages = {
15
- cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
16
- store: Shrine::Storage::FileSystem.new("public", prefix: "uploads"),
17
- new_store: Shrine::Storage::S3.new(**s3_options),
18
- }
19
-
20
- Shrine.plugin :default_storage, store: :new_store
21
- ```
22
-
23
- This will make already uploaded files stay uploaded on `:store`, and all new
24
- files will be uploaded to `:new_store`.
25
-
26
- ## Phase 2: Copying existing files
27
-
28
- After you've deployed the previous change, it's time to copy all the existing
29
- files to the new storage, and update the records. This is how you can do it
30
- if you're using Sequel:
31
-
32
- ```rb
33
- User.paged_each do |user|
34
- if (attacher = user.avatar_attacher).stored?
35
- uploaded_file = attacher.store!(user.avatar)
36
- attacher.swap(uploaded_file)
37
- end
38
- end
39
-
40
- # Repeat for all other attachments and models
41
- ```
42
-
43
- Now your uploaded files are successfully copied to the new storage, so you
44
- should be able to safely delete the old one.
45
-
46
- ## Phase 3 and 4: Renaming new storage (optional)
47
-
48
- The uploads will now be happening on the right storage, but if you would rather
49
- rename `:new_store` back to `:store`, you can do two more phases. **First** you
50
- need to deploy aliasing `:new_store` to `:store` (and make the default storage
51
- be `:store` again):
52
-
53
- ```rb
54
- Shrine.storages = {
55
- cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"),
56
- store: Shrine::Storage::S3.new(**s3_options),
57
- }
58
-
59
- Shrine.storages[:new_store] = Shrine.storages[:store]
60
- ```
61
-
62
- **Second**, you should rename the storage names on existing records. With
63
- Sequel it would be something like:
64
-
65
- ```rb
66
- User.paged_each do |user|
67
- if user.avatar_attacher.stored?
68
- user.update(avatar_data: user.avatar_data.gsub('"new_store"', '"store"'))
69
- end
70
- end
71
-
72
- # Repeat for all other attachments and models
73
- ```
74
-
75
- Now everything should be in order and you should be able to remove the
76
- `:new_store` alias.
@@ -1,143 +0,0 @@
1
- # Reprocessing Versions
2
-
3
- While your app is serving uploads in production, you may realize that you want
4
- to change how your attachment's versions are generated. This means that, in
5
- addition to changing your processing code, you also need to reprocess the
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
-
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
- # ...
19
-
20
- process(:store) do |io, context|
21
- thumbnail = process_thumbnail(io.download)
22
- {original: io, thumbnail: thumbnail}
23
- end
24
- end
25
- ```
26
- ```rb
27
- # In your views add the version name to all <attachment>_url calls.
28
- user.avatar_url(:thumb)
29
- ```
30
-
31
- Note that you should deploy both of these changes at once, because the
32
- `<attachment>_url` method will fail if there are versions generated but no
33
- version name was passed in. If a version name was passed in but versions aren't
34
- generated yet (which will be the case here), it will just return the
35
- unprocessed file URL.
36
-
37
- Afterwards you should run a script which reprocesses the versions for existing
38
- files:
39
-
40
- ```rb
41
- User.paged_each do |user|
42
- attacher, attachment = user.avatar_attacher, user.avatar
43
- if attacher.stored? && !attachment.is_a?(Hash)
44
- file = some_processing(attachment.download)
45
- thumb = attacher.store!(file, version: :thumb)
46
- attacher.swap({original: attachment, thumb: thumb})
47
- end
48
- end
49
- ```
50
-
51
- ## Reprocessing a single version
52
-
53
- The simplest scenario is where you need to regenerate an existing version.
54
- First you need to change and deploy your updated processing code, and
55
- afterwards you can run a script like this on your production database:
56
-
57
- ```rb
58
- User.paged_each do |user|
59
- attacher, attachment = user.avatar_attacher, user.avatar
60
- if attacher.stored?
61
- file = some_processing(attachment[:original].download)
62
- thumb = attachment[:thumb].replace(thumb)
63
- attacher.swap(attachment.merge(thumb: thumb))
64
- end
65
- end
66
- ```
67
-
68
- ### Adding a new version
69
-
70
- When adding a new version to a production app, first add it to the list and
71
- update your processing code to generate it, and deploy it:
72
-
73
- ```rb
74
- class ImageUploader < Shrine
75
- # ...
76
-
77
- process(:store) do |io, context|
78
- # ...
79
- new = some_processing(io.download, *args)
80
- {small: small, medium: medium, new: new} # we generate the ":new" version
81
- end
82
- end
83
- ```
84
-
85
- After you've deployed this change, you should run a script that will generate
86
- the new version for all existing records:
87
-
88
- ```rb
89
- User.paged_each do |user|
90
- attacher, attachment = user.avatar_attacher, user.avatar
91
- if attacher.stored? && !attachment[:new]
92
- file = some_processing(attachment[:original].download, *args)
93
- new = attacher.store!(file, version: :new)
94
- attacher.swap(attachment.merge(new: new))
95
- end
96
- end
97
- ```
98
-
99
- After you've run this script on your production database, all records should
100
- have the new version, and now you should be able to safely update your app to
101
- use it.
102
-
103
- ### Removing a version
104
-
105
- Before removing a version, you first need to update your processing to not
106
- generate it (but keep the version name in the list), as well as update your app
107
- not to use the new version, and deploy that code. After you've done that, you
108
- can run a script which removes that version:
109
-
110
- ```rb
111
- old_versions = []
112
-
113
- User.paged_each do |user|
114
- attacher, attachment = user.avatar_attacher, user.avatar
115
- if attacher.stored? && attachment[:old_version]
116
- old_versions << attachment.delete(:old_version)
117
- attacher.swap(attachment)
118
- end
119
- end
120
-
121
- if old_versions.any?
122
- uploader = old_versions.first.uploader
123
- uploader.delete(old_versions)
124
- end
125
- ```
126
-
127
- After the script has finished, you should be able to safely remove the version
128
- name from the list.
129
-
130
- ## Reprocessing all versions
131
-
132
- If you made a lot of changes to versions, it might make sense to simply
133
- regenerate all versions. After you've deployed the change in processing, you
134
- can run a script which updates existing records:
135
-
136
- ```rb
137
- User.paged_each do |user|
138
- if user.avatar_attacher.stored?
139
- # assuming your largest version is named ":original"
140
- user.update(avatar: user.avatar[:original])
141
- end
142
- end
143
- ```