perobs 1.1.0 → 2.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 34ecb42146422fb57aac3c2e41b914417f90521c
4
- data.tar.gz: 941af4dcb9c8758ec16e82a4a14ec3c568d5a6de
3
+ metadata.gz: cea78483599ce586c57310a9d4aa622b3bfc3ca2
4
+ data.tar.gz: febaf3328e7a157a38529507a4a5a91a0bc49d2e
5
5
  SHA512:
6
- metadata.gz: 84082c2754a7da996b04b09c1530f09f420ef03c4ff38db6287fb9fed96911cae9e6f442e20fb1d38185d477fb7ab7f95604a5bf0c7207b4feb5642984bfc8f8
7
- data.tar.gz: 5df236bfeb70ca8434d9dd436407f17b5af159d20dc4a4398603d8200fb6964ddefa1fea247e16886c9bbbf2284336b4862efc2a64030be1bf8aff1b8eaa7afe
6
+ metadata.gz: 6be86d3f49ff2e7bb816d7a6a257f07642b01cd8bdb94636fc2ccbd571fd225e0cd281786372051fc940f84f5440001ab5519994980775bd710c73fef59cffeb
7
+ data.tar.gz: fc1632708462f8f81b15f28f8a0226d49521443bf6fc6a0b0f1470efcec0a9f81412a08093f956718d84ab9e02b3c5f6e31fa37fa9da3b949250f8c5904a8200
data/README.md CHANGED
@@ -60,7 +60,7 @@ class Person < PEROBS::Object
60
60
  def initialize(store, name)
61
61
  super
62
62
  attr_init(:name, name)
63
- attr_init(:kids, PEROBS::Array.new(store))
63
+ attr_init(:kids, store.new(PEROBS::Array))
64
64
  end
65
65
 
66
66
  def to_s
@@ -71,9 +71,9 @@ class Person < PEROBS::Object
71
71
  end
72
72
 
73
73
  store = PEROBS::Store.new('family')
74
- store['grandpa'] = joe = Person.new(store, 'Joe')
75
- store['grandma'] = jane = Person.new(store, 'Jane')
76
- jim = Person.new(store, 'Jim')
74
+ store['grandpa'] = joe = store.new(Person, 'Joe')
75
+ store['grandma'] = jane = store.new(Person, 'Jane')
76
+ jim = store.new(Person, 'Jim')
77
77
  jim.father = joe
78
78
  joe.kids << jim
79
79
  jim.mother = jane
data/Rakefile CHANGED
@@ -16,7 +16,5 @@ Dir.glob( 'tasks/*.rake').each do |fn|
16
16
  end
17
17
  end
18
18
 
19
- task :default => :spec
20
- task :test => :spec
21
-
22
- desc 'Run all unit and spec tests'
19
+ task :default => :test
20
+ desc 'Run all tests'
data/lib/perobs/Array.rb CHANGED
@@ -30,11 +30,55 @@ require 'perobs/ObjectBase'
30
30
  module PEROBS
31
31
 
32
32
  # An Array that is transparently persisted onto the back-end storage. It is
33
- # very similar to the Ruby built-in Array class but has some additional
34
- # limitations. The hash key must always be a String.
33
+ # very similar to the Ruby built-in Array class but like other PEROBS
34
+ # object classes it converts direct references to other PEROBS objects into
35
+ # POXReference objects that only indirectly reference the other object. It
36
+ # also tracks all reads and write to any Array element and updates the cache
37
+ # accordingly.
38
+ #
39
+ # We don't support an Array.initialize_copy proxy as this would conflict
40
+ # with BasicObject.initialize_copy. You can use PEROBS::Array.replace()
41
+ # instead.
35
42
  class Array < ObjectBase
36
43
 
