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
@@ -0,0 +1,128 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Saviour do
|
4
|
+
it "raises error if included in a non active record class" do
|
5
|
+
expect {
|
6
|
+
Class.new do
|
7
|
+
include Saviour::Model
|
8
|
+
end
|
9
|
+
}.to raise_error(Saviour::NoActiveRecordDetected)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "error if column not present" do
|
13
|
+
expect {
|
14
|
+
Class.new(Test) do
|
15
|
+
include Saviour::Model
|
16
|
+
|
17
|
+
attach_file :not_present, Saviour::BaseUploader
|
18
|
+
end
|
19
|
+
}.to raise_error(RuntimeError)
|
20
|
+
end
|
21
|
+
|
22
|
+
context do
|
23
|
+
it "error if column not present on version" do
|
24
|
+
uploader = Class.new(Saviour::BaseUploader) do
|
25
|
+
store_dir { "/store/dir" }
|
26
|
+
|
27
|
+
version(:thumb) do
|
28
|
+
store_dir { "/versions/store/dir" }
|
29
|
+
end
|
30
|
+
|
31
|
+
version(:not_present)
|
32
|
+
end
|
33
|
+
|
34
|
+
expect {
|
35
|
+
Class.new(Test) do
|
36
|
+
include Saviour::Model
|
37
|
+
|
38
|
+
attach_file :file, uploader
|
39
|
+
end
|
40
|
+
}.to raise_error(RuntimeError)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it "does not raise error if table is not present" do
|
45
|
+
allow(Test).to receive(:table_exists?).and_return(false)
|
46
|
+
|
47
|
+
expect {
|
48
|
+
Class.new(Test) do
|
49
|
+
include Saviour::Model
|
50
|
+
|
51
|
+
attach_file :not_present, Saviour::BaseUploader
|
52
|
+
end
|
53
|
+
}.to_not raise_error
|
54
|
+
end
|
55
|
+
|
56
|
+
describe ".attached_files" do
|
57
|
+
it "includes a mapping of the currently attached files and their versions" do
|
58
|
+
uploader = Class.new(Saviour::BaseUploader) do
|
59
|
+
store_dir { "/store/dir" }
|
60
|
+
|
61
|
+
version(:thumb)
|
62
|
+
version(:thumb_2)
|
63
|
+
end
|
64
|
+
|
65
|
+
klass = Class.new(Test) do
|
66
|
+
include Saviour::Model
|
67
|
+
attach_file :file, uploader
|
68
|
+
end
|
69
|
+
|
70
|
+
expect(klass.attached_files).to eq({file: [:thumb, :thumb_2]})
|
71
|
+
|
72
|
+
klass2 = Class.new(Test) do
|
73
|
+
include Saviour::Model
|
74
|
+
attach_file :file, Saviour::BaseUploader
|
75
|
+
end
|
76
|
+
|
77
|
+
expect(klass2.attached_files).to eq({file: []})
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe ".attached_files" do
|
82
|
+
it "includes a mapping of the currently attached files and their versions" do
|
83
|
+
uploader = Class.new(Saviour::BaseUploader) do
|
84
|
+
store_dir { "/store/dir" }
|
85
|
+
|
86
|
+
version(:thumb)
|
87
|
+
version(:thumb_2)
|
88
|
+
end
|
89
|
+
|
90
|
+
klass = Class.new(Test) do
|
91
|
+
include Saviour::Model
|
92
|
+
attach_file :file, uploader
|
93
|
+
end
|
94
|
+
|
95
|
+
expect(klass.attached_files).to eq({file: [:thumb, :thumb_2]})
|
96
|
+
|
97
|
+
klass2 = Class.new(Test) do
|
98
|
+
include Saviour::Model
|
99
|
+
attach_file :file, Saviour::BaseUploader
|
100
|
+
end
|
101
|
+
|
102
|
+
expect(klass2.attached_files).to eq({file: []})
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it "doens't mess with default File constant" do
|
107
|
+
# Constant lookup in ruby works by lexical scope, so we can't create classes dynamically like above.
|
108
|
+
expect(TestForSaviourFileResolution.new.foo).to be_falsey
|
109
|
+
end
|
110
|
+
|
111
|
+
it "shares model definitions with subclasses" do
|
112
|
+
uploader = Class.new(Saviour::BaseUploader) do
|
113
|
+
store_dir { "/store/dir" }
|
114
|
+
version(:thumb)
|
115
|
+
end
|
116
|
+
|
117
|
+
klass = Class.new(Test) do
|
118
|
+
include Saviour::Model
|
119
|
+
attach_file :file, uploader
|
120
|
+
end
|
121
|
+
expect(klass.attached_files).to eq({file: [:thumb]})
|
122
|
+
|
123
|
+
klass2 = Class.new(klass)
|
124
|
+
expect(klass2.attached_files).to eq({file: [:thumb]})
|
125
|
+
|
126
|
+
expect(klass2.new.file).to respond_to :exists?
|
127
|
+
end
|
128
|
+
end
|
@@ -11,12 +11,6 @@ describe Saviour::S3Storage do
|
|
11
11
|
Saviour::S3Storage.new(bucket: "fake-bucket")
|
12
12
|
}.to raise_error(ArgumentError)
|
13
13
|
end
|
14
|
-
|
15
|
-
it "fails when the bucket doesn't exists" do
|
16
|
-
expect {
|
17
|
-
Saviour::S3Storage.new(bucket: "no-bucket", aws_access_key_id: "stub", aws_secret_access_key: "stub")
|
18
|
-
}.to raise_error(ArgumentError)
|
19
|
-
end
|
20
14
|
end
|
21
15
|
|
22
16
|
describe "#write" do
|
data/spec/spec_helper.rb
CHANGED
@@ -3,9 +3,34 @@ SimpleCov.start
|
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
require 'rspec'
|
6
|
+
require 'active_record'
|
7
|
+
require 'sqlite3'
|
6
8
|
|
7
9
|
require File.expand_path("../../lib/saviour", __FILE__)
|
8
10
|
|
11
|
+
connection_opts = case ENV.fetch('DB', "sqlite")
|
12
|
+
when "sqlite"
|
13
|
+
{adapter: "sqlite3", database: ":memory:"}
|
14
|
+
when "mysql"
|
15
|
+
{adapter: "mysql2", database: "saviour", username: "root", encoding: "utf8"}
|
16
|
+
when "postgres"
|
17
|
+
{adapter: "postgresql", database: "saviour", username: "postgres"}
|
18
|
+
end
|
19
|
+
|
20
|
+
ActiveRecord::Base.establish_connection(connection_opts)
|
21
|
+
|
22
|
+
def silence_stream(stream)
|
23
|
+
old_stream = stream.dup
|
24
|
+
stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null')
|
25
|
+
stream.sync = true
|
26
|
+
yield
|
27
|
+
ensure
|
28
|
+
stream.reopen(old_stream)
|
29
|
+
old_stream.close
|
30
|
+
end
|
31
|
+
|
32
|
+
silence_stream(STDOUT) { require 'support/schema' }
|
33
|
+
|
9
34
|
require 'support/models'
|
10
35
|
|
11
36
|
RSpec.configure do |config|
|
data/spec/support/models.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
class Test < ActiveRecord::Base
|
2
|
+
|
3
|
+
end
|
4
|
+
|
1
5
|
# Constant lookup in ruby works by lexical scope, so we can't create classes dynamically to test this.
|
2
|
-
class TestForSaviourFileResolution
|
3
|
-
include Saviour::
|
6
|
+
class TestForSaviourFileResolution < Test
|
7
|
+
include Saviour::Model
|
4
8
|
|
5
9
|
def foo
|
6
10
|
File.file?("/tasdasdasdmp/blabla.txt")
|
7
11
|
end
|
8
|
-
end
|
12
|
+
end
|
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.3.
|
4
|
+
version: 0.3.1
|
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: 2017-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fog-aws
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activerecord
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +80,20 @@ dependencies:
|
|
52
80
|
- - ">="
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
55
97
|
- !ruby/object:Gem::Dependency
|
56
98
|
name: rake
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -67,7 +109,21 @@ dependencies:
|
|
67
109
|
- !ruby/object:Gem::Version
|
68
110
|
version: '0'
|
69
111
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: appraisal
|
71
127
|
requirement: !ruby/object:Gem::Requirement
|
72
128
|
requirements:
|
73
129
|
- - ">="
|
@@ -89,20 +145,25 @@ extra_rdoc_files: []
|
|
89
145
|
files:
|
90
146
|
- ".gitignore"
|
91
147
|
- ".travis.yml"
|
92
|
-
-
|
148
|
+
- Appraisals
|
93
149
|
- Gemfile
|
94
150
|
- LICENSE.txt
|
95
151
|
- README.md
|
96
152
|
- Rakefile
|
153
|
+
- gemfiles/4.0.gemfile
|
154
|
+
- gemfiles/4.1.gemfile
|
155
|
+
- gemfiles/4.2.gemfile
|
156
|
+
- gemfiles/5.0.gemfile
|
97
157
|
- lib/saviour.rb
|
98
158
|
- lib/saviour/attribute_name_calculator.rb
|
99
|
-
- lib/saviour/base_integrator.rb
|
100
159
|
- lib/saviour/base_uploader.rb
|
101
|
-
- lib/saviour/basic_model.rb
|
102
160
|
- lib/saviour/config.rb
|
103
161
|
- lib/saviour/file.rb
|
162
|
+
- lib/saviour/integrator.rb
|
104
163
|
- lib/saviour/life_cycle.rb
|
105
164
|
- lib/saviour/local_storage.rb
|
165
|
+
- lib/saviour/model.rb
|
166
|
+
- lib/saviour/persistence_layer.rb
|
106
167
|
- lib/saviour/s3_storage.rb
|
107
168
|
- lib/saviour/source_filename_extractor.rb
|
108
169
|
- lib/saviour/string_source.rb
|
@@ -110,17 +171,21 @@ files:
|
|
110
171
|
- lib/saviour/uploader/processors_runner.rb
|
111
172
|
- lib/saviour/uploader/store_dir_extractor.rb
|
112
173
|
- lib/saviour/url_source.rb
|
113
|
-
- lib/saviour/
|
174
|
+
- lib/saviour/validator.rb
|
114
175
|
- lib/saviour/version.rb
|
115
176
|
- saviour.gemspec
|
116
177
|
- spec/feature/access_to_model_and_mounted_as_spec.rb
|
178
|
+
- spec/feature/crud_workflows_spec.rb
|
179
|
+
- spec/feature/persisted_path_spec.rb
|
180
|
+
- spec/feature/reload_model_spec.rb
|
181
|
+
- spec/feature/validations_spec.rb
|
117
182
|
- spec/feature/versions_spec.rb
|
118
183
|
- spec/models/attribute_name_calculator_spec.rb
|
119
184
|
- spec/models/base_uploader_spec.rb
|
120
|
-
- spec/models/basic_model_spec.rb
|
121
185
|
- spec/models/config_spec.rb
|
122
186
|
- spec/models/file_spec.rb
|
123
187
|
- spec/models/local_storage_spec.rb
|
188
|
+
- spec/models/model_spec.rb
|
124
189
|
- spec/models/s3_storage_spec.rb
|
125
190
|
- spec/models/url_source_spec.rb
|
126
191
|
- spec/spec_helper.rb
|
@@ -128,6 +193,7 @@ files:
|
|
128
193
|
- spec/support/data/example.xml
|
129
194
|
- spec/support/data/text.txt
|
130
195
|
- spec/support/models.rb
|
196
|
+
- spec/support/schema.rb
|
131
197
|
homepage: https://github.com/rogercampos/saviour
|
132
198
|
licenses:
|
133
199
|
- MIT
|
@@ -148,7 +214,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
148
214
|
version: '0'
|
149
215
|
requirements: []
|
150
216
|
rubyforge_project:
|
151
|
-
rubygems_version: 2.5.1
|
217
|
+
rubygems_version: 2.4.5.1
|
152
218
|
signing_key:
|
153
219
|
specification_version: 4
|
154
220
|
summary: File storage handler following active record model lifecycle
|
data/DECOMPOSE.md
DELETED
@@ -1,66 +0,0 @@
|
|
1
|
-
- New saviour-ar gem will provide what's currently in saviour gem
|
2
|
-
- saviour gem will be data-storage independent. It will provide a way to manage files with processors, but without a
|
3
|
-
lifecycle attached to a database-model. It will provide a more low level api.
|
4
|
-
- saviour-ar will provide the specific integration with active record.
|
5
|
-
|
6
|
-
|
7
|
-
# Saviour agnostic gem API
|
8
|
-
|
9
|
-
|
10
|
-
First, you'll need to define what files can be saved in what objects (any Ruby class). You can do that by including the `Saviour::BasicModel` module, example:
|
11
|
-
|
12
|
-
```
|
13
|
-
class MyObject
|
14
|
-
include Saviour::BasicModel
|
15
|
-
|
16
|
-
attach_file :image, ImageUploader
|
17
|
-
attach_file :scheme, FileUploader
|
18
|
-
end
|
19
|
-
```
|
20
|
-
|
21
|
-
Now, you can assign and work with the files associated to instances of MyObject with the following api:
|
22
|
-
|
23
|
-
```
|
24
|
-
# New file
|
25
|
-
|
26
|
-
a = MyObject.new
|
27
|
-
a.image = File.open('/path/image.jpg')
|
28
|
-
a.image.assign File.open('newfile.jpg')
|
29
|
-
a.image.changed? # => true
|
30
|
-
saved_path = a.image.write # => persists file in the storage and returns the path in which the file has been saved
|
31
|
-
|
32
|
-
b = MyObject.new
|
33
|
-
b.image.set_path!(saved_path) # => Link this image to the persisted image from before
|
34
|
-
b.image.exists? # => true
|
35
|
-
b.image.read # -> return bytes
|
36
|
-
b.image.delete # -> delete
|
37
|
-
b.image.exists? # => false
|
38
|
-
|
39
|
-
# ...
|
40
|
-
```
|
41
|
-
|
42
|
-
If you want to work directly managing the files associated to those objects, you can use the `Saviour::File` public API directly.
|
43
|
-
|
44
|
-
However, Saviour is designed to work with models that are saved in some kind of persistent storage, like a database of some sort. This is why Saviour also provides a generic `LifeCycle` service which you can use to simulate the persistence lifecycle of the object. You can then use:
|
45
|
-
|
46
|
-
```
|
47
|
-
a = MyObject.new image: File.open('image.jpg')
|
48
|
-
|
49
|
-
Saviour::LifeCycle.new(a).save!
|
50
|
-
Saviour::LifeCycle.new(a).delete!
|
51
|
-
```
|
52
|
-
|
53
|
-
`save!` will have the effect of saving all the attachments associated with the object, and `delete!` will have the effect of removing all the files associated with this object from the file storage defined.
|
54
|
-
|
55
|
-
Using LifeCycle you consider the object as a whole, while working with individual files you have more control, but you always operate with individual files.
|
56
|
-
|
57
|
-
The feature of versions, for example, only applies when you use the LifeCycle approach, since, by definition, a version is automatically constructed from the original file while this one is saved, and this involved operating in two or more attachments at the same time over an specific object. Since in Saviour versions can be managed exactly like regular attachments, such behavior don't apply when you work with File instances directly.
|
58
|
-
|
59
|
-
|
60
|
-
# Saviour API for developers
|
61
|
-
|
62
|
-
If you want to develop a new gem to integrate Saviour with another persistence technology, you need to do two things.
|
63
|
-
|
64
|
-
1) Write a class with the api #read, #write and #persisted? and give it to Savior, so he knows how to work with the persistence layer.
|
65
|
-
2) Write a new module for the final users to include in their models, providing the expected hooks so that the usage of the `LifeCycle` is automatic and transparent.
|
66
|
-
|
data/lib/saviour/basic_model.rb
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
# Port and simplification of ActiveSupport class attribute
|
2
|
-
module Saviour
|
3
|
-
module ClassAttribute
|
4
|
-
def class_attribute(*attrs)
|
5
|
-
attrs.each do |name|
|
6
|
-
singleton_class.instance_eval do
|
7
|
-
undef_method(name) if method_defined?(name)
|
8
|
-
end
|
9
|
-
|
10
|
-
define_singleton_method(name) { nil }
|
11
|
-
|
12
|
-
singleton_class.instance_eval do
|
13
|
-
undef_method("#{name}=") if method_defined?("#{name}=")
|
14
|
-
end
|
15
|
-
|
16
|
-
define_singleton_method("#{name}=") do |val|
|
17
|
-
singleton_class.class_eval do
|
18
|
-
undef_method(name) if method_defined?(name)
|
19
|
-
define_method(name) { val }
|
20
|
-
end
|
21
|
-
val
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|