saviour 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +2 -2
- data/lib/saviour.rb +0 -4
- data/lib/saviour/base_uploader.rb +4 -25
- data/lib/saviour/config.rb +6 -1
- data/lib/saviour/file.rb +9 -4
- data/lib/saviour/integrator.rb +30 -36
- data/lib/saviour/life_cycle.rb +10 -25
- data/lib/saviour/model.rb +2 -2
- data/lib/saviour/s3_storage.rb +6 -4
- data/lib/saviour/string_source.rb +8 -3
- data/lib/saviour/uploader/processors_runner.rb +11 -11
- data/lib/saviour/uploader/store_dir_extractor.rb +4 -20
- data/lib/saviour/url_source.rb +14 -6
- data/lib/saviour/validator.rb +8 -10
- data/lib/saviour/version.rb +1 -1
- data/saviour.gemspec +2 -2
- data/spec/feature/allow_overriding_attached_as_method.rb +33 -0
- data/spec/feature/follow_file_spec.rb +53 -0
- data/spec/feature/rewind_source_before_read.rb +21 -0
- data/spec/feature/uploader_declaration.rb +38 -0
- data/spec/feature/validations_spec.rb +0 -25
- data/spec/models/base_uploader_spec.rb +10 -173
- data/spec/models/config_spec.rb +20 -0
- data/spec/models/file_spec.rb +11 -7
- data/spec/models/model_spec.rb +4 -84
- data/spec/models/url_source_spec.rb +0 -7
- metadata +26 -26
- data/lib/saviour/attribute_name_calculator.rb +0 -15
- data/lib/saviour/uploader/element.rb +0 -19
- data/spec/feature/versions_spec.rb +0 -185
- data/spec/models/attribute_name_calculator_spec.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc893ccb84bdea17562e885649be535589247449
|
4
|
+
data.tar.gz: b626abd923dbcde6e9a44f0a6c6e5f608d9e7f85
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 76b2b7986d9c44f006396a445ec188973a44ffc5c631a48a7b089ce3585428ad9565e29cc42d6c1153d0aaf662c0d1039abc218d0121d37b3fd7357a9fe166b8
|
7
|
+
data.tar.gz: 42e2ca1a05a2e57233e80c4f4f90909095ec7b64b8858bc40b0bf2448d951391765521d618146f115b5bb61ac648a77568ec7d25196950f3316bc30bd7b74683
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -270,12 +270,12 @@ to verify existence for every write.
|
|
270
270
|
|
271
271
|
## Source abstraction
|
272
272
|
|
273
|
-
As mentioned before, you can use `File#assign` with any object that responds to `read`. This is already the case for `::File`,
|
273
|
+
As mentioned before, you can use `File#assign` with any io like object that responds to `read` and `rewind`. This is already the case for `::File`,
|
274
274
|
`Tempfile` or `IO`. Since a file requires also a filename, however, in those cases a random filename will be assigned
|
275
275
|
(you can always set the filename using a processor later on).
|
276
276
|
|
277
277
|
Additionally, if the object responds to `#original_filename` then that will be used as a filename instead of generating
|
278
|
-
a random one.
|
278
|
+
a random one, or, if the object responds to `#path` then `File.basename(path)` will be used as a name.
|
279
279
|
|
280
280
|
You can create your own classes implementing this API to extend functionality. This library includes two of them: StringSource
|
281
281
|
and UrlSource.
|
data/lib/saviour.rb
CHANGED
@@ -1,6 +1,3 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'fog/aws'
|
3
|
-
|
4
1
|
require 'saviour/version'
|
5
2
|
require 'saviour/base_uploader'
|
6
3
|
require 'saviour/file'
|
@@ -11,7 +8,6 @@ require 'saviour/string_source'
|
|
11
8
|
require 'saviour/url_source'
|
12
9
|
require 'saviour/model'
|
13
10
|
require 'saviour/integrator'
|
14
|
-
require 'saviour/attribute_name_calculator'
|
15
11
|
require 'saviour/source_filename_extractor'
|
16
12
|
require 'saviour/life_cycle'
|
17
13
|
require 'saviour/persistence_layer'
|
@@ -1,13 +1,9 @@
|
|
1
|
-
require_relative 'uploader/element'
|
2
1
|
require_relative 'uploader/store_dir_extractor'
|
3
2
|
require_relative 'uploader/processors_runner'
|
4
3
|
|
5
4
|
module Saviour
|
6
5
|
class BaseUploader
|
7
|
-
attr_reader :version_name
|
8
|
-
|
9
6
|
def initialize(opts = {})
|
10
|
-
@version_name = opts[:version]
|
11
7
|
@data = opts.fetch(:data, {})
|
12
8
|
end
|
13
9
|
|
@@ -28,7 +24,7 @@ module Saviour
|
|
28
24
|
raise RuntimeError, "Please use `store_dir` before trying to write" unless store_dir
|
29
25
|
|
30
26
|
if Config.processing_enabled
|
31
|
-
contents, filename = Uploader::ProcessorsRunner.new(self
|
27
|
+
contents, filename = Uploader::ProcessorsRunner.new(self).run!(contents, filename)
|
32
28
|
end
|
33
29
|
|
34
30
|
path = ::File.join(store_dir, filename)
|
@@ -45,17 +41,11 @@ module Saviour
|
|
45
41
|
@processors ||= []
|
46
42
|
end
|
47
43
|
|
48
|
-
def versions
|
49
|
-
@versions ||= []
|
50
|
-
end
|
51
|
-
|
52
44
|
def process(name = nil, opts = {}, type = :memory, &block)
|
53
|
-
element = Uploader::Element.new(@current_version, name || block)
|
54
|
-
|
55
45
|
if block_given?
|
56
|
-
processors.push(
|
46
|
+
processors.push(method_or_block: name || block, type: type)
|
57
47
|
else
|
58
|
-
processors.push(
|
48
|
+
processors.push(method_or_block: name || block, type: type, opts: opts)
|
59
49
|
end
|
60
50
|
end
|
61
51
|
|
@@ -65,18 +55,7 @@ module Saviour
|
|
65
55
|
|
66
56
|
|
67
57
|
def store_dir(name = nil, &block)
|
68
|
-
|
69
|
-
store_dirs.push(element)
|
70
|
-
end
|
71
|
-
|
72
|
-
def version(name, &block)
|
73
|
-
versions.push(name)
|
74
|
-
|
75
|
-
if block
|
76
|
-
@current_version = name
|
77
|
-
instance_eval(&block)
|
78
|
-
@current_version = nil
|
79
|
-
end
|
58
|
+
store_dirs.push(name || block)
|
80
59
|
end
|
81
60
|
end
|
82
61
|
end
|
data/lib/saviour/config.rb
CHANGED
@@ -19,12 +19,17 @@ module Saviour
|
|
19
19
|
|
20
20
|
def storage
|
21
21
|
Thread.current["Saviour::Config"] ||= {}
|
22
|
-
Thread.current["Saviour::Config"][:storage] || Thread.main["Saviour::Config"][:storage] || NotImplemented.new
|
22
|
+
Thread.current["Saviour::Config"][:storage] || (Thread.main["Saviour::Config"] && Thread.main["Saviour::Config"][:storage]) || NotImplemented.new
|
23
23
|
end
|
24
24
|
|
25
25
|
def storage=(value)
|
26
26
|
Thread.current["Saviour::Config"] ||= {}
|
27
27
|
Thread.current["Saviour::Config"][:storage] = value
|
28
|
+
|
29
|
+
if Thread.main["Saviour::Config"].nil?
|
30
|
+
Thread.main["Saviour::Config"] ||= {}
|
31
|
+
Thread.main["Saviour::Config"][:storage] = value
|
32
|
+
end
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
data/lib/saviour/file.rb
CHANGED
@@ -4,9 +4,8 @@ module Saviour
|
|
4
4
|
class File
|
5
5
|
attr_reader :persisted_path
|
6
6
|
|
7
|
-
def initialize(uploader_klass, model, attached_as
|
7
|
+
def initialize(uploader_klass, model, attached_as)
|
8
8
|
@uploader_klass, @model, @attached_as = uploader_klass, model, attached_as
|
9
|
-
@version = version
|
10
9
|
@source_was = @source = nil
|
11
10
|
end
|
12
11
|
|
@@ -35,6 +34,9 @@ module Saviour
|
|
35
34
|
def assign(object)
|
36
35
|
raise(RuntimeError, "must respond to `read`") if object && !object.respond_to?(:read)
|
37
36
|
|
37
|
+
followers = @model.class.attached_followers_per_leader[@attached_as]
|
38
|
+
followers.each { |x| @model.send(x).assign(object) unless @model.send(x).changed? } if followers
|
39
|
+
|
38
40
|
@source_data = nil
|
39
41
|
@source = object
|
40
42
|
@persisted_path = nil if object
|
@@ -86,7 +88,10 @@ module Saviour
|
|
86
88
|
end
|
87
89
|
|
88
90
|
def source_data
|
89
|
-
@source_data ||=
|
91
|
+
@source_data ||= begin
|
92
|
+
@source.rewind
|
93
|
+
@source.read
|
94
|
+
end
|
90
95
|
end
|
91
96
|
|
92
97
|
def blank?
|
@@ -97,7 +102,7 @@ module Saviour
|
|
97
102
|
private
|
98
103
|
|
99
104
|
def uploader
|
100
|
-
@uploader ||= @uploader_klass.new(
|
105
|
+
@uploader ||= @uploader_klass.new(data: { model: @model, attached_as: @attached_as })
|
101
106
|
end
|
102
107
|
end
|
103
108
|
end
|
data/lib/saviour/integrator.rb
CHANGED
@@ -5,46 +5,40 @@ module Saviour
|
|
5
5
|
@persistence_klass = persistence_klass
|
6
6
|
end
|
7
7
|
|
8
|
-
def file_instantiator_hook(model, file_instance, attach_as, version)
|
9
|
-
layer = @persistence_klass.new(model)
|
10
|
-
file_instance.set_path!(layer.read(Saviour::AttributeNameCalculator.new(attach_as, version).name)) if layer.persisted?
|
11
|
-
end
|
12
|
-
|
13
|
-
def attach_file_hook(klass, attach_as, uploader_klass)
|
14
|
-
versions = uploader_klass.versions || []
|
15
|
-
|
16
|
-
([nil] + versions).each do |version|
|
17
|
-
column_name = Saviour::AttributeNameCalculator.new(attach_as, version).name
|
18
|
-
|
19
|
-
if klass.table_exists? && !klass.column_names.include?(column_name.to_s)
|
20
|
-
raise RuntimeError, "#{klass} must have a database string column named '#{column_name}'"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
8
|
def setup!
|
26
9
|
raise "You cannot include Saviour twice in the same class" if @klass.respond_to?(:attached_files)
|
27
10
|
|
28
11
|
@klass.class_attribute :attached_files
|
29
|
-
@klass.attached_files =
|
12
|
+
@klass.attached_files = []
|
13
|
+
@klass.class_attribute :attached_followers_per_leader
|
14
|
+
@klass.attached_followers_per_leader = {}
|
15
|
+
|
16
|
+
klass = @klass
|
17
|
+
persistence_klass = @persistence_klass
|
30
18
|
|
31
|
-
|
32
|
-
|
19
|
+
@klass.define_singleton_method "attach_file" do |attach_as, *maybe_uploader_klass, **opts, &block|
|
20
|
+
klass.attached_files.push(attach_as)
|
21
|
+
uploader_klass = maybe_uploader_klass[0]
|
33
22
|
|
34
|
-
|
35
|
-
|
23
|
+
if opts[:follow]
|
24
|
+
klass.attached_followers_per_leader[opts[:follow]] ||= []
|
25
|
+
klass.attached_followers_per_leader[opts[:follow]].push(attach_as)
|
26
|
+
end
|
36
27
|
|
37
|
-
|
38
|
-
|
39
|
-
|
28
|
+
if uploader_klass.nil? && block.nil?
|
29
|
+
raise ArgumentError, "you must provide either an UploaderClass or a block to define it."
|
30
|
+
end
|
40
31
|
|
41
|
-
|
42
|
-
define_method(attach_as) do
|
43
|
-
instance_variable_get("@__uploader_#{
|
44
|
-
|
45
|
-
|
32
|
+
mod = Module.new do
|
33
|
+
define_method(attach_as) do
|
34
|
+
instance_variable_get("@__uploader_#{attach_as}") || begin
|
35
|
+
uploader_klass = Class.new(Saviour::BaseUploader, &block) if block
|
36
|
+
new_file = ::Saviour::File.new(uploader_klass, self, attach_as)
|
46
37
|
|
47
|
-
|
38
|
+
layer = persistence_klass.new(self)
|
39
|
+
new_file.set_path!(layer.read(attach_as)) if layer.persisted?
|
40
|
+
|
41
|
+
instance_variable_set("@__uploader_#{attach_as}", new_file)
|
48
42
|
end
|
49
43
|
end
|
50
44
|
|
@@ -56,15 +50,15 @@ module Saviour
|
|
56
50
|
send(attach_as).changed?
|
57
51
|
end
|
58
52
|
end
|
53
|
+
|
54
|
+
klass.include mod
|
59
55
|
end
|
60
56
|
|
61
57
|
@klass.class_attribute :__saviour_validations
|
62
58
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
self.__saviour_validations[attach_as] += [method_name || block]
|
67
|
-
end
|
59
|
+
@klass.define_singleton_method("attach_validation") do |attach_as, method_name = nil, &block|
|
60
|
+
klass.__saviour_validations ||= Hash.new { [] }
|
61
|
+
klass.__saviour_validations[attach_as] += [method_name || block]
|
68
62
|
end
|
69
63
|
end
|
70
64
|
end
|
data/lib/saviour/life_cycle.rb
CHANGED
@@ -8,50 +8,35 @@ module Saviour
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def delete!
|
11
|
-
attached_files.each do |column
|
12
|
-
|
11
|
+
attached_files.each do |column|
|
12
|
+
@model.send(column).delete if @model.send(column).exists?
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
def save!
|
17
|
-
attached_files.each do |column
|
17
|
+
attached_files.each do |column|
|
18
18
|
base_file_changed = @model.send(column).changed?
|
19
|
-
|
20
|
-
|
21
|
-
versions.each do |version|
|
22
|
-
if @model.send(column, version).changed?
|
23
|
-
upload_file(column, version)
|
24
|
-
elsif base_file_changed
|
25
|
-
@model.send(column, version).assign(StringSource.new(original_content, default_version_filename(column, version)))
|
26
|
-
upload_file(column, version)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
upload_file(column, nil) if base_file_changed
|
19
|
+
|
20
|
+
upload_file(column) if base_file_changed
|
31
21
|
end
|
32
22
|
end
|
33
23
|
|
34
24
|
|
35
25
|
private
|
36
26
|
|
37
|
-
def default_version_filename(column, version)
|
38
|
-
filename = @model.send(column).filename_to_be_assigned
|
39
|
-
"#{::File.basename(filename, ".*")}_#{version}#{::File.extname(filename)}"
|
40
|
-
end
|
41
27
|
|
42
|
-
def upload_file(column
|
43
|
-
name = AttributeNameCalculator.new(column, version).name
|
28
|
+
def upload_file(column)
|
44
29
|
persistence_layer = @persistence_klass.new(@model) if @persistence_klass
|
45
|
-
current_path = persistence_layer.read(
|
30
|
+
current_path = persistence_layer.read(column) if persistence_layer
|
46
31
|
|
47
32
|
Config.storage.delete(current_path) if current_path
|
48
33
|
|
49
|
-
new_path = @model.send(column
|
50
|
-
persistence_layer.write(
|
34
|
+
new_path = @model.send(column).write
|
35
|
+
persistence_layer.write(column, new_path) if persistence_layer
|
51
36
|
end
|
52
37
|
|
53
38
|
def attached_files
|
54
|
-
@model.class.attached_files
|
39
|
+
@model.class.attached_files
|
55
40
|
end
|
56
41
|
end
|
57
42
|
end
|
data/lib/saviour/model.rb
CHANGED
@@ -15,8 +15,8 @@ module Saviour
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def reload
|
18
|
-
self.class.attached_files.each do |attach_as
|
19
|
-
|
18
|
+
self.class.attached_files.each do |attach_as|
|
19
|
+
instance_variable_set("@__uploader_#{attach_as}", nil)
|
20
20
|
end
|
21
21
|
super
|
22
22
|
end
|
data/lib/saviour/s3_storage.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fog/aws'
|
2
|
+
|
1
3
|
module Saviour
|
2
4
|
class S3Storage
|
3
5
|
def initialize(conf = {})
|
@@ -6,8 +8,8 @@ module Saviour
|
|
6
8
|
@conf = conf
|
7
9
|
@overwrite_protection = conf.delete(:overwrite_protection) { true }
|
8
10
|
@create_options = conf.delete(:create_options) { {} }
|
9
|
-
conf.fetch(:aws_access_key_id) { raise ArgumentError.new("aws_access_key_id is required")}
|
10
|
-
conf.fetch(:aws_secret_access_key) { raise ArgumentError.new("aws_secret_access_key is required")}
|
11
|
+
conf.fetch(:aws_access_key_id) { raise ArgumentError.new("aws_access_key_id is required") }
|
12
|
+
conf.fetch(:aws_secret_access_key) { raise ArgumentError.new("aws_secret_access_key is required") }
|
11
13
|
end
|
12
14
|
|
13
15
|
def write(contents, path)
|
@@ -66,11 +68,11 @@ module Saviour
|
|
66
68
|
end
|
67
69
|
|
68
70
|
def directory
|
69
|
-
@directory ||= connection.directories.
|
71
|
+
@directory ||= connection.directories.new(key: @bucket)
|
70
72
|
end
|
71
73
|
|
72
74
|
def connection
|
73
|
-
@connection ||= Fog::Storage.new({provider: 'AWS'}.merge(@conf))
|
75
|
+
@connection ||= Fog::Storage.new({ provider: 'AWS' }.merge(@conf))
|
74
76
|
end
|
75
77
|
end
|
76
78
|
end
|
@@ -1,11 +1,16 @@
|
|
1
1
|
module Saviour
|
2
2
|
class StringSource
|
3
3
|
def initialize(value, filename = nil)
|
4
|
-
@value
|
4
|
+
@value = StringIO.new(value)
|
5
|
+
@filename = filename
|
5
6
|
end
|
6
7
|
|
7
|
-
def read
|
8
|
-
@value
|
8
|
+
def read(*args)
|
9
|
+
@value.read(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewind
|
13
|
+
@value.rewind
|
9
14
|
end
|
10
15
|
|
11
16
|
def original_filename
|
@@ -5,26 +5,26 @@ module Saviour
|
|
5
5
|
attr_accessor :contents
|
6
6
|
attr_accessor :filename
|
7
7
|
|
8
|
-
def initialize(uploader
|
9
|
-
@uploader
|
8
|
+
def initialize(uploader)
|
9
|
+
@uploader = uploader
|
10
10
|
end
|
11
11
|
|
12
12
|
def matching_processors
|
13
|
-
@uploader.class.processors
|
13
|
+
@uploader.class.processors
|
14
14
|
end
|
15
15
|
|
16
16
|
def file
|
17
17
|
@file ||= Tempfile.new([SecureRandom.hex, ::File.extname(filename)]).tap { |x| x.binmode }
|
18
18
|
end
|
19
19
|
|
20
|
-
def
|
21
|
-
if
|
22
|
-
@uploader.instance_exec(data, filename, &
|
20
|
+
def run_method_or_block(method_or_block, opts, data)
|
21
|
+
if method_or_block.respond_to?(:call)
|
22
|
+
@uploader.instance_exec(data, filename, &method_or_block)
|
23
23
|
else
|
24
24
|
if opts.empty?
|
25
|
-
@uploader.send(
|
25
|
+
@uploader.send(method_or_block, data, filename)
|
26
26
|
else
|
27
|
-
@uploader.send(
|
27
|
+
@uploader.send(method_or_block, data, filename, opts)
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -43,11 +43,11 @@ module Saviour
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def run_processor(processor)
|
46
|
-
|
46
|
+
method_or_block = processor[:method_or_block]
|
47
47
|
opts = processor[:opts]
|
48
48
|
|
49
49
|
if processor[:type] == :memory
|
50
|
-
result =
|
50
|
+
result = run_method_or_block(method_or_block, opts, contents)
|
51
51
|
|
52
52
|
self.contents = result[0]
|
53
53
|
self.filename = result[1]
|
@@ -55,7 +55,7 @@ module Saviour
|
|
55
55
|
else
|
56
56
|
file.rewind
|
57
57
|
|
58
|
-
result =
|
58
|
+
result = run_method_or_block(method_or_block, opts, file)
|
59
59
|
|
60
60
|
self.file = result[0]
|
61
61
|
self.filename = result[1]
|