37
- # Create a new PersistentArray object.
44
+ attr_reader :data
45
+
46
+ # These methods do not mutate the Array. They only perform read
47
+ # operations.
48
+ ([
49
+ :&, :*, :+, :-, :==, :[], :<=>, :at, :abbrev, :assoc, :bsearch, :collect,
50
+ :combination, :compact, :count, :cycle, :dclone, :drop, :drop_while,
51
+ :each, :each_index, :empty?, :eql?, :fetch, :find_index, :first,
52
+ :flatten, :frozen?, :hash, :include?, :index, :inspect, :join, :last,
53
+ :length, :map, :pack, :permutation, :pretty_print, :pretty_print_cycle,
54
+ :product, :rassoc, :reject, :repeated_combination,
55
+ :repeated_permutation, :reverse, :reverse_each, :rindex, :rotate,
56
+ :sample, :select, :shelljoin, :shuffle, :size, :slice, :sort, :take,
57
+ :take_while, :to_a, :to_ary, :to_s, :transpose, :uniq, :values_at, :zip,
58
+ :|
59
+ ] + Enumerable.instance_methods).uniq.each do |method_sym|
60
+ define_method(method_sym) do |*args, &block|
61
+ @store.cache.cache_read(self)
62
+ @data.send(method_sym, *args, &block)
63
+ end
64
+ end
65
+
66
+ # These methods mutate the Array.
67
+ [
68
+ :<<, :[]=, :clear, :collect!, :compact!, :concat, :delete, :delete_at,
69
+ :delete_if, :fill, :flatten!, :insert, :keep_if, :map!, :pop, :push,
70
+ :reject!, :replace, :select!, :reverse!, :rotate!, :shift, :shuffle!,
71
+ :slice!, :sort!, :sort_by!, :uniq!, :unshift
72
+ ].each do |method_sym|
73
+ define_method(method_sym) do |*args, &block|
74
+ @store.cache.cache_write(self)
75
+ @data.send(method_sym, *args, &block)
76
+ end
77
+ end
78
+
79
+ # New PEROBS objects must always be created by calling # Store.new().
80
+ # PEROBS users should never call this method or equivalents of derived
81
+ # methods directly.
38
82
  # @param store [Store] The Store this hash is stored in
39
83
  # @param size [Fixnum] The requested size of the Array
40
84
  # @param default [Any] The default value that is returned when no value is
@@ -44,113 +88,22 @@ module PEROBS
44
88
  @data = ::Array.new(size, default)
45
89
  end
46
90
 
47
- # Equivalent to Array::[]
48
- def [](index)
49
- _dereferenced(@data[index])
50
- end
51
-
52
- # Equivalent to Array::[]=
53
- def []=(index, obj)
54
- @data[index] = _referenced(obj)
55
- @store.cache.cache_write(self)
56
-
57
- obj
58
- end
59
-
60
- # Equivalent to Array::<<
61
- def <<(obj)
62
- @store.cache.cache_write(self)
63
- @data << _referenced(obj)
64
- end
65
-
66
- # Equivalent to Array::+
67
- def +(ary)
68
- @store.cache.cache_write(self)
69
- @data + ary
70
- end
71
-
72
- # Equivalent to Array::push
73
- def push(obj)
74
- @store.cache.cache_write(self)
75
- @data.push(_referenced(obj))
76
- end
77
-
78
- # Equivalent to Array::pop
79
- def pop
80
- @store.cache.cache_write(self)
81
- _dereferenced(@data.pop)
82
- end
83
-
84
- # Equivalent to Array::clear
85
- def clear
86
- @store.cache.cache_write(self)
87
- @data.clear
88
- end
89
-
90
- # Equivalent to Array::delete
91
- def delete(obj)
92
- @store.cache.cache_write(self)
93
- @data.delete { |v| _dereferenced(v) == obj }
94
- end
95
-
96
- # Equivalent to Array::delete_at
97
- def delete_at(index)
98
- @store.cache.cache_write(self)
99
- @data.delete_at(index)
100
- end
101
-
102
- # Equivalent to Array::delete_if
103
- def delete_if
104
- @data.delete_if do |item|
105
- yield(_dereferenced(item))
106
- end
107
- end
108
-
109
- # Equivalent to Array::each
110
- def each
111
- @data.each do |item|
112
- yield(_dereferenced(item))
113
- end
114
- end
115
-
116
- # Equivalent to Array::empty?
117
- def empty?
118
- @data.empty?
119
- end
120
-
121
- # Equivalent to Array::include?
122
- def include?(obj)
123
- @data.each { |v| return true if _dereferenced(v) == obj }
124
-
125
- false
126
- end
127
-
128
- # Equivalent to Array::length
129
- def length
130
- @data.length
131
- end
132
- alias size length
133
-
134
- # Equivalent to Array::map
135
- def map
136
- @data.map do |item|
137
- yield(_dereferenced(item))
138
- end
139
- end
140
- alias collect map
141
-
142
91
  # Return a list of all object IDs of all persistend objects that this Array
143
92
  # is referencing.
144
93
  # @return [Array of Fixnum or Bignum] IDs of referenced objects
145
94
  def _referenced_object_ids
