saviour 0.5.11 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|