jamesmacaulay-asset_cloud 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -1
- data/VERSION +1 -1
- data/asset_cloud.gemspec +23 -13
- data/lib/asset_cloud.rb +19 -7
- data/lib/asset_cloud/asset.rb +20 -2
- data/lib/asset_cloud/base.rb +19 -5
- data/lib/asset_cloud/bucket.rb +12 -26
- data/lib/asset_cloud/buckets/active_record_bucket.rb +57 -0
- data/lib/asset_cloud/{blackhole_bucket.rb → buckets/blackhole_bucket.rb} +0 -0
- data/lib/asset_cloud/buckets/bucket_chain.rb +84 -0
- data/lib/asset_cloud/{file_system_bucket.rb → buckets/file_system_bucket.rb} +1 -1
- data/lib/asset_cloud/{invalid_bucket.rb → buckets/invalid_bucket.rb} +0 -0
- data/lib/asset_cloud/{memory_bucket.rb → buckets/memory_bucket.rb} +7 -5
- data/lib/asset_cloud/buckets/versioned_memory_bucket.rb +33 -0
- data/lib/asset_cloud/callbacks.rb +27 -24
- data/lib/asset_cloud/validations.rb +43 -0
- data/spec/active_record_bucket_spec.rb +95 -0
- data/spec/asset_spec.rb +4 -0
- data/spec/base_spec.rb +21 -0
- data/spec/bucket_chain_spec.rb +158 -0
- data/spec/callbacks_spec.rb +31 -1
- data/spec/files/versioned_stuff/foo +1 -0
- data/spec/memory_bucket_spec.rb +16 -0
- data/spec/validations_spec.rb +45 -0
- data/spec/versioned_memory_bucket_spec.rb +30 -0
- metadata +20 -9
- data/spec/bucket_spec.rb +0 -41
data/CHANGELOG
CHANGED
@@ -1,7 +1,14 @@
|
|
1
|
+
July 2009:
|
2
|
+
|
3
|
+
* Added ActiveRecordBucket
|
4
|
+
|
1
5
|
June 2009:
|
2
6
|
|
7
|
+
* Buckets can be versioned
|
8
|
+
* Transactional writes with BucketChains
|
9
|
+
* Asset callbacks
|
3
10
|
* Use different Asset subclasses for each bucket in a cloud (james)
|
4
11
|
* AssetCloud::Base#find now calls asset_at but will raise errors if the asset doesn't exist (james)
|
5
12
|
* original AssetCloud::Base#find renamed to #asset_at (james)
|
6
13
|
* Added simple bucket multiplexing with AssetCloud::Bucket.chain (james)
|
7
|
-
* extraction from Shopify
|
14
|
+
* extraction from Shopify (tobi)
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.5.
|
1
|
+
0.5.2
|
data/asset_cloud.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{asset_cloud}
|
5
|
-
s.version = "0.5.
|
5
|
+
s.version = "0.5.2"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Shopify"]
|
9
|
-
s.date = %q{2009-
|
9
|
+
s.date = %q{2009-07-03}
|
10
10
|
s.description = %q{An abstraction layer around arbitrary and diverse asset stores.}
|
11
11
|
s.email = %q{developers@shopify.com}
|
12
12
|
s.extra_rdoc_files = [
|
@@ -27,44 +27,54 @@ Gem::Specification.new do |s|
|
|
27
27
|
"lib/asset_cloud.rb",
|
28
28
|
"lib/asset_cloud/asset.rb",
|
29
29
|
"lib/asset_cloud/base.rb",
|
30
|
-
"lib/asset_cloud/blackhole_bucket.rb",
|
31
30
|
"lib/asset_cloud/bucket.rb",
|
31
|
+
"lib/asset_cloud/buckets/active_record_bucket.rb",
|
32
|
+
"lib/asset_cloud/buckets/blackhole_bucket.rb",
|
33
|
+
"lib/asset_cloud/buckets/bucket_chain.rb",
|
34
|
+
"lib/asset_cloud/buckets/file_system_bucket.rb",
|
35
|
+
"lib/asset_cloud/buckets/invalid_bucket.rb",
|
36
|
+
"lib/asset_cloud/buckets/memory_bucket.rb",
|
37
|
+
"lib/asset_cloud/buckets/versioned_memory_bucket.rb",
|
32
38
|
"lib/asset_cloud/callbacks.rb",
|
33
|
-
"lib/asset_cloud/file_system_bucket.rb",
|
34
39
|
"lib/asset_cloud/free_key_locator.rb",
|
35
|
-
"lib/asset_cloud/invalid_bucket.rb",
|
36
|
-
"lib/asset_cloud/memory_bucket.rb",
|
37
40
|
"lib/asset_cloud/metadata.rb",
|
41
|
+
"lib/asset_cloud/validations.rb",
|
42
|
+
"spec/active_record_bucket_spec.rb",
|
38
43
|
"spec/asset_spec.rb",
|
39
44
|
"spec/base_spec.rb",
|
40
45
|
"spec/blackhole_bucket_spec.rb",
|
41
|
-
"spec/
|
46
|
+
"spec/bucket_chain_spec.rb",
|
42
47
|
"spec/callbacks_spec.rb",
|
43
48
|
"spec/file_system_spec.rb",
|
44
49
|
"spec/files/products/key.txt",
|
50
|
+
"spec/files/versioned_stuff/foo",
|
45
51
|
"spec/find_free_key_spec.rb",
|
46
52
|
"spec/memory_bucket_spec.rb",
|
47
53
|
"spec/regexp_spec.rb",
|
48
54
|
"spec/spec.opts",
|
49
|
-
"spec/spec_helper.rb"
|
55
|
+
"spec/spec_helper.rb",
|
56
|
+
"spec/validations_spec.rb",
|
57
|
+
"spec/versioned_memory_bucket_spec.rb"
|
50
58
|
]
|
51
|
-
s.has_rdoc = true
|
52
59
|
s.homepage = %q{http://github.com/Shopify/asset_cloud}
|
53
60
|
s.rdoc_options = ["--charset=UTF-8"]
|
54
61
|
s.require_paths = ["lib"]
|
55
|
-
s.rubygems_version = %q{1.3.
|
62
|
+
s.rubygems_version = %q{1.3.4}
|
56
63
|
s.summary = %q{An abstraction layer around arbitrary and diverse asset stores.}
|
57
64
|
s.test_files = [
|
58
|
-
"spec/
|
65
|
+
"spec/active_record_bucket_spec.rb",
|
66
|
+
"spec/asset_spec.rb",
|
59
67
|
"spec/base_spec.rb",
|
60
68
|
"spec/blackhole_bucket_spec.rb",
|
61
|
-
"spec/
|
69
|
+
"spec/bucket_chain_spec.rb",
|
62
70
|
"spec/callbacks_spec.rb",
|
63
71
|
"spec/file_system_spec.rb",
|
64
72
|
"spec/find_free_key_spec.rb",
|
65
73
|
"spec/memory_bucket_spec.rb",
|
66
74
|
"spec/regexp_spec.rb",
|
67
|
-
"spec/spec_helper.rb"
|
75
|
+
"spec/spec_helper.rb",
|
76
|
+
"spec/validations_spec.rb",
|
77
|
+
"spec/versioned_memory_bucket_spec.rb"
|
68
78
|
]
|
69
79
|
|
70
80
|
if s.respond_to? :specification_version then
|
data/lib/asset_cloud.rb
CHANGED
@@ -4,20 +4,32 @@ require 'active_support'
|
|
4
4
|
require File.dirname(__FILE__) + '/asset_cloud/asset'
|
5
5
|
require File.dirname(__FILE__) + '/asset_cloud/metadata'
|
6
6
|
require File.dirname(__FILE__) + '/asset_cloud/bucket'
|
7
|
-
require File.dirname(__FILE__) + '/asset_cloud/
|
8
|
-
require File.dirname(__FILE__) + '/asset_cloud/blackhole_bucket'
|
9
|
-
require File.dirname(__FILE__) + '/asset_cloud/
|
10
|
-
require File.dirname(__FILE__) + '/asset_cloud/file_system_bucket'
|
11
|
-
require File.dirname(__FILE__) + '/asset_cloud/
|
7
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/active_record_bucket'
|
8
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/blackhole_bucket'
|
9
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/bucket_chain'
|
10
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/file_system_bucket'
|
11
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/invalid_bucket'
|
12
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/memory_bucket'
|
13
|
+
require File.dirname(__FILE__) + '/asset_cloud/buckets/versioned_memory_bucket'
|
14
|
+
require File.dirname(__FILE__) + '/asset_cloud/base'
|
12
15
|
|
13
16
|
|
14
17
|
# Extensions
|
15
18
|
require File.dirname(__FILE__) + '/asset_cloud/free_key_locator'
|
16
19
|
require File.dirname(__FILE__) + '/asset_cloud/callbacks'
|
20
|
+
require File.dirname(__FILE__) + '/asset_cloud/validations'
|
17
21
|
|
18
22
|
|
19
23
|
AssetCloud::Base.class_eval do
|
20
|
-
include AssetCloud::FreeKeyLocator
|
21
|
-
include AssetCloud::Callbacks
|
24
|
+
include AssetCloud::FreeKeyLocator
|
25
|
+
include AssetCloud::Callbacks
|
26
|
+
callback_methods :write, :delete
|
27
|
+
end
|
28
|
+
|
29
|
+
AssetCloud::Asset.class_eval do
|
30
|
+
include AssetCloud::Callbacks
|
31
|
+
callback_methods :store, :delete
|
32
|
+
|
33
|
+
include AssetCloud::Validations
|
22
34
|
end
|
23
35
|
|
data/lib/asset_cloud/asset.rb
CHANGED
@@ -6,7 +6,7 @@ module AssetCloud
|
|
6
6
|
class AssetNotSaved < AssetError
|
7
7
|
end
|
8
8
|
|
9
|
-
class Asset
|
9
|
+
class Asset
|
10
10
|
include Comparable
|
11
11
|
attr_accessor :key, :value, :cloud, :metadata
|
12
12
|
attr_accessor :new_asset
|
@@ -65,6 +65,10 @@ module AssetCloud
|
|
65
65
|
|
66
66
|
def created_at
|
67
67
|
metadata.created_at
|
68
|
+
end
|
69
|
+
|
70
|
+
def updated_at
|
71
|
+
metadata.updated_at
|
68
72
|
end
|
69
73
|
|
70
74
|
def delete
|
@@ -110,10 +114,24 @@ module AssetCloud
|
|
110
114
|
def url
|
111
115
|
cloud.url_for key
|
112
116
|
end
|
113
|
-
|
117
|
+
|
118
|
+
def bucket_name
|
119
|
+
@key.split('/').first
|
120
|
+
end
|
114
121
|
|
115
122
|
def inspect
|
116
123
|
"#<#{self.class.name}: #{key}>"
|
117
124
|
end
|
125
|
+
|
126
|
+
# versioning
|
127
|
+
|
128
|
+
def rollback(version)
|
129
|
+
self.value = cloud.read_version(key, version)
|
130
|
+
self
|
131
|
+
end
|
132
|
+
|
133
|
+
def versions
|
134
|
+
cloud.versions(key)
|
135
|
+
end
|
118
136
|
end
|
119
137
|
end
|
data/lib/asset_cloud/base.rb
CHANGED
@@ -5,7 +5,7 @@ module AssetCloud
|
|
5
5
|
end
|
6
6
|
|
7
7
|
class Base
|
8
|
-
cattr_accessor :logger
|
8
|
+
cattr_accessor :logger
|
9
9
|
|
10
10
|
VALID_PATHS = /^[a-z0-9][a-z0-9_\-\/]+([a-z0-9][\w\-\ \.]*\.\w{2,6})?$/i
|
11
11
|
|
@@ -73,10 +73,10 @@ module AssetCloud
|
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
76
|
-
def asset_at(
|
77
|
-
check_key_for_errors(
|
76
|
+
def asset_at(*args)
|
77
|
+
check_key_for_errors(args.first)
|
78
78
|
|
79
|
-
asset_class_for(
|
79
|
+
asset_class_for(args.first).at(self, *args)
|
80
80
|
end
|
81
81
|
|
82
82
|
def move(source, destination)
|
@@ -145,12 +145,26 @@ module AssetCloud
|
|
145
145
|
end
|
146
146
|
|
147
147
|
def []=(key, value)
|
148
|
-
|
148
|
+
asset = self[key]
|
149
|
+
asset.value = value
|
150
|
+
asset.store
|
149
151
|
end
|
150
152
|
|
151
153
|
def [](key)
|
152
154
|
asset_at(key)
|
153
155
|
end
|
156
|
+
|
157
|
+
# versioning
|
158
|
+
|
159
|
+
def read_version(key, version)
|
160
|
+
logger.info { " [#{self.class.name}] Reading from #{key} at version #{version}" } if logger
|
161
|
+
bucket_for(key).read_version(key, version)
|
162
|
+
end
|
163
|
+
|
164
|
+
def versions(key)
|
165
|
+
logger.info { " [#{self.class.name}] Getting all versions for #{key}" } if logger
|
166
|
+
bucket_for(key).versions(key)
|
167
|
+
end
|
154
168
|
|
155
169
|
protected
|
156
170
|
|
data/lib/asset_cloud/bucket.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
|
2
2
|
module AssetCloud
|
3
3
|
class AssetNotFoundError < StandardError
|
4
|
-
def initialize(
|
5
|
-
super(
|
4
|
+
def initialize(key, version=nil)
|
5
|
+
super(version ? "Could not find version #{version} of asset #{key}" : "Could not find asset #{key}")
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
@@ -10,30 +10,6 @@ module AssetCloud
|
|
10
10
|
attr_reader :name
|
11
11
|
attr_accessor :cloud
|
12
12
|
|
13
|
-
# returns a new Bucket class which writes to each given Bucket
|
14
|
-
# but only uses the first one for reading
|
15
|
-
def self.chain(*klasses)
|
16
|
-
Class.new(self) do
|
17
|
-
attr_reader :chained_buckets
|
18
|
-
define_method 'initialize' do |cloud, name|
|
19
|
-
super
|
20
|
-
@chained_buckets = klasses.map {|klass| klass.new(cloud,name)}
|
21
|
-
end
|
22
|
-
def ls(key=nil)
|
23
|
-
@chained_buckets.first.ls(key)
|
24
|
-
end
|
25
|
-
def read(key)
|
26
|
-
@chained_buckets.first.read(key)
|
27
|
-
end
|
28
|
-
def write(key, data)
|
29
|
-
@chained_buckets.each { |b| b.write(key, data)}
|
30
|
-
end
|
31
|
-
def delete(key)
|
32
|
-
@chained_buckets.each { |b| b.delete(key)}
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
13
|
def initialize(cloud, name)
|
38
14
|
@cloud, @name = cloud, name
|
39
15
|
end
|
@@ -53,5 +29,15 @@ module AssetCloud
|
|
53
29
|
def delete(key)
|
54
30
|
raise NotImplementedError
|
55
31
|
end
|
32
|
+
|
33
|
+
# versioning
|
34
|
+
|
35
|
+
def read_version(key, version)
|
36
|
+
raise NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
def versions(key)
|
40
|
+
raise NotImplementedError
|
41
|
+
end
|
56
42
|
end
|
57
43
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
class ActiveRecordBucket < AssetCloud::Bucket
|
3
|
+
class_inheritable_accessor :key_attribute, :value_attribute
|
4
|
+
self.key_attribute = 'key'
|
5
|
+
self.value_attribute = 'value'
|
6
|
+
|
7
|
+
def ls(key=name)
|
8
|
+
col = records.connection.quote_column_name(self.key_attribute)
|
9
|
+
records.all(:conditions => ["#{col} LIKE ?", "#{key}%"]).map do |r|
|
10
|
+
cloud[r.send(self.key_attribute)]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def read(key)
|
15
|
+
find_record!(key).send(self.value_attribute)
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(key, value)
|
19
|
+
record = records.send("find_or_initialize_by_#{self.key_attribute}", key.to_s)
|
20
|
+
record.send("#{self.value_attribute}=", value)
|
21
|
+
record.save!
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(key)
|
25
|
+
if record = find_record(key)
|
26
|
+
record.destroy
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stat(key)
|
31
|
+
if record = find_record(key)
|
32
|
+
AssetCloud::Metadata.new(true, record.send(self.value_attribute).size, record.created_at, record.updated_at)
|
33
|
+
else
|
34
|
+
AssetCloud::Metadata.new(false)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
# override to return @cloud.user.assets or some other ActiveRecord Enumerable
|
41
|
+
# which responds to .connection, .find, etc.
|
42
|
+
#
|
43
|
+
# model must have columns for this class's key_attribute and value_attribute,
|
44
|
+
# plus created_at and updated_at.
|
45
|
+
def records
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_record(key)
|
50
|
+
records.first(:conditions => {self.key_attribute => key.to_s})
|
51
|
+
end
|
52
|
+
|
53
|
+
def find_record!(key)
|
54
|
+
find_record(key) or raise(AssetCloud::AssetNotFoundError, key)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
File without changes
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
class BucketChain < Bucket
|
3
|
+
# returns a new Bucket class which writes to each given Bucket
|
4
|
+
# but only uses the first one for reading
|
5
|
+
def self.chain(*klasses)
|
6
|
+
Class.new(self) do
|
7
|
+
attr_reader :chained_buckets
|
8
|
+
define_method 'initialize' do |cloud, name|
|
9
|
+
super
|
10
|
+
@chained_buckets = klasses.map {|klass| klass.new(cloud,name)}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def ls(key=nil)
|
17
|
+
first_possible_bucket {|b| b.ls(key)}
|
18
|
+
end
|
19
|
+
def read(key)
|
20
|
+
first_possible_bucket {|b| b.read(key)}
|
21
|
+
end
|
22
|
+
def stat(key=nil)
|
23
|
+
first_possible_bucket {|b| b.stat(key)}
|
24
|
+
end
|
25
|
+
def read_version(key, version)
|
26
|
+
first_possible_bucket {|b| b.read_version(key, version)}
|
27
|
+
end
|
28
|
+
def versions(key)
|
29
|
+
first_possible_bucket {|b| b.versions(key)}
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def write(key, data)
|
34
|
+
every_bucket_with_transaction_on_key(key) {|b| b.write(key, data)}
|
35
|
+
end
|
36
|
+
def delete(key)
|
37
|
+
every_bucket_with_transaction_on_key(key) {|b| b.delete(key)}
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to?(sym)
|
41
|
+
@chained_buckets.any? {|b| b.respond_to?(sym)}
|
42
|
+
end
|
43
|
+
def method_missing(sym, *args)
|
44
|
+
first_possible_bucket {|b| b.send(sym, *args)}
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def first_possible_bucket(&block)
|
50
|
+
@chained_buckets.each do |bucket|
|
51
|
+
begin
|
52
|
+
return yield(bucket)
|
53
|
+
rescue NoMethodError, NotImplementedError => e
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def every_bucket_with_transaction_on_key(key, i=0, &block)
|
60
|
+
return unless bucket = @chained_buckets[i]
|
61
|
+
|
62
|
+
old_value = begin
|
63
|
+
bucket.read(key)
|
64
|
+
rescue AssetCloud::AssetNotFoundError
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
result = yield(bucket)
|
68
|
+
|
69
|
+
begin
|
70
|
+
every_bucket_with_transaction_on_key(key, i+1, &block)
|
71
|
+
return result
|
72
|
+
rescue StandardError => e
|
73
|
+
if old_value
|
74
|
+
bucket.write(key, old_value)
|
75
|
+
else
|
76
|
+
bucket.delete(key)
|
77
|
+
end
|
78
|
+
raise e
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
File without changes
|
@@ -6,11 +6,13 @@ module AssetCloud
|
|
6
6
|
super
|
7
7
|
@memory = {}
|
8
8
|
end
|
9
|
-
|
10
|
-
def ls(
|
11
|
-
|
12
|
-
|
9
|
+
|
10
|
+
def ls(prefix=nil)
|
11
|
+
results = []
|
12
|
+
@memory.each do |k,v|
|
13
|
+
results.push(cloud[k]) if prefix.nil? || k.starts_with?(prefix)
|
13
14
|
end
|
15
|
+
results
|
14
16
|
end
|
15
17
|
|
16
18
|
def read(key)
|
@@ -31,7 +33,7 @@ module AssetCloud
|
|
31
33
|
def stat(key)
|
32
34
|
return Metadata.non_existing unless @memory.has_key?(key)
|
33
35
|
|
34
|
-
Metadata.new(true,
|
36
|
+
Metadata.new(true, read(key).size)
|
35
37
|
end
|
36
38
|
|
37
39
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
class VersionedMemoryBucket < MemoryBucket
|
4
|
+
|
5
|
+
def read(key)
|
6
|
+
raise AssetCloud::AssetNotFoundError, key unless @memory.has_key?(key)
|
7
|
+
read_version(key, latest_version(key))
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(key, data)
|
11
|
+
@memory[key] ||= []
|
12
|
+
@memory[key] << data
|
13
|
+
true
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_version(key, version)
|
17
|
+
@memory[key][version - 1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def versions(key)
|
21
|
+
(1..latest_version(key)).to_a
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def latest_version(key)
|
27
|
+
@memory[key].size
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
end
|
@@ -2,37 +2,40 @@ module AssetCloud
|
|
2
2
|
|
3
3
|
module Callbacks
|
4
4
|
|
5
|
-
CALLBACKS = [:delete, :write]
|
6
|
-
|
7
5
|
def self.included(base)
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def callback_methods(*symbols)
|
11
|
+
symbols.each do |method|
|
12
|
+
code = <<-"end_eval"
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def self.before_#{method}(*callbacks, &block)
|
15
|
+
callbacks << block if block_given?
|
16
|
+
write_inheritable_array(:before_#{method}, callbacks)
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
def self.after_#{method}(*callbacks, &block)
|
20
|
+
callbacks << block if block_given?
|
21
|
+
write_inheritable_array(:after_#{method}, callbacks)
|
22
|
+
end
|
21
23
|
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
def #{method}_with_callbacks(*args)
|
26
|
+
if execute_callbacks(:before_#{method}, args)
|
27
|
+
result = #{method}_without_callbacks(*args)
|
28
|
+
execute_callbacks(:after_#{method}, args)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
30
32
|
|
31
|
-
|
32
|
-
|
33
|
+
alias_method_chain :#{method}, 'callbacks'
|
34
|
+
end_eval
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
+
self.class_eval code, __FILE__, __LINE__
|
37
|
+
end
|
38
|
+
end
|
36
39
|
end
|
37
40
|
|
38
41
|
def execute_callbacks(symbol, args)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
module Validations
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
5
|
+
base.class_eval do
|
6
|
+
include AssetCloud::Callbacks
|
7
|
+
|
8
|
+
alias_method_chain :store, :validation
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def validate(*validations, &block)
|
14
|
+
validations << block if block_given?
|
15
|
+
write_inheritable_array(:validate, validations)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def store_with_validation
|
20
|
+
validate
|
21
|
+
errors.empty? ? store_without_validation : false
|
22
|
+
end
|
23
|
+
|
24
|
+
def errors
|
25
|
+
@errors ||= []
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid?
|
29
|
+
validate
|
30
|
+
errors.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
def add_error(msg)
|
34
|
+
errors << msg
|
35
|
+
errors.uniq!
|
36
|
+
end
|
37
|
+
|
38
|
+
def validate
|
39
|
+
errors.clear
|
40
|
+
execute_callbacks(:validate, [])
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
MockRecords = Object.new
|
4
|
+
|
5
|
+
class MockActiveRecordBucket < AssetCloud::ActiveRecordBucket
|
6
|
+
self.key_attribute = 'name'
|
7
|
+
self.value_attribute = 'body'
|
8
|
+
protected
|
9
|
+
def records
|
10
|
+
MockRecords
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class RecordCloud < AssetCloud::Base
|
15
|
+
bucket :stuff, MockActiveRecordBucket
|
16
|
+
end
|
17
|
+
|
18
|
+
describe AssetCloud::ActiveRecordBucket do
|
19
|
+
directory = File.dirname(__FILE__) + '/files'
|
20
|
+
|
21
|
+
before do
|
22
|
+
@cloud = RecordCloud.new(directory , 'http://assets/files' )
|
23
|
+
@bucket = @cloud.buckets[:stuff]
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#ls' do
|
27
|
+
before do
|
28
|
+
MockRecords.should_receive(:connection).and_return(@mock_connection = mock("connection"))
|
29
|
+
@mock_connection.should_receive(:quote_column_name).with('name').and_return("`name`")
|
30
|
+
(@mock_record = mock("record")).should_receive(:name).and_return('stuff/a1')
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should return a list of assets which start with the given prefix" do
|
34
|
+
MockRecords.should_receive(:all).with(:conditions => ["`name` LIKE ?", "stuff/a%"]).and_return([@mock_record])
|
35
|
+
|
36
|
+
@bucket.ls('stuff/a').size.should == 1
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return a list of all assets when a prefix is not given" do
|
40
|
+
MockRecords.should_receive(:all).with(:conditions => ["`name` LIKE ?", "stuff%"]).and_return([@mock_record])
|
41
|
+
|
42
|
+
@bucket.ls.size.should == 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#read' do
|
47
|
+
it "should return the value of a key when it exists" do
|
48
|
+
(@mock_record = mock("record")).should_receive(:body).and_return('foo')
|
49
|
+
MockRecords.should_receive(:first).with(:conditions => {'name' => 'stuff/a1'}).and_return(@mock_record)
|
50
|
+
|
51
|
+
@bucket.read('stuff/a1')
|
52
|
+
end
|
53
|
+
it "should raise AssetNotFoundError when nothing is there" do
|
54
|
+
MockRecords.should_receive(:first).with(:conditions => {'name' => 'stuff/a1'}).and_return(nil)
|
55
|
+
|
56
|
+
lambda {@bucket.read('stuff/a1')}.should raise_error(AssetCloud::AssetNotFoundError)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#write' do
|
61
|
+
it "should write to the DB" do
|
62
|
+
(@mock_record = mock("record")).should_receive(:body=).with('foo').and_return('foo')
|
63
|
+
@mock_record.should_receive(:save!).and_return(true)
|
64
|
+
MockRecords.should_receive(:find_or_initialize_by_name).with('stuff/a1').and_return(@mock_record)
|
65
|
+
|
66
|
+
@bucket.write('stuff/a1', 'foo')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#delete' do
|
71
|
+
it "should destroy records" do
|
72
|
+
(@mock_record = mock("record")).should_receive(:destroy).and_return(true)
|
73
|
+
MockRecords.should_receive(:first).with(:conditions => {'name' => 'stuff/a1'}).and_return(@mock_record)
|
74
|
+
|
75
|
+
@bucket.delete('stuff/a1')
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#stat' do
|
80
|
+
it "should return appropriate metadata" do
|
81
|
+
(@mock_record = mock("record")).should_receive(:created_at).and_return(1982)
|
82
|
+
@mock_record.should_receive(:updated_at).and_return(2002)
|
83
|
+
@mock_record.should_receive(:body).and_return('foo')
|
84
|
+
MockRecords.should_receive(:first).with(:conditions => {'name' => 'stuff/a1'}).and_return(@mock_record)
|
85
|
+
|
86
|
+
metadata = @bucket.stat('stuff/a1')
|
87
|
+
metadata.created_at.should == 1982
|
88
|
+
metadata.updated_at.should == 2002
|
89
|
+
metadata.size.should == 3
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
end
|
data/spec/asset_spec.rb
CHANGED
data/spec/base_spec.rb
CHANGED
@@ -64,6 +64,27 @@ describe BasicCloud do
|
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
|
+
describe "#[]" do
|
68
|
+
it "should return the appropriate asset when one exists" do
|
69
|
+
asset = @fs['products/key.txt']
|
70
|
+
asset.key.should == 'products/key.txt'
|
71
|
+
asset.value.should == 'value'
|
72
|
+
end
|
73
|
+
it "should not raise any errors when the asset doesn't exist" do
|
74
|
+
lambda { @fs['products/not-there.txt'] }.should_not raise_error
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe "#[]=" do
|
79
|
+
it "should write through the Asset object (and thus run any callbacks on the asset)" do
|
80
|
+
special_asset = mock(:special_asset)
|
81
|
+
special_asset.should_receive(:value=).with('fancy fancy!')
|
82
|
+
special_asset.should_receive(:store)
|
83
|
+
SpecialAsset.should_receive(:at).and_return(special_asset)
|
84
|
+
@fs['special/fancy.txt'] = 'fancy fancy!'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
67
88
|
describe "#bucket" do
|
68
89
|
it "should allow specifying a class to use for assets in this bucket" do
|
69
90
|
@fs['assets/rails_logo.gif'].should be_instance_of(AssetCloud::Asset)
|
@@ -0,0 +1,158 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class ChainedCloud < AssetCloud::Base
|
4
|
+
bucket :stuff, AssetCloud::BucketChain.chain( AssetCloud::MemoryBucket,
|
5
|
+
AssetCloud::MemoryBucket,
|
6
|
+
AssetCloud::FileSystemBucket )
|
7
|
+
|
8
|
+
bucket :versioned_stuff, AssetCloud::BucketChain.chain( AssetCloud::FileSystemBucket,
|
9
|
+
AssetCloud::VersionedMemoryBucket,
|
10
|
+
AssetCloud::MemoryBucket )
|
11
|
+
end
|
12
|
+
|
13
|
+
describe AssetCloud::BucketChain do
|
14
|
+
directory = File.dirname(__FILE__) + '/files'
|
15
|
+
|
16
|
+
before(:each) do
|
17
|
+
@cloud = ChainedCloud.new(directory , 'http://assets/files' )
|
18
|
+
@bucket_chain = @cloud.buckets[:stuff]
|
19
|
+
@chained_buckets = @bucket_chain.chained_buckets
|
20
|
+
@chained_buckets.each {|b| b.ls('stuff').each {|asset| asset.delete}}
|
21
|
+
|
22
|
+
@versioned_stuff = @cloud.buckets[:versioned_stuff]
|
23
|
+
end
|
24
|
+
|
25
|
+
describe ".chain" do
|
26
|
+
it 'should take multiple Bucket classes and return a new Bucket class' do
|
27
|
+
@bucket_chain.should be_a_kind_of(AssetCloud::BucketChain)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "#write" do
|
32
|
+
it 'should write to each sub-bucket when everything is kosher and return the result of the first write' do
|
33
|
+
@chained_buckets.each do |bucket|
|
34
|
+
bucket.should_receive(:write).with('stuff/foo', 'successful creation').and_return('successful creation')
|
35
|
+
end
|
36
|
+
|
37
|
+
@bucket_chain.write('stuff/foo', 'successful creation').should == 'successful creation'
|
38
|
+
end
|
39
|
+
it 'should roll back creation-writes and re-raise an error when a bucket raises one' do
|
40
|
+
@chained_buckets.last.should_receive(:write).with('stuff/foo', 'unsuccessful creation').and_raise('hell')
|
41
|
+
@chained_buckets[0..-2].each do |bucket|
|
42
|
+
bucket.should_receive(:write).with('stuff/foo', 'unsuccessful creation').and_return(true)
|
43
|
+
bucket.should_receive(:delete).with('stuff/foo').and_return(true)
|
44
|
+
end
|
45
|
+
|
46
|
+
lambda { @bucket_chain.write('stuff/foo', 'unsuccessful creation') }.should raise_error(RuntimeError)
|
47
|
+
end
|
48
|
+
it 'should roll back update-writes and re-raise an error when a bucket raises one' do
|
49
|
+
@bucket_chain.write('stuff/foo', "original value")
|
50
|
+
|
51
|
+
@chained_buckets.last.should_receive(:write).with('stuff/foo', 'new value').and_raise('hell')
|
52
|
+
|
53
|
+
lambda { @bucket_chain.write('stuff/foo', 'new value') }.should raise_error(RuntimeError)
|
54
|
+
@chained_buckets.each do |bucket|
|
55
|
+
bucket.read('stuff/foo').should == 'original value'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#delete" do
|
61
|
+
it 'should delete from each sub-bucket when everything is kosher' do
|
62
|
+
@bucket_chain.write('stuff/foo', "successful deletion comin' up")
|
63
|
+
|
64
|
+
@chained_buckets.each do |bucket|
|
65
|
+
bucket.should_receive(:delete).with('stuff/foo').and_return(true)
|
66
|
+
end
|
67
|
+
|
68
|
+
@bucket_chain.delete('stuff/foo')
|
69
|
+
end
|
70
|
+
it 'should roll back deletions and re-raise an error when a bucket raises one' do
|
71
|
+
@bucket_chain.write('stuff/foo', "this deletion will fail")
|
72
|
+
|
73
|
+
@chained_buckets.last.should_receive(:delete).with('stuff/foo').and_raise('hell')
|
74
|
+
@chained_buckets[0..-2].each do |bucket|
|
75
|
+
bucket.should_receive(:delete).with('stuff/foo').and_return(true)
|
76
|
+
bucket.should_receive(:write).with('stuff/foo', 'this deletion will fail').and_return(true)
|
77
|
+
end
|
78
|
+
|
79
|
+
lambda { @bucket_chain.delete('stuff/foo') }.should raise_error(RuntimeError)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe "#read" do
|
84
|
+
it 'should read from only the first available sub-bucket' do
|
85
|
+
@chained_buckets[0].should_receive(:read).with('stuff/foo').and_raise(NotImplementedError)
|
86
|
+
@chained_buckets[0].should_receive(:ls).with(nil).and_raise(NoMethodError)
|
87
|
+
@chained_buckets[0].should_receive(:stat).and_return(:metadata)
|
88
|
+
|
89
|
+
@chained_buckets[1].should_receive(:read).with('stuff/foo').and_return('bar')
|
90
|
+
@chained_buckets[1].should_receive(:ls).with(nil).and_return(:some_assets)
|
91
|
+
@chained_buckets[1].should_not_receive(:stat)
|
92
|
+
|
93
|
+
@chained_buckets[2..-1].each do |bucket|
|
94
|
+
bucket.should_not_receive(:read)
|
95
|
+
bucket.should_not_receive(:ls)
|
96
|
+
bucket.should_not_receive(:stat)
|
97
|
+
end
|
98
|
+
|
99
|
+
@bucket_chain.read('stuff/foo').should == 'bar'
|
100
|
+
@bucket_chain.ls.should == :some_assets
|
101
|
+
@bucket_chain.stat.should == :metadata
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
describe "#read_version" do
|
107
|
+
it 'should read from only the first available sub-bucket' do
|
108
|
+
buckets = @versioned_stuff.chained_buckets
|
109
|
+
|
110
|
+
buckets[1].should_receive(:read_version).with('stuff/foo',3).and_return('bar')
|
111
|
+
buckets.last.should_not_receive(:read_version)
|
112
|
+
|
113
|
+
@versioned_stuff.read_version('stuff/foo', 3).should == 'bar'
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#versions" do
|
118
|
+
it 'should read from only the first available sub-bucket' do
|
119
|
+
buckets = @versioned_stuff.chained_buckets
|
120
|
+
|
121
|
+
buckets[1].should_receive(:versions).with('versioned_stuff/foo').and_return([1,2,3])
|
122
|
+
buckets.last.should_not_receive(:versions)
|
123
|
+
|
124
|
+
@versioned_stuff.versions('versioned_stuff/foo').should == [1,2,3]
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "with versioned buckets" do
|
129
|
+
it 'should store and retrieve versions seamlessly' do
|
130
|
+
%w{one two three}.each do |content|
|
131
|
+
@cloud['versioned_stuff/foo'] = content
|
132
|
+
end
|
133
|
+
asset = @cloud['versioned_stuff/foo']
|
134
|
+
asset.value.should == 'three'
|
135
|
+
asset.rollback(1).value.should == 'one'
|
136
|
+
asset.versions.should == [1,2,3]
|
137
|
+
asset.value = 'four'
|
138
|
+
asset.store
|
139
|
+
asset.versions.should == [1,2,3,4]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#respond_to?' do
|
144
|
+
it 'should return true if any chained buckets respond to the given method' do
|
145
|
+
@bucket_chain.respond_to?(:foo).should == false
|
146
|
+
@chained_buckets[1].should_receive(:respond_to?).with(:bar).and_return(true)
|
147
|
+
@bucket_chain.respond_to?(:bar).should == true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#method_missing' do
|
152
|
+
it 'should try each bucket' do
|
153
|
+
@chained_buckets[1].should_receive(:buzz).and_return(true)
|
154
|
+
@chained_buckets[2].should_not_receive(:buzz)
|
155
|
+
@bucket_chain.buzz.should == true
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
data/spec/callbacks_spec.rb
CHANGED
@@ -1,9 +1,17 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/spec_helper'
|
2
2
|
|
3
|
+
class CallbackAsset < AssetCloud::Asset
|
4
|
+
before_store :callback_before_store
|
5
|
+
after_delete :callback_after_delete
|
6
|
+
end
|
7
|
+
|
8
|
+
class BasicCloud < AssetCloud::Base
|
9
|
+
bucket :callback_assets, AssetCloud::MemoryBucket, :asset_class => CallbackAsset
|
10
|
+
end
|
11
|
+
|
3
12
|
class CallbackCloud < AssetCloud::Base
|
4
13
|
bucket :tmp, AssetCloud::MemoryBucket
|
5
14
|
|
6
|
-
|
7
15
|
after_delete :callback_after_delete
|
8
16
|
before_delete :callback_before_delete
|
9
17
|
|
@@ -75,4 +83,26 @@ describe MethodRecordingCloud do
|
|
75
83
|
@fs['tmp/file.txt'] = 'random data'
|
76
84
|
@fs.run_callbacks.should == [:callback_before_write, :callback_before_write]
|
77
85
|
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe CallbackAsset do
|
89
|
+
before(:each) do
|
90
|
+
@fs = BasicCloud.new(File.dirname(__FILE__) + '/files', 'http://assets/')
|
91
|
+
@fs.write('callback_assets/foo', 'bar')
|
92
|
+
@asset = @fs.asset_at('callback_assets/foo')
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should run its before_store callback before store is called" do
|
96
|
+
@asset.should_receive(:callback_before_store).and_return(true)
|
97
|
+
@asset.should_not_receive(:callback_after_delete)
|
98
|
+
|
99
|
+
@asset.store
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should run its after_delete callback after delete is called" do
|
103
|
+
@asset.should_not_receive(:callback_before_store)
|
104
|
+
@asset.should_receive(:callback_after_delete).and_return(true)
|
105
|
+
|
106
|
+
@asset.delete
|
107
|
+
end
|
78
108
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
four
|
data/spec/memory_bucket_spec.rb
CHANGED
@@ -25,6 +25,22 @@ describe AssetCloud::MemoryBucket do
|
|
25
25
|
|
26
26
|
end
|
27
27
|
|
28
|
+
describe '#ls' do
|
29
|
+
before do
|
30
|
+
%w{a b}.each do |letter|
|
31
|
+
2.times {|number| @fs.write("memory/#{letter}#{number}",'.')}
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should return a list of assets which start with the given prefix" do
|
36
|
+
@fs.buckets[:memory].ls('memory/a').size.should == 2
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return a list of all assets when a prefix is not given" do
|
40
|
+
@fs.buckets[:memory].ls.size.should == 4
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
28
44
|
|
29
45
|
|
30
46
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class ValidatedAsset < AssetCloud::Asset
|
4
|
+
validate :no_cats
|
5
|
+
|
6
|
+
private
|
7
|
+
def no_cats
|
8
|
+
add_error('no cats allowed!') if value =~ /cat/i
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class BasicCloud < AssetCloud::Base
|
13
|
+
bucket :dog_pound, AssetCloud::MemoryBucket, :asset_class => ValidatedAsset
|
14
|
+
end
|
15
|
+
|
16
|
+
describe ValidatedAsset do
|
17
|
+
before(:each) do
|
18
|
+
@cloud = BasicCloud.new(File.dirname(__FILE__) + '/files', 'http://assets/')
|
19
|
+
@cat = @cloud.build('dog_pound/fido', 'cat')
|
20
|
+
@dog = @cloud.build('dog_pound/fido', 'dog')
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#store" do
|
24
|
+
it "should not store the asset unless validations pass" do
|
25
|
+
@cloud.should_receive(:write).with('dog_pound/fido', 'dog').and_return(true)
|
26
|
+
|
27
|
+
@cat.store
|
28
|
+
@cat.store.should == false
|
29
|
+
@cat.errors.should == ['no cats allowed!']
|
30
|
+
@dog.store.should == true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#valid?" do
|
35
|
+
it "should clear errors, run validations, and return validity" do
|
36
|
+
@cat.store
|
37
|
+
@cat.errors.should == ['no cats allowed!']
|
38
|
+
@cat.valid?.should == false
|
39
|
+
@cat.errors.should == ['no cats allowed!']
|
40
|
+
@cat.value = 'disguised feline'
|
41
|
+
@cat.valid?.should == true
|
42
|
+
@cat.errors.should be_empty
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class VersionedMemoryCloud < AssetCloud::Base
|
4
|
+
bucket :memory, AssetCloud::VersionedMemoryBucket
|
5
|
+
end
|
6
|
+
|
7
|
+
describe AssetCloud::VersionedMemoryBucket do
|
8
|
+
directory = File.dirname(__FILE__) + '/files'
|
9
|
+
|
10
|
+
before do
|
11
|
+
@fs = VersionedMemoryCloud.new(directory , 'http://assets/files' )
|
12
|
+
%w{one two three}.each do |content|
|
13
|
+
@fs.write("memory/foo", content)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#read_version' do
|
18
|
+
it "should return the appropriate data when given a key and version" do
|
19
|
+
@fs.read_version('memory/foo', 1).should == 'one'
|
20
|
+
@fs.read_version('memory/foo', 3).should == 'three'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#versions' do
|
25
|
+
it "should return a list of available version identifiers for the given key" do
|
26
|
+
@fs.versions('memory/foo').should == [1,2,3]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jamesmacaulay-asset_cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-07-03 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -36,27 +36,35 @@ files:
|
|
36
36
|
- lib/asset_cloud.rb
|
37
37
|
- lib/asset_cloud/asset.rb
|
38
38
|
- lib/asset_cloud/base.rb
|
39
|
-
- lib/asset_cloud/blackhole_bucket.rb
|
40
39
|
- lib/asset_cloud/bucket.rb
|
40
|
+
- lib/asset_cloud/buckets/active_record_bucket.rb
|
41
|
+
- lib/asset_cloud/buckets/blackhole_bucket.rb
|
42
|
+
- lib/asset_cloud/buckets/bucket_chain.rb
|
43
|
+
- lib/asset_cloud/buckets/file_system_bucket.rb
|
44
|
+
- lib/asset_cloud/buckets/invalid_bucket.rb
|
45
|
+
- lib/asset_cloud/buckets/memory_bucket.rb
|
46
|
+
- lib/asset_cloud/buckets/versioned_memory_bucket.rb
|
41
47
|
- lib/asset_cloud/callbacks.rb
|
42
|
-
- lib/asset_cloud/file_system_bucket.rb
|
43
48
|
- lib/asset_cloud/free_key_locator.rb
|
44
|
-
- lib/asset_cloud/invalid_bucket.rb
|
45
|
-
- lib/asset_cloud/memory_bucket.rb
|
46
49
|
- lib/asset_cloud/metadata.rb
|
50
|
+
- lib/asset_cloud/validations.rb
|
51
|
+
- spec/active_record_bucket_spec.rb
|
47
52
|
- spec/asset_spec.rb
|
48
53
|
- spec/base_spec.rb
|
49
54
|
- spec/blackhole_bucket_spec.rb
|
50
|
-
- spec/
|
55
|
+
- spec/bucket_chain_spec.rb
|
51
56
|
- spec/callbacks_spec.rb
|
52
57
|
- spec/file_system_spec.rb
|
53
58
|
- spec/files/products/key.txt
|
59
|
+
- spec/files/versioned_stuff/foo
|
54
60
|
- spec/find_free_key_spec.rb
|
55
61
|
- spec/memory_bucket_spec.rb
|
56
62
|
- spec/regexp_spec.rb
|
57
63
|
- spec/spec.opts
|
58
64
|
- spec/spec_helper.rb
|
59
|
-
|
65
|
+
- spec/validations_spec.rb
|
66
|
+
- spec/versioned_memory_bucket_spec.rb
|
67
|
+
has_rdoc: false
|
60
68
|
homepage: http://github.com/Shopify/asset_cloud
|
61
69
|
post_install_message:
|
62
70
|
rdoc_options:
|
@@ -83,13 +91,16 @@ signing_key:
|
|
83
91
|
specification_version: 3
|
84
92
|
summary: An abstraction layer around arbitrary and diverse asset stores.
|
85
93
|
test_files:
|
94
|
+
- spec/active_record_bucket_spec.rb
|
86
95
|
- spec/asset_spec.rb
|
87
96
|
- spec/base_spec.rb
|
88
97
|
- spec/blackhole_bucket_spec.rb
|
89
|
-
- spec/
|
98
|
+
- spec/bucket_chain_spec.rb
|
90
99
|
- spec/callbacks_spec.rb
|
91
100
|
- spec/file_system_spec.rb
|
92
101
|
- spec/find_free_key_spec.rb
|
93
102
|
- spec/memory_bucket_spec.rb
|
94
103
|
- spec/regexp_spec.rb
|
95
104
|
- spec/spec_helper.rb
|
105
|
+
- spec/validations_spec.rb
|
106
|
+
- spec/versioned_memory_bucket_spec.rb
|
data/spec/bucket_spec.rb
DELETED
@@ -1,41 +0,0 @@
|
|
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
|