jferris-hashpipe 0.0.5.0.1247583913

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/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Justin S. Leitgeb
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,71 @@
1
+ = HashPipe
2
+
3
+ Transparently maintains an attribute of an ActiveRecord-backed model in a separate key-value data store such
4
+ as Amazon S3, memcached, Tokyo Cabinet, or the filesystem.
5
+
6
+ == Description
7
+
8
+ HashPipe makes it easy to use an external key-value based data storage system alongside a traditional ActiveRecord-
9
+ backed model. In this sense, it acts as a pipe from the ActiveRecord model to a hash - a hash pipe!
10
+
11
+ It functions by hooking into the native callbacks provided by ActiveRecord models. HashPipe keeps track of when the
12
+ hashed attribute has been written to, and flushes it out to the external data source on saves of the ActiveRecord
13
+ model. Hashed attributes are lazy-loaded, so they aren't pulled from the external data source until they're requested.
14
+ Finally, when the ActiveRecord model is deleted, the hashed attributes for that model are also deleted automatically.
15
+
16
+ HashPipe supports transparent gzip compression/decompression of attribute data and serialization/deserialization
17
+ using Marshal in case you wish to store Ruby objects in the attribute.
18
+
19
+ The backend of HashPipe is implemented using Moneta, which is a universal library for key-value storage systems.
20
+ This means that it supports all of the backend storage types supported by this common library, and that new
21
+ backends are added regularly.
22
+
23
+ == Use Cases
24
+
25
+ There are certain cases where it makes more sense to store data objects in a separate storage system from the RDBMS:
26
+
27
+ * Certain parts of your data set require frequent reads and writes, and need to be fast without the overhead introduced by the ACID properties of traditional relational database systems
28
+ * You store large objects, and don't want to worry about increasing the size of your RDBMS when the size grows out of the space you've allocated
29
+
30
+ = Quick Start
31
+
32
+ Getting started with HashPipe is easy.
33
+
34
+ * Install the gem 'hashpipe' available from github as jsl-hashpipe. If you're using a recent version of Rails you may
35
+ want to add a line like the following to your environment.rb:
36
+
37
+ config.gem "jsl-hashpipe", :lib => 'hashpipe', :source => 'http://gems.github.com'
38
+
39
+ * If you wanted to serialize an attribute 'elephant' in model Foo, you would call hattr :elephant in your model.
40
+ For example, class 'Foo' may look like:
41
+
42
+ class Foo < ActiveRecord::Base
43
+ hattr :elephant
44
+ end
45
+
46
+ * Configure hashpipe by creating a file hashpipe.yml in your projects' config directory according to the
47
+ instructions in the 'Configuration' section of this document
48
+
49
+ * After following the above steps, saving to the model and retrieving from it saves to the backend store
50
+ defined in your options. Likewise, deleting the model triggers a callback to delete the element in the
51
+ associated store.
52
+
53
+ == Configuration
54
+
55
+ HashPipe supports the backends supported by Moneta. Configure the backend that you want to use in your YAML
56
+ configuration file 'hashpipe.yml':
57
+
58
+ test:
59
+ moneta_klass: Moneta::Memory
60
+
61
+ development:
62
+ moneta_klass: Moneta::Memcache
63
+ moneta_options:
64
+ server: localhost:1978
65
+
66
+ You can define a default backend globally, and override it in individual models as needed (e.g., store one
67
+ attribute in the filesystem and another in S3).
68
+
69
+ == Author
70
+
71
+ Justin S. Leitgeb, <justin@phq.org>
data/Rakefile ADDED
@@ -0,0 +1,47 @@
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
26
+
27
+ gem_spec = Gem::Specification.new do |s|
28
+ s.name = "hashpipe"
29
+ s.version = "0.0.5.0.#{Time.now.to_i}"
30
+ s.summary = "ActiveRecord plugin to save content to a pluggable, hash-style backend"
31
+ s.email = "justin@phq.org"
32
+ s.homepage = "http://github.com/jsl/hashpipe"
33
+ s.description = "HashPipe connects an AR-backed model to a key-value store"
34
+ s.has_rdoc = true
35
+ s.authors = ["Justin Leitgeb"]
36
+ s.files = FileList['[A-Z]*', '{lib,spec}/**/*.rb', 'init.rb', 'config/hashpipe.yml']
37
+ s.test_files = FileList['spec/**/*.rb']
38
+ s.add_dependency("wycats-moneta", ["> 0.0.0"])
39
+ s.add_dependency("activesupport", ["> 0.0.0"])
40
+ end
41
+
42
+ desc "Generate a gemspec file"
43
+ task :gemspec do
44
+ File.open("#{gem_spec.name}.gemspec", 'w') do |f|
45
+ f.write gem_spec.to_yaml
46
+ end
47
+ end
@@ -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/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'hashpipe'
@@ -0,0 +1,60 @@
1
+ require 'activesupport'
2
+ require 'moneta'
3
+
4
+ module HashPipe
5
+
6
+ class ArchivedAttribute
7
+ attr_reader :name, :backend
8
+ attr_accessor :scope
9
+
10
+ def initialize(name, scope, backend, opts = {})
11
+ @name = name
12
+ @scope = scope
13
+ @dirty = false
14
+ @options = HashPipe::GlobalConfiguration.instance.to_hash.merge(opts)
15
+ @backend = backend
16
+ end
17
+
18
+ def value
19
+ val = defined?(@stashed_value) ? @stashed_value : backend[key]
20
+ val = compress? && !val.nil? ? Zlib::Inflate.inflate(val) : val
21
+ val = marshal? ? Marshal.load(val) : val
22
+ end
23
+
24
+ def value=(other)
25
+ other = marshal? ? Marshal.dump(other) : other
26
+ other = compress? && !other.nil? ? Zlib::Deflate.deflate(other) : other
27
+ @stashed_value = other
28
+ @dirty = true
29
+ end
30
+
31
+ def dirty?
32
+ @dirty
33
+ end
34
+
35
+ def save
36
+ backend[key] = @stashed_value if self.dirty?
37
+ @dirty = false
38
+ end
39
+
40
+ def destroy
41
+ backend.delete(key)
42
+ end
43
+
44
+ def options
45
+ @options
46
+ end
47
+
48
+ def key
49
+ [scope, name].join('_')
50
+ end
51
+
52
+ [:marshal, :compress].each do |sym|
53
+ define_method("#{sym}?") do # def marshal?
54
+ options[sym].nil? ? false : options[sym] # options[:marshal].nil? ? false : options[:marshal]
55
+ end # end
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,43 @@
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
+ :moneta_klass => 'Moneta::Memory',
10
+ :marshal => false,
11
+ :compress => false
12
+ })
13
+
14
+ def [](val)
15
+ config[val]
16
+ end
17
+
18
+ def to_hash
19
+ config
20
+ end
21
+
22
+ def to_s
23
+ config.inspect
24
+ end
25
+
26
+ private
27
+
28
+ def config
29
+ @config ||= HashWithIndifferentAccess.new(
30
+ DEFAULTS.merge(load_yaml_configuration)
31
+ )
32
+
33
+ @config.dup
34
+ end
35
+
36
+ def load_yaml_configuration
37
+ YAML.load_file(
38
+ File.join( RAILS_ROOT, 'config', 'hashpipe.yml' )
39
+ )[RAILS_ENV]
40
+ end
41
+ end
42
+
43
+ end
data/lib/hashpipe.rb ADDED
@@ -0,0 +1,96 @@
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
+ module HashPipe
5
+
6
+ def self.included(base)
7
+ base.extend(SingletonMethods)
8
+ end
9
+
10
+ module SingletonMethods
11
+
12
+ def hattr(*args)
13
+ attribute = args.first
14
+
15
+ options = args.extract_options!
16
+ options.reverse_merge! :marshalled => false
17
+
18
+ if archived_attribute_definitions.nil?
19
+ write_inheritable_attribute(:archived_attribute_definitions, {})
20
+
21
+ after_save :save_archived_attributes
22
+ before_destroy :destroy_archived_attributes
23
+ end
24
+
25
+ archived_attribute_definitions[attribute] = options
26
+
27
+ include InstanceMethods
28
+
29
+ define_method attribute do
30
+ archive_stash_for(attribute).value
31
+ end
32
+
33
+ define_method "#{attribute}=" do |value|
34
+ archive_stash_for(attribute).value = value
35
+ end
36
+ end
37
+
38
+ # Returns the attachment definitions defined by each call to
39
+ # has_attached_file.
40
+ def archived_attribute_definitions
41
+ read_inheritable_attribute(:archived_attribute_definitions)
42
+ end
43
+
44
+ def backend
45
+ @backend ||= initialize_cache_klass(HashPipe::GlobalConfiguration.instance[:moneta_klass])
46
+ end
47
+
48
+ def initialize_cache_klass(cache_klass)
49
+ require_moneta_library_for(cache_klass)
50
+ klass_const = cache_klass.respond_to?(:constantize) ? cache_klass.constantize : cache_klass
51
+ klass_const.new HashPipe::GlobalConfiguration.instance[:moneta_options]
52
+ end
53
+
54
+ def require_moneta_library_for(cache_klass)
55
+ require cache_klass.to_s.gsub(/::/, '/').downcase
56
+ end
57
+ end
58
+
59
+ module InstanceMethods
60
+ def archive_stash_for(attribute)
61
+ @_archived_attribute_stashes ||= {}
62
+ @_archived_attribute_stashes[attribute] ||= ArchivedAttribute.new(
63
+ attribute,
64
+ archived_attribute_scope,
65
+ self.class.backend,
66
+ self.class.archived_attribute_definitions[attribute]
67
+ )
68
+ end
69
+
70
+ def archived_attribute_scope
71
+ "#{self.class.table_name}_#{id}"
72
+ end
73
+
74
+ def each_archived_stash
75
+ self.class.archived_attribute_definitions.each do |name, definition|
76
+ yield(name, archive_stash_for(name))
77
+ end
78
+ end
79
+
80
+ def save_archived_attributes
81
+ each_archived_stash do |name, stash|
82
+ stash.scope = archived_attribute_scope
83
+ stash.save
84
+ end
85
+ end
86
+
87
+ def destroy_archived_attributes
88
+ each_archived_stash do |name, stash|
89
+ stash.destroy
90
+ end
91
+ end
92
+
93
+ end
94
+ end
95
+
96
+ ActiveRecord::Base.send(:include, HashPipe)
@@ -0,0 +1,170 @@
1
+ require File.join(File.dirname(__FILE__), %w[ .. spec_helper ])
2
+
3
+ module ArchivedAttributeHelpers
4
+ def build_attribute(opts = {})
5
+ opts[:name] ||= :glorp
6
+ opts[:backend] ||= Moneta::Memory.new
7
+ opts[:scope] ||= 'unique-id'
8
+ HashPipe::ArchivedAttribute.new(opts.delete(:name),
9
+ opts.delete(:scope),
10
+ opts.delete(:backend),
11
+ opts)
12
+ end
13
+
14
+ def clone_attribute(attribute)
15
+ build_attribute(:marshal => attribute.marshal?,
16
+ :compress => attribute.compress?,
17
+ :backend => attribute.backend,
18
+ :scope => attribute.scope,
19
+ :name => attribute.name)
20
+ end
21
+
22
+ def backend
23
+ subject.backend
24
+ end
25
+ end
26
+
27
+ describe HashPipe::ArchivedAttribute do
28
+
29
+ include ArchivedAttributeHelpers
30
+
31
+ before { @attribute = build_attribute }
32
+
33
+ subject { @attribute }
34
+
35
+ it "should not be dirty when a value has not been set" do
36
+ should_not be_dirty
37
+ end
38
+
39
+ it "should join the scope and the attribute name as a hash key" do
40
+ subject.key.should == "#{subject.scope}_#{subject.name}"
41
+ end
42
+
43
+ it "should allow the scope to be set" do
44
+ new_scope = 'another scope'
45
+ subject.scope = new_scope
46
+ subject.scope.should == new_scope
47
+ end
48
+
49
+ it "should use the provided backend" do
50
+ backend = Moneta::Memory.new
51
+ build_attribute(:backend => backend).backend.should == backend
52
+ end
53
+
54
+ it "should return when the value has been set" do
55
+ subject.value = 'stuff'
56
+ should be_dirty
57
+ end
58
+
59
+ it "should not be dirty after save is called" do
60
+ subject.value = 'stuff'
61
+ subject.save
62
+ should_not be_dirty
63
+ end
64
+
65
+ it "should add itself to the backend when saved" do
66
+ value = 'a value'
67
+
68
+ backend.should_not have_key(subject.key)
69
+
70
+ subject.value = value
71
+ subject.save
72
+
73
+ backend[subject.key].should == value
74
+ end
75
+
76
+ it "should retrieve itself from the backend" do
77
+ value = 'a value'
78
+ backend[subject.key] = value
79
+
80
+ subject.value.should == value
81
+ end
82
+
83
+ it "should remove itself from the backend when destroyed" do
84
+ backend[subject.key] = 'value'
85
+ subject.destroy
86
+ backend.should_not have_key(subject.key)
87
+ end
88
+
89
+ it "should correctly store a marshalled, compressed value" do
90
+ value = 'the value'
91
+ attribute = build_attribute(:marshal => true, :compress => true)
92
+ attribute.value = value
93
+ attribute.save
94
+
95
+ clone_attribute(attribute).value.should == value
96
+ end
97
+ end
98
+
99
+
100
+ describe HashPipe::ArchivedAttribute, "when marshal is on" do
101
+
102
+ include ArchivedAttributeHelpers
103
+
104
+ before { @attribute = build_attribute(:marshal => true) }
105
+ subject { @attribute }
106
+
107
+ it "should be marshalled" do
108
+ subject.marshal?.should be
109
+ end
110
+
111
+ it "should store a marshalled value" do
112
+ stored = 'stored value'
113
+ actual = 'a value'
114
+
115
+ Marshal.expects(:dump).with(actual).returns(stored)
116
+
117
+ subject.value = actual
118
+ subject.save
119
+
120
+ backend[subject.key].should == stored
121
+ end
122
+
123
+ it "should retrieve a marshalled value" do
124
+ stored = 'stored value'
125
+ actual = 'a value'
126
+ backend[subject.key] = stored
127
+
128
+ Marshal.expects(:load).with(stored).returns(actual)
129
+
130
+ subject.value.should == actual
131
+ end
132
+
133
+ it "should correctly store a nil value" do
134
+ subject.value = nil
135
+ subject.save
136
+ clone_attribute(subject).value.should be_nil
137
+ end
138
+ end
139
+
140
+ describe HashPipe::ArchivedAttribute, "when compression is on" do
141
+
142
+ include ArchivedAttributeHelpers
143
+
144
+ before { @attribute = build_attribute(:compress => true) }
145
+ subject { @attribute }
146
+
147
+ it "should be compressed" do
148
+ subject.compress?.should be
149
+ end
150
+
151
+ it "should store a compressed value" do
152
+ value = 'a value'
153
+ subject.value = value
154
+ subject.save
155
+ backend[subject.key].should == Zlib::Deflate.deflate(value)
156
+ end
157
+
158
+ it "should retrieve a compressed value" do
159
+ value = 'a value'
160
+ backend[subject.key] = Zlib::Deflate.deflate(value)
161
+ subject.value.should == value
162
+ end
163
+
164
+ it "should correctly store a nil value" do
165
+ subject.value = nil
166
+ subject.save
167
+ clone_attribute(subject).value.should be_nil
168
+ end
169
+ end
170
+
@@ -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
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ ActiveRecord::Base.establish_connection(
4
+ :adapter => 'sqlite3',
5
+ :dbfile => ":memory:"
6
+ )
7
+
8
+ ActiveRecord::Migration.verbose = false
9
+
10
+ ActiveRecord::Base.silence do
11
+ ActiveRecord::Schema.define do
12
+ create_table :stories do |table|
13
+ table.string :title
14
+ table.string :content_key
15
+ table.string :description_key
16
+ end
17
+ end
18
+ end
19
+
20
+ class Story < ActiveRecord::Base
21
+ hattr :content
22
+ hattr :description, { :marshalled => true }
23
+ end
24
+
25
+ describe HashPipe do
26
+ before do
27
+ @lamb_text = 'Baaah!'
28
+ @bear_struct = OpenStruct.new(:blah => 'arg')
29
+ @lamb_story = Story.create!(:title => 'lamb story', :content => @lamb_text)
30
+ @bear_story = Story.create!(:title => 'bear story', :content => 'Raaaar!', :description => @bear_struct)
31
+ end
32
+
33
+ def refind(instance)
34
+ instance.class.find(instance.id)
35
+ end
36
+
37
+ it "should retrieve textual content" do
38
+ refind(@lamb_story).content.should == @lamb_text
39
+ end
40
+
41
+ it "should load marshalled objects" do
42
+ refind(@bear_story).description.should == @bear_struct
43
+ end
44
+
45
+ [:save_archived_attributes, :destroy_archived_attributes].each do |sym|
46
+ it "should respond to attached storage method #{sym}" do
47
+ Story.new.should respond_to(sym)
48
+ end
49
+ end
50
+
51
+ describe "#destroy_archived_attributes" do
52
+ it "should be called on destruction of object" do
53
+ @bear_story.expects(:destroy_archived_attributes).at_least_once
54
+ @bear_story.destroy
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'moneta'
3
+ require 'moneta/memory'
4
+ require 'ostruct'
5
+ require 'mocha'
6
+ require 'spec'
7
+ require 'activerecord'
8
+ require 'zlib'
9
+
10
+ Spec::Runner.configure do |config|
11
+ config.mock_with(:mocha)
12
+ end
13
+
14
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
15
+ require File.join(File.dirname(__FILE__), %w[.. init])
16
+
17
+ RAILS_DEFAULT_LOGGER = Logger.new(STDOUT) unless defined?(RAILS_DEFAULT_LOGGER)
18
+ RAILS_ROOT = File.join(File.dirname(__FILE__), '..') unless defined?(RAILS_ROOT)
19
+ RAILS_ENV = 'test' unless defined?(RAILS_ENV)
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jferris-hashpipe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5.0.1247583913
5
+ platform: ruby
6
+ authors:
7
+ - Justin Leitgeb
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-07-13 21:00:00 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: wycats-moneta
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: activesupport
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 connects an AR-backed model to a key-value store
36
+ email: justin@phq.org
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - LICENSE
45
+ - Rakefile
46
+ - README.rdoc
47
+ - lib/hashpipe/archived_attribute.rb
48
+ - lib/hashpipe/global_configuration.rb
49
+ - lib/hashpipe.rb
50
+ - spec/hashpipe/archived_attribute_spec.rb
51
+ - spec/hashpipe/global_configuration_spec.rb
52
+ - spec/hashpipe_spec.rb
53
+ - spec/spec_helper.rb
54
+ - init.rb
55
+ - config/hashpipe.yml
56
+ has_rdoc: true
57
+ homepage: http://github.com/jsl/hashpipe
58
+ post_install_message:
59
+ rdoc_options: []
60
+
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.2.0
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: ActiveRecord plugin to save content to a pluggable, hash-style backend
82
+ test_files:
83
+ - spec/hashpipe/archived_attribute_spec.rb
84
+ - spec/hashpipe/global_configuration_spec.rb
85
+ - spec/hashpipe_spec.rb
86
+ - spec/spec_helper.rb