kvom 6.8.0.beta.200.809.bdfa8c3 → 6.8.0.beta.200.856.8c0fec5

Sign up to get free protection for your applications and to get access to all the features.
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