146
- @data.each.select { |v| v && v.is_a?(POReference) }.map { |o| o.id }
95
+ @data.each.select do |v|
96
+ v && v.respond_to?(:is_poxreference?)
97
+ end.map { |o| o.id }
147
98
  end
148
99
 
149
100
  # This method should only be used during store repair operations. It will
150
101
  # delete all referenced to the given object ID.
151
102
  # @param id [Fixnum/Bignum] targeted object ID
152
103
  def _delete_reference_to_id(id)
153
- @data.delete_if { |v| v && v.is_a?(POReference) && v.id == id }
104
+ @data.delete_if do |v|
105
+ v && v.respond_to?(:is_poxreference?) && v.id == id
106
+ end
154
107
  end
155
108
 
156
109
  # Restore the persistent data from a single data structure.
@@ -158,19 +111,16 @@ module PEROBS
158
111
  # @param data [Array] the actual Array object
159
112
  # @private
160
113
  def _deserialize(data)
161
- @data = data
162
- end
163
-
164
- # Textual dump for debugging purposes
165
- # @return [String]
166
- def inspect
167
- "[\n" + @data.map { |v| " #{v.inspect}" }.join(",\n") + "\n]\n"
114
+ @data = data.map { |v| v.is_a?(POReference) ?
115
+ POXReference.new(@store, v.id) : v }
168
116
  end
169
117
 
170
118
  private
171
119
 
172
120
  def _serialize
173
- @data
121
+ @data.map do |v|
122
+ v.respond_to?(:is_poxreference?) ? POReference.new(v.id) : v
123
+ end
174
124
  end
175
125
 
176
126
  end
@@ -103,7 +103,7 @@ module PEROBS
103
103
  # Return true if the object with given ID exists
104
104
  # @param id [Fixnum or Bignum]
105
105
  def include?(id)
106
- (blob = find_blob(id)) && blob.find(id)
106
+ !(blob = find_blob(id)).nil? && !blob.find(id).nil?
107
107
  end
108
108
 
109
109
  # Store a simple Hash as a JSON encoded file into the DB directory.
data/lib/perobs/Cache.rb CHANGED
@@ -51,12 +51,24 @@ module PEROBS
51
51
  # Add an PEROBS::Object to the read cache.
52
52
  # @param obj [PEROBS::ObjectBase]
53
53
  def cache_read(obj)
54
+ # This is just a safety check. It can probably be disabled in the future
55
+ # to increase performance.
56
+ if obj.respond_to?(:is_poxreference?)
57
+ # If this condition triggers, we have a bug in the library.
58
+ raise RuntimeError, "POXReference objects should never be cached"
59
+ end
54
60
  @reads[index(obj)] = obj
55
61
  end
56
62
 
57
63
  # Add a PEROBS::Object to the write cache.
58
64
  # @param obj [PEROBS::ObjectBase]
59
65
  def cache_write(obj)
66
+ # This is just a safety check. It can probably be disabled in the future
67
+ # to increase performance.
68
+ if obj.respond_to?(:is_poxreference?)
69
+ # If this condition triggers, we have a bug in the library.
70
+ raise RuntimeError, "POXReference objects should never be cached"
71
+ end
60
72
  if @transaction_stack.empty?
61
73
  idx = index(obj)
62
74
  if (old_obj = @writes[idx]) && old_obj._id != obj._id
@@ -0,0 +1,78 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = Delegator.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'perobs/ClassMap'
29
+
30
+ module PEROBS
31
+
32
+ # This Delegator module provides the methods to turn the PEROBS::Array and
33
+ # PEROBS::Hash into proxies for Array and Hash.
34
+ module Delegator
35
+
36
+ # Proxy all calls to unknown methods to the data object.
37
+ def method_missing(method_sym, *args, &block)
38
+ if self.class::READERS.include?(method_sym) ||
39
+ Enumerable.instance_methods.include?(method_sym)
40
+ # If any element of this class is read, we register this object as
41
+ # being read with the cache.
42
+ @store.cache.cache_read(self)
43
+ @data.send(method_sym, *args, &block)
44
+ elsif self.class::REWRITERS.include?(method_sym)
45
+ # Re-writers don't introduce any new elements. We just mark the object
46
+ # as written in the cache and call the class' method.
47
+ @store.cache.cache_write(self)
48
+ @data.send(method_sym, *args, &block)
49
+ elsif (alias_sym = self.class::ALIASES[method_sym])
50
+ @store.cache.cache_write(self)
51
+ send(alias_sym, *args, &block)
52
+ else
53
+ # Any method we don't know about must cause an error. A new class
54
+ # method needs to be added to the right bucket first.
55
+ raise NoMethodError.new("undefined method '#{method_sym}' for " +
56
+ "#{self.class}")
57
+ end
58
+ end
59
+
60
+ def respond_to?(method_sym, include_private = false)
61
+ self.class::READERS.include?(method_sym) ||
62
+ Enumerable.instance_methods.include?(method_sym) ||
63
+ self.class::REWRITERS.include?(method_sym) ||
64
+ super
65
+ end
66
+
67
+ # Equivalent to Class::==
68
+ # This method is just a reader but also part of BasicObject. Hence
69
+ # BasicObject::== would be called instead of method_missing.
70
+ def ==(obj)
71
+ @store.cache.cache_read(self)
72
+ @data == obj
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
@@ -87,7 +87,7 @@ module PEROBS
87
87
  # Return true if the object with given ID exists
