jamesmacaulay-asset_cloud 0.5.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.
- data/.document +3 -0
- data/.gitignore +3 -0
- data/CHANGELOG +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +17 -0
- data/Rakefile +37 -0
- data/VERSION +1 -0
- data/asset_cloud.gemspec +79 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/asset_cloud/asset.rb +119 -0
- data/lib/asset_cloud/base.rb +178 -0
- data/lib/asset_cloud/blackhole_bucket.rb +24 -0
- data/lib/asset_cloud/bucket.rb +57 -0
- data/lib/asset_cloud/callbacks.rb +62 -0
- data/lib/asset_cloud/file_system_bucket.rb +79 -0
- data/lib/asset_cloud/free_key_locator.rb +39 -0
- data/lib/asset_cloud/invalid_bucket.rb +28 -0
- data/lib/asset_cloud/memory_bucket.rb +40 -0
- data/lib/asset_cloud/metadata.rb +30 -0
- data/lib/asset_cloud.rb +23 -0
- data/spec/asset_spec.rb +135 -0
- data/spec/base_spec.rb +77 -0
- data/spec/blackhole_bucket_spec.rb +41 -0
- data/spec/bucket_spec.rb +41 -0
- data/spec/callbacks_spec.rb +78 -0
- data/spec/file_system_spec.rb +73 -0
- data/spec/files/products/key.txt +1 -0
- data/spec/find_free_key_spec.rb +66 -0
- data/spec/memory_bucket_spec.rb +30 -0
- data/spec/regexp_spec.rb +20 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +4 -0
- metadata +95 -0
@@ -0,0 +1,79 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
class FileSystemBucket < Bucket
|
4
|
+
|
5
|
+
def ls(key = nil)
|
6
|
+
objects = []
|
7
|
+
base_path = File.join(path_for(key), '*')
|
8
|
+
|
9
|
+
Dir.glob(base_path).each do |f|
|
10
|
+
next unless File.file?(f)
|
11
|
+
objects.push Asset.at(cloud, relative_path_for(f) )
|
12
|
+
end
|
13
|
+
objects
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(key)
|
17
|
+
File.read(path_for(key))
|
18
|
+
rescue Errno::ENOENT => e
|
19
|
+
raise AssetCloud::AssetNotFoundError, key
|
20
|
+
end
|
21
|
+
|
22
|
+
def delete(key)
|
23
|
+
File.delete(path_for(key))
|
24
|
+
rescue Errno::ENOENT
|
25
|
+
end
|
26
|
+
|
27
|
+
def write(key, data)
|
28
|
+
full_path = path_for(key)
|
29
|
+
|
30
|
+
retried = false
|
31
|
+
|
32
|
+
begin
|
33
|
+
File.open(full_path, "wb+") { |fp| fp << data }
|
34
|
+
true
|
35
|
+
rescue Errno::ENOENT => e
|
36
|
+
if retried == false
|
37
|
+
directory = File.dirname(full_path)
|
38
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
39
|
+
retried = true
|
40
|
+
retry
|
41
|
+
else
|
42
|
+
raise
|
43
|
+
end
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def stat(key)
|
49
|
+
begin
|
50
|
+
stat = File.stat(path_for(key))
|
51
|
+
Metadata.new(true, stat.size, stat.ctime, stat.mtime)
|
52
|
+
rescue Errno::ENOENT => e
|
53
|
+
Metadata.new(false)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
|
59
|
+
def path_for(key)
|
60
|
+
cloud.path_for(key)
|
61
|
+
end
|
62
|
+
|
63
|
+
def path
|
64
|
+
cloud.path
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def remove_full_path_regexp
|
70
|
+
@regexp ||= /^#{path}\//
|
71
|
+
end
|
72
|
+
|
73
|
+
def relative_path_for(f)
|
74
|
+
f.sub(remove_full_path_regexp, '')
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
module FreeKeyLocator
|
4
|
+
|
5
|
+
def find_free_key_like(key, options = {})
|
6
|
+
# Check weather the suggested key name is free. If so we
|
7
|
+
# simply return it.
|
8
|
+
|
9
|
+
if not exist?(key)
|
10
|
+
key
|
11
|
+
else
|
12
|
+
|
13
|
+
ext = File.extname(key)
|
14
|
+
dirname = File.dirname(key)
|
15
|
+
base = dirname == '.' ? File.basename(key, ext) : File.join(File.dirname(key), File.basename(key, ext))
|
16
|
+
count = base.scan(/\d+$/).flatten.first.to_i
|
17
|
+
base = base.gsub(/([\-\_]?)\d+$/,'')
|
18
|
+
separator = $1 || '_'
|
19
|
+
|
20
|
+
|
21
|
+
# increase the count until you find a unused key
|
22
|
+
10.times do
|
23
|
+
count += 1
|
24
|
+
key = "#{base}#{separator}#{count}#{ext}"
|
25
|
+
return key unless exist?(key)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Ok we have to go random here...
|
29
|
+
100.times do
|
30
|
+
count += rand(9999999)
|
31
|
+
key = "#{base}#{separator}#{count}#{ext}"
|
32
|
+
return key unless exist?(key)
|
33
|
+
end
|
34
|
+
|
35
|
+
raise StandardError, 'Filesystem out of free filenames'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
class InvalidBucketError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class InvalidBucket < Bucket
|
6
|
+
Error = "No such namespace: %s".freeze
|
7
|
+
|
8
|
+
def ls(namespace)
|
9
|
+
raise InvalidBucketError, Error % key
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(key)
|
13
|
+
raise InvalidBucketError, Error % key
|
14
|
+
end
|
15
|
+
|
16
|
+
def write(key, data)
|
17
|
+
raise InvalidBucketError, Error % key
|
18
|
+
end
|
19
|
+
|
20
|
+
def delete(key)
|
21
|
+
raise InvalidBucketError, Error % key
|
22
|
+
end
|
23
|
+
|
24
|
+
def stat(key)
|
25
|
+
raise InvalidBucketError, Error % key
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
class MemoryBucket < Bucket
|
4
|
+
|
5
|
+
def initialize(*args)
|
6
|
+
super
|
7
|
+
@memory = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def ls(key = nil)
|
11
|
+
@memory.find_all do |key, value|
|
12
|
+
key.left(key.size) == namespace
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(key)
|
17
|
+
raise AssetCloud::AssetNotFoundError, key unless @memory.has_key?(key)
|
18
|
+
@memory[key]
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete(key)
|
22
|
+
@memory.delete(key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def write(key, data)
|
26
|
+
@memory[key] = data
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
|
31
|
+
def stat(key)
|
32
|
+
return Metadata.non_existing unless @memory.has_key?(key)
|
33
|
+
|
34
|
+
Metadata.new(true, @memory[key].size)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
class Metadata
|
4
|
+
attr_accessor :exist, :size, :created_at, :updated_at
|
5
|
+
|
6
|
+
def new?
|
7
|
+
!self.exist
|
8
|
+
end
|
9
|
+
|
10
|
+
def exist?
|
11
|
+
self.exist
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(exist, size = nil, created_at = nil, updated_at = nil)
|
15
|
+
self.exist, self.size, self.created_at, self.updated_at = exist, size, created_at, updated_at
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.existing
|
19
|
+
self.new(true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.non_existing
|
23
|
+
self.new false
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#<#{self.class.name}: exist:#{exist} size:#{size.inspect} bytes>"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/asset_cloud.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
|
3
|
+
# Core
|
4
|
+
require File.dirname(__FILE__) + '/asset_cloud/asset'
|
5
|
+
require File.dirname(__FILE__) + '/asset_cloud/metadata'
|
6
|
+
require File.dirname(__FILE__) + '/asset_cloud/bucket'
|
7
|
+
require File.dirname(__FILE__) + '/asset_cloud/invalid_bucket'
|
8
|
+
require File.dirname(__FILE__) + '/asset_cloud/blackhole_bucket'
|
9
|
+
require File.dirname(__FILE__) + '/asset_cloud/memory_bucket'
|
10
|
+
require File.dirname(__FILE__) + '/asset_cloud/file_system_bucket'
|
11
|
+
require File.dirname(__FILE__) + '/asset_cloud/base'
|
12
|
+
|
13
|
+
|
14
|
+
# Extensions
|
15
|
+
require File.dirname(__FILE__) + '/asset_cloud/free_key_locator'
|
16
|
+
require File.dirname(__FILE__) + '/asset_cloud/callbacks'
|
17
|
+
|
18
|
+
|
19
|
+
AssetCloud::Base.class_eval do
|
20
|
+
include AssetCloud::FreeKeyLocator
|
21
|
+
include AssetCloud::Callbacks
|
22
|
+
end
|
23
|
+
|
data/spec/asset_spec.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
3
|
+
|
4
|
+
describe "Asset" do
|
5
|
+
include AssetCloud
|
6
|
+
|
7
|
+
before do
|
8
|
+
@cloud = mock('Bucket')
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "when first created (without a value)" do
|
12
|
+
before do
|
13
|
+
@asset = AssetCloud::Asset.new(@cloud, "products/key.txt")
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should be return new_asset? => true" do
|
17
|
+
@asset.new_asset?.should == true
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should have a key" do
|
21
|
+
@asset.key.should == 'products/key.txt'
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should have a value of nil" do
|
25
|
+
|
26
|
+
@asset.value.should == nil
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have a basename" do
|
30
|
+
@asset.basename.should == 'key.txt'
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should have a basename without ext (if required)" do
|
34
|
+
@asset.basename_without_ext.should == 'key'
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should have an ext" do
|
38
|
+
@asset.extname.should == '.txt'
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
it "should store data to the bucket" do
|
43
|
+
@cloud.should_receive(:write).with("products/key.txt", 'value')
|
44
|
+
|
45
|
+
@asset.value = 'value'
|
46
|
+
@asset.store
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should not try to store data when it's value is nil" do
|
50
|
+
@cloud.should_receive(:write).never
|
51
|
+
|
52
|
+
@asset.store
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should not try to read data from bucket if its a new_asset" do
|
56
|
+
@cloud.should_receive(:read).never
|
57
|
+
|
58
|
+
@asset.value.should == nil
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should simply ignore calls to delete" do
|
62
|
+
@cloud.should_receive(:delete).never
|
63
|
+
|
64
|
+
@asset.delete
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "when first created with value" do
|
70
|
+
before do
|
71
|
+
@asset = AssetCloud::Asset.new(@cloud, "products/key.txt", 'value')
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should be return new_asset? => true" do
|
75
|
+
@asset.new_asset?.should == true
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
it "should have a value of 'value'" do
|
80
|
+
@asset.value.should == 'value'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should return false when asked if it exists because its still a new_asset" do
|
84
|
+
@asset.exist?.should == false
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
it "should not try to read data from bucket if its a new_asset" do
|
89
|
+
@cloud.should_receive(:read).never
|
90
|
+
|
91
|
+
@asset.value.should == 'value'
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should write data to the bucket" do
|
95
|
+
@cloud.should_receive(:write).with("products/key.txt", 'value')
|
96
|
+
@asset.store
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe "when fetched from the bucket" do
|
102
|
+
before do
|
103
|
+
@asset = AssetCloud::Asset.at(@cloud, "products/key.txt", 'value', AssetCloud::Metadata.new(true, 'value'.size, Time.now, Time.now))
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should be return new_asset? => false" do
|
107
|
+
@asset.new_asset?.should == false
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should indicate that it exists" do
|
111
|
+
|
112
|
+
@asset.exist?.should == true
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
it "should read the value from the bucket" do
|
117
|
+
@asset.value.should == 'value'
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
it "should simply ignore calls to delete" do
|
122
|
+
@cloud.should_receive(:delete).and_return(true)
|
123
|
+
|
124
|
+
@asset.delete
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should ask the bucket to create a full url" do
|
128
|
+
@cloud.should_receive(:url_for).with('products/key.txt').and_return('http://assets/products/key.txt')
|
129
|
+
|
130
|
+
@asset.url.should == 'http://assets/products/key.txt'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class SpecialAsset < AssetCloud::Asset
|
4
|
+
end
|
5
|
+
|
6
|
+
class BasicCloud < AssetCloud::Base
|
7
|
+
bucket :special, AssetCloud::MemoryBucket, :asset_class => SpecialAsset
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
describe BasicCloud do
|
12
|
+
directory = File.dirname(__FILE__) + '/files'
|
13
|
+
|
14
|
+
before do
|
15
|
+
@fs = BasicCloud.new(directory , 'http://assets/files' )
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should raise invalid bucket if none is given" do
|
19
|
+
@fs['image.jpg'].exist?.should == false
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
it "should be backed by a file system bucket" do
|
24
|
+
@fs['products/key.txt'].exist?.should == true
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise when listing non existing buckets" do
|
28
|
+
@fs.ls('products').should == [AssetCloud::Asset.new(@fs, 'products/key.txt')]
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
it "should allow you to create new assets" do
|
33
|
+
obj = @fs.build('new_file.test')
|
34
|
+
obj.should be_an_instance_of(AssetCloud::Asset)
|
35
|
+
obj.cloud.should be_an_instance_of(BasicCloud)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should raise error when using with minus relative or absolute paths" do
|
39
|
+
lambda { @fs['../test'] }.should raise_error(AssetCloud::IllegalPath)
|
40
|
+
lambda { @fs['/test'] }.should raise_error(AssetCloud::IllegalPath)
|
41
|
+
lambda { @fs['.../test'] }.should raise_error(AssetCloud::IllegalPath)
|
42
|
+
lambda { @fs['./test'] }.should raise_error(AssetCloud::IllegalPath)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should allow sensible relative filenames" do
|
46
|
+
@fs['assets/rails_logo.gif']
|
47
|
+
@fs['assets/rails-2.gif']
|
48
|
+
@fs['assets/223434.gif']
|
49
|
+
@fs['files/1.JPG']
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should compute complete urls to assets" do
|
53
|
+
@fs.url_for('products/key.txt').should == 'http://assets/files/products/key.txt'
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "#find" do
|
57
|
+
it "should return the appropriate asset when one exists" do
|
58
|
+
asset = @fs.find('products/key.txt')
|
59
|
+
asset.key.should == 'products/key.txt'
|
60
|
+
asset.value.should == 'value'
|
61
|
+
end
|
62
|
+
it "should raise AssetNotFoundError when the asset doesn't exist" do
|
63
|
+
lambda { @fs.find('products/not-there.txt') }.should raise_error(AssetCloud::AssetNotFoundError)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#bucket" do
|
68
|
+
it "should allow specifying a class to use for assets in this bucket" do
|
69
|
+
@fs['assets/rails_logo.gif'].should be_instance_of(AssetCloud::Asset)
|
70
|
+
@fs['special/fancy.txt'].should be_instance_of(SpecialAsset)
|
71
|
+
|
72
|
+
@fs.build('assets/foo').should be_instance_of(AssetCloud::Asset)
|
73
|
+
@fs.build('special/foo').should be_instance_of(SpecialAsset)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class BlackholeCloud < AssetCloud::Base
|
4
|
+
bucket AssetCloud::BlackholeBucket
|
5
|
+
end
|
6
|
+
|
7
|
+
describe BlackholeCloud do
|
8
|
+
directory = File.dirname(__FILE__) + '/files'
|
9
|
+
|
10
|
+
before do
|
11
|
+
@fs = BlackholeCloud.new(directory , 'http://assets/files' )
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should allow access to files using the [] operator" do
|
15
|
+
@fs['tmp/image.jpg']
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should return nil for non existent files" do
|
19
|
+
@fs['tmp/image.jpg'].exist?.should == false
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should still return nil, even if you wrote something there" do
|
23
|
+
@fs['tmp/image.jpg'] = 'test'
|
24
|
+
@fs['tmp/image.jpg'].exist?.should == false
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "when using a sub path" do
|
28
|
+
it "should allow access to files using the [] operator" do
|
29
|
+
@fs['tmp/image.jpg']
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should return nil for non existent files" do
|
33
|
+
@fs['tmp/image.jpg'].exist?.should == false
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should still return nil, even if you wrote something there" do
|
37
|
+
@fs['tmp/image.jpg'] = 'test'
|
38
|
+
@fs['tmp/image.jpg'].exist?.should == false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/spec/bucket_spec.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class ChainedCloud < AssetCloud::Base
|
4
|
+
bucket :stuff, AssetCloud::Bucket.chain(AssetCloud::MemoryBucket, AssetCloud::BlackholeBucket)
|
5
|
+
end
|
6
|
+
|
7
|
+
describe AssetCloud::Bucket do
|
8
|
+
directory = File.dirname(__FILE__) + '/files'
|
9
|
+
|
10
|
+
before(:each) do
|
11
|
+
@cloud = ChainedCloud.new(directory , 'http://assets/files' )
|
12
|
+
@bucket_chain = @cloud.buckets[:stuff]
|
13
|
+
@memory_bucket, @blackhole_bucket = @bucket_chain.chained_buckets
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#chain" do
|
17
|
+
it 'should take multiple Bucket classes and return a new Bucket class' do
|
18
|
+
@bucket_chain.should be_a_kind_of(AssetCloud::Bucket)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should return a Bucket which writes to each sub-bucket' do
|
22
|
+
@bucket_chain.chained_buckets.each do |bucket|
|
23
|
+
bucket.should_receive(:write).with('stuff/foo', 'bar').and_return(true)
|
24
|
+
bucket.should_receive(:delete).with('stuff/foo').and_return(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
@bucket_chain.write('stuff/foo', 'bar')
|
28
|
+
@bucket_chain.delete('stuff/foo')
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should return a Bucket which reads from only the first sub-bucket' do
|
32
|
+
@memory_bucket.should_receive(:read).with('stuff/foo').and_return('bar')
|
33
|
+
@memory_bucket.should_receive(:ls).with(nil).and_return(:some_assets)
|
34
|
+
@blackhole_bucket.should_not_receive(:read)
|
35
|
+
@blackhole_bucket.should_not_receive(:ls)
|
36
|
+
|
37
|
+
@bucket_chain.read('stuff/foo').should == 'bar'
|
38
|
+
@bucket_chain.ls.should == :some_assets
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class CallbackCloud < AssetCloud::Base
|
4
|
+
bucket :tmp, AssetCloud::MemoryBucket
|
5
|
+
|
6
|
+
|
7
|
+
after_delete :callback_after_delete
|
8
|
+
before_delete :callback_before_delete
|
9
|
+
|
10
|
+
after_write :callback_after_write
|
11
|
+
before_write :callback_before_write
|
12
|
+
end
|
13
|
+
|
14
|
+
class MethodRecordingCloud < AssetCloud::Base
|
15
|
+
attr_accessor :run_callbacks
|
16
|
+
|
17
|
+
bucket :tmp, AssetCloud::MemoryBucket
|
18
|
+
|
19
|
+
before_write :callback_before_write
|
20
|
+
after_write :callback_before_write
|
21
|
+
|
22
|
+
|
23
|
+
def method_missing(method, *args)
|
24
|
+
@run_callbacks << method.to_sym
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe CallbackCloud do
|
29
|
+
before { @fs = CallbackCloud.new(File.dirname(__FILE__) + '/files', 'http://assets/') }
|
30
|
+
|
31
|
+
it "should invoke callbacks after store" do
|
32
|
+
|
33
|
+
@fs.should_receive(:callback_before_write).with('tmp/file.txt', 'text').and_return(true)
|
34
|
+
@fs.should_receive(:callback_after_write).with('tmp/file.txt', 'text').and_return(true)
|
35
|
+
|
36
|
+
|
37
|
+
@fs.write 'tmp/file.txt', 'text'
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should invoke callbacks after delete" do
|
42
|
+
|
43
|
+
@fs.should_receive(:callback_before_delete).with('tmp/file.txt').and_return(true)
|
44
|
+
@fs.should_receive(:callback_after_delete).with('tmp/file.txt').and_return(true)
|
45
|
+
|
46
|
+
|
47
|
+
@fs.delete 'tmp/file.txt'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should invoke callbacks even when constructing a new asset" do
|
51
|
+
@fs.should_receive(:callback_before_write).with('tmp/file.txt', 'hello').and_return(true)
|
52
|
+
@fs.should_receive(:callback_after_write).with('tmp/file.txt', 'hello').and_return(true)
|
53
|
+
|
54
|
+
|
55
|
+
asset = @fs.build('tmp/file.txt')
|
56
|
+
asset.value = 'hello'
|
57
|
+
asset.store
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe MethodRecordingCloud do
|
64
|
+
before do
|
65
|
+
@fs = MethodRecordingCloud.new(File.dirname(__FILE__) + '/files', 'http://assets/')
|
66
|
+
@fs.run_callbacks = []
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should record event when invoked' do
|
70
|
+
@fs.write('tmp/file.txt', 'random data')
|
71
|
+
@fs.run_callbacks.should == [:callback_before_write, :callback_before_write]
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'should record event when assignment operator is used' do
|
75
|
+
@fs['tmp/file.txt'] = 'random data'
|
76
|
+
@fs.run_callbacks.should == [:callback_before_write, :callback_before_write]
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class FileSystemCloud < AssetCloud::Base
|
4
|
+
bucket AssetCloud::InvalidBucket
|
5
|
+
bucket :products, AssetCloud::FileSystemBucket
|
6
|
+
bucket :tmp, AssetCloud::FileSystemBucket
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
describe FileSystemCloud do
|
11
|
+
directory = File.dirname(__FILE__) + '/files'
|
12
|
+
|
13
|
+
before do
|
14
|
+
@fs = FileSystemCloud.new(directory , 'http://assets/files' )
|
15
|
+
FileUtils.mkdir_p(directory + '/tmp')
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
FileUtils.rm_rf(directory + '/tmp')
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should use invalid bucket for random directories" do
|
23
|
+
@fs.bucket_for('does-not-exist/file.txt').should be_an_instance_of(AssetCloud::InvalidBucket)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should use filesystem bucekt for products/ and tmp/ directories" do
|
27
|
+
@fs.bucket_for('products/file.txt').should be_an_instance_of(AssetCloud::FileSystemBucket)
|
28
|
+
@fs.bucket_for('tmp/file.txt').should be_an_instance_of(AssetCloud::FileSystemBucket)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should return Asset for existing files" do
|
32
|
+
@fs['products/key.txt'].exist?.should == true
|
33
|
+
@fs['products/key.txt'].should be_an_instance_of(AssetCloud::Asset)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to test if a file exists or not" do
|
37
|
+
@fs.stat('products/key.txt').exist?.should == true
|
38
|
+
@fs.stat('products/key2.txt').exist?.should == false
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should be able to list files" do
|
42
|
+
@fs.ls('products').collect(&:key).should == ['products/key.txt']
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'when modifying file system' do
|
46
|
+
|
47
|
+
it "should call write after storing an asset" do
|
48
|
+
@fs.buckets[:tmp].should_receive(:write).with('tmp/new_file.test', 'hello world').and_return(true)
|
49
|
+
|
50
|
+
@fs.build('tmp/new_file.test', 'hello world').store
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should be able to create new files" do
|
54
|
+
@fs.build('tmp/new_file.test', 'hello world').store
|
55
|
+
|
56
|
+
@fs.stat('tmp/new_file.test').exist.should == true
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should be able to create new files with simple assignment" do
|
60
|
+
@fs['tmp/new_file.test'] = 'hello world'
|
61
|
+
|
62
|
+
@fs.stat('tmp/new_file.test').exist.should == true
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should create directories as needed" do
|
66
|
+
@fs.build('tmp/new_file.test', 'hello world').store
|
67
|
+
|
68
|
+
@fs['tmp/new_file.test'].exist?.should == true
|
69
|
+
@fs['tmp/new_file.test'].value.should == 'hello world'
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
value
|