kvom 6.8.0.beta.200.809.bdfa8c3 → 6.8.0.beta.200.856.8c0fec5
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/Gemfile +8 -3
- data/kvom.gemspec +6 -6
- data/lib/kvom.rb +2 -1
- data/lib/kvom/adapter.rb +4 -4
- data/lib/kvom/adapter/base.rb +5 -0
- data/lib/kvom/adapter/couchdb_adapter.rb +18 -16
- data/lib/kvom/adapter/couchdb_document.rb +6 -2
- data/lib/kvom/adapter/dynamodb_adapter.rb +6 -2
- data/lib/kvom/adapter/dynamodb_document.rb +27 -51
- data/lib/kvom/adapter/filesystem_adapter.rb +130 -0
- data/lib/kvom/adapter/filesystem_document.rb +11 -0
- data/lib/kvom/adapter/key_attributes_document.rb +26 -0
- data/lib/kvom/base.rb +3 -0
- data/lib/kvom/document.rb +7 -1
- data/lib/kvom/lib.rb +5 -0
- data/lib/kvom/lib/json.rb +14 -0
- data/lib/kvom/lib/json_value.rb +39 -0
- data/lib/kvom/model_identity.rb +2 -0
- data/lib/kvom/not_found.rb +2 -0
- data/lib/kvom/storage.rb +4 -4
- data/lib/kvom/storage/base.rb +50 -72
- data/lib/kvom/storage/cache_with_prefix.rb +7 -1
- data/lib/kvom/storage/file_system_storage.rb +18 -17
- data/lib/kvom/storage/not_found.rb +3 -6
- data/lib/kvom/storage/s3_storage.rb +55 -53
- data/spec/adaptor/base_spec.rb +87 -0
- data/spec/lib/json_spec.rb +30 -0
- data/spec/spec_helper.rb +15 -3
- data/tmp/.gitignore +3 -0
- metadata +47 -52
data/Gemfile
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
gem "
|
4
|
-
gem "
|
3
|
+
gem "activesupport" # adapter (core), storage (core), file_system_storage
|
4
|
+
gem "couchrest" # couchdb_adapter
|
5
|
+
gem "aws-sdk" # dynamodb_adapter, s3_storage
|
6
|
+
gem "rack" # file_system_storage
|
7
|
+
# active_support/cache/file_store requires rack/utils
|
8
|
+
|
9
|
+
# not that if any kvom source depends on it ;-)
|
10
|
+
gem "pry" # the debugging shell
|
5
11
|
|
6
|
-
# Specify your gem's dependencies in kvom.gemspec
|
7
12
|
gemspec
|
data/kvom.gemspec
CHANGED
@@ -20,12 +20,12 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
21
|
s.require_paths = ["lib"]
|
22
22
|
|
23
|
-
s.
|
23
|
+
s.add_runtime_dependency "multi_json", "~> 1"
|
24
|
+
s.add_runtime_dependency "activesupport", ">= 3" # does not support rational versioning, does it?
|
25
|
+
s.add_runtime_dependency "activemodel", ">= 3" # adapters
|
26
|
+
|
27
|
+
s.add_development_dependency "rspec", "~> 2.8"
|
24
28
|
s.add_development_dependency "helpful_configuration"
|
29
|
+
s.add_development_dependency "yajl-ruby" # a specific json provider
|
25
30
|
|
26
|
-
s.add_runtime_dependency "couchrest"
|
27
|
-
s.add_runtime_dependency "activemodel"
|
28
|
-
s.add_runtime_dependency "aws-sdk"
|
29
|
-
s.add_runtime_dependency "multi_json"
|
30
|
-
s.add_runtime_dependency "rack" # rack/utils by active_support/cache/file_store
|
31
31
|
end
|
data/lib/kvom.rb
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
module Kvom
|
2
2
|
# :nodoc:
|
3
3
|
def self.setup_autoload(mod, mod_source)
|
4
|
+
@base_dir ||= File.expand_path("..", __FILE__).to_s + "/"
|
4
5
|
dir = File.expand_path(".", mod_source)[0..-4]
|
5
6
|
pattern = "#{dir}/*.rb"
|
6
7
|
Dir.glob(pattern).each do |file|
|
7
8
|
const = file[dir.length..-4].gsub(%r{[_/](.)}) {$1.upcase}
|
8
|
-
mod.autoload const.to_sym, file
|
9
|
+
mod.autoload const.to_sym, file[@base_dir.length..-1]
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
data/lib/kvom/adapter.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
require 'kvom'
|
2
|
+
|
3
|
+
module Kvom::Adapter
|
4
|
+
Kvom.setup_autoload(self, __FILE__)
|
5
5
|
end
|
data/lib/kvom/adapter/base.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'kvom/adapter'
|
1
2
|
require 'active_support/core_ext/hash/keys'
|
2
3
|
|
3
4
|
module Kvom; module Adapter
|
@@ -28,6 +29,10 @@ class Base
|
|
28
29
|
raise "implement me in subclass!"
|
29
30
|
end
|
30
31
|
|
32
|
+
def query(hash_value, range_value)
|
33
|
+
raise "implement me in subclass!"
|
34
|
+
end
|
35
|
+
|
31
36
|
protected
|
32
37
|
|
33
38
|
def count_request(num_requests = 1)
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'kvom/adapter'
|
1
2
|
require 'couchrest'
|
2
3
|
|
3
4
|
module Kvom; module Adapter
|
4
5
|
|
5
6
|
class CouchdbAdapter < Base
|
7
|
+
|
6
8
|
def new_document(key, attributes)
|
7
9
|
new_document_from_attributes(attributes.merge("_id" => key))
|
8
10
|
end
|
@@ -16,7 +18,7 @@ class CouchdbAdapter < Base
|
|
16
18
|
begin
|
17
19
|
count_request {database.get(key) }
|
18
20
|
rescue RestClient::ResourceNotFound
|
19
|
-
raise
|
21
|
+
raise NotFound.for_key(key)
|
20
22
|
end
|
21
23
|
CouchdbDocument.new(doc)
|
22
24
|
end
|
@@ -45,21 +47,6 @@ class CouchdbAdapter < Base
|
|
45
47
|
doc.couchrest_document.destroy
|
46
48
|
end
|
47
49
|
|
48
|
-
module BlobSupport
|
49
|
-
|
50
|
-
def blob_url(id)
|
51
|
-
"#{ database_url }/#{ id }/blob"
|
52
|
-
end
|
53
|
-
|
54
|
-
def blob_attachment(id)
|
55
|
-
document = count_request {database.get(id)}
|
56
|
-
document["_attachments"]["blob"]
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
60
|
-
|
61
|
-
include BlobSupport
|
62
|
-
|
63
50
|
private
|
64
51
|
|
65
52
|
def database_url
|
@@ -76,6 +63,21 @@ class CouchdbAdapter < Base
|
|
76
63
|
@database ||= CouchRest.database(database_url)
|
77
64
|
end
|
78
65
|
|
66
|
+
module BlobSupport
|
67
|
+
|
68
|
+
def blob_url(id)
|
69
|
+
"#{ database_url }/#{ id }/blob"
|
70
|
+
end
|
71
|
+
|
72
|
+
def blob_attachment(id)
|
73
|
+
document = count_request {database.get(id)}
|
74
|
+
document["_attachments"]["blob"]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
include BlobSupport
|
80
|
+
|
79
81
|
end
|
80
82
|
|
81
83
|
end; end # module Kvom::Adapter
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require 'kvom/adapter'
|
2
|
+
|
1
3
|
module Kvom; module Adapter
|
2
4
|
|
3
|
-
class
|
5
|
+
class CouchdbDocument < Document
|
6
|
+
|
4
7
|
attr_accessor :couchrest_document
|
5
8
|
|
6
9
|
def initialize(couchrest_document)
|
@@ -18,6 +21,7 @@ class ::CouchdbDocument < Kvom::Document
|
|
18
21
|
def key
|
19
22
|
couchrest_document.id
|
20
23
|
end
|
24
|
+
|
21
25
|
end
|
22
26
|
|
23
|
-
end; end # module Kvom
|
27
|
+
end; end # module Kvom::Adapter
|
@@ -1,16 +1,20 @@
|
|
1
|
+
require 'kvom/adapter'
|
1
2
|
require 'aws-sdk'
|
2
3
|
|
3
4
|
module Kvom; module Adapter
|
4
5
|
|
5
6
|
class DynamodbAdapter < Base
|
7
|
+
|
6
8
|
def new_document(key, attributes = nil)
|
7
9
|
DynamodbDocument.new_document(attributes, key, item_for_key(key))
|
8
10
|
end
|
9
11
|
|
10
12
|
def get(key)
|
11
13
|
item = item_for_key(key)
|
12
|
-
count_request {
|
13
|
-
|
14
|
+
count_request {
|
15
|
+
(attributes = item.attributes.to_h).empty? and raise NotFound.for_key(key)
|
16
|
+
DynamodbDocument.document_from_query(attributes, key, item)
|
17
|
+
}
|
14
18
|
end
|
15
19
|
|
16
20
|
def save(doc)
|
@@ -1,38 +1,25 @@
|
|
1
|
+
require 'kvom/adapter'
|
2
|
+
|
1
3
|
module Kvom; module Adapter
|
2
4
|
|
3
|
-
class DynamodbDocument <
|
5
|
+
class DynamodbDocument < KeyAttributesDocument
|
4
6
|
class << self
|
5
7
|
def new_document(attributes, key, item)
|
6
|
-
new(key, item,
|
7
|
-
end
|
8
|
-
|
9
|
-
def document_with_item(key, item)
|
10
|
-
new(key, item)
|
8
|
+
new(key, item, attributes, nil)
|
11
9
|
end
|
12
10
|
|
13
|
-
def document_from_query(
|
14
|
-
|
11
|
+
def document_from_query(item_attributes, key, item)
|
12
|
+
attributes = AttributesFromItemAttributes.from(item_attributes)
|
13
|
+
new(key, item, attributes, attributes["@rev"])
|
15
14
|
end
|
16
15
|
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
def initialize(key, item, provided_attributes = nil)
|
22
|
-
@key = key
|
18
|
+
def initialize(key, item, attributes, revision)
|
19
|
+
super(key, attributes)
|
23
20
|
@dynamo_item = item
|
24
|
-
|
25
|
-
|
26
|
-
@kv = provided_attributes[:attributes] || {}
|
27
|
-
@modified = true
|
28
|
-
elsif provided_attributes.key?(:item_attributes)
|
29
|
-
@kv = attributes_from_item_attributes(provided_attributes[:item_attributes])
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
def [](name)
|
35
|
-
kv[name]
|
21
|
+
@revision = revision
|
22
|
+
@modified = @revision == nil
|
36
23
|
end
|
37
24
|
|
38
25
|
def []=(name, value)
|
@@ -42,18 +29,21 @@ class DynamodbDocument < Document
|
|
42
29
|
else
|
43
30
|
raise "Unsupported value type: #{value.class.name}"
|
44
31
|
end
|
45
|
-
|
46
|
-
|
32
|
+
unless self[name] == value
|
33
|
+
@modified = true
|
34
|
+
super
|
35
|
+
end
|
47
36
|
end
|
48
37
|
|
49
38
|
def save
|
50
39
|
return unless modified?
|
51
|
-
|
40
|
+
raise "Not yet implemented" if AttributesFromItemAttributes === attributes
|
41
|
+
dynamo_attributes = attributes.inject({}) do |memo, (name, value)|
|
52
42
|
case value
|
53
43
|
when nil
|
54
44
|
# skip
|
55
45
|
when String, Fixnum, BigDecimal
|
56
|
-
memo[
|
46
|
+
memo[name] = value
|
57
47
|
else
|
58
48
|
raise "Unsupported value type #{value.class.to_s}"
|
59
49
|
end
|
@@ -64,7 +54,7 @@ class DynamodbDocument < Document
|
|
64
54
|
dynamo_attributes["@rev"] = new_revision = (@rev || 0) + 1
|
65
55
|
# might be improved to saving only changed attributes
|
66
56
|
@dynamo_item.table.items.put(dynamo_attributes, revision_condition)
|
67
|
-
@
|
57
|
+
@revision = new_revision
|
68
58
|
@modified = false
|
69
59
|
end
|
70
60
|
|
@@ -78,32 +68,18 @@ class DynamodbDocument < Document
|
|
78
68
|
@modified
|
79
69
|
end
|
80
70
|
|
81
|
-
def attributes_from_item_attributes(item_attributes)
|
82
|
-
attributes = AttributesFromItemAttributes.from(item_attributes)
|
83
|
-
@rev = attributes["@rev"]
|
84
|
-
attributes
|
85
|
-
end
|
86
|
-
|
87
|
-
def kv
|
88
|
-
@kv ||= attributes_from_item_attributes(@dynamo_item.attributes.to_h)
|
89
|
-
end
|
90
|
-
|
91
|
-
def revision
|
92
|
-
@rev
|
93
|
-
end
|
94
|
-
|
95
71
|
def revision_condition
|
96
|
-
dynamo_condition("@rev" => @
|
72
|
+
dynamo_condition("@rev" => @revision)
|
97
73
|
end
|
98
74
|
|
99
75
|
def dynamo_condition(condition)
|
100
76
|
present_condition = {}
|
101
77
|
missing_condition = []
|
102
|
-
condition.each do |(
|
78
|
+
condition.each do |(name, value)|
|
103
79
|
if value
|
104
|
-
present_condition[
|
80
|
+
present_condition[name] = value
|
105
81
|
else
|
106
|
-
missing_condition <<
|
82
|
+
missing_condition << name
|
107
83
|
end
|
108
84
|
end
|
109
85
|
options = {}
|
@@ -118,7 +94,7 @@ class DynamodbDocument < Document
|
|
118
94
|
attributes.delete("range_key")
|
119
95
|
meta = attributes.delete("@meta.json")
|
120
96
|
return attributes unless meta
|
121
|
-
meta =
|
97
|
+
meta = Lib::Json.load(meta)
|
122
98
|
coding = meta["coding"]
|
123
99
|
return attributes if !coding || coding.empty?
|
124
100
|
new(attributes, coding)
|
@@ -136,10 +112,9 @@ class DynamodbDocument < Document
|
|
136
112
|
self[name] =
|
137
113
|
case coding
|
138
114
|
when "json"
|
139
|
-
|
115
|
+
Lib::Json.load(value)
|
140
116
|
when "jsonvalue"
|
141
|
-
|
142
|
-
MultiJson.decode("{\"k\":#{value}}")["k"]
|
117
|
+
Lib::JsonValue.load(value)
|
143
118
|
else
|
144
119
|
raise "Unexpected coding #{coding} for attribute #{name}"
|
145
120
|
end
|
@@ -151,6 +126,7 @@ class DynamodbDocument < Document
|
|
151
126
|
end
|
152
127
|
|
153
128
|
end
|
129
|
+
|
154
130
|
end
|
155
131
|
|
156
132
|
end; end # module Kvom::Adapter
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'kvom/adapter'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Kvom; module Adapter
|
6
|
+
|
7
|
+
class FilesystemAdapter < Base
|
8
|
+
|
9
|
+
EXT = ".doc"
|
10
|
+
EXT_SIZE = EXT.length
|
11
|
+
EXT_OFFSET = -(EXT_SIZE + 1)
|
12
|
+
|
13
|
+
def new_document(key, attributes)
|
14
|
+
# there is no "was loaded from db" yet (e.g. for conditional put)
|
15
|
+
FilesystemDocument.new(key, attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(key)
|
19
|
+
count_request { document_for_key(key) } or raise NotFound.for_key(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def destroy(doc)
|
23
|
+
FileUtils.rm_f(filepath_for_document(doc))
|
24
|
+
end
|
25
|
+
|
26
|
+
def save(doc)
|
27
|
+
path = filepath_for_document(doc)
|
28
|
+
FileUtils.mkdir_p(path.dirname)
|
29
|
+
# TODO: conditional put
|
30
|
+
File.open(path, "w") {|f| f.write(doc.to_json)}
|
31
|
+
end
|
32
|
+
|
33
|
+
def query(hash_value, range_value)
|
34
|
+
pathes_and_range_values =
|
35
|
+
count_request do
|
36
|
+
case range_value
|
37
|
+
when Range
|
38
|
+
range_file_query(hash_value, range_value.first, range_value.last)
|
39
|
+
else
|
40
|
+
single_file_query(hash_value, range_value)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
pathes_and_range_values.inject([]) do |memo, (path, doc_range_value)|
|
44
|
+
key = [hash_value, doc_range_value].join("|")
|
45
|
+
doc = document_for_key_and_path(key, path)
|
46
|
+
memo << doc if doc
|
47
|
+
memo
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def base_dir
|
54
|
+
@dir ||= begin
|
55
|
+
dir = Pathname.new(connection_spec[:path])
|
56
|
+
FileUtils.mkdir_p(dir)
|
57
|
+
dir
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def document_for_key(key)
|
62
|
+
document_for_key_and_path(key, filepath_for_key(key))
|
63
|
+
end
|
64
|
+
|
65
|
+
def document_for_key_and_path(key, path)
|
66
|
+
begin
|
67
|
+
serialized_doc = File.read(path)
|
68
|
+
rescue ::Errno::ENOENT
|
69
|
+
return nil
|
70
|
+
end
|
71
|
+
# there is no "was loaded from db" yet (e.g. for conditional put)
|
72
|
+
FilesystemDocument.new(key, Lib::Json.load(serialized_doc))
|
73
|
+
end
|
74
|
+
|
75
|
+
def filepath_for_document(doc)
|
76
|
+
filepath_for_key(doc.key)
|
77
|
+
end
|
78
|
+
|
79
|
+
def filepath_for_key(key)
|
80
|
+
filepath_for_hash_and_range(*(key.split("|", 2)))
|
81
|
+
end
|
82
|
+
|
83
|
+
def filepath_for_hash_and_range(hash_value, range_value)
|
84
|
+
dir_for_hash_value(hash_value) + path_component_for_range_value(range_value)
|
85
|
+
end
|
86
|
+
|
87
|
+
def dir_for_hash_value(hash_value)
|
88
|
+
base_dir + path_component_for_value(hash_value)
|
89
|
+
end
|
90
|
+
|
91
|
+
def path_component_for_value(value)
|
92
|
+
# value should not have a pipe character
|
93
|
+
value.gsub("/", "|")
|
94
|
+
end
|
95
|
+
|
96
|
+
def path_component_for_range_value(range_value)
|
97
|
+
path_component_for_value(range_value) << EXT
|
98
|
+
end
|
99
|
+
|
100
|
+
def value_from_path_component(path_component)
|
101
|
+
path_component.to_s.gsub("|", "/")
|
102
|
+
end
|
103
|
+
|
104
|
+
def range_value_from_path_component(path_component)
|
105
|
+
value_from_path_component(path_component.to_s[0..EXT_OFFSET])
|
106
|
+
end
|
107
|
+
|
108
|
+
def single_file_query(hash_value, range_value)
|
109
|
+
path = filepath_for_hash_and_range(hash_value, range_value)
|
110
|
+
[[path, range_value]]
|
111
|
+
end
|
112
|
+
|
113
|
+
def range_file_query(hash_value, start_range_value, end_range_value)
|
114
|
+
hash_dir = dir_for_hash_value(hash_value)
|
115
|
+
Dir.foreach(hash_dir).inject([]) do |memo, entry|
|
116
|
+
if entry[-EXT_SIZE..-1] == EXT
|
117
|
+
range_value = range_value_from_path_component(entry)
|
118
|
+
if start_range_value <= range_value && range_value <= end_range_value
|
119
|
+
memo << [hash_dir + entry, range_value]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
memo
|
123
|
+
end
|
124
|
+
rescue Errno::ENOENT
|
125
|
+
[]
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
end; end # module Kvom::Adapter
|