vuderacha-activekv 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +34 -0
- data/Rakefile +66 -0
- data/lib/activekv/base.rb +150 -0
- data/lib/activekv.rb +14 -0
- data/spec/base.rb +115 -0
- data/spec/simple-config.yml +3 -0
- metadata +69 -0
data/README.rdoc
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
= ActiveKV: Active Key/Value objects
|
2
|
+
ActiveKV provides a wrapper around Moneta to allow for active objects that can be
|
3
|
+
searched and persisted into configured key value stores. Values are serialised JSON.
|
4
|
+
|
5
|
+
== Getting Started
|
6
|
+
Create an ActiveKV configuration file:
|
7
|
+
-- config/kv.yml
|
8
|
+
development:
|
9
|
+
default:
|
10
|
+
driver: memory
|
11
|
+
test:
|
12
|
+
default:
|
13
|
+
driver: memory
|
14
|
+
|
15
|
+
Initialise the ActiveKV infrastructure:
|
16
|
+
require 'activekv'
|
17
|
+
ActiveKV::Base.configure('config/kv.yml')
|
18
|
+
|
19
|
+
Create your ActiveKV object:
|
20
|
+
class MyPersistentObj < ActiveKV::Base
|
21
|
+
key :name
|
22
|
+
attr_accessor :x
|
23
|
+
end
|
24
|
+
|
25
|
+
Store your object:
|
26
|
+
o = MyPersistentObj.new
|
27
|
+
o.name = "asdasd"
|
28
|
+
o.x = 12
|
29
|
+
o.save!
|
30
|
+
|
31
|
+
Load your object:
|
32
|
+
r = MyPersistentObj.find("asdasd")
|
33
|
+
p r.name
|
34
|
+
p r.x
|
data/Rakefile
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'spec/rake/spectask'
|
3
|
+
|
4
|
+
desc "Run all examples"
|
5
|
+
Spec::Rake::SpecTask.new('examples') do |t|
|
6
|
+
t.spec_opts = ["-cfs"]
|
7
|
+
t.spec_files = FileList['spec/**/*.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Print specdocs"
|
11
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
12
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
13
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
14
|
+
end
|
15
|
+
|
16
|
+
desc "Generate RCov code coverage report"
|
17
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
18
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
19
|
+
t.rcov = true
|
20
|
+
t.rcov_opts = ['--exclude', 'examples']
|
21
|
+
end
|
22
|
+
|
23
|
+
task :default => :spec
|
24
|
+
|
25
|
+
######################################################
|
26
|
+
|
27
|
+
require 'rake'
|
28
|
+
require 'rake/testtask'
|
29
|
+
require 'rake/clean'
|
30
|
+
require 'rake/gempackagetask'
|
31
|
+
require 'rake/rdoctask'
|
32
|
+
require 'fileutils'
|
33
|
+
require 'jeweler'
|
34
|
+
include FileUtils
|
35
|
+
# $:.push File.join(File.dirname(__FILE__), 'lib')
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'jeweler'
|
39
|
+
Jeweler::Tasks.new do |s|
|
40
|
+
s.name = "activekv"
|
41
|
+
s.summary = "Ruby Active Key/Value Objects."
|
42
|
+
s.description = "ActiveKV provides ActiveRecord style data storage for Key/Value stores."
|
43
|
+
s.author = "Paul Jones"
|
44
|
+
s.email = "pauljones23@gmail.com"
|
45
|
+
s.homepage = "http://github.com/vuderacha/activekv/"
|
46
|
+
|
47
|
+
s.files = %w(Rakefile README.rdoc) + Dir.glob("{bin,lib,spec}/**/*")
|
48
|
+
s.require_path = "lib"
|
49
|
+
s.add_dependency('activesupport', '>= 2.2.2')
|
50
|
+
end
|
51
|
+
rescue LoadError
|
52
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
53
|
+
end
|
54
|
+
|
55
|
+
task :test => [ :spec ]
|
56
|
+
|
57
|
+
Rake::RDocTask.new do |t|
|
58
|
+
t.rdoc_dir = 'rdoc'
|
59
|
+
t.title = "ActiveKV -- Active Key/Value Objects"
|
60
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
61
|
+
t.options << '--charset' << 'utf-8'
|
62
|
+
t.rdoc_files.include('README.rdoc')
|
63
|
+
t.rdoc_files.include('lib/activekv/*.rb')
|
64
|
+
end
|
65
|
+
|
66
|
+
CLEAN.include [ 'build/*', '**/*.o', '**/*.so', '**/*.a', 'lib/*-*', '**/*.log', 'pkg', 'lib/*.bundle', '*.gem', '.config' ]
|
@@ -0,0 +1,150 @@
|
|
1
|
+
module ActiveKV
|
2
|
+
class Base
|
3
|
+
# Default the key property and configured state to empty states
|
4
|
+
@@configured = nil, false
|
5
|
+
|
6
|
+
def initialize(config = {})
|
7
|
+
apply_hash!(config)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Configures the ActiveKV support with the given configuration file
|
11
|
+
def self.configure(config_file)
|
12
|
+
# Determine our environment
|
13
|
+
env = if defined? RACK_ENV then RACK_ENV else :development end
|
14
|
+
|
15
|
+
# Load the configuration, and find the appropriate section
|
16
|
+
config = YAML::load_file(config_file)
|
17
|
+
env_config = config[env.to_s]
|
18
|
+
if env_config.nil?
|
19
|
+
raise "No configuration found for environment #{env}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Work through each store
|
23
|
+
@@stores = {}
|
24
|
+
@@default_store = nil
|
25
|
+
env_config.each do |store_name, store_config|
|
26
|
+
@@stores[store_name] = load_store(store_name, store_config)
|
27
|
+
@@default_store = @@stores[store_name] if @@default_store.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
# Remember that we're configured
|
31
|
+
@@configured = true
|
32
|
+
end
|
33
|
+
|
34
|
+
# Prepares a configuration store based on the given configuration
|
35
|
+
def self.load_store(name, config)
|
36
|
+
raise "No driver specified in configuration #{store_name}" if config['driver'].nil?
|
37
|
+
|
38
|
+
# Try to create a store driver
|
39
|
+
driver_name = config.delete 'driver'
|
40
|
+
require "moneta/#{driver_name}"
|
41
|
+
class_name = Moneta.constants.find { |c| c.to_s.downcase == driver_name.gsub(/_/, "").downcase }
|
42
|
+
raise "No implementation found for driver #{config['driver']}" if class_name.nil?
|
43
|
+
driver_class = Moneta.const_get(class_name)
|
44
|
+
driver_class.new transform_hash(config)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Requests that the ActiveKV forget its configuration
|
48
|
+
def self.unconfigure!
|
49
|
+
@@configured = false
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self # Class Methods
|
53
|
+
# Sets the property that will be used to work out the key for the item
|
54
|
+
def key(key_prop = nil)
|
55
|
+
if key_prop.nil?
|
56
|
+
return nil if not defined? @key_prop
|
57
|
+
return @key_prop
|
58
|
+
end
|
59
|
+
|
60
|
+
@key_prop = key_prop
|
61
|
+
attr_accessor key_prop
|
62
|
+
end
|
63
|
+
|
64
|
+
# Changes the pattern used to build keys
|
65
|
+
def key_pattern(pattern = nil)
|
66
|
+
if pattern.nil?
|
67
|
+
return ":class/:key" if not defined? @key_pattern or @key_pattern.nil?
|
68
|
+
return @key_pattern
|
69
|
+
end
|
70
|
+
|
71
|
+
@key_pattern = pattern
|
72
|
+
end
|
73
|
+
|
74
|
+
# Sets the store that is used for instances of this class
|
75
|
+
def store(name)
|
76
|
+
@store = name
|
77
|
+
end
|
78
|
+
|
79
|
+
# Finds a record with the given key
|
80
|
+
def find(key)
|
81
|
+
# Create the key and lookup the value
|
82
|
+
key_val = create_key key
|
83
|
+
found_val = store_instance[key_val]
|
84
|
+
return nil if found_val.nil? or found_val == {}
|
85
|
+
# Check if result is {}, because the memory implementation returns this as a default value
|
86
|
+
# when the real value isn't found
|
87
|
+
|
88
|
+
# Decode the found value, then apply each property in the JSON to the object
|
89
|
+
vals = ActiveSupport::JSON.decode(found_val)
|
90
|
+
result = new
|
91
|
+
result.apply_hash!(vals)
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
# Creates a store key based on the pattern and provided key
|
96
|
+
def create_key(key)
|
97
|
+
key_pattern.gsub(':class', self.name).gsub(':key', key)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Retrieves an instance of the store that is used for this class
|
101
|
+
def store_instance
|
102
|
+
if defined? @store and defined? @@stores[@store] then
|
103
|
+
@@stores[@store]
|
104
|
+
else
|
105
|
+
@@default_store
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Saves the record to the appropriate datastore
|
111
|
+
def save!
|
112
|
+
# Ensure that we have everything we need
|
113
|
+
raise NotConfiguredError unless @@configured
|
114
|
+
raise NoKeySpecifiedError if self.class.key.nil?
|
115
|
+
raise NilKeyError if send(self.class.key).nil?
|
116
|
+
|
117
|
+
# Transform ourselves to json, and get our key
|
118
|
+
new_val = to_json
|
119
|
+
key_val = self.class.create_key send(self.class.key)
|
120
|
+
|
121
|
+
# Retrieve the appropriate store, and save
|
122
|
+
self.class.store_instance[key_val] = new_val
|
123
|
+
end
|
124
|
+
|
125
|
+
def apply_hash!(props)
|
126
|
+
props.each do |k, v|
|
127
|
+
send("#{k}=", v)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Transforms a hash so it also indexes by symbol
|
132
|
+
def self.transform_hash h
|
133
|
+
result = {}
|
134
|
+
h.each do |k,v|
|
135
|
+
result_v = if v.is_a? Hash then transform_hash(v) else v end
|
136
|
+
|
137
|
+
result[k] = result_v
|
138
|
+
result[k.to_sym] = result_v
|
139
|
+
end
|
140
|
+
result
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
class NotConfiguredError < Exception
|
145
|
+
end
|
146
|
+
class NilKeyError < Exception
|
147
|
+
end
|
148
|
+
class NoKeySpecifiedError < Exception
|
149
|
+
end
|
150
|
+
end
|
data/lib/activekv.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
begin
|
3
|
+
require 'moneta'
|
4
|
+
rescue LoadError
|
5
|
+
puts "You need the moneta gem to use ActiveKV"
|
6
|
+
exit
|
7
|
+
end
|
8
|
+
begin
|
9
|
+
require 'active_support'
|
10
|
+
rescue LoadError
|
11
|
+
puts "You need the ActiveSupport gem to use ActiveKV"
|
12
|
+
exit
|
13
|
+
end
|
14
|
+
require 'activekv/base'
|
data/spec/base.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
$:.push File.join(File.dirname(__FILE__), '..', 'lib')
|
2
|
+
require 'activekv'
|
3
|
+
|
4
|
+
RACK_ENV = :test unless defined? RACK_ENV
|
5
|
+
|
6
|
+
describe "ActiveKV::Base" do
|
7
|
+
before do
|
8
|
+
ActiveKV::Base.unconfigure!
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should be inheritable" do
|
12
|
+
class InheritanceTest < ActiveKV::Base
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should allow a configuration file to be provided" do
|
17
|
+
ActiveKV::Base.configure File.join(File.dirname(__FILE__), 'simple-config.yml')
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should fail to save an object if not configured" do
|
21
|
+
class Unconfigured < ActiveKV::Base
|
22
|
+
end
|
23
|
+
|
24
|
+
u = Unconfigured.new
|
25
|
+
begin
|
26
|
+
u.save!
|
27
|
+
raise "Should not have allowed an instance to be saved when not configured"
|
28
|
+
rescue ActiveKV::NotConfiguredError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should make the key available on the class" do
|
33
|
+
class WithKeyA < ActiveKV::Base
|
34
|
+
key :a
|
35
|
+
end
|
36
|
+
class WithKeyB < ActiveKV::Base
|
37
|
+
key :b
|
38
|
+
end
|
39
|
+
|
40
|
+
WithKeyA.key.should == :a
|
41
|
+
WithKeyB.key.should == :b
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "ActiveKV::Base when configured" do
|
46
|
+
before do
|
47
|
+
ActiveKV::Base.unconfigure!
|
48
|
+
ActiveKV::Base.configure File.join(File.dirname(__FILE__), 'simple-config.yml')
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should fail to save an object if no key is specified" do
|
52
|
+
class NoKey < ActiveKV::Base
|
53
|
+
end
|
54
|
+
|
55
|
+
u = NoKey.new
|
56
|
+
begin
|
57
|
+
u.save!
|
58
|
+
raise "Should not have allowed an instance to be saved when it does not specify a key"
|
59
|
+
rescue ActiveKV::NoKeySpecifiedError
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should fail to save an object if key is nil" do
|
64
|
+
class WithKey < ActiveKV::Base
|
65
|
+
key :name
|
66
|
+
end
|
67
|
+
|
68
|
+
u = WithKey.new
|
69
|
+
begin
|
70
|
+
u.save!
|
71
|
+
raise "Should not have allowed an instance to be saved when key value is nil"
|
72
|
+
rescue ActiveKV::NilKeyError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should allow an object to be saved if configured and a key is specified" do
|
77
|
+
class WithKey < ActiveKV::Base
|
78
|
+
key :name
|
79
|
+
end
|
80
|
+
|
81
|
+
u = WithKey.new
|
82
|
+
u.name = 'TestName'
|
83
|
+
u.save!
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should not find an object that has not been created" do
|
87
|
+
class NotFound < ActiveKV::Base
|
88
|
+
end
|
89
|
+
|
90
|
+
NotFound.find('missing').should be_nil
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should find an object that has previously been saved" do
|
94
|
+
class Found < ActiveKV::Base
|
95
|
+
key :name
|
96
|
+
end
|
97
|
+
|
98
|
+
f = Found.new
|
99
|
+
f.name = 'myname'
|
100
|
+
f.save!
|
101
|
+
|
102
|
+
Found.find('myname').name.should == 'myname'
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should allow an object to be created with a hash of parameters" do
|
106
|
+
class WithSomeParams < ActiveKV::Base
|
107
|
+
key :name
|
108
|
+
attr_accessor :another_prop
|
109
|
+
end
|
110
|
+
|
111
|
+
p = WithSomeParams.new :name => 'asr', :another_prop => 'world'
|
112
|
+
p.name.should == 'asr'
|
113
|
+
p.another_prop.should == 'world'
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vuderacha-activekv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Paul Jones
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-22 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: activesupport
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.2.2
|
24
|
+
version:
|
25
|
+
description: ActiveKV provides ActiveRecord style data storage for Key/Value stores.
|
26
|
+
email: pauljones23@gmail.com
|
27
|
+
executables: []
|
28
|
+
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- README.rdoc
|
33
|
+
files:
|
34
|
+
- Rakefile
|
35
|
+
- README.rdoc
|
36
|
+
- lib/activekv
|
37
|
+
- lib/activekv/base.rb
|
38
|
+
- lib/activekv.rb
|
39
|
+
- spec/base.rb
|
40
|
+
- spec/simple-config.yml
|
41
|
+
has_rdoc: true
|
42
|
+
homepage: http://github.com/vuderacha/activekv/
|
43
|
+
post_install_message:
|
44
|
+
rdoc_options:
|
45
|
+
- --inline-source
|
46
|
+
- --charset=UTF-8
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0"
|
54
|
+
version:
|
55
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: "0"
|
60
|
+
version:
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.2.0
|
65
|
+
signing_key:
|
66
|
+
specification_version: 2
|
67
|
+
summary: Ruby Active Key/Value Objects.
|
68
|
+
test_files: []
|
69
|
+
|