saviour 0.5.11 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +67 -44
- data/lib/saviour/base_uploader.rb +12 -0
- data/lib/saviour/file.rb +8 -8
- data/lib/saviour/integrator.rb +3 -2
- data/lib/saviour/life_cycle.rb +11 -10
- data/lib/saviour/read_only_file.rb +5 -4
- data/lib/saviour/version.rb +1 -1
- data/spec/feature/storage_overriding_spec.rb +44 -0
- data/spec/models/base_uploader_spec.rb +14 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f9031d0f7f9624571c39792d2953c5eb11f50f7edd7f4c7117bf7951166c43b8
|
4
|
+
data.tar.gz: 3ef5fac874948c7b1a330b756c25c88c357108e08237ab940f4e0cb0d71ff55e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa7d00ae67fe68da25c4eb9652813b9a6f0d751a0ddf9729efe43c6dcfd5af2ffa8b7db14e9bb9dcc06b8c5a4cf8bcea503c3394abf2d5e82e8213b6d0f3589f
|
7
|
+
data.tar.gz: 348f05edc2b3f0d5185412f741e5d9aedecfdff44430833aaed0ab7221a70fc6c834d4489d0ff8c1bd01c06c20fb1bd56a3b996b6d3c75cdd15471c911159636
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
Saviour is a tool to help you manage files attached to Active Record models. It tries to be minimal about the
|
8
8
|
use cases it covers, but with a deep and complete coverage on the ones it does. For example, it offers
|
9
|
-
no support for image manipulation, but it does implement dirty tracking and transactional-aware behavior.
|
9
|
+
no support for image manipulation, but it does implement dirty tracking and transactional-aware behavior.
|
10
10
|
|
11
11
|
It also tries to have a flexible design, so that additional features can be added by the user on top of it.
|
12
12
|
You can see an example of such typical features on the [FAQ section at the end of this document](#faq).
|
@@ -24,15 +24,15 @@ that wants to be solved.
|
|
24
24
|
They offer a complete out-of-the-box solution that covers many different needs:
|
25
25
|
image management, caching of files for seamless integration with html forms, direct uploads to s3, metadata
|
26
26
|
extraction, background jobs integration or support for different ORMs are some of the features you can find on
|
27
|
-
those libraries.
|
27
|
+
those libraries.
|
28
28
|
|
29
29
|
If you need those functionalities and they suit your needs, they can be perfect solutions for you.
|
30
30
|
|
31
31
|
The counterpart, however, is that they have more dependencies and, as they cover a broader spectrum of
|
32
|
-
use cases, they tend to impose more conventions that are expected to be followed as is. If you don't want,
|
32
|
+
use cases, they tend to impose more conventions that are expected to be followed as is. If you don't want,
|
33
33
|
or can't follow some of those conventions then you're out of luck.
|
34
34
|
|
35
|
-
Saviour provides a battle-tested infrastructure for storing files following an AR model
|
35
|
+
Saviour provides a battle-tested infrastructure for storing files following an AR model
|
36
36
|
life-cycle which can be easily extended to suit your custom needs.
|
37
37
|
|
38
38
|
|
@@ -53,6 +53,7 @@ And then execute:
|
|
53
53
|
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
54
54
|
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
55
55
|
|
56
|
+
|
56
57
|
- [Quick start](#quick-start)
|
57
58
|
- [General Usage](#general-usage)
|
58
59
|
- [Api on attachment](#api-on-attachment)
|
@@ -62,6 +63,7 @@ And then execute:
|
|
62
63
|
- [S3 Storage](#s3-storage)
|
63
64
|
- [Uploader classes](#uploader-classes)
|
64
65
|
- [store_dir](#store_dir)
|
66
|
+
- [with_storage](#with_storage)
|
65
67
|
- [Processors](#processors)
|
66
68
|
- [halt_process](#halt_process)
|
67
69
|
- [Versions](#versions)
|
@@ -160,7 +162,7 @@ The filename given to the file will be obtained by following this process:
|
|
160
162
|
|
161
163
|
If none of that works, a random filename will be assigned.
|
162
164
|
|
163
|
-
The actual storing of the file and any possible related processing (more on this [later](#processors)) will
|
165
|
+
The actual storing of the file and any possible related processing (more on this [later](#processors)) will
|
164
166
|
happen on after save, not on assignation. You can assign and re-assign different values to an attachment at no
|
165
167
|
cost.
|
166
168
|
|
@@ -180,7 +182,7 @@ Given the previous example of a User with an avatar attachment, the following me
|
|
180
182
|
- `user.avatar.filename`: Returns the filename of the stored file.
|
181
183
|
- `user.avatar.persisted_path`: If persisted, returns the path of the file as stored in the storage, otherwise nil. It's the same as the db column value.
|
182
184
|
- `user.avatar.changed?`: Returns true/false if the attachment has been assigned but not yet saved.
|
183
|
-
|
185
|
+
|
184
186
|
Usage example:
|
185
187
|
|
186
188
|
```ruby
|
@@ -218,7 +220,7 @@ user.avatar.with_copy # => yields a tempfile with the image
|
|
218
220
|
user.avatar.read # => bytecontents
|
219
221
|
```
|
220
222
|
|
221
|
-
|
223
|
+
|
222
224
|
#### Additional api on the model
|
223
225
|
|
224
226
|
When you declare an attachment in an AR model, the model is extended with:
|
@@ -331,7 +333,7 @@ Those options will be forwarded directly to aws-sdk, you can see the complete re
|
|
331
333
|
|
332
334
|
https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#put_object-instance_method
|
333
335
|
|
334
|
-
Currently, there's no support for different create options on a per-file basis. All stored files will be created
|
336
|
+
Currently, there's no support for different create options on a per-file basis. All stored files will be created
|
335
337
|
using the same options. If you want a public access on those files, you can make them public with a general
|
336
338
|
rule at the bucket level or using the `acl` create option:
|
337
339
|
|
@@ -372,7 +374,7 @@ syntax is usually more convenient if you don't have a lot of code in your upload
|
|
372
374
|
```ruby
|
373
375
|
class Post < ApplicationRecord
|
374
376
|
include Saviour::Model
|
375
|
-
|
377
|
+
|
376
378
|
attach_file :image do
|
377
379
|
store_dir { "uploads/posts/images/#{model.id}/" }
|
378
380
|
end
|
@@ -392,16 +394,16 @@ At runtime the model is available as `model`, and the name of the attachment as
|
|
392
394
|
```ruby
|
393
395
|
class PostImageUploader < Saviour::BaseUploader
|
394
396
|
store_dir { "uploads/posts/images/#{model.id}/" }
|
395
|
-
|
397
|
+
|
396
398
|
# or
|
397
399
|
store_dir { "uploads/posts/#{model.id}/#{attached_as}" }
|
398
|
-
|
400
|
+
|
399
401
|
# or more generic
|
400
402
|
store_dir { "uploads/#{model.class.name.parameterize}/#{model.id}/#{attached_as}" }
|
401
|
-
|
403
|
+
|
402
404
|
# or with a method
|
403
405
|
store_dir :calculate_dir
|
404
|
-
|
406
|
+
|
405
407
|
def calculate_dir
|
406
408
|
"uploads/posts/images/#{model.id}/"
|
407
409
|
end
|
@@ -417,9 +419,31 @@ the store dir is a common approach to ensure there will be no collisions. Other
|
|
417
419
|
random token generation.
|
418
420
|
|
419
421
|
|
422
|
+
#### with_storage
|
423
|
+
|
424
|
+
This method allows you to override storage on a per-attachment basis.
|
425
|
+
It could be useful if generally all attachments in your app are using storage declared
|
426
|
+
in `Saviour::Config` but you need more control for some of them.
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
class Post < ApplicationRecord
|
430
|
+
include Saviour::Model
|
431
|
+
|
432
|
+
attach_file :user_image do
|
433
|
+
store_dir { "uploads/posts/images/#{model.id}/" }
|
434
|
+
end
|
435
|
+
|
436
|
+
attach_file :admin_image do
|
437
|
+
store_dir { "uploads/posts/images/#{model.id}/" }
|
438
|
+
with_storage Saviour::S3Storage.new(bucket: "private-bucket", ...)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
```
|
442
|
+
|
443
|
+
|
420
444
|
#### Processors
|
421
445
|
|
422
|
-
Processors are methods (or lambdas) that receive the contents of the file being saved and its filename,
|
446
|
+
Processors are methods (or lambdas) that receive the contents of the file being saved and its filename,
|
423
447
|
and in turn return file contents and filename. You can use them to change both values, for example:
|
424
448
|
|
425
449
|
```ruby
|
@@ -428,8 +452,8 @@ class PostImageUploader < Saviour::BaseUploader
|
|
428
452
|
|
429
453
|
process do |contents, filename|
|
430
454
|
new_filename = "#{Digest::MD5.hexdigest(contents)}-#{filename}"
|
431
|
-
new_contents = Zlib::Deflate.deflate(contents)
|
432
|
-
|
455
|
+
new_contents = Zlib::Deflate.deflate(contents)
|
456
|
+
|
433
457
|
[new_contents, new_filename]
|
434
458
|
end
|
435
459
|
end
|
@@ -445,7 +469,7 @@ and share them via a ruby module or via inheritance. In this form, you can pass
|
|
445
469
|
module ProcessorsHelpers
|
446
470
|
def resize(contents, filename, width:, height:)
|
447
471
|
new_contents = SomeImageManipulationImplementation.new(contents).resize_to(width, height)
|
448
|
-
|
472
|
+
|
449
473
|
[new_contents, filename]
|
450
474
|
end
|
451
475
|
end
|
@@ -454,7 +478,7 @@ class PostImageUploader < Saviour::BaseUploader
|
|
454
478
|
include ProcessorsHelpers
|
455
479
|
store_dir { "uploads/posts/#{model.id}/#{attached_as}" }
|
456
480
|
|
457
|
-
process :resize, width: 100, height: 100
|
481
|
+
process :resize, width: 100, height: 100
|
458
482
|
end
|
459
483
|
```
|
460
484
|
|
@@ -481,8 +505,8 @@ class PostImageUploader < Saviour::BaseUploader
|
|
481
505
|
|
482
506
|
process_with_file do |file, filename|
|
483
507
|
`convert -thumbnail 100x100^ #{Shellwords.escape(file.path)}`
|
484
|
-
|
485
|
-
[file, filename]
|
508
|
+
|
509
|
+
[file, filename]
|
486
510
|
end
|
487
511
|
end
|
488
512
|
```
|
@@ -495,7 +519,7 @@ return a new one. If you return a different file instance, you're expected to cl
|
|
495
519
|
You can mix `process` with `process_with_file` but you should try to avoid it, as it will be a performance penalty
|
496
520
|
having to convert between formats.
|
497
521
|
|
498
|
-
Also, even if there's just one `process`, the whole contents of the file will be loaded into memory. Avoid that usage
|
522
|
+
Also, even if there's just one `process`, the whole contents of the file will be loaded into memory. Avoid that usage
|
499
523
|
if you're conservative about memory usage or take care of restricting the allowed file size you can work with on
|
500
524
|
any file upload you accept across your application.
|
501
525
|
|
@@ -529,7 +553,7 @@ end
|
|
529
553
|
### Versions
|
530
554
|
|
531
555
|
Versions is a common and popular feature on other file management libraries, however, they're usually implemented
|
532
|
-
in a way that makes the "versioned" attachments behave differently than normal attachments.
|
556
|
+
in a way that makes the "versioned" attachments behave differently than normal attachments.
|
533
557
|
|
534
558
|
Saviour takes another approach: there's no such concept as a "versioned attachment", there're only attachments.
|
535
559
|
The way this works with Saviour is by making one attachment "follow" another one, so that whatever is assigned on
|
@@ -553,8 +577,8 @@ class Post < ApplicationRecord
|
|
553
577
|
end
|
554
578
|
```
|
555
579
|
|
556
|
-
Using the `follow: :image` syntax you declare that the `image_thumb` attachment has to be automatically assigned
|
557
|
-
to the same contents as `image` every time `image` is assigned.
|
580
|
+
Using the `follow: :image` syntax you declare that the `image_thumb` attachment has to be automatically assigned
|
581
|
+
to the same contents as `image` every time `image` is assigned.
|
558
582
|
|
559
583
|
The `:dependent` part is mandatory and indicates if the `image_thumb` attachment has to be removed when the
|
560
584
|
`image` is removed (with `dependent: :destroy`) or not (with `dependent: :ignore`).
|
@@ -839,12 +863,12 @@ swap storages on the fly:
|
|
839
863
|
|
840
864
|
```ruby
|
841
865
|
# config/env/test.rb
|
842
|
-
Saviour::Config.storage = ::LocalStorage.new(...)
|
866
|
+
Saviour::Config.storage = ::LocalStorage.new(...)
|
843
867
|
|
844
868
|
# spec/support/saviour.rb
|
845
869
|
module S3Stub
|
846
870
|
mattr_accessor :storage
|
847
|
-
|
871
|
+
|
848
872
|
self.storage = Saviour::S3Storage.new(...)
|
849
873
|
end
|
850
874
|
|
@@ -859,7 +883,7 @@ RSpec.configure do |config|
|
|
859
883
|
end
|
860
884
|
end
|
861
885
|
|
862
|
-
it "some regular test" do
|
886
|
+
it "some regular test" do
|
863
887
|
# local storage here
|
864
888
|
end
|
865
889
|
|
@@ -877,9 +901,9 @@ Saviour::Config.processing_enabled = false
|
|
877
901
|
```
|
878
902
|
|
879
903
|
This will skip all processors, so you'll avoid image manipulations, etc. If you have a more complex application
|
880
|
-
and you can't disable all processors, but still would want to skip only the ones related to image manipulation,
|
881
|
-
I would recommend to delegate image manipulation to a specialized class and then stub all of their methods.
|
882
|
-
|
904
|
+
and you can't disable all processors, but still would want to skip only the ones related to image manipulation,
|
905
|
+
I would recommend to delegate image manipulation to a specialized class and then stub all of their methods.
|
906
|
+
|
883
907
|
|
884
908
|
### Sources: url and string
|
885
909
|
|
@@ -948,15 +972,15 @@ the changes and work from there.
|
|
948
972
|
|
949
973
|
Since Saviour is by design model-based, there may be use cases when this becomes a performance issue, for example:
|
950
974
|
|
951
|
-
##### Bypass example: Nested Cloning
|
975
|
+
##### Bypass example: Nested Cloning
|
952
976
|
|
953
|
-
Say that you have a model `Post` that has many `Image`s, and you're working with S3. `Post` has 3 attachments and
|
954
|
-
`Image` has 2 attachments. If you want to do a feature to "clone" a post, a simple implementation would be to
|
977
|
+
Say that you have a model `Post` that has many `Image`s, and you're working with S3. `Post` has 3 attachments and
|
978
|
+
`Image` has 2 attachments. If you want to do a feature to "clone" a post, a simple implementation would be to
|
955
979
|
basically `dup` the instances and save them.
|
956
980
|
|
957
981
|
However, for a post with many related images, this would represent many api calls and roundtrips to download
|
958
982
|
contents and re-upload them. It would be a lot faster to work with s3 directly, issue api calls to copy the
|
959
|
-
files inside s3 directly (no download/upload, and even you could issue those api calls concurrently),
|
983
|
+
files inside s3 directly (no download/upload, and even you could issue those api calls concurrently),
|
960
984
|
and then assign manually crafted paths directly to the new instances.
|
961
985
|
|
962
986
|
|
@@ -969,7 +993,7 @@ extract common behaviors into a module:
|
|
969
993
|
|
970
994
|
```ruby
|
971
995
|
module FileAttachmentHelpers
|
972
|
-
# Shared processors
|
996
|
+
# Shared processors
|
973
997
|
end
|
974
998
|
|
975
999
|
module FileAttachment
|
@@ -1016,9 +1040,9 @@ end
|
|
1016
1040
|
|
1017
1041
|
class Post < ApplicationRecord
|
1018
1042
|
include FileAttachment
|
1019
|
-
|
1043
|
+
|
1020
1044
|
attach_file_with_defaults :cover # Nothing extra needed
|
1021
|
-
|
1045
|
+
|
1022
1046
|
attach_file_with_defaults :image do
|
1023
1047
|
process_with_file :some_extra_thing
|
1024
1048
|
end
|
@@ -1046,7 +1070,7 @@ module FileAttachment
|
|
1046
1070
|
def attach_file_with_defaults(*args, &block)
|
1047
1071
|
attached_as = args[0]
|
1048
1072
|
# ...
|
1049
|
-
|
1073
|
+
|
1050
1074
|
define_method("remove_#{attached_as}") do
|
1051
1075
|
instance_variable_get("@remove_#{attached_as}")
|
1052
1076
|
end
|
@@ -1056,7 +1080,7 @@ module FileAttachment
|
|
1056
1080
|
define_method("remove_#{attached_as}=") do |value|
|
1057
1081
|
instance_variable_set "@remove_#{attached_as}", ActiveRecord::Type::Boolean.new.cast(value)
|
1058
1082
|
end
|
1059
|
-
|
1083
|
+
|
1060
1084
|
before_update do
|
1061
1085
|
send("remove_#{attached_as}!") if send("remove_#{attached_as}?")
|
1062
1086
|
end
|
@@ -1078,7 +1102,7 @@ a.update_attributes(remove_image: "t")
|
|
1078
1102
|
|
1079
1103
|
### How to extract metadata from files
|
1080
1104
|
|
1081
|
-
You can use processors to accomplish this. Just be aware that processors run concurrently, so if you want to
|
1105
|
+
You can use processors to accomplish this. Just be aware that processors run concurrently, so if you want to
|
1082
1106
|
persist you extracted information in the database probably you'll want to use `stash`, see [the section
|
1083
1107
|
about stash feature for examples](#stash).
|
1084
1108
|
|
@@ -1097,8 +1121,8 @@ enqueuing of the job when you detect a change in the attachment:
|
|
1097
1121
|
class Post < ApplicationRecord
|
1098
1122
|
include Saviour::Model
|
1099
1123
|
attach_file :image
|
1100
|
-
|
1101
|
-
before_save do
|
1124
|
+
|
1125
|
+
before_save do
|
1102
1126
|
if image_changed?
|
1103
1127
|
# On after commit, enqueue the job
|
1104
1128
|
end
|
@@ -1112,7 +1136,7 @@ The job then should take the model and the attachment to process and run the pro
|
|
1112
1136
|
a = Post.find(42)
|
1113
1137
|
a.image.with_copy do |f|
|
1114
1138
|
# manipulate f as desired
|
1115
|
-
a.update_attributes! image: f
|
1139
|
+
a.update_attributes! image: f
|
1116
1140
|
end
|
1117
1141
|
```
|
1118
1142
|
|
@@ -1162,4 +1186,3 @@ You can use a processor like this one:
|
|
1162
1186
|
## License
|
1163
1187
|
|
1164
1188
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
1165
|
-
|
@@ -64,6 +64,10 @@ module Saviour
|
|
64
64
|
@store_dir ||= Uploader::StoreDirExtractor.new(self).store_dir
|
65
65
|
end
|
66
66
|
|
67
|
+
def storage
|
68
|
+
self.class.storage
|
69
|
+
end
|
70
|
+
|
67
71
|
class << self
|
68
72
|
def store_dirs
|
69
73
|
@store_dirs ||= []
|
@@ -73,6 +77,10 @@ module Saviour
|
|
73
77
|
@processors ||= []
|
74
78
|
end
|
75
79
|
|
80
|
+
def storage
|
81
|
+
@storage ||= Config.storage
|
82
|
+
end
|
83
|
+
|
76
84
|
def process(name = nil, opts = {}, type = :memory, &block)
|
77
85
|
if block_given?
|
78
86
|
processors.push(method_or_block: name || block, type: type)
|
@@ -89,6 +97,10 @@ module Saviour
|
|
89
97
|
store_dirs.push(name || block)
|
90
98
|
end
|
91
99
|
|
100
|
+
def with_storage(storage)
|
101
|
+
@storage = storage
|
102
|
+
end
|
103
|
+
|
92
104
|
def after_upload(&block)
|
93
105
|
after_upload_hooks.push(block)
|
94
106
|
end
|
data/lib/saviour/file.rb
CHANGED
@@ -11,17 +11,17 @@ module Saviour
|
|
11
11
|
@persisted_path = persisted_path
|
12
12
|
|
13
13
|
if persisted_path
|
14
|
-
@model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path))
|
14
|
+
@model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @uploader_klass))
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
18
|
def exists?
|
19
|
-
persisted? &&
|
19
|
+
persisted? && @uploader_klass.storage.exists?(@persisted_path)
|
20
20
|
end
|
21
21
|
|
22
22
|
def read
|
23
23
|
return nil unless persisted?
|
24
|
-
|
24
|
+
@uploader_klass.storage.read(@persisted_path)
|
25
25
|
end
|
26
26
|
|
27
27
|
def delete
|
@@ -32,7 +32,7 @@ module Saviour
|
|
32
32
|
|
33
33
|
def public_url
|
34
34
|
return nil unless persisted?
|
35
|
-
|
35
|
+
@uploader_klass.storage.public_url(@persisted_path)
|
36
36
|
end
|
37
37
|
|
38
38
|
def ==(another_file)
|
@@ -111,7 +111,7 @@ module Saviour
|
|
111
111
|
temp_file.binmode
|
112
112
|
|
113
113
|
begin
|
114
|
-
|
114
|
+
@uploader_klass.storage.read_to_file(@persisted_path, temp_file)
|
115
115
|
|
116
116
|
yield(temp_file)
|
117
117
|
ensure
|
@@ -154,13 +154,13 @@ module Saviour
|
|
154
154
|
|
155
155
|
case source_type
|
156
156
|
when :stream
|
157
|
-
|
157
|
+
@uploader_klass.storage.write(contents, path)
|
158
158
|
when :file
|
159
|
-
|
159
|
+
@uploader_klass.storage.write_from_file(contents, path)
|
160
160
|
end
|
161
161
|
|
162
162
|
@persisted_path = path
|
163
|
-
@model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path))
|
163
|
+
@model.instance_variable_set("@__uploader_#{@attached_as}_was", ReadOnlyFile.new(persisted_path, @uploader_klass))
|
164
164
|
path
|
165
165
|
end
|
166
166
|
end
|
data/lib/saviour/integrator.rb
CHANGED
@@ -96,7 +96,8 @@ module Saviour
|
|
96
96
|
send(attach_as).delete
|
97
97
|
|
98
98
|
work = proc do
|
99
|
-
|
99
|
+
file = send(attach_as)
|
100
|
+
file.uploader.storage.delete(deletion_path) if deletion_path && file.persisted_path.nil?
|
100
101
|
end
|
101
102
|
|
102
103
|
if ActiveRecord::Base.connection.current_transaction.open?
|
@@ -140,4 +141,4 @@ module Saviour
|
|
140
141
|
end
|
141
142
|
end
|
142
143
|
end
|
143
|
-
end
|
144
|
+
end
|
data/lib/saviour/life_cycle.rb
CHANGED
@@ -14,7 +14,7 @@ module Saviour
|
|
14
14
|
return unless @new_path
|
15
15
|
|
16
16
|
DbHelpers.run_after_rollback(@connection) do
|
17
|
-
|
17
|
+
uploader.storage.delete(@new_path)
|
18
18
|
end
|
19
19
|
|
20
20
|
[@column, @new_path]
|
@@ -37,14 +37,14 @@ module Saviour
|
|
37
37
|
dup_temp_path = SecureRandom.hex
|
38
38
|
|
39
39
|
dup_file = proc do
|
40
|
-
|
40
|
+
uploader.storage.cp @current_path, dup_temp_path
|
41
41
|
|
42
42
|
DbHelpers.run_after_commit(@connection) do
|
43
|
-
|
43
|
+
uploader.storage.delete dup_temp_path
|
44
44
|
end
|
45
45
|
|
46
46
|
DbHelpers.run_after_rollback(@connection) do
|
47
|
-
|
47
|
+
uploader.storage.mv dup_temp_path, @current_path
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
@@ -56,14 +56,14 @@ module Saviour
|
|
56
56
|
|
57
57
|
if @current_path && @current_path != @new_path
|
58
58
|
DbHelpers.run_after_commit(@connection) do
|
59
|
-
|
59
|
+
uploader.storage.delete(@current_path)
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
63
|
# Delete the newly uploaded file only if it's an update in a different path
|
64
64
|
if @current_path.nil? || @current_path != @new_path
|
65
65
|
DbHelpers.run_after_rollback(@connection) do
|
66
|
-
|
66
|
+
uploader.storage.delete(@new_path)
|
67
67
|
end
|
68
68
|
end
|
69
69
|
|
@@ -88,9 +88,10 @@ module Saviour
|
|
88
88
|
|
89
89
|
futures = attached_files.map do |column|
|
90
90
|
Concurrent::Future.execute(executor: pool) {
|
91
|
-
|
92
|
-
|
93
|
-
|
91
|
+
file = @model.send(column)
|
92
|
+
path = file.persisted_path
|
93
|
+
file.uploader.storage.delete(path) if path
|
94
|
+
file.delete
|
94
95
|
}
|
95
96
|
end
|
96
97
|
|
@@ -167,4 +168,4 @@ module Saviour
|
|
167
168
|
@model.class.attached_files
|
168
169
|
end
|
169
170
|
end
|
170
|
-
end
|
171
|
+
end
|
@@ -2,8 +2,9 @@ module Saviour
|
|
2
2
|
class ReadOnlyFile
|
3
3
|
attr_reader :persisted_path
|
4
4
|
|
5
|
-
def initialize(persisted_path)
|
5
|
+
def initialize(persisted_path, uploader_klass)
|
6
6
|
@persisted_path = persisted_path
|
7
|
+
@uploader_klass = uploader_klass
|
7
8
|
end
|
8
9
|
|
9
10
|
def exists?
|
@@ -12,12 +13,12 @@ module Saviour
|
|
12
13
|
|
13
14
|
def read
|
14
15
|
return nil unless persisted?
|
15
|
-
|
16
|
+
@uploader_klass.storage.read(@persisted_path)
|
16
17
|
end
|
17
18
|
|
18
19
|
def public_url
|
19
20
|
return nil unless persisted?
|
20
|
-
|
21
|
+
@uploader_klass.storage.public_url(@persisted_path)
|
21
22
|
end
|
22
23
|
alias_method :url, :public_url
|
23
24
|
|
@@ -32,4 +33,4 @@ module Saviour
|
|
32
33
|
true
|
33
34
|
end
|
34
35
|
end
|
35
|
-
end
|
36
|
+
end
|
data/lib/saviour/version.rb
CHANGED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "uploader declaration" do
|
4
|
+
let!(:default_storage) do
|
5
|
+
Saviour::LocalStorage.new(
|
6
|
+
local_prefix: @tmpdir,
|
7
|
+
public_url_prefix: "http://domain.com"
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
let!(:custom_storage) do
|
12
|
+
Saviour::LocalStorage.new(
|
13
|
+
local_prefix: @tmpdir,
|
14
|
+
public_url_prefix: "http://custom-domain.com"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
before { allow(Saviour::Config).to receive(:storage).and_return(default_storage) }
|
19
|
+
|
20
|
+
it "lets you override storage on attachment basis" do
|
21
|
+
klass = Class.new(Test) { include Saviour::Model }
|
22
|
+
kustom_storage = custom_storage
|
23
|
+
|
24
|
+
klass.attach_file(:file) do
|
25
|
+
store_dir { "/store/dir" }
|
26
|
+
end
|
27
|
+
|
28
|
+
klass.attach_file(:file_thumb) do
|
29
|
+
store_dir { "/store/dir" }
|
30
|
+
with_storage kustom_storage
|
31
|
+
end
|
32
|
+
|
33
|
+
a = klass.create!(
|
34
|
+
file: Saviour::StringSource.new("content", "houhou.txt"),
|
35
|
+
file_thumb: Saviour::StringSource.new("content", "custom_houhou.txt")
|
36
|
+
)
|
37
|
+
|
38
|
+
expect(a.file.filename).to eq "houhou.txt"
|
39
|
+
expect(a.file.url).to eq 'http://domain.com/store/dir/houhou.txt'
|
40
|
+
|
41
|
+
expect(a.file_thumb.filename).to eq "custom_houhou.txt"
|
42
|
+
expect(a.file_thumb.url).to eq 'http://custom-domain.com/store/dir/custom_houhou.txt'
|
43
|
+
end
|
44
|
+
end
|
@@ -44,6 +44,20 @@ describe Saviour::BaseUploader do
|
|
44
44
|
expect(subject.store_dirs[1].call).to eq "/my/dir/4"
|
45
45
|
end
|
46
46
|
|
47
|
+
it "uses storage from config by default" do
|
48
|
+
mocked_storage = double(:mocked_storage)
|
49
|
+
allow(Saviour::Config).to receive(:storage).and_return(mocked_storage)
|
50
|
+
|
51
|
+
expect(subject.storage).to eq mocked_storage
|
52
|
+
end
|
53
|
+
|
54
|
+
it "allows to override storage" do
|
55
|
+
custom_storage = double(:custom_storage)
|
56
|
+
|
57
|
+
subject.with_storage(custom_storage)
|
58
|
+
expect(subject.storage).to eq custom_storage
|
59
|
+
end
|
60
|
+
|
47
61
|
it "is not accessible from subclasses, works in isolation" do
|
48
62
|
subject.process :hola
|
49
63
|
expect(subject.processors[0][:method_or_block]).to eq :hola
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saviour
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Campos
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -216,6 +216,7 @@ files:
|
|
216
216
|
- spec/feature/reopens_file_at_every_process_spec.rb
|
217
217
|
- spec/feature/rewind_source_before_read_spec.rb
|
218
218
|
- spec/feature/stash_spec.rb
|
219
|
+
- spec/feature/storage_overriding_spec.rb
|
219
220
|
- spec/feature/transactional_behavior_spec.rb
|
220
221
|
- spec/feature/uploader_declaration_spec.rb
|
221
222
|
- spec/feature/validations_spec.rb
|
@@ -254,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
254
255
|
version: '0'
|
255
256
|
requirements: []
|
256
257
|
rubyforge_project:
|
257
|
-
rubygems_version: 2.7.
|
258
|
+
rubygems_version: 2.7.3
|
258
259
|
signing_key:
|
259
260
|
specification_version: 4
|
260
261
|
summary: File storage handler following active record model lifecycle
|