perobs 1.1.0 → 2.0.0

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