jamesmacaulay-asset_cloud 0.5.1 → 0.5.2
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/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
|