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