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 CHANGED
@@ -1,7 +1,12 @@
1
1
  source "http://rubygems.org"
2
2
 
3
- gem "pry"
4
- gem "yajl-ruby"
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.add_development_dependency "rspec", "~> 2.8.0"
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
- module Kvom
2
- module Adapter
3
- Kvom.setup_autoload(self, __FILE__)
4
- end
1
+ require 'kvom'
2
+
3
+ module Kvom::Adapter
4
+ Kvom.setup_autoload(self, __FILE__)
5
5
  end
@@ -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 Kvom::NotFound.for_key(key)
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 ::CouchdbDocument < Kvom::Document
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 {item.exists?} or raise Kvom::NotFound.for_key(key)
13
- DynamodbDocument.document_with_item(key, item)
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 < Document
5
+ class DynamodbDocument < KeyAttributesDocument
4
6
  class << self
5
7
  def new_document(attributes, key, item)
6
- new(key, item, :attributes => attributes)
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(attributes, key, item)
14
- new(key, item, :item_attributes => attributes)
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
- attr_reader :key
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
- if provided_attributes
25
- if provided_attributes.key?(:attributes)
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
- @modified = true
46
- kv[name] = value
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
- dynamo_attributes = @kv.inject({}) do |memo, (key, value)|
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[key] = value
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
- @rev = new_revision
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" => @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 |(key, value)|
78
+ condition.each do |(name, value)|
103
79
  if value
104
- present_condition[key] = value
80
+ present_condition[name] = value
105
81
  else
106
- missing_condition << key
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 = MultiJson.decode(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
- MultiJson.decode(value)
115
+ Lib::Json.load(value)
140
116
  when "jsonvalue"
141
- # currently the only values to decode are: null, true, false, ""
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