saviour 0.3.0 → 0.3.1
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/.travis.yml +19 -0
- data/Appraisals +19 -0
- data/gemfiles/4.0.gemfile +9 -0
- data/gemfiles/4.1.gemfile +9 -0
- data/gemfiles/4.2.gemfile +9 -0
- data/gemfiles/5.0.gemfile +9 -0
- data/lib/saviour.rb +4 -5
- data/lib/saviour/base_uploader.rb +6 -4
- data/lib/saviour/{base_integrator.rb → integrator.rb} +23 -5
- data/lib/saviour/local_storage.rb +1 -1
- data/lib/saviour/model.rb +24 -0
- data/lib/saviour/persistence_layer.rb +19 -0
- data/lib/saviour/s3_storage.rb +2 -5
- data/lib/saviour/url_source.rb +1 -0
- data/lib/saviour/validator.rb +50 -0
- data/lib/saviour/version.rb +1 -1
- data/saviour.gemspec +6 -1
- data/spec/feature/access_to_model_and_mounted_as_spec.rb +2 -2
- data/spec/feature/crud_workflows_spec.rb +143 -0
- data/spec/feature/persisted_path_spec.rb +34 -0
- data/spec/feature/reload_model_spec.rb +24 -0
- data/spec/feature/validations_spec.rb +178 -0
- data/spec/feature/versions_spec.rb +49 -72
- data/spec/models/model_spec.rb +128 -0
- data/spec/models/s3_storage_spec.rb +0 -6
- data/spec/spec_helper.rb +25 -0
- data/spec/support/models.rb +7 -3
- data/spec/support/schema.rb +9 -0
- metadata +75 -9
- data/DECOMPOSE.md +0 -66
- data/lib/saviour/basic_model.rb +0 -7
- data/lib/saviour/utils/class_attribute.rb +0 -26
- data/spec/models/basic_model_spec.rb +0 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa3f38c97bf8c5fee8d2696496b167b7ea312367
|
4
|
+
data.tar.gz: 6ca9294640f87778851e17d87676273e4f183472
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fa86cf2f8def862e78f0fb019cd90eb085495c4da0247a6b3fc97a8514b6277e1ec7252960ecb8a32cd6c242f520dde6406cbdb4e171ead83bff92b484fa3cd
|
7
|
+
data.tar.gz: ee7cf2411e9d881f2cd5c70fcc6a6bfa93e11b8c5197b9d73cff27a4823ad63991fdfe3a683c50e2bccc226c73f9fafcf43bf54c31a2b9f60d69881c38b5a160
|
data/.travis.yml
CHANGED
@@ -5,6 +5,13 @@ rvm:
|
|
5
5
|
- 2.1.8
|
6
6
|
- 2.2.4
|
7
7
|
- 2.3.0
|
8
|
+
- 2.4.0
|
9
|
+
|
10
|
+
gemfile:
|
11
|
+
- gemfiles/4.0.gemfile
|
12
|
+
- gemfiles/4.1.gemfile
|
13
|
+
- gemfiles/4.2.gemfile
|
14
|
+
- gemfiles/5.0.gemfile
|
8
15
|
|
9
16
|
addons:
|
10
17
|
code_climate:
|
@@ -12,3 +19,15 @@ addons:
|
|
12
19
|
|
13
20
|
after_success:
|
14
21
|
- bundle exec codeclimate-test-reporter
|
22
|
+
|
23
|
+
matrix:
|
24
|
+
exclude:
|
25
|
+
# rails 5 requires 2.2+
|
26
|
+
- rvm: 2.1.8
|
27
|
+
gemfile: gemfiles/5.0.gemfile
|
28
|
+
|
29
|
+
# Arel support for ruby 2.4 starts in 4.2
|
30
|
+
- rvm: 2.4.0
|
31
|
+
gemfile: gemfiles/4.0.gemfile
|
32
|
+
- rvm: 2.4.0
|
33
|
+
gemfile: gemfiles/4.1.gemfile
|
data/Appraisals
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
appraise "4.0" do
|
2
|
+
gem "activesupport", "~> 4.0.0"
|
3
|
+
gem "activerecord", "~> 4.0.0"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "4.1" do
|
7
|
+
gem "activesupport", "~> 4.1.0"
|
8
|
+
gem "activerecord", "~> 4.1.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise "4.2" do
|
12
|
+
gem "activesupport", "~> 4.2.0"
|
13
|
+
gem "activerecord", "~> 4.2.0"
|
14
|
+
end
|
15
|
+
|
16
|
+
appraise "5.0" do
|
17
|
+
gem "activesupport", "~> 5.0.0"
|
18
|
+
gem "activerecord", "~> 5.0.0"
|
19
|
+
end
|
data/lib/saviour.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
require 'fileutils'
|
2
2
|
require 'fog/aws'
|
3
3
|
|
4
|
-
require 'saviour/utils/class_attribute'
|
5
|
-
|
6
4
|
require 'saviour/version'
|
7
5
|
require 'saviour/base_uploader'
|
8
6
|
require 'saviour/file'
|
@@ -11,12 +9,13 @@ require 'saviour/s3_storage'
|
|
11
9
|
require 'saviour/config'
|
12
10
|
require 'saviour/string_source'
|
13
11
|
require 'saviour/url_source'
|
14
|
-
require 'saviour/
|
15
|
-
require 'saviour/
|
12
|
+
require 'saviour/model'
|
13
|
+
require 'saviour/integrator'
|
16
14
|
require 'saviour/attribute_name_calculator'
|
17
15
|
require 'saviour/source_filename_extractor'
|
18
16
|
require 'saviour/life_cycle'
|
19
|
-
|
17
|
+
require 'saviour/persistence_layer'
|
18
|
+
require 'saviour/validator'
|
20
19
|
|
21
20
|
module Saviour
|
22
21
|
end
|
@@ -19,7 +19,7 @@ module Saviour
|
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
|
-
def
|
22
|
+
def respond_to_missing?(name, *)
|
23
23
|
@data.key?(name) || super
|
24
24
|
end
|
25
25
|
|
@@ -27,7 +27,9 @@ module Saviour
|
|
27
27
|
store_dir = Uploader::StoreDirExtractor.new(self).store_dir
|
28
28
|
raise RuntimeError, "Please use `store_dir` before trying to write" unless store_dir
|
29
29
|
|
30
|
-
|
30
|
+
if Config.processing_enabled
|
31
|
+
contents, filename = Uploader::ProcessorsRunner.new(self, @version_name).run!(contents, filename)
|
32
|
+
end
|
31
33
|
|
32
34
|
path = ::File.join(store_dir, filename)
|
33
35
|
Config.storage.write(contents, path)
|
@@ -51,9 +53,9 @@ module Saviour
|
|
51
53
|
element = Uploader::Element.new(@current_version, name || block)
|
52
54
|
|
53
55
|
if block_given?
|
54
|
-
processors.push(
|
56
|
+
processors.push(element: element, type: type)
|
55
57
|
else
|
56
|
-
processors.push(
|
58
|
+
processors.push(element: element, type: type, opts: opts)
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
@@ -1,21 +1,30 @@
|
|
1
1
|
module Saviour
|
2
|
-
class
|
3
|
-
def initialize(klass)
|
2
|
+
class Integrator
|
3
|
+
def initialize(klass, persistence_klass)
|
4
4
|
@klass = klass
|
5
|
+
@persistence_klass = persistence_klass
|
5
6
|
end
|
6
7
|
|
7
8
|
def file_instantiator_hook(model, file_instance, attach_as, version)
|
8
|
-
|
9
|
+
layer = @persistence_klass.new(model)
|
10
|
+
file_instance.set_path!(layer.read(Saviour::AttributeNameCalculator.new(attach_as, version).name)) if layer.persisted?
|
9
11
|
end
|
10
12
|
|
11
13
|
def attach_file_hook(klass, attach_as, uploader_klass)
|
12
|
-
|
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
|
13
23
|
end
|
14
24
|
|
15
25
|
def setup!
|
16
26
|
raise "You cannot include Saviour twice in the same class" if @klass.respond_to?(:attached_files)
|
17
27
|
|
18
|
-
@klass.send :extend, ClassAttribute
|
19
28
|
@klass.class_attribute :attached_files
|
20
29
|
@klass.attached_files = {}
|
21
30
|
|
@@ -48,6 +57,15 @@ module Saviour
|
|
48
57
|
end
|
49
58
|
end
|
50
59
|
end
|
60
|
+
|
61
|
+
@klass.class_attribute :__saviour_validations
|
62
|
+
|
63
|
+
class << @klass
|
64
|
+
def attach_validation(attach_as, method_name = nil, &block)
|
65
|
+
self.__saviour_validations ||= Hash.new { [] }
|
66
|
+
self.__saviour_validations[attach_as] += [method_name || block]
|
67
|
+
end
|
68
|
+
end
|
51
69
|
end
|
52
70
|
end
|
53
71
|
end
|
@@ -63,7 +63,7 @@ module Saviour
|
|
63
63
|
basedir = ::File.dirname(path)
|
64
64
|
return if basedir == "."
|
65
65
|
|
66
|
-
while basedir != "/" && Dir.entries(real_path(basedir))
|
66
|
+
while basedir != "/" && Dir.entries(real_path(basedir)) - [".", ".."] ==[]
|
67
67
|
Dir.rmdir(real_path(basedir))
|
68
68
|
basedir = ::File.dirname(basedir)
|
69
69
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Saviour
|
2
|
+
NoActiveRecordDetected = Class.new(StandardError)
|
3
|
+
|
4
|
+
module Model
|
5
|
+
def self.included(klass)
|
6
|
+
Integrator.new(klass, PersistenceLayer).setup!
|
7
|
+
|
8
|
+
klass.class_eval do
|
9
|
+
raise(NoActiveRecordDetected, "Error: ActiveRecord not detected in #{self}") unless self.ancestors.include?(ActiveRecord::Base)
|
10
|
+
|
11
|
+
after_destroy { Saviour::LifeCycle.new(self, PersistenceLayer).delete! }
|
12
|
+
after_save { Saviour::LifeCycle.new(self, PersistenceLayer).save! }
|
13
|
+
validate { Saviour::Validator.new(self).validate! }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def reload
|
18
|
+
self.class.attached_files.each do |attach_as, versions|
|
19
|
+
(versions + [nil]).each { |version| instance_variable_set("@__uploader_#{version}_#{attach_as}", nil) }
|
20
|
+
end
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Saviour
|
2
|
+
class PersistenceLayer
|
3
|
+
def initialize(model)
|
4
|
+
@model = model
|
5
|
+
end
|
6
|
+
|
7
|
+
def read(attr)
|
8
|
+
@model.read_attribute(attr)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(attr, value)
|
12
|
+
@model.update_columns(attr => value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def persisted?
|
16
|
+
@model.persisted? || @model.destroyed?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/saviour/s3_storage.rb
CHANGED
@@ -6,7 +6,8 @@ module Saviour
|
|
6
6
|
@conf = conf
|
7
7
|
@overwrite_protection = conf.delete(:overwrite_protection) { true }
|
8
8
|
@create_options = conf.delete(:create_options) { {} }
|
9
|
-
|
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")}
|
10
11
|
end
|
11
12
|
|
12
13
|
def write(contents, path)
|
@@ -60,10 +61,6 @@ module Saviour
|
|
60
61
|
path.gsub(/\A\/*/, '')
|
61
62
|
end
|
62
63
|
|
63
|
-
def assert_directory_exists!
|
64
|
-
directory || raise(ArgumentError, "The bucket #{@bucket} doesn't exists or misconfigured connection.")
|
65
|
-
end
|
66
|
-
|
67
64
|
def assert_exists(path)
|
68
65
|
raise RuntimeError, "File does not exists: #{path}" unless exists?(path)
|
69
66
|
end
|
data/lib/saviour/url_source.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Saviour
|
2
|
+
class Validator
|
3
|
+
def initialize(model)
|
4
|
+
raise "Please provide an object compatible with Saviour." unless model.class.respond_to?(:attached_files)
|
5
|
+
|
6
|
+
@model = model
|
7
|
+
end
|
8
|
+
|
9
|
+
def validate!
|
10
|
+
validations.each do |column, method_or_blocks|
|
11
|
+
raise(ArgumentError, "There is no attachment defined as '#{column}'") unless attached_files[column]
|
12
|
+
(attached_files[column] + [nil]).each do |version|
|
13
|
+
if @model.send(column, version).changed?
|
14
|
+
method_or_blocks.each { |method_or_block| run_validation(column, version, method_or_block) }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def run_validation(column, version, method_or_block)
|
23
|
+
data = @model.send(column, version).source_data
|
24
|
+
filename = @model.send(column, version).filename_to_be_assigned
|
25
|
+
opts = {attached_as: column, version: version}
|
26
|
+
|
27
|
+
if method_or_block.respond_to?(:call)
|
28
|
+
if method_or_block.arity == 2
|
29
|
+
@model.instance_exec(data, filename, &method_or_block)
|
30
|
+
else
|
31
|
+
@model.instance_exec(data, filename, opts, &method_or_block)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
if @model.method(method_or_block).arity == 2
|
35
|
+
@model.send(method_or_block, data, filename)
|
36
|
+
else
|
37
|
+
@model.send(method_or_block, data, filename, opts)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def attached_files
|
43
|
+
@model.class.attached_files || {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def validations
|
47
|
+
@model.class.__saviour_validations || {}
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/saviour/version.rb
CHANGED
data/saviour.gemspec
CHANGED
@@ -17,7 +17,12 @@ Gem::Specification.new do |spec|
|
|
17
17
|
|
18
18
|
spec.add_dependency "fog-aws"
|
19
19
|
spec.add_dependency "mime-types"
|
20
|
+
spec.add_dependency "activerecord", ">= 4.0"
|
21
|
+
spec.add_dependency "activesupport", ">= 4.0"
|
22
|
+
|
20
23
|
spec.add_development_dependency "bundler"
|
21
|
-
spec.add_development_dependency "rake"
|
22
24
|
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "sqlite3"
|
27
|
+
spec.add_development_dependency "appraisal"
|
23
28
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "saving a new file" do
|
4
|
+
before { allow(Saviour::Config).to receive(:storage).and_return(Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com")) }
|
5
|
+
|
6
|
+
let(:uploader) {
|
7
|
+
Class.new(Saviour::BaseUploader) {
|
8
|
+
store_dir { "/store/dir" }
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
let(:klass) {
|
13
|
+
a = Class.new(Test) { include Saviour::Model }
|
14
|
+
a.attach_file :file, uploader
|
15
|
+
a
|
16
|
+
}
|
17
|
+
|
18
|
+
describe "creation" do
|
19
|
+
it do
|
20
|
+
with_test_file("example.xml") do |example|
|
21
|
+
a = klass.create!
|
22
|
+
expect(a.update_attributes(file: example)).to be_truthy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it do
|
27
|
+
with_test_file("example.xml") do |example|
|
28
|
+
a = klass.create!
|
29
|
+
a.update_attributes(file: example)
|
30
|
+
|
31
|
+
expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it do
|
36
|
+
with_test_file("example.xml") do |example, real_filename|
|
37
|
+
a = klass.create!
|
38
|
+
a.update_attributes(file: example)
|
39
|
+
expect(a[:file]).to eq "/store/dir/#{real_filename}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it do
|
44
|
+
with_test_file("example.xml") do |example|
|
45
|
+
a = klass.create!
|
46
|
+
a.update_attributes(file: example)
|
47
|
+
|
48
|
+
example.rewind
|
49
|
+
expect(a.file.read).to eq example.read
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it do
|
54
|
+
with_test_file("example.xml") do |example|
|
55
|
+
a = klass.create!
|
56
|
+
a.update_attributes(file: example)
|
57
|
+
|
58
|
+
expect(a.file.exists?).to be_truthy
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it do
|
63
|
+
with_test_file("example.xml") do |example, real_filename|
|
64
|
+
a = klass.create!
|
65
|
+
a.update_attributes(file: example)
|
66
|
+
|
67
|
+
expect(a.file.filename).to eq real_filename
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
it do
|
72
|
+
with_test_file("example.xml") do |example, real_filename|
|
73
|
+
a = klass.create!
|
74
|
+
a.update_attributes(file: example)
|
75
|
+
|
76
|
+
expect(a.file.url).to eq "http://domain.com/store/dir/#{real_filename}"
|
77
|
+
expect(a.file.public_url).to eq a.file.url
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it "don't create anything if save do not completes (halt during before_save)" do
|
82
|
+
klass = Class.new(Test) do
|
83
|
+
attr_accessor :fail_at_save
|
84
|
+
before_save {
|
85
|
+
if ActiveRecord.version >= Gem::Version.new("5.0")
|
86
|
+
throw(:abort) if fail_at_save
|
87
|
+
else
|
88
|
+
!fail_at_save
|
89
|
+
end
|
90
|
+
}
|
91
|
+
include Saviour::Model
|
92
|
+
end
|
93
|
+
klass.attach_file :file, uploader
|
94
|
+
|
95
|
+
expect {
|
96
|
+
a = klass.new
|
97
|
+
a.fail_at_save = true
|
98
|
+
a.save!
|
99
|
+
}.to raise_error(ActiveRecord::RecordNotSaved)
|
100
|
+
|
101
|
+
with_test_file("example.xml") do |example, _|
|
102
|
+
a = klass.new
|
103
|
+
a.fail_at_save = true
|
104
|
+
a.file = example
|
105
|
+
|
106
|
+
expect(Saviour::Config.storage).not_to receive(:write)
|
107
|
+
a.save
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "deletion" do
|
113
|
+
it do
|
114
|
+
with_test_file("example.xml") do |example|
|
115
|
+
a = klass.create!
|
116
|
+
a.update_attributes(file: example)
|
117
|
+
expect(a.file.exists?).to be_truthy
|
118
|
+
expect(a.destroy).to be_truthy
|
119
|
+
|
120
|
+
expect(Saviour::Config.storage.exists?(a[:file])).to be_falsey
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
describe "updating" do
|
126
|
+
it do
|
127
|
+
with_test_file("example.xml") do |example|
|
128
|
+
a = klass.create!
|
129
|
+
a.update_attributes(file: example)
|
130
|
+
|
131
|
+
expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
|
132
|
+
previous_location = a[:file]
|
133
|
+
|
134
|
+
with_test_file("camaloon.jpg") do |example_2|
|
135
|
+
a.update_attributes(file: example_2)
|
136
|
+
expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
|
137
|
+
|
138
|
+
expect(Saviour::Config.storage.exists?(previous_location)).to be_falsey
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|