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
data/.document
ADDED
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
June 2009:
|
2
|
+
|
3
|
+
* Use different Asset subclasses for each bucket in a cloud (james)
|
4
|
+
* AssetCloud::Base#find now calls asset_at but will raise errors if the asset doesn't exist (james)
|
5
|
+
* original AssetCloud::Base#find renamed to #asset_at (james)
|
6
|
+
* Added simple bucket multiplexing with AssetCloud::Bucket.chain (james)
|
7
|
+
* extraction from Shopify
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008-2009 Tobias Lütke & Jaded Pixel, Inc.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
= AssetCloud
|
2
|
+
|
3
|
+
An abstraction layer around arbitrary and diverse asset stores.
|
4
|
+
|
5
|
+
== Installation
|
6
|
+
|
7
|
+
=== as a Gem
|
8
|
+
|
9
|
+
gem install Shopify-asset_cloud -s http://gems.github.com
|
10
|
+
|
11
|
+
=== as a Rails plugin
|
12
|
+
|
13
|
+
script/plugin install git://github.com/Shopify/asset_cloud.git
|
14
|
+
|
15
|
+
== Copyright
|
16
|
+
|
17
|
+
Copyright (c) 2008-2009 Tobias Lütke & Jaded Pixel, Inc. Released under the MIT license (see LICENSE for details).
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
desc 'Default: run unit tests.'
|
7
|
+
task :default => [:spec]
|
8
|
+
|
9
|
+
desc "Run all spec examples"
|
10
|
+
Spec::Rake::SpecTask.new do |t|
|
11
|
+
t.libs << "spec"
|
12
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
13
|
+
t.spec_opts = ['--options', %\"#{File.dirname(__FILE__)}/spec/spec.opts"\]
|
14
|
+
end
|
15
|
+
|
16
|
+
desc 'Generate documentation for the asset_cloud plugin.'
|
17
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
18
|
+
rdoc.rdoc_dir = 'rdoc'
|
19
|
+
rdoc.title = 'AssetCloud'
|
20
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
21
|
+
rdoc.rdoc_files.include('README')
|
22
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
require 'jeweler'
|
27
|
+
Jeweler::Tasks.new do |gemspec|
|
28
|
+
gemspec.name = "asset_cloud"
|
29
|
+
gemspec.summary = "An abstraction layer around arbitrary and diverse asset stores."
|
30
|
+
gemspec.email = "developers@shopify.com"
|
31
|
+
gemspec.homepage = "http://github.com/Shopify/asset_cloud"
|
32
|
+
gemspec.description = "An abstraction layer around arbitrary and diverse asset stores."
|
33
|
+
gemspec.authors = ["Shopify"]
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
37
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.1
|
data/asset_cloud.gemspec
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{asset_cloud}
|
5
|
+
s.version = "0.5.0"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Shopify"]
|
9
|
+
s.date = %q{2009-06-16}
|
10
|
+
s.description = %q{An abstraction layer around arbitrary and diverse asset stores.}
|
11
|
+
s.email = %q{developers@shopify.com}
|
12
|
+
s.extra_rdoc_files = [
|
13
|
+
"LICENSE",
|
14
|
+
"README.rdoc"
|
15
|
+
]
|
16
|
+
s.files = [
|
17
|
+
".document",
|
18
|
+
".gitignore",
|
19
|
+
"CHANGELOG",
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"asset_cloud.gemspec",
|
25
|
+
"init.rb",
|
26
|
+
"install.rb",
|
27
|
+
"lib/asset_cloud.rb",
|
28
|
+
"lib/asset_cloud/asset.rb",
|
29
|
+
"lib/asset_cloud/base.rb",
|
30
|
+
"lib/asset_cloud/blackhole_bucket.rb",
|
31
|
+
"lib/asset_cloud/bucket.rb",
|
32
|
+
"lib/asset_cloud/callbacks.rb",
|
33
|
+
"lib/asset_cloud/file_system_bucket.rb",
|
34
|
+
"lib/asset_cloud/free_key_locator.rb",
|
35
|
+
"lib/asset_cloud/invalid_bucket.rb",
|
36
|
+
"lib/asset_cloud/memory_bucket.rb",
|
37
|
+
"lib/asset_cloud/metadata.rb",
|
38
|
+
"spec/asset_spec.rb",
|
39
|
+
"spec/base_spec.rb",
|
40
|
+
"spec/blackhole_bucket_spec.rb",
|
41
|
+
"spec/bucket_spec.rb",
|
42
|
+
"spec/callbacks_spec.rb",
|
43
|
+
"spec/file_system_spec.rb",
|
44
|
+
"spec/files/products/key.txt",
|
45
|
+
"spec/find_free_key_spec.rb",
|
46
|
+
"spec/memory_bucket_spec.rb",
|
47
|
+
"spec/regexp_spec.rb",
|
48
|
+
"spec/spec.opts",
|
49
|
+
"spec/spec_helper.rb"
|
50
|
+
]
|
51
|
+
s.has_rdoc = true
|
52
|
+
s.homepage = %q{http://github.com/Shopify/asset_cloud}
|
53
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
54
|
+
s.require_paths = ["lib"]
|
55
|
+
s.rubygems_version = %q{1.3.2}
|
56
|
+
s.summary = %q{An abstraction layer around arbitrary and diverse asset stores.}
|
57
|
+
s.test_files = [
|
58
|
+
"spec/asset_spec.rb",
|
59
|
+
"spec/base_spec.rb",
|
60
|
+
"spec/blackhole_bucket_spec.rb",
|
61
|
+
"spec/bucket_spec.rb",
|
62
|
+
"spec/callbacks_spec.rb",
|
63
|
+
"spec/file_system_spec.rb",
|
64
|
+
"spec/find_free_key_spec.rb",
|
65
|
+
"spec/memory_bucket_spec.rb",
|
66
|
+
"spec/regexp_spec.rb",
|
67
|
+
"spec/spec_helper.rb"
|
68
|
+
]
|
69
|
+
|
70
|
+
if s.respond_to? :specification_version then
|
71
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
72
|
+
s.specification_version = 3
|
73
|
+
|
74
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
75
|
+
else
|
76
|
+
end
|
77
|
+
else
|
78
|
+
end
|
79
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Include hook code here
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
class AssetError < StandardError
|
4
|
+
end
|
5
|
+
|
6
|
+
class AssetNotSaved < AssetError
|
7
|
+
end
|
8
|
+
|
9
|
+
class Asset
|
10
|
+
include Comparable
|
11
|
+
attr_accessor :key, :value, :cloud, :metadata
|
12
|
+
attr_accessor :new_asset
|
13
|
+
|
14
|
+
def initialize(cloud, key, value = nil, metadata = Metadata.non_existing)
|
15
|
+
@new_asset = true
|
16
|
+
@cloud = cloud
|
17
|
+
@key = key
|
18
|
+
@value = value
|
19
|
+
@metadata = metadata
|
20
|
+
|
21
|
+
if @cloud.blank?
|
22
|
+
raise ArgumentError, "cloud is not a valid AssetCloud::Base"
|
23
|
+
end
|
24
|
+
|
25
|
+
yield self if block_given?
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.at(cloud, key, value = nil, metadata = nil, &block)
|
29
|
+
file = self.new(cloud, key, value, metadata, &block)
|
30
|
+
file.new_asset = false
|
31
|
+
file
|
32
|
+
end
|
33
|
+
|
34
|
+
def <=>(other)
|
35
|
+
cloud.object_id <=> other.cloud.object_id && key <=> other.key
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_asset?
|
39
|
+
@new_asset
|
40
|
+
end
|
41
|
+
|
42
|
+
def dirname
|
43
|
+
File.dirname(@key)
|
44
|
+
end
|
45
|
+
|
46
|
+
def extname
|
47
|
+
File.extname(@key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def basename
|
51
|
+
File.basename(@key)
|
52
|
+
end
|
53
|
+
|
54
|
+
def basename_without_ext
|
55
|
+
File.basename(@key, extname)
|
56
|
+
end
|
57
|
+
|
58
|
+
def size
|
59
|
+
metadata.size
|
60
|
+
end
|
61
|
+
|
62
|
+
def exist?
|
63
|
+
metadata.exist?
|
64
|
+
end
|
65
|
+
|
66
|
+
def created_at
|
67
|
+
metadata.created_at
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete
|
71
|
+
if new_asset?
|
72
|
+
false
|
73
|
+
else
|
74
|
+
cloud.delete(key)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def metadata
|
79
|
+
@metadata ||= cloud.stat(key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def value
|
83
|
+
@value ||= if new_asset?
|
84
|
+
nil
|
85
|
+
else
|
86
|
+
cloud.read(key)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def store
|
91
|
+
unless @value.nil?
|
92
|
+
@new_asset = false
|
93
|
+
@metadata = nil
|
94
|
+
cloud.write(key, value)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def store!
|
99
|
+
store or raise AssetNotSaved
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_param
|
103
|
+
basename
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle
|
107
|
+
basename.to_handle
|
108
|
+
end
|
109
|
+
|
110
|
+
def url
|
111
|
+
cloud.url_for key
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
def inspect
|
116
|
+
"#<#{self.class.name}: #{key}>"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
|
2
|
+
module AssetCloud
|
3
|
+
|
4
|
+
class IllegalPath < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class Base
|
8
|
+
cattr_accessor :logger
|
9
|
+
|
10
|
+
VALID_PATHS = /^[a-z0-9][a-z0-9_\-\/]+([a-z0-9][\w\-\ \.]*\.\w{2,6})?$/i
|
11
|
+
|
12
|
+
attr_accessor :url, :root
|
13
|
+
|
14
|
+
class_inheritable_accessor :root_bucket_class
|
15
|
+
self.root_bucket_class = FileSystemBucket
|
16
|
+
class_inheritable_accessor :root_asset_class
|
17
|
+
self.root_asset_class = Asset
|
18
|
+
|
19
|
+
class_inheritable_hash :bucket_classes
|
20
|
+
self.bucket_classes = {}
|
21
|
+
class_inheritable_hash :asset_classes
|
22
|
+
self.asset_classes = {}
|
23
|
+
|
24
|
+
def self.bucket(*args)
|
25
|
+
asset_class = if args.last.is_a? Hash
|
26
|
+
args.pop[:asset_class]
|
27
|
+
end
|
28
|
+
|
29
|
+
if args.last.is_a? Class
|
30
|
+
bucket_class = args.pop
|
31
|
+
else
|
32
|
+
raise ArgumentError, 'requires a bucket class'
|
33
|
+
end
|
34
|
+
|
35
|
+
if bucket_name = args.first
|
36
|
+
self.bucket_classes[bucket_name.to_sym] = bucket_class
|
37
|
+
self.asset_classes[bucket_name.to_sym] = asset_class if asset_class
|
38
|
+
else
|
39
|
+
self.root_bucket_class = bucket_class
|
40
|
+
self.root_asset_class = asset_class if asset_class
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def buckets
|
45
|
+
@buckets ||= Hash.new do |hash, key|
|
46
|
+
if klass = self.class.bucket_classes[key]
|
47
|
+
hash[key] = klass.new(self, key)
|
48
|
+
else
|
49
|
+
hash[key] = nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(root, url = '/')
|
55
|
+
@root, @url = root, url
|
56
|
+
end
|
57
|
+
|
58
|
+
def url_for(key, secure = false)
|
59
|
+
File.join(@url, key)
|
60
|
+
end
|
61
|
+
|
62
|
+
def path_for(key)
|
63
|
+
File.join(path, key)
|
64
|
+
end
|
65
|
+
|
66
|
+
def path
|
67
|
+
root
|
68
|
+
end
|
69
|
+
|
70
|
+
def find(key)
|
71
|
+
returning asset_at(key) do |asset|
|
72
|
+
asset.value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def asset_at(key)
|
77
|
+
check_key_for_errors(key)
|
78
|
+
|
79
|
+
asset_class_for(key).at(self, key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def move(source, destination)
|
83
|
+
return if source == destination
|
84
|
+
|
85
|
+
object = copy(source, destination)
|
86
|
+
asset_at(source).delete
|
87
|
+
object
|
88
|
+
end
|
89
|
+
|
90
|
+
def copy(source, destination)
|
91
|
+
return if source == destination
|
92
|
+
|
93
|
+
object = build(destination, read(source))
|
94
|
+
object.store
|
95
|
+
object
|
96
|
+
end
|
97
|
+
|
98
|
+
def build(key, value = nil, &block)
|
99
|
+
logger.info { " [#{self.class.name}] Building asset #{key}" } if logger
|
100
|
+
asset_class_for(key).new(self, key, value, Metadata.non_existing, &block)
|
101
|
+
end
|
102
|
+
|
103
|
+
def write(key, value)
|
104
|
+
check_key_for_errors(key)
|
105
|
+
logger.info { " [#{self.class.name}] Writing #{value.size} bytes to #{key}" } if logger
|
106
|
+
|
107
|
+
bucket_for(key).write(key, value)
|
108
|
+
end
|
109
|
+
|
110
|
+
def read(key)
|
111
|
+
logger.info { " [#{self.class.name}] Reading from #{key}" } if logger
|
112
|
+
|
113
|
+
bucket_for(key).read(key)
|
114
|
+
end
|
115
|
+
|
116
|
+
def stat(key)
|
117
|
+
logger.info { " [#{self.class.name}] Statting #{key}" } if logger
|
118
|
+
|
119
|
+
bucket_for(key).stat(key)
|
120
|
+
end
|
121
|
+
|
122
|
+
def ls(key)
|
123
|
+
logger.info { " [#{self.class.name}] Listing objects in #{key}" } if logger
|
124
|
+
|
125
|
+
bucket_for(key).ls(key)
|
126
|
+
end
|
127
|
+
|
128
|
+
def exist?(key)
|
129
|
+
if fp = stat(key)
|
130
|
+
fp.exist?
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def delete(key)
|
137
|
+
logger.info { " [#{self.class.name}] Deleting #{key}" } if logger
|
138
|
+
|
139
|
+
bucket_for(key).delete(key)
|
140
|
+
end
|
141
|
+
|
142
|
+
def bucket_for(key)
|
143
|
+
bucket = buckets[bucket_symbol_for_key(key)]
|
144
|
+
bucket ? bucket : root_bucket
|
145
|
+
end
|
146
|
+
|
147
|
+
def []=(key, value)
|
148
|
+
write(key, value)
|
149
|
+
end
|
150
|
+
|
151
|
+
def [](key)
|
152
|
+
asset_at(key)
|
153
|
+
end
|
154
|
+
|
155
|
+
protected
|
156
|
+
|
157
|
+
def asset_class_for(key)
|
158
|
+
self.class.asset_classes[bucket_symbol_for_key(key)] || self.class.root_asset_class
|
159
|
+
end
|
160
|
+
|
161
|
+
def bucket_symbol_for_key(key)
|
162
|
+
$1.to_sym if key =~ /^(\w+)(\/|$)/
|
163
|
+
end
|
164
|
+
|
165
|
+
def root_bucket
|
166
|
+
@default_bucket ||= self.class.root_bucket_class.new(self, '')
|
167
|
+
end
|
168
|
+
|
169
|
+
def check_key_for_errors(key)
|
170
|
+
raise IllegalPath, "key cannot be empty" if key.blank?
|
171
|
+
raise IllegalPath, "#{key.inspect} contains illegal characters" unless key =~ VALID_PATHS
|
172
|
+
rescue => e
|
173
|
+
logger.info { " [#{self.class.name}] bad key #{e.message}" } if logger
|
174
|
+
raise
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module AssetCloud
|
3
|
+
class BlackholeBucket < Bucket
|
4
|
+
def ls(namespace = nil)
|
5
|
+
[]
|
6
|
+
end
|
7
|
+
|
8
|
+
def read(key)
|
9
|
+
nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def write(key, data)
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def delete(key)
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def stat(key)
|
21
|
+
Metadata.new(false)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
|
2
|
+
module AssetCloud
|
3
|
+
class AssetNotFoundError < StandardError
|
4
|
+
def initialize(key_or_message, message=false)
|
5
|
+
super(message ? key_or_message : "Could not find asset #{key_or_message.to_s.inspect}")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Bucket
|
10
|
+
attr_reader :name
|
11
|
+
attr_accessor :cloud
|
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
|
+
def initialize(cloud, name)
|
38
|
+
@cloud, @name = cloud, name
|
39
|
+
end
|
40
|
+
|
41
|
+
def ls(key = nil)
|
42
|
+
raise NotImplementedError
|
43
|
+
end
|
44
|
+
|
45
|
+
def read(key)
|
46
|
+
raise NotImplementedError
|
47
|
+
end
|
48
|
+
|
49
|
+
def write(key, data)
|
50
|
+
raise NotImplementedError
|
51
|
+
end
|
52
|
+
|
53
|
+
def delete(key)
|
54
|
+
raise NotImplementedError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module AssetCloud
|
2
|
+
|
3
|
+
module Callbacks
|
4
|
+
|
5
|
+
CALLBACKS = [:delete, :write]
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
|
9
|
+
CALLBACKS.each do |method|
|
10
|
+
code = <<-"end_eval"
|
11
|
+
|
12
|
+
def self.before_#{method}(*callbacks, &block)
|
13
|
+
callbacks << block if block_given?
|
14
|
+
write_inheritable_array(:before_#{method}, callbacks)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.after_#{method}(*callbacks, &block)
|
18
|
+
callbacks << block if block_given?
|
19
|
+
write_inheritable_array(:after_#{method}, callbacks)
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def #{method}_with_callbacks(*args)
|
24
|
+
if execute_callbacks(:before_#{method}, args)
|
25
|
+
result = #{method}_without_callbacks(*args)
|
26
|
+
execute_callbacks(:after_#{method}, args)
|
27
|
+
end
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method_chain :#{method}, 'callbacks'
|
32
|
+
end_eval
|
33
|
+
|
34
|
+
base.class_eval code, __FILE__, __LINE__
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def execute_callbacks(symbol, args)
|
39
|
+
callbacks_for(symbol).each do |callback|
|
40
|
+
|
41
|
+
result = case callback
|
42
|
+
when Symbol
|
43
|
+
self.send(callback, *args)
|
44
|
+
when Proc, Method
|
45
|
+
callback.call(self, *args)
|
46
|
+
else
|
47
|
+
if callback.respond_to?(method)
|
48
|
+
callback.send(method, self, *args)
|
49
|
+
else
|
50
|
+
raise StandardError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
|
51
|
+
end
|
52
|
+
end
|
53
|
+
return false if result == false
|
54
|
+
end
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
def callbacks_for(symbol)
|
59
|
+
self.class.read_inheritable_attribute(symbol) || []
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|