saviour 0.2.0

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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.travis.yml +10 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +491 -0
  7. data/Rakefile +6 -0
  8. data/lib/saviour.rb +170 -0
  9. data/lib/saviour/base_uploader.rb +81 -0
  10. data/lib/saviour/config.rb +13 -0
  11. data/lib/saviour/file.rb +124 -0
  12. data/lib/saviour/local_storage.rb +72 -0
  13. data/lib/saviour/processors/digest.rb +16 -0
  14. data/lib/saviour/s3_storage.rb +77 -0
  15. data/lib/saviour/string_source.rb +15 -0
  16. data/lib/saviour/uploader/element.rb +19 -0
  17. data/lib/saviour/uploader/processors_runner.rb +88 -0
  18. data/lib/saviour/uploader/store_dir_extractor.rb +41 -0
  19. data/lib/saviour/url_source.rb +55 -0
  20. data/lib/saviour/version.rb +3 -0
  21. data/saviour.gemspec +26 -0
  22. data/spec/feature/access_to_model_and_mounted_as.rb +30 -0
  23. data/spec/feature/crud_workflows_spec.rb +138 -0
  24. data/spec/feature/persisted_path_spec.rb +35 -0
  25. data/spec/feature/reload_model_spec.rb +25 -0
  26. data/spec/feature/validations_spec.rb +172 -0
  27. data/spec/feature/versions_spec.rb +186 -0
  28. data/spec/models/base_uploader_spec.rb +396 -0
  29. data/spec/models/config_spec.rb +16 -0
  30. data/spec/models/file_spec.rb +210 -0
  31. data/spec/models/local_storage_spec.rb +154 -0
  32. data/spec/models/processors/digest_spec.rb +22 -0
  33. data/spec/models/s3_storage_spec.rb +170 -0
  34. data/spec/models/saviour_spec.rb +80 -0
  35. data/spec/models/url_source_spec.rb +76 -0
  36. data/spec/spec_helper.rb +93 -0
  37. data/spec/support/data/camaloon.jpg +0 -0
  38. data/spec/support/data/example.xml +21 -0
  39. data/spec/support/data/text.txt +1 -0
  40. data/spec/support/models.rb +3 -0
  41. data/spec/support/schema.rb +9 -0
  42. metadata +196 -0
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe "persisted path" do
4
+ before { Saviour::Config.storage = Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com") }
5
+ after { Saviour::Config.storage = nil }
6
+
7
+ context "can change the default_path on the uploader and previous instances are not affected" do
8
+ it do
9
+ uploader = Class.new(Saviour::BaseUploader) { store_dir { "/store/dir" } }
10
+ klass = Class.new(Test) { include Saviour }
11
+ klass.attach_file :file, uploader
12
+
13
+ with_test_file("example.xml") do |example|
14
+ a = klass.create!
15
+ expect(a.update_attributes(file: example)).to be_truthy
16
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
17
+ expect(File.dirname(a[:file])).to eq "/store/dir"
18
+
19
+
20
+ uploader.class_eval { store_dir { "/another/dir" } }
21
+
22
+ with_test_file("camaloon.jpg") do |example_2|
23
+ b = klass.create!
24
+ expect(b.update_attributes(file: example_2)).to be_truthy
25
+
26
+ expect(Saviour::Config.storage.exists?(b[:file])).to be_truthy
27
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
28
+
29
+ expect(File.dirname(b[:file])).to eq "/another/dir"
30
+ expect(File.dirname(a[:file])).to eq "/store/dir"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe "reload model" do
4
+ before { Saviour::Config.storage = Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com") }
5
+ after { Saviour::Config.storage = nil }
6
+
7
+ context "updates the Saviour::File instance" do
8
+ it do
9
+ uploader = Class.new(Saviour::BaseUploader) { store_dir { "/store/dir" } }
10
+ klass = Class.new(Test) { include Saviour }
11
+ klass.attach_file :file, uploader
12
+ a = klass.create!
13
+ b = klass.find(a.id)
14
+
15
+ with_test_file("example.xml") do |example|
16
+ a.update_attributes! file: example
17
+ expect(a.file.exists?).to be_truthy
18
+ expect(b.file.exists?).to be_falsey
19
+
20
+ b.reload
21
+ expect(b.file.exists?).to be_truthy
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,172 @@
1
+ require 'spec_helper'
2
+
3
+ describe "validations saving a new file" do
4
+ before { Saviour::Config.storage = Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com") }
5
+ after { Saviour::Config.storage = nil }
6
+
7
+ let(:uploader) {
8
+ Class.new(Saviour::BaseUploader) {
9
+ store_dir { "/store/dir" }
10
+ }
11
+ }
12
+
13
+ let(:base_klass) {
14
+ a = Class.new(Test) { include Saviour }
15
+ a.attach_file :file, uploader
16
+ a
17
+ }
18
+
19
+ it "fails at block validation" do
20
+ klass = Class.new(base_klass) do
21
+ attach_validation(:file) do |contents, _|
22
+ errors.add(:file, "Cannot start with X") if contents[0] == 'X'
23
+ end
24
+ end
25
+
26
+ with_test_file("example.xml") do |example|
27
+ allow(example).to receive(:read).and_return("X-Extra contents for the file")
28
+ a = klass.new
29
+ a.file = example
30
+ expect(a).not_to be_valid
31
+ expect(a.errors[:file][0]).to eq "Cannot start with X"
32
+ end
33
+
34
+ with_test_file("example.xml") do |example|
35
+ a = klass.new
36
+ a.file = example
37
+ expect(a).to be_valid
38
+ expect(a.save).to be_truthy
39
+ end
40
+ end
41
+
42
+
43
+ it "fails at method validation" do
44
+ klass = Class.new(base_klass) do
45
+ attach_validation :file, :check_filesize
46
+
47
+ def check_filesize(contents, _)
48
+ errors.add(:file, "Filesize must be less than 10 bytes") if contents.bytesize >= 10
49
+ end
50
+ end
51
+
52
+ with_test_file("example.xml") do |example|
53
+ allow(example).to receive(:read).and_return("1234567890")
54
+ a = klass.new
55
+ a.file = example
56
+ expect(a).not_to be_valid
57
+ expect(a.errors[:file][0]).to eq "Filesize must be less than 10 bytes"
58
+ end
59
+
60
+ with_test_file("example.xml") do |example|
61
+ allow(example).to receive(:read).and_return("123456789")
62
+ a = klass.new
63
+ a.file = example
64
+ expect(a).to be_valid
65
+ end
66
+ end
67
+
68
+ it "combined validatinos" do
69
+ klass = Class.new(base_klass) do
70
+ attach_validation :file, :check_filesize
71
+ attach_validation(:file) do |contents, _|
72
+ errors.add(:file, "Cannot start with X") if contents[0] == 'X'
73
+ end
74
+
75
+ def check_filesize(contents, _)
76
+ errors.add(:file, "Filesize must be less than 10 bytes") if contents.bytesize >= 10
77
+ end
78
+ end
79
+
80
+ with_test_file("example.xml") do |example|
81
+ allow(example).to receive(:read).and_return("X-Ex")
82
+ a = klass.new
83
+ a.file = example
84
+ expect(a).not_to be_valid
85
+ expect(a.errors[:file][0]).to eq "Cannot start with X"
86
+ end
87
+
88
+ with_test_file("example.xml") do |example|
89
+ allow(example).to receive(:read).and_return("Ex too long content")
90
+ a = klass.new
91
+ a.file = example
92
+ expect(a).not_to be_valid
93
+ expect(a.errors[:file][0]).to eq "Filesize must be less than 10 bytes"
94
+ end
95
+
96
+ # Consistent order
97
+ with_test_file("example.xml") do |example|
98
+ allow(example).to receive(:read).and_return("X-Ex too long content")
99
+ a = klass.new
100
+ a.file = example
101
+ expect(a).not_to be_valid
102
+ expect(a.errors[:file][0]).to eq "Filesize must be less than 10 bytes"
103
+ expect(a.errors[:file][1]).to eq "Cannot start with X"
104
+ end
105
+ end
106
+
107
+ it "validates by filename" do
108
+ klass = Class.new(base_klass) do
109
+ attach_validation :file, :check_filename
110
+
111
+ def check_filename(_, filename)
112
+ errors.add(:file, "Only .jpg files") unless filename =~ /\.jpg/
113
+ end
114
+ end
115
+
116
+ with_test_file("example.xml") do |example|
117
+ allow(example).to receive(:read).and_return("X-Ex")
118
+ a = klass.new
119
+ a.file = example
120
+ expect(a).not_to be_valid
121
+ expect(a.errors[:file][0]).to eq "Only .jpg files"
122
+ end
123
+
124
+ with_test_file("camaloon.jpg") do |example|
125
+ allow(example).to receive(:read).and_return("X-Ex")
126
+ a = klass.new
127
+ a.file = example
128
+ expect(a).to be_valid
129
+ end
130
+ end
131
+
132
+ it "receives the attached_as information" do
133
+ klass = Class.new(base_klass) do
134
+ attach_validation :file, :check_filename
135
+
136
+ def check_filename(_, _, opts)
137
+ errors.add(:file, "Received error in #{opts[:attached_as]}")
138
+ end
139
+ end
140
+
141
+ with_test_file("example.xml") do |example|
142
+ a = klass.new file: example
143
+ expect(a).not_to be_valid
144
+ expect(a.errors[:file][0]).to eq "Received error in file"
145
+ end
146
+ end
147
+
148
+ context "versions are validated" do
149
+ let(:uploader) {
150
+ Class.new(Saviour::BaseUploader) {
151
+ store_dir { "/store/dir" }
152
+ version(:thumb)
153
+ }
154
+ }
155
+ let(:klass) {
156
+ Class.new(base_klass) do
157
+ attach_validation(:file) do |contents, _, opts|
158
+ errors.add(:file, "Cannot start with X in version #{opts[:version]}") if contents[0] == 'X'
159
+ end
160
+ end
161
+ }
162
+
163
+ it do
164
+ a = klass.create!
165
+ a.file.assign Saviour::StringSource.new("correct contents")
166
+ a.file(:thumb).assign Saviour::StringSource.new("X Incorrect contents")
167
+
168
+ expect(a).not_to be_valid
169
+ expect(a.errors[:file][0]).to eq "Cannot start with X in version thumb"
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe "saving a new file" do
4
+ before { Saviour::Config.storage = Saviour::LocalStorage.new(local_prefix: @tmpdir, public_url_prefix: "http://domain.com") }
5
+ after { Saviour::Config.storage = nil }
6
+
7
+ let(:uploader) {
8
+ Class.new(Saviour::BaseUploader) do
9
+ store_dir { "/store/dir" }
10
+
11
+ version(:thumb) do
12
+ store_dir { "/versions/store/dir" }
13
+ end
14
+ end
15
+ }
16
+
17
+ let(:klass) {
18
+ a = Class.new(Test) { include Saviour }
19
+ a.attach_file :file, uploader
20
+ a
21
+ }
22
+
23
+ describe "creation following main file" do
24
+ it do
25
+ with_test_file("example.xml") do |example|
26
+ a = klass.create!
27
+ expect(a.update_attributes(file: example)).to be_truthy
28
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
29
+ end
30
+ end
31
+ end
32
+
33
+ describe "deletion" do
34
+ it do
35
+ with_test_file("example.xml") do |example|
36
+ a = klass.create!
37
+ a.update_attributes(file: example)
38
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
39
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
40
+
41
+ a.destroy
42
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_falsey
43
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_falsey
44
+ end
45
+ end
46
+ end
47
+
48
+ describe "changes following main file" do
49
+ it do
50
+ with_test_file("example.xml") do |example|
51
+ a = klass.create!
52
+ expect(a.update_attributes(file: example)).to be_truthy
53
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
54
+
55
+ with_test_file("camaloon.jpg") do |file|
56
+ a.update_attributes(file: file)
57
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
58
+ file.rewind
59
+ expect(a.file(:thumb).read).to eq file.read
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "accessing file features directly" do
66
+ let(:uploader) {
67
+ Class.new(Saviour::BaseUploader) do
68
+ store_dir { "/store/dir" }
69
+
70
+ version(:thumb) do
71
+ store_dir { "/versions/store/dir" }
72
+ process { |contents, name| ["#{contents}_for_version_thumb", name] }
73
+ end
74
+ end
75
+ }
76
+
77
+ it "#url" do
78
+ with_test_file("example.xml") do |example, name|
79
+ a = klass.create!
80
+ expect(a.update_attributes(file: example)).to be_truthy
81
+
82
+ versioned_name = "#{File.basename(name, ".*")}_thumb#{File.extname(name)}"
83
+ expect(a.file(:thumb).url).to eq "http://domain.com/versions/store/dir/#{versioned_name}"
84
+ end
85
+ end
86
+
87
+ it "#read" do
88
+ with_test_file("text.txt") do |example|
89
+ a = klass.create!
90
+ a.update_attributes(file: example)
91
+
92
+ expect(a.file(:thumb).read).to eq "Hello world\n_for_version_thumb"
93
+ end
94
+ end
95
+
96
+ it "#delete" do
97
+ with_test_file("example.xml") do |example|
98
+ a = klass.create!
99
+ expect(a.update_attributes(file: example)).to be_truthy
100
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
101
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
102
+
103
+ a.file(:thumb).delete
104
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_falsey
105
+ expect(Saviour::Config.storage.exists?(a[:file])).to be_truthy
106
+ end
107
+ end
108
+
109
+ it "#exists?" do
110
+ with_test_file("example.xml") do |example|
111
+ a = klass.create!
112
+ expect(a.update_attributes(file: example)).to be_truthy
113
+ expect(a.file(:thumb).exists?).to be_truthy
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "assign specific version after first creation" do
119
+ it do
120
+ with_test_file("example.xml") do |example|
121
+ a = klass.create!
122
+ expect(a.update_attributes(file: example)).to be_truthy
123
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
124
+ expect(a[:file_thumb]).to eq "/versions/store/dir/#{File.basename(example, ".*")}_thumb.xml"
125
+
126
+ with_test_file("camaloon.jpg") do |ex2, filename|
127
+ a.file(:thumb).assign(ex2)
128
+
129
+ expect(a.save!).to be_truthy
130
+
131
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
132
+ expect(a[:file_thumb]).to eq "/versions/store/dir/#{File.basename(filename, ".*")}.jpg"
133
+ end
134
+ end
135
+ end
136
+
137
+ context do
138
+ let(:uploader) {
139
+ Class.new(Saviour::BaseUploader) do
140
+ store_dir { "/store/dir" }
141
+
142
+ version(:thumb) do
143
+ store_dir { "/versions/store/dir" }
144
+ process { |_, filename| ["modified_content", filename] }
145
+ end
146
+ end
147
+ }
148
+
149
+ it "runs the processors for that version only" do
150
+ with_test_file("example.xml") do |example|
151
+ a = klass.create!
152
+ expect(a.update_attributes(file: example)).to be_truthy
153
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
154
+ expect(a[:file_thumb]).to eq "/versions/store/dir/#{File.basename(example, ".*")}_thumb.xml"
155
+
156
+ with_test_file("camaloon.jpg") do |ex2, filename|
157
+ a.file(:thumb).assign(ex2)
158
+
159
+ expect(a.save!).to be_truthy
160
+
161
+ expect(Saviour::Config.storage.exists?(a[:file_thumb])).to be_truthy
162
+ expect(a[:file_thumb]).to eq "/versions/store/dir/#{File.basename(filename, ".*")}.jpg"
163
+ expect(Saviour::Config.storage.read(a[:file_thumb])).to eq "modified_content"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+
170
+ describe "respects version assignation vs main file assignation on conflict" do
171
+ it do
172
+ a = klass.create!
173
+
174
+ with_test_file("example.xml") do |file1, fname1|
175
+ with_test_file("camaloon.jpg") do |file2, fname2|
176
+ a.file.assign(file1)
177
+ a.file(:thumb).assign(file2)
178
+ a.save!
179
+
180
+ expect(a[:file]).to eq "/store/dir/#{fname1}"
181
+ expect(a[:file_thumb]).to eq "/versions/store/dir/#{fname2}"
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,396 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saviour::BaseUploader do
4
+ let(:mocked_storage) {
5
+ Class.new {
6
+ def write(content, filename)
7
+ # pass
8
+ end
9
+ }.new
10
+ }
11
+ before { allow(Saviour::Config).to receive(:storage).and_return(mocked_storage) }
12
+
13
+
14
+ describe "DSL" do
15
+ subject { Class.new(Saviour::BaseUploader) }
16
+
17
+ it do
18
+ subject.process :hola
19
+ expect(subject.processors[0][:element].method_or_block).to eq :hola
20
+ expect(subject.processors[0][:opts]).to eq({})
21
+ end
22
+
23
+ it do
24
+ subject.process :a
25
+ subject.process :resize, width: 50
26
+
27
+ expect(subject.processors[0][:element].method_or_block).to eq :a
28
+ expect(subject.processors[0][:opts]).to eq({})
29
+
30
+ expect(subject.processors[1][:element].method_or_block).to eq :resize
31
+ expect(subject.processors[1][:opts]).to eq({width: 50})
32
+ end
33
+
34
+ it do
35
+ subject.process { 5 }
36
+ expect(subject.processors[0][:element].method_or_block).to respond_to :call
37
+ expect(subject.processors[0][:element].method_or_block.call).to eq 5
38
+ end
39
+
40
+ it do
41
+ subject.store_dir { "/my/dir" }
42
+ expect(subject.store_dirs[0].method_or_block.call).to eq "/my/dir"
43
+ end
44
+
45
+ it do
46
+ subject.store_dir :method_to_return_the_dir
47
+ expect(subject.store_dirs[0].method_or_block).to eq :method_to_return_the_dir
48
+ end
49
+
50
+ it "can use store_dir twice and last prevails" do
51
+ subject.store_dir { "/my/dir" }
52
+ subject.store_dir { "/my/dir/4" }
53
+ expect(subject.store_dirs[0].method_or_block.call).to eq "/my/dir"
54
+ expect(subject.store_dirs[1].method_or_block.call).to eq "/my/dir/4"
55
+ end
56
+
57
+ it "is not accessible from subclasses, works in isolation" do
58
+ subject.process :hola
59
+ expect(subject.processors[0][:element].method_or_block).to eq :hola
60
+ expect(subject.processors[0][:opts]).to eq({})
61
+
62
+ subclass = Class.new(subject)
63
+ expect(subclass.processors).to eq []
64
+ end
65
+
66
+ describe "version" do
67
+ it "stores as elements with the given version" do
68
+ subject.process :hola
69
+ subject.version(:thumb) do
70
+ process :resize_to_thumb
71
+ end
72
+
73
+ expect(subject.processors[0][:element].method_or_block).to eq :hola
74
+ expect(subject.processors[0][:element].version).to eq nil
75
+ expect(subject.processors[0][:opts]).to eq({})
76
+
77
+ expect(subject.processors[1][:element].method_or_block).to eq :resize_to_thumb
78
+ expect(subject.processors[1][:element].version).to eq :thumb
79
+ expect(subject.processors[1][:opts]).to eq({})
80
+ end
81
+
82
+ it "respects ordering" do
83
+ subject.process :hola
84
+ subject.version(:thumb) { process :resize_to_thumb }
85
+ subject.process :top_level
86
+ subject.version(:another) { process(:foo) }
87
+
88
+ expect(subject.processors[0][:element].method_or_block).to eq :hola
89
+ expect(subject.processors[0][:element].version).to eq nil
90
+ expect(subject.processors[1][:element].method_or_block).to eq :resize_to_thumb
91
+ expect(subject.processors[1][:element].version).to eq :thumb
92
+ expect(subject.processors[2][:element].method_or_block).to eq :top_level
93
+ expect(subject.processors[2][:element].version).to eq nil
94
+ expect(subject.processors[3][:element].method_or_block).to eq :foo
95
+ expect(subject.processors[3][:element].version).to eq :another
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "initialization with data" do
101
+ it "can declare wathever" do
102
+ uploader = Class.new(Saviour::BaseUploader).new(data: {a: "2", data: "my file"})
103
+ expect(uploader).to respond_to :a
104
+ expect(uploader).to respond_to :data
105
+ expect(uploader.a).to eq "2"
106
+ end
107
+
108
+ it do
109
+ uploader = Class.new(Saviour::BaseUploader).new(data: {a: "2", data: "my file"})
110
+ expect(uploader).to respond_to :a
111
+
112
+ uploader = Class.new(Saviour::BaseUploader).new(data: {name: "johny"})
113
+ expect(uploader).not_to respond_to :a
114
+ end
115
+ end
116
+
117
+ describe "#write" do
118
+ subject { uploader.new(data: {model: "model", attached_as: "attached_as"}) }
119
+
120
+ context do
121
+ let(:uploader) { Class.new(Saviour::BaseUploader) }
122
+
123
+ it "error if no store_dir" do
124
+ expect { subject.write("contents", "filename.jpg") }.to raise_error(RuntimeError)
125
+ end
126
+ end
127
+
128
+ context do
129
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
130
+ store_dir { "/store/dir" }
131
+ } }
132
+
133
+ it "calls storage write" do
134
+ expect(Saviour::Config.storage).to receive(:write).with("contents", "/store/dir/file.jpg")
135
+ subject.write("contents", "file.jpg")
136
+ end
137
+
138
+ it "returns the fullpath" do
139
+ expect(subject.write("contents", "file.jpg")).to eq '/store/dir/file.jpg'
140
+ end
141
+ end
142
+
143
+ context do
144
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
145
+ store_dir { "/store/dir" }
146
+
147
+ def resize(contents, filename)
148
+ ["#{contents}-x2", filename]
149
+ end
150
+
151
+ process :resize
152
+ } }
153
+
154
+ it "calls the processors" do
155
+ expect(subject).to receive(:resize).with("content", "output.png").and_call_original
156
+ expect(Saviour::Config.storage).to receive(:write).with("content-x2", "/store/dir/output.png")
157
+ subject.write("content", "output.png")
158
+ end
159
+ end
160
+
161
+ context do
162
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
163
+ store_dir { "/store/dir" }
164
+
165
+ def resize(contents, filename)
166
+ ["#{contents}-x2", filename]
167
+ end
168
+
169
+ process :resize
170
+ process { |content, filename| ["#{content}_x9", "prefix-#{filename}"] }
171
+ } }
172
+
173
+ it "respects ordering on processor calling" do
174
+ expect(Saviour::Config.storage).to receive(:write).with("content-x2_x9", "/store/dir/prefix-output.png")
175
+ subject.write("content", "output.png")
176
+ end
177
+ end
178
+
179
+ context do
180
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
181
+ store_dir { "/store/dir" }
182
+
183
+ def resize(contents, filename, opts = {})
184
+ ["#{contents}-#{opts[:width]}-#{opts[:height]}", filename]
185
+ end
186
+
187
+ process :resize, width: 50, height: 10
188
+ } }
189
+
190
+ it "calls the method using the stored arguments" do
191
+ expect(Saviour::Config.storage).to receive(:write).with("content-50-10", "/store/dir/output.png")
192
+ subject.write("content", "output.png")
193
+ end
194
+ end
195
+
196
+ context do
197
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
198
+ store_dir { "/store/dir" }
199
+
200
+ def rename(contents, filename)
201
+ [contents, "#{model.id}_#{filename}"]
202
+ end
203
+
204
+ process :rename
205
+ process { |content, filename| [content, "#{model.name}_#{filename}"] }
206
+ } }
207
+
208
+ let(:model) { double(id: 8, name: "Robert") }
209
+ subject { uploader.new(data: {model: model, attached_as: "attached_as"}) }
210
+
211
+ it "can access model from processors" do
212
+ expect(Saviour::Config.storage).to receive(:write).with("content", "/store/dir/Robert_8_output.png")
213
+ subject.write("content", "output.png")
214
+ end
215
+ end
216
+ end
217
+
218
+ describe "version" do
219
+ subject { uploader.new(version: :thumb) }
220
+
221
+ describe "store_dir" do
222
+ context "is the last one defined for the given version" do
223
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
224
+ store_dir { "/store/dir" }
225
+ version(:thumb) do
226
+ store_dir { "/thumb/store/dir" }
227
+ end
228
+ store_dir { "/store/dir/second" }
229
+ } }
230
+
231
+ it do
232
+ expect(Saviour::Uploader::StoreDirExtractor.new(subject).store_dir).to eq "/thumb/store/dir"
233
+ end
234
+ end
235
+
236
+ context "is the last one defined without version if not specified per version" do
237
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
238
+ store_dir { "/store/dir" }
239
+ version(:thumb) { process(:whatever) }
240
+ store_dir { "/store/dir/second" }
241
+ } }
242
+
243
+ it do
244
+ expect(Saviour::Uploader::StoreDirExtractor.new(subject).store_dir).to eq "/store/dir/second"
245
+ end
246
+ end
247
+
248
+ end
249
+
250
+ describe "processing behaviour on write" do
251
+ context "fails if no store_dir defined for root version" do
252
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
253
+ version(:thumb) { store_dir { "/store/dir" } }
254
+ } }
255
+
256
+ it do
257
+ a = uploader.new(data: {model: "model", attached_as: "attached_as"})
258
+ expect { a.write('1', '2') }.to raise_error(RuntimeError)
259
+ end
260
+ end
261
+
262
+ context "with only one version" do
263
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
264
+ store_dir { "/store/dir" }
265
+
266
+ version(:thumb) do
267
+ store_dir { "/versions/store/dir" }
268
+ process { |contents, name| [contents, "2_#{name}"] }
269
+ end
270
+ } }
271
+
272
+ it do
273
+ a = uploader.new(version: :thumb)
274
+ expect(Saviour::Config.storage).to receive(:write).with("content", "/versions/store/dir/2_output.png")
275
+ a.write("content", "output.png")
276
+ end
277
+
278
+ it do
279
+ a = uploader.new
280
+ expect(Saviour::Config.storage).to receive(:write).with("content", "/store/dir/output.png")
281
+ a.write("content", "output.png")
282
+ end
283
+ end
284
+
285
+ context "multiple definitions" do
286
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
287
+ store_dir { "/store/dir" }
288
+ process { |contents, name| ["#{contents}_altered", name] }
289
+
290
+ version(:thumb) do
291
+ store_dir { "/versions/store/dir" }
292
+ process { |contents, name| [contents, "2_#{name}"] }
293
+ end
294
+ } }
295
+
296
+ it do
297
+ a = uploader.new(version: :thumb)
298
+ expect(Saviour::Config.storage).to receive(:write).with("content_altered", "/versions/store/dir/2_output.png")
299
+ a.write("content", "output.png")
300
+ end
301
+
302
+ it do
303
+ a = uploader.new
304
+ expect(Saviour::Config.storage).to receive(:write).with("content_altered", "/store/dir/output.png")
305
+ a.write("content", "output.png")
306
+ end
307
+ end
308
+
309
+ context "consecutive versions" do
310
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
311
+ store_dir { "/store/dir" }
312
+ process { |contents, name| ["#{contents}_altered", name] }
313
+
314
+ version(:thumb) do
315
+ store_dir { "/versions/store/dir" }
316
+ process { |contents, name| ["thumb_#{contents}", "2_#{name}"] }
317
+ end
318
+
319
+ version(:thumb_2) do
320
+ store_dir { "/versions/store/dir" }
321
+ process { |contents, name| ["thumb_2_#{contents}", "3_#{name}"] }
322
+ end
323
+
324
+ process { |contents, name| ["last_transform_#{contents}", name] }
325
+ } }
326
+
327
+ it do
328
+ a = uploader.new
329
+ expect(Saviour::Config.storage).to receive(:write).with("last_transform_content_altered", "/store/dir/output.png")
330
+ a.write("content", "output.png")
331
+ end
332
+
333
+ it do
334
+ a = uploader.new(version: :thumb)
335
+ expect(Saviour::Config.storage).to receive(:write).with("last_transform_thumb_content_altered", "/versions/store/dir/2_output.png")
336
+ a.write("content", "output.png")
337
+ end
338
+
339
+ it do
340
+ a = uploader.new(version: :thumb_2)
341
+ expect(Saviour::Config.storage).to receive(:write).with("last_transform_thumb_2_content_altered", "/versions/store/dir/3_output.png")
342
+ a.write("content", "output.png")
343
+ end
344
+ end
345
+ end
346
+ end
347
+
348
+ describe "#process_with_file" do
349
+ subject { uploader.new(data: {model: "model", attached_as: "attached_as"}) }
350
+
351
+ context do
352
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
353
+ store_dir { "/store/dir" }
354
+
355
+ def foo(file, filename)
356
+ ::File.write(file.path, "modified-contents")
357
+ [file, filename]
358
+ end
359
+
360
+ process_with_file :foo
361
+ } }
362
+
363
+ it "calls the processors" do
364
+ expect(subject).to receive(:foo).with(an_instance_of(Tempfile), "output.png").and_call_original
365
+ expect(Saviour::Config.storage).to receive(:write).with("modified-contents", "/store/dir/output.png")
366
+ subject.write("contents", "output.png")
367
+ end
368
+ end
369
+
370
+ context do
371
+ let(:uploader) { Class.new(Saviour::BaseUploader) {
372
+ store_dir { "/store/dir" }
373
+
374
+ process do |contents, filename|
375
+ ["#{contents}_first_run", filename]
376
+ end
377
+
378
+ process_with_file do |file, filename|
379
+ ::File.write(file.path, "#{::File.read(file.path)}-modified-contents")
380
+ [file, filename]
381
+ end
382
+
383
+ process :last_run
384
+
385
+ def last_run(contents, filename)
386
+ ["pre-#{contents}", "pre-#{filename}"]
387
+ end
388
+ } }
389
+
390
+ it "can mix types of runs between file and contents" do
391
+ expect(Saviour::Config.storage).to receive(:write).with("pre-contents_first_run-modified-contents", "/store/dir/pre-aaa.png")
392
+ subject.write("contents", "aaa.png")
393
+ end
394
+ end
395
+ end
396
+ end