88
88
  # @param id [Fixnum or Bignum]
89
89
  def include?(id)
90
- dynamo_get_item(id.to_s)
90
+ !dynamo_get_item(id.to_s).nil?
91
91
  end
92
92
 
93
93
  # Store a simple Hash as a JSON encoded file into the DB directory.
data/lib/perobs/Hash.rb CHANGED
@@ -32,9 +32,51 @@ module PEROBS
32
32
  # A Hash that is transparently persisted in the back-end storage. It is very
33
33
  # similar to the Ruby built-in Hash class but has some additional
34
34
  # limitations. The hash key must always be a String.
35
+ #
36
+ # The implementation is largely a proxy around the standard Hash class. But
37
+ # all mutating methods must be re-implemented to convert PEROBS::Objects to
38
+ # POXReference objects and to register the object as modified with the
39
+ # cache.
40
+ #
41
+ # We explicitely don't support Hash::store() as it conflicts with
42
+ # ObjectBase::store() method to access the store.
35
43
  class Hash < ObjectBase
36
44
 
37
- # Create a new PersistentHash object.
45
+ # These methods do not mutate the Hash. They only perform read
46
+ # operations.
47
+ ([
48
+ :==, :[], :assoc, :compare_by_identity, :compare_by_identity?, :default,
49
+ :default_proc, :each, :each_key, :each_pair, :each_value, :empty?,
50
+ :eql?, :fetch, :flatten, :has_key?, :has_value?, :hash, :include?,
51
+ :inspect, :invert, :key, :key?, :keys, :length, :member?, :merge,
52
+ :pretty_print, :pretty_print_cycle, :rassoc, :reject, :select, :size,
53
+ :to_a, :to_h, :to_hash, :to_s, :value?, :values, :values_at
54
+ ] + Enumerable.instance_methods).uniq.each do |method_sym|
55
+ # Create a wrapper method that passes the call to @data.
56
+ define_method(method_sym) do |*args, &block|
57
+ # Register the read operation with the cache.
58
+ @store.cache.cache_read(self)
59
+ @data.send(method_sym, *args, &block)
60
+ end
61
+ end
62
+
63
+ # These methods mutate the Hash.
64
+ [
65
+ :[]=, :clear, :default=, :default_proc=, :delete, :delete_if,
66
+ :initialize_copy, :keep_if, :merge!, :rehash, :reject!, :replace,
67
+ :select!, :shift, :update
68
+ ].each do |method_sym|
69
+ # Create a wrapper method that passes the call to @data.
70
+ define_method(method_sym) do |*args, &block|
71
+ # Register the write operation with the cache.
72
+ @store.cache.cache_write(self)
73
+ @data.send(method_sym, *args, &block)
74
+ end
75
+ end
76
+
77
+ # New PEROBS objects must always be created by calling # Store.new().
78
+ # PEROBS users should never call this method or equivalents of derived
79
+ # methods directly.
38
80
  # @param store [Store] The Store this hash is stored in
39
81
  # @param default [Any] The default value that is returned when no value is
40
82
  # stored for a specific key.
@@ -44,115 +86,21 @@ module PEROBS
44
86
  @data = {}
45
87
  end
46
88
 
