jsl-hashpipe 0.0.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/Rakefile +25 -0
- data/config/hashpipe.yml +23 -0
- data/hashpipe.gemspec +31 -0
- data/init.rb +4 -0
- data/lib/hashpipe/archived_attribute.rb +71 -0
- data/lib/hashpipe/backends/filesystem.rb +60 -0
- data/lib/hashpipe/backends/s3.rb +58 -0
- data/lib/hashpipe/global_configuration.rb +52 -0
- data/lib/hashpipe.rb +80 -0
- data/spec/hashpipe/archived_attribute_spec.rb +120 -0
- data/spec/hashpipe/backends/filesystem_spec.rb +82 -0
- data/spec/hashpipe/backends/s3_spec.rb +79 -0
- data/spec/hashpipe/global_configuration_spec.rb +72 -0
- metadata +83 -0
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'spec'
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'spec/rake/spectask'
|
6
|
+
require 'rake/rdoctask'
|
7
|
+
|
8
|
+
desc 'Test the plugin.'
|
9
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
10
|
+
t.spec_opts = ["--format", "specdoc", "--colour"]
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "Run all the tests"
|
16
|
+
task :default => :spec
|
17
|
+
|
18
|
+
desc 'Generate documentation for the hashpipe plugin.'
|
19
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
20
|
+
rdoc.rdoc_dir = 'rdoc'
|
21
|
+
rdoc.title = 'HashPipe'
|
22
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
23
|
+
rdoc.rdoc_files.include('README')
|
24
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
25
|
+
end
|
data/config/hashpipe.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
test:
|
2
|
+
storage: filesystem
|
3
|
+
s3:
|
4
|
+
bucket: test_archived_attributes
|
5
|
+
access_key: your access key
|
6
|
+
secret_key: your secret key
|
7
|
+
protocol: https
|
8
|
+
|
9
|
+
development:
|
10
|
+
storage: s3
|
11
|
+
s3:
|
12
|
+
bucket: development_archived_attributes
|
13
|
+
access_key: your access key
|
14
|
+
secret_key: your secret key
|
15
|
+
|
16
|
+
anotherenv:
|
17
|
+
storage: filesystem
|
18
|
+
filesystem:
|
19
|
+
archive_root: /tmp/archived_attributes
|
20
|
+
|
21
|
+
qa:
|
22
|
+
storage: memcache
|
23
|
+
namespace: hashit
|
data/hashpipe.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "hashpipe"
|
3
|
+
s.version = "0.0.2"
|
4
|
+
s.date = "2009-04-12"
|
5
|
+
s.summary = "Rails plugin to save content to a pluggable, hash-style backend"
|
6
|
+
s.email = "justin@phq.org"
|
7
|
+
s.homepage = "http://github.com/jsl/hashpipe"
|
8
|
+
s.description = "HashPipe allows for data to be saved to a backend other than the rdbms"
|
9
|
+
s.has_rdoc = true
|
10
|
+
s.authors = ["Justin Leitgeb"]
|
11
|
+
s.files = [
|
12
|
+
"Rakefile",
|
13
|
+
"hashpipe.gemspec",
|
14
|
+
"init.rb",
|
15
|
+
"config/hashpipe.yml",
|
16
|
+
"lib/hashpipe.rb",
|
17
|
+
"lib/hashpipe/archived_attribute.rb",
|
18
|
+
"lib/hashpipe/global_configuration.rb",
|
19
|
+
"lib/hashpipe/backends/s3.rb",
|
20
|
+
"lib/hashpipe/backends/filesystem.rb"
|
21
|
+
]
|
22
|
+
s.test_files = [
|
23
|
+
"spec/hashpipe/global_configuration_spec.rb",
|
24
|
+
"spec/hashpipe/archived_attribute_spec.rb",
|
25
|
+
"spec/hashpipe/backends/filesystem_spec.rb",
|
26
|
+
"spec/hashpipe/backends/s3_spec.rb"
|
27
|
+
]
|
28
|
+
|
29
|
+
s.add_dependency("right_aws", ["> 0.0.0"])
|
30
|
+
s.add_dependency("assaf-uuid", ["> 0.0.0"])
|
31
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'activesupport'
|
2
|
+
|
3
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[ backends filesystem ]))
|
4
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[ backends s3 ]))
|
5
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[ backends memcache ]))
|
6
|
+
|
7
|
+
module HashPipe
|
8
|
+
|
9
|
+
class ArchivedAttribute
|
10
|
+
attr_reader :name, :instance
|
11
|
+
|
12
|
+
def initialize(name, instance, opts = {})
|
13
|
+
@name = name
|
14
|
+
@instance = instance
|
15
|
+
@dirty = false
|
16
|
+
|
17
|
+
@_options = HashPipe::GlobalConfiguration.instance.to_hash.
|
18
|
+
merge(opts)
|
19
|
+
|
20
|
+
@backend = instantiate_backend_from(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def value
|
24
|
+
val = defined?(@stashed_value) ? @stashed_value : @backend.load
|
25
|
+
val = compress? ? Zlib::Inflate.inflate(val) : val
|
26
|
+
val = marshal? ? Marshal.load(val) : val
|
27
|
+
end
|
28
|
+
|
29
|
+
def value=(other)
|
30
|
+
other = marshal? ? Marshal.dump(other) : other
|
31
|
+
other = compress? && !other.nil? ? Zlib::Deflate.deflate(other) : other
|
32
|
+
@stashed_value = other
|
33
|
+
@dirty = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def dirty?
|
37
|
+
@dirty
|
38
|
+
end
|
39
|
+
|
40
|
+
# First saves this record to the back-end. If backend storage raises an
|
41
|
+
# error, we capture it and add it to the AR validation errors.
|
42
|
+
def save
|
43
|
+
@backend.save(@stashed_value) if self.dirty?
|
44
|
+
end
|
45
|
+
|
46
|
+
def destroy
|
47
|
+
@backend.destroy
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a backend object based on the options given (e.g., filesystem,
|
51
|
+
# s3).
|
52
|
+
def instantiate_backend_from(options)
|
53
|
+
"HashPipe::Backends::#{options[:storage].to_s.camelize}".
|
54
|
+
constantize.new(self)
|
55
|
+
end
|
56
|
+
|
57
|
+
def options
|
58
|
+
@_options
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
[:marshal, :compress].each do |sym|
|
64
|
+
define_method("#{sym}?") do # def marshal?
|
65
|
+
options[sym].nil? ? false : options[sym] # options[:marshal].nil? ? false : options[:marshal]
|
66
|
+
end # end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module HashPipe
|
2
|
+
module Backends
|
3
|
+
|
4
|
+
# The filesystem backend is mostly useful for development or test work. It's not currently sharded
|
5
|
+
# in any way, so would probably have serious issues for production use. You have been warned.
|
6
|
+
class Filesystem
|
7
|
+
|
8
|
+
def initialize(archived_attribute)
|
9
|
+
@archived_attribute = archived_attribute
|
10
|
+
end
|
11
|
+
|
12
|
+
def save(content)
|
13
|
+
create_filesystem_path unless File.exist?(filepath)
|
14
|
+
write_to_disk(content)
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
FileUtils.rm_f(filename)
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
File.read( filename ) if File.exist?( filename )
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the full file path + name of this archived attribute
|
26
|
+
def filename
|
27
|
+
File.join(filepath, @archived_attribute.name.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Writes content to disk, or raises an error if the content is unable to
|
33
|
+
# be saved
|
34
|
+
def write_to_disk(content)
|
35
|
+
File.open(filename, 'w') { |f| f.write(content) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_filesystem_path
|
39
|
+
FileUtils.mkdir_p(filepath)
|
40
|
+
end
|
41
|
+
|
42
|
+
# The file path used for archiving this attribute. Includes either the
|
43
|
+
# options[:filesystem][:archive_root] attribute, if available, or the
|
44
|
+
# RAILS_ROOT/tmp/archived_attribute_archive/attribute_name.
|
45
|
+
def filepath
|
46
|
+
config_path = @archived_attribute.options[:filesystem][:archive_root]
|
47
|
+
|
48
|
+
base_path = config_path || File.join(
|
49
|
+
%W[#{RAILS_ROOT} tmp hashpipe_stash]
|
50
|
+
)
|
51
|
+
|
52
|
+
File.expand_path( File.join(base_path,
|
53
|
+
@archived_attribute.name.to_s,
|
54
|
+
@archived_attribute.instance.uuid ))
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'right_aws'
|
2
|
+
|
3
|
+
module HashPipe
|
4
|
+
module Backends
|
5
|
+
|
6
|
+
class S3
|
7
|
+
|
8
|
+
def initialize(archived_attribute)
|
9
|
+
@archived_attribute = archived_attribute
|
10
|
+
@config = HashPipe::GlobalConfiguration.instance[:s3]
|
11
|
+
end
|
12
|
+
|
13
|
+
def save(content)
|
14
|
+
bucket.put( key_name, StringIO.new( content ) ) unless content.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy
|
18
|
+
bucket.key(key_name).delete
|
19
|
+
end
|
20
|
+
|
21
|
+
def load
|
22
|
+
bucket.get(key_name)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def bucket
|
28
|
+
@bucket ||= right_aws_s3.bucket(bucket_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def right_aws_s3
|
32
|
+
@s3 ||= RightAws::S3.new(
|
33
|
+
@config[:access_key],
|
34
|
+
@config[:secret_key]
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the bucket name to be used based on attributes of the archived
|
39
|
+
# attribute.
|
40
|
+
def bucket_name
|
41
|
+
@config[:bucket]
|
42
|
+
end
|
43
|
+
|
44
|
+
def key_name
|
45
|
+
[ @archived_attribute.instance.class.table_name,
|
46
|
+
@archived_attribute.name,
|
47
|
+
@archived_attribute.instance.uuid ].join('/')
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a string representing whether we use http or https for this
|
51
|
+
# connection.
|
52
|
+
def protocol
|
53
|
+
@config[:protocol] || 'https'
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module HashPipe
|
2
|
+
|
3
|
+
# Singleton class for reading the defaults archived attribute configuration
|
4
|
+
# for this environment.
|
5
|
+
class GlobalConfiguration
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
DEFAULTS = HashWithIndifferentAccess.new({
|
9
|
+
:storage => 'filesystem',
|
10
|
+
:marshal => false,
|
11
|
+
:compress => false,
|
12
|
+
:s3 => {
|
13
|
+
:protocol => 'https'
|
14
|
+
},
|
15
|
+
:filesystem => {
|
16
|
+
:archive_root => nil
|
17
|
+
},
|
18
|
+
:memcache => {
|
19
|
+
:port => 1978
|
20
|
+
}
|
21
|
+
})
|
22
|
+
|
23
|
+
def [](val)
|
24
|
+
config[val]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_hash
|
28
|
+
config
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
config.inspect
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def config
|
38
|
+
@config ||= HashWithIndifferentAccess.new(
|
39
|
+
DEFAULTS.merge(load_yaml_configuration)
|
40
|
+
)
|
41
|
+
|
42
|
+
@config.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_yaml_configuration
|
46
|
+
YAML.load_file(
|
47
|
+
File.join( RAILS_ROOT, 'config', 'hashpipe.yml' )
|
48
|
+
)[RAILS_ENV]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/hashpipe.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[hashpipe archived_attribute] ))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), %w[hashpipe global_configuration] ))
|
3
|
+
|
4
|
+
require 'uuid'
|
5
|
+
|
6
|
+
module HashPipe
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(SingletonMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module SingletonMethods
|
13
|
+
|
14
|
+
def hattr(*args)
|
15
|
+
attribute = args.first
|
16
|
+
|
17
|
+
options = args.extract_options!
|
18
|
+
options.reverse_merge! :marshalled => false
|
19
|
+
|
20
|
+
if archived_attribute_definitions.nil?
|
21
|
+
write_inheritable_attribute(:archived_attribute_definitions, {})
|
22
|
+
|
23
|
+
before_save :generate_uuid
|
24
|
+
before_save :save_archived_attributes
|
25
|
+
before_destroy :destroy_archived_attributes
|
26
|
+
end
|
27
|
+
|
28
|
+
archived_attribute_definitions[attribute] = options
|
29
|
+
|
30
|
+
self.__send__(:include, InstanceMethods)
|
31
|
+
|
32
|
+
define_method attribute do
|
33
|
+
archive_stash_for(attribute).value
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method "#{attribute}=" do |value|
|
37
|
+
archive_stash_for(attribute).value = value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the attachment definitions defined by each call to
|
42
|
+
# has_attached_file.
|
43
|
+
def archived_attribute_definitions
|
44
|
+
read_inheritable_attribute(:archived_attribute_definitions)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
module InstanceMethods
|
50
|
+
def archive_stash_for(model)
|
51
|
+
@_archived_attribute_stashes ||= {}
|
52
|
+
@_archived_attribute_stashes[model] ||= ArchivedAttribute.new(
|
53
|
+
model, self, self.class.archived_attribute_definitions[model]
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
def each_archived_stash
|
58
|
+
self.class.archived_attribute_definitions.each do |name, definition|
|
59
|
+
yield(name, archive_stash_for(name))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def save_archived_attributes
|
64
|
+
each_archived_stash do |name, stash|
|
65
|
+
stash.__send__(:save)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def destroy_archived_attributes
|
70
|
+
each_archived_stash do |name, stash|
|
71
|
+
stash.__send__(:destroy)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def generate_uuid
|
76
|
+
self.uuid = UUID.new.generate if self.new_record?
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[ .. spec_helper ])
|
2
|
+
|
3
|
+
describe HashPipe::ArchivedAttribute do
|
4
|
+
before do
|
5
|
+
options = { :storage => 'filesystem' }
|
6
|
+
stub_model = stub(:uuid => '43')
|
7
|
+
@aa = HashPipe::ArchivedAttribute.new(:glorp, stub_model, options)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#dirty?" do
|
11
|
+
it "should return false when a value has not been set" do
|
12
|
+
@aa.should_not be_dirty
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return when the value has been set" do
|
16
|
+
@aa.value = 'stuff'
|
17
|
+
@aa.should be_dirty
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "#marshal?" do
|
22
|
+
it "should return false if configuration option is nil" do
|
23
|
+
@aa.expects(:options).returns({:marshal => nil})
|
24
|
+
@aa.__send__(:marshal?).should be_false
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should return value from configuration if configuration option is not nil" do
|
28
|
+
@aa.expects(:options).returns({:marshal => false}).times(2)
|
29
|
+
@aa.__send__(:marshal?).should be_false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#compress?" do
|
34
|
+
it "should return false if configuration option is nil" do
|
35
|
+
@aa.expects(:options).returns({:compress => nil})
|
36
|
+
@aa.__send__(:compress?).should be_false
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return value from configuration if configuration option is not nil" do
|
40
|
+
@aa.expects(:options).returns({:compress => false}).times(2)
|
41
|
+
@aa.__send__(:compress?).should be_false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "#destroy" do
|
46
|
+
it "should call destroy on the backend object" do
|
47
|
+
backend = mock('backend', :destroy => true)
|
48
|
+
@aa.instance_variable_set(:'@backend', backend)
|
49
|
+
@aa.destroy
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "when marshal is on" do
|
54
|
+
before do
|
55
|
+
@content = 'mary had a little lamb'
|
56
|
+
stub_backend = stub('backend', :load => Marshal.dump(@content))
|
57
|
+
@aa.stubs(:options).
|
58
|
+
returns({ :storage => 'filesystem', :marshal => true })
|
59
|
+
@aa.__send__(:instance_variable_set, :'@backend', stub_backend)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should call Marshal.load to restore value" do
|
63
|
+
Marshal.expects(:load)
|
64
|
+
@aa.value
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should call Marshal.dump to save value" do
|
68
|
+
Marshal.expects(:dump)
|
69
|
+
@aa.value = 'foo'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should retrieve same content string stored in Marshalled form" do
|
73
|
+
@aa.value.should == @content
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when gzip is on" do
|
78
|
+
before do
|
79
|
+
@content = "The band formed in 1988 in Oxford"
|
80
|
+
stub_backend = stub('backend', :load => Zlib::Deflate.deflate(@content))
|
81
|
+
@aa.stubs(:options).
|
82
|
+
returns({ :storage => 'filesystem', :compress => true })
|
83
|
+
@aa.__send__(:instance_variable_set, :'@backend', stub_backend)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should call Zlib::Inflate.inflate to restore value" do
|
87
|
+
Zlib::Inflate.expects(:inflate)
|
88
|
+
@aa.value
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should call Zlib::Deflate.deflate to save value" do
|
92
|
+
Zlib::Deflate.expects(:deflate)
|
93
|
+
@aa.value = 'foo'
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should retrieve same content string stored in gzip form" do
|
97
|
+
@aa.value.should == @content
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should not raise an error with nil content" do
|
101
|
+
lambda {
|
102
|
+
@aa.value = nil
|
103
|
+
}.should_not raise_error
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
describe "when compress and marshal are on" do
|
108
|
+
before do
|
109
|
+
@content = "The band formed in 1988 in Oxford"
|
110
|
+
stub_backend = stub('backend', :load => Zlib::Deflate.deflate(Marshal.dump(@content)))
|
111
|
+
@aa.stubs(:options).
|
112
|
+
returns({ :storage => 'filesystem', :compress => true, :marshal => true })
|
113
|
+
@aa.__send__(:instance_variable_set, :'@backend', stub_backend)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should retrieve the same content given" do
|
117
|
+
@aa.value.should == @content
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[ .. .. spec_helper ])
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
describe HashPipe::Backends::Filesystem do
|
6
|
+
|
7
|
+
before do
|
8
|
+
@unique_path_part = 'archived_attributes_test'
|
9
|
+
@path = File.join(Dir.tmpdir, @unique_path_part)
|
10
|
+
|
11
|
+
|
12
|
+
@instance = stub('http_retrieval',
|
13
|
+
:uuid => '63d3a120-caca-012b-d468-002332d4f91e',
|
14
|
+
:name => :foo
|
15
|
+
)
|
16
|
+
|
17
|
+
aa = HashPipe::ArchivedAttribute.new(:content, @instance,
|
18
|
+
:filesystem => { :archive_root => @path } )
|
19
|
+
|
20
|
+
@fs = HashPipe::Backends::Filesystem.new(aa)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should write to the correct path" do
|
24
|
+
@fs.__send__(:filepath).should ==
|
25
|
+
File.expand_path(File.join(@path, 'content', @instance.uuid))
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#save" do
|
29
|
+
before do
|
30
|
+
if File.exist?(@path)
|
31
|
+
err = <<-EOS
|
32
|
+
Test directory #{@path} already exists. Please remove it and run the
|
33
|
+
test suite again.
|
34
|
+
EOS
|
35
|
+
raise RuntimeError, err
|
36
|
+
else
|
37
|
+
@remove_path = true
|
38
|
+
FileUtils.mkdir(@path)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should call methods to create path and save file to disk" do
|
43
|
+
@fs.save('test')
|
44
|
+
File.exist?(@fs.filename).should be_true
|
45
|
+
end
|
46
|
+
|
47
|
+
after(:all) do
|
48
|
+
FileUtils.rm_rf(@path) if @remove_path
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "#destroy" do
|
54
|
+
before do
|
55
|
+
if File.exist?(@path)
|
56
|
+
err = <<-EOS
|
57
|
+
Test directory #{@path} already exists. Please remove it and run the
|
58
|
+
test suite again.
|
59
|
+
EOS
|
60
|
+
raise RuntimeError, err
|
61
|
+
else
|
62
|
+
@remove_path = true
|
63
|
+
FileUtils.mkdir(@path)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should call methods to remove path and file" do
|
68
|
+
@fs.save('test')
|
69
|
+
File.exist?(@fs.filename).should be_true
|
70
|
+
|
71
|
+
@fs.destroy
|
72
|
+
File.exist?(@fs.filename).should be_false
|
73
|
+
end
|
74
|
+
|
75
|
+
after(:all) do
|
76
|
+
FileUtils.rm_rf(@path) if @remove_path
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[ .. .. spec_helper ])
|
2
|
+
|
3
|
+
describe HashPipe::Backends::S3 do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@instance = stub('ar_instance',
|
7
|
+
:uuid => '63d3a120-caca-012b-d468-002332d4f91e',
|
8
|
+
:table_name => 'glorps'
|
9
|
+
)
|
10
|
+
|
11
|
+
@aa = HashPipe::ArchivedAttribute.new(:stuff, @instance)
|
12
|
+
@s3 = HashPipe::Backends::S3.new(@aa)
|
13
|
+
@config = HashPipe::GlobalConfiguration.instance
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#initialize" do
|
17
|
+
it "should initialize an s3 object without error" do
|
18
|
+
lambda { HashPipe::Backends::S3.new(@aa) }.should_not raise_error
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "#protocol" do
|
23
|
+
it "should be https by default" do
|
24
|
+
@s3.__send__(:protocol).should == "https"
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should use http if specified in the configuration object" do
|
28
|
+
config = { :protocol => 'http' }
|
29
|
+
@s3.instance_variable_set(:"@config", config)
|
30
|
+
|
31
|
+
@s3.__send__(:protocol).should == "http"
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#load" do
|
37
|
+
it "should call method to load data from s3" do
|
38
|
+
bucket = mock('bucket')
|
39
|
+
bucket.expects(:get).once
|
40
|
+
aws_s3 = mock('aws_s3')
|
41
|
+
aws_s3.expects(:bucket).returns(bucket)
|
42
|
+
@s3.expects(:key_name).returns('some-key')
|
43
|
+
@s3.expects(:right_aws_s3).returns(aws_s3)
|
44
|
+
@s3.load
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "#save" do
|
49
|
+
it "should call method to store data in s3" do
|
50
|
+
bucket = mock('bucket')
|
51
|
+
content = 'hey'
|
52
|
+
key = 'fookey'
|
53
|
+
@s3.expects(:key_name).returns(key)
|
54
|
+
bucket.expects(:put).once
|
55
|
+
aws_s3 = mock('aws_s3')
|
56
|
+
aws_s3.expects(:bucket).returns(bucket)
|
57
|
+
@s3.expects(:right_aws_s3).returns(aws_s3)
|
58
|
+
@s3.save(content)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "#destroy" do
|
63
|
+
it "should call method to delete key" do
|
64
|
+
key = mock('key', :delete => true)
|
65
|
+
bucket = mock('bucket', :key => key)
|
66
|
+
aws_s3 = mock('aws_s3')
|
67
|
+
aws_s3.expects(:bucket).returns(bucket)
|
68
|
+
@s3.expects(:right_aws_s3).returns(aws_s3)
|
69
|
+
@s3.expects(:key_name).returns('foo-key')
|
70
|
+
@s3.destroy
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#bucket_name" do
|
75
|
+
it "should be based on the attribute class and model" do
|
76
|
+
@s3.__send__(:bucket_name).should == @config[:s3]['bucket']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), %w[ .. spec_helper ])
|
2
|
+
|
3
|
+
describe HashPipe::GlobalConfiguration do
|
4
|
+
before do
|
5
|
+
@conf = HashPipe::GlobalConfiguration.instance
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "defaults" do
|
9
|
+
it "should set default marshal value to false" do
|
10
|
+
HashPipe::GlobalConfiguration::DEFAULTS[:marshal].should == false
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should set gzip value to false" do
|
14
|
+
HashPipe::GlobalConfiguration::DEFAULTS[:marshal].should == false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should read default access key from the configuration file" do
|
19
|
+
@conf[:s3]['access_key'].should == 'your access key'
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should the default secret key from configuration" do
|
23
|
+
@conf[:s3]['secret_key'].should == 'your secret key'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should read the default bucket from the configuration file" do
|
27
|
+
@conf[:s3]['bucket'].should == 'test_archived_attributes'
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#to_s" do
|
31
|
+
it "should return a string" do
|
32
|
+
@conf.to_s.should be_an_instance_of(String)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "hash cloning" do
|
37
|
+
it "should be able to alter a Hash without affecting the original object" do
|
38
|
+
conf = @conf.to_hash
|
39
|
+
previous = @conf[:storage]
|
40
|
+
conf[:storage] = 'foo'
|
41
|
+
@conf[:storage].should == previous
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should not affect deeply nested attributes when values are changed" do
|
45
|
+
conf = @conf.to_hash
|
46
|
+
previous = @conf[:s3][:protocol]
|
47
|
+
conf[:s3][:protocol] = 'puddle'
|
48
|
+
@conf[:s3][:protocol].should == previous
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#to_hash" do
|
53
|
+
it "should return an instance of Hash" do
|
54
|
+
@conf.to_hash.should be_an_instance_of(HashWithIndifferentAccess)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe "when an option is not specified in the yaml config file" do
|
59
|
+
it "should have a section for s3 options in hash" do
|
60
|
+
@conf[:s3].should_not be_nil
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should default to https for protocol" do
|
64
|
+
@conf[:s3][:protocol].should == 'https'
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should set default storage attribute to :filesystem" do
|
68
|
+
@conf[:storage].should == 'filesystem'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: jsl-hashpipe
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Leitgeb
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-12 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: right_aws
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: assaf-uuid
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.0.0
|
34
|
+
version:
|
35
|
+
description: HashPipe allows for data to be saved to a backend other than the rdbms
|
36
|
+
email: justin@phq.org
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- Rakefile
|
45
|
+
- hashpipe.gemspec
|
46
|
+
- init.rb
|
47
|
+
- config/hashpipe.yml
|
48
|
+
- lib/hashpipe.rb
|
49
|
+
- lib/hashpipe/archived_attribute.rb
|
50
|
+
- lib/hashpipe/global_configuration.rb
|
51
|
+
- lib/hashpipe/backends/s3.rb
|
52
|
+
- lib/hashpipe/backends/filesystem.rb
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://github.com/jsl/hashpipe
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: "0"
|
65
|
+
version:
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.2.0
|
76
|
+
signing_key:
|
77
|
+
specification_version: 2
|
78
|
+
summary: Rails plugin to save content to a pluggable, hash-style backend
|
79
|
+
test_files:
|
80
|
+
- spec/hashpipe/global_configuration_spec.rb
|
81
|
+
- spec/hashpipe/archived_attribute_spec.rb
|
82
|
+
- spec/hashpipe/backends/filesystem_spec.rb
|
83
|
+
- spec/hashpipe/backends/s3_spec.rb
|