47
- # Retrieves the value object corresponding to the
48
- # key object. If not found, returns the default value.
49
- def [](key)
50
- #unless key.is_a?(String)
51
- # raise ArgumentError, 'The Hash key must be of type String'
52
- #end
53
- _dereferenced(@data.include?(key) ? @data[key] : @default)
54
- end
55
-
56
- # Associates the value given by value with the key given by key.
57
- # @param key [String] The key
58
- # @param value [Any] The value to store
59
- def []=(key, value)
60
- #unless key.is_a?(String)
61
- # raise ArgumentError, 'The Hash key must be of type String'
62
- #end
63
- @data[key] = _referenced(value)
64
- @store.cache.cache_write(self)
65
-
66
- value
67
- end
68
-
69
- # Equivalent to Hash::clear
70
- def clear
71
- @store.cache.cache_write(self)
72
- @data.clear
73
- end
74
-
75
- # Equivalent to Hash::delete
76
- def delete(key)
77
- @store.cache.cache_write(self)
78
- @data.delete(key)
79
- end
80
-
81
- # Equivalent to Hash::delete_if
82
- def delete_if
83
- @store.cache.cache_write(self)
84
- @data.delete_if do |k, v|
85
- yield(k, _dereferenced(v))
86
- end
87
- end
88
-
89
- # Equivalent to Hash::each
90
- def each
91
- @data.each do |k, v|
92
- yield(k, _dereferenced(v))
93
- end
94
- end
95
-
96
- # Equivalent to Hash::each_key
97
- def each_key
98
- @data.each_key { |k| yield(k) }
99
- end
100
-
101
- # Equivalent to Hash::each_value
102
- def each_value
103
- @data.each_value do |v|
104
- yield(_dereferenced(v))
105
- end
106
- end
107
-
108
- # Equivalent to Hash::empty?
109
- def emtpy?
110
- @data.empty?
111
- end
112
-
113
- # Equivalent to Hash::has_key?
114
- def has_key?(key)
115
- @data.has_key?(key)
116
- end
117
- alias include? has_key?
118
- alias key? has_key?
119
- alias member? has_key?
120
-
121
- # Equivalent to Hash::keys
122
- def keys
123
- @data.keys
124
- end
125
-
126
- # Equivalent to Hash::length
127
- def length
128
- @data.length
129
- end
130
- alias size length
131
-
132
- # Equivalent to Hash::map
133
- def map
134
- @data.map do |k, v|
135
- yield(k, _dereferenced(v))
136
- end
137
- end
138
-
139
- # Equivalent to Hash::values
140
- def values
141
- @data.values.map { |v| _dereferenced(v) }
142
- end
143
-
144
89
  # Return a list of all object IDs of all persistend objects that this Hash
145
90
  # is referencing.
146
91
  # @return [Array of Fixnum or Bignum] IDs of referenced objects
147
92
  def _referenced_object_ids
148
- @data.each_value.select { |v| v && v.is_a?(POReference) }.map { |o| o.id }
93
+ @data.each_value.select { |v| v && v.respond_to?(:is_poxreference?) }.
94
+ map { |o| o.id }
149
95
  end
150
96
 
151
97
  # This method should only be used during store repair operations. It will
152
98
  # delete all referenced to the given object ID.
153
99
  # @param id [Fixnum/Bignum] targeted object ID
154
100
  def _delete_reference_to_id(id)
155
- @data.delete_if { |k, v| v && v.is_a?(POReference) && v.id == id }
101
+ @data.delete_if do |k, v|
102
+ v && v.respond_to?(:is_poxreference?) && v.id == id
103
+ end
156
104
  end
157
105
 
158
106
  # Restore the persistent data from a single data structure.
@@ -160,21 +108,19 @@ module PEROBS
160
108
  # @param data [Hash] the actual Hash object
161
109
  # @private
162
110
  def _deserialize(data)
163
- @data = data
164
- end
165
-
166
- # Textual dump for debugging purposes
167
- # @return [String]
168
- def inspect
169
- "{\n" +
170
- @data.map { |k, v| " #{k.inspect}=>#{v.inspect}" }.join(",\n") +
171
- "\n}\n"
111
+ @data = {}
112
+ data.each { |k, v| @data[k] = v.is_a?(POReference) ?
113
+ POXReference.new(@store, v.id) : v }
114
+ @data
172
115
  end
173
116
 
174
117
  private
175
118
 
176
119
  def _serialize
177
- @data
120
+ data = {}
121
+ @data.each { |k, v| data[k] = v.respond_to?(:is_poxreference?) ?
122
+ POReference.new(v.id) : v }
123
+ data
178
124
  end
179
125
 
180
126
  end