odba 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +5 -0
- data/LICENSE +459 -0
- data/Manifest.txt +38 -0
- data/README.txt +32 -0
- data/Rakefile +28 -0
- data/install.rb +1098 -0
- data/lib/odba.rb +72 -0
- data/lib/odba/18_19_loading_compatibility.rb +71 -0
- data/lib/odba/cache.rb +603 -0
- data/lib/odba/cache_entry.rb +122 -0
- data/lib/odba/connection_pool.rb +87 -0
- data/lib/odba/drbwrapper.rb +88 -0
- data/lib/odba/id_server.rb +26 -0
- data/lib/odba/index.rb +395 -0
- data/lib/odba/index_definition.rb +24 -0
- data/lib/odba/marshal.rb +18 -0
- data/lib/odba/odba.rb +45 -0
- data/lib/odba/odba_error.rb +12 -0
- data/lib/odba/persistable.rb +621 -0
- data/lib/odba/storage.rb +628 -0
- data/lib/odba/stub.rb +187 -0
- data/sql/collection.sql +6 -0
- data/sql/create_tables.sql +6 -0
- data/sql/object.sql +8 -0
- data/sql/object_connection.sql +7 -0
- data/test/suite.rb +8 -0
- data/test/test_array.rb +108 -0
- data/test/test_cache.rb +725 -0
- data/test/test_cache_entry.rb +109 -0
- data/test/test_connection_pool.rb +77 -0
- data/test/test_drbwrapper.rb +102 -0
- data/test/test_hash.rb +144 -0
- data/test/test_id_server.rb +43 -0
- data/test/test_index.rb +371 -0
- data/test/test_marshal.rb +28 -0
- data/test/test_persistable.rb +625 -0
- data/test/test_storage.rb +829 -0
- data/test/test_stub.rb +226 -0
- metadata +134 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- IndexDefinition -- odba -- 20.09.2004 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module ODBA
|
5
|
+
# IndexDefinition is a convenience class. Load a yaml-dump of this and pass it
|
6
|
+
# to Cache#create_index to introduce new indices
|
7
|
+
class IndexDefinition
|
8
|
+
attr_accessor :index_name, :dictionary, :origin_klass,
|
9
|
+
:target_klass, :resolve_search_term, :resolve_target,
|
10
|
+
:resolve_origin, :fulltext, :init_source, :class_filter
|
11
|
+
def initialize
|
12
|
+
@index_name = ""
|
13
|
+
@origin_klass = ""
|
14
|
+
@target_klass = ""
|
15
|
+
@resolve_search_term = ""
|
16
|
+
@resolve_target = ""
|
17
|
+
@resolve_origin = ""
|
18
|
+
@dictionary = ""
|
19
|
+
@init_source = ""
|
20
|
+
@fulltext = false
|
21
|
+
@class_filter = :is_a?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/odba/marshal.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- Marshal -- odba -- 29.04.2004 -- hwyss@ywesee.com rwaltert@ywesee.com mwalder@ywesee.com
|
3
|
+
|
4
|
+
module ODBA
|
5
|
+
# Marshal is a simple extension of ::Marshal. To be able to store our data
|
6
|
+
# using the DBI-Interface, we need to escape invalid characters from the
|
7
|
+
# standard binary dump.
|
8
|
+
module Marshal
|
9
|
+
def Marshal.dump(obj)
|
10
|
+
binary = ::Marshal.dump(obj)
|
11
|
+
binary.unpack('H*').first
|
12
|
+
end
|
13
|
+
def Marshal.load(hexdump)
|
14
|
+
binary = [hexdump].pack('H*')
|
15
|
+
::Marshal.load(binary)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/odba/odba.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# ODBA -- odba -- 26.01.2007 -- hwyss@ywesee.com
|
3
|
+
|
4
|
+
module ODBA
|
5
|
+
# reader for the Cache server. Defaults to ODBA::Cache.instance
|
6
|
+
def ODBA.cache
|
7
|
+
@cache ||= ODBA::Cache.instance
|
8
|
+
end
|
9
|
+
# writer for the Cache server. You will probably never need this.
|
10
|
+
def ODBA.cache=(cache_server)
|
11
|
+
@cache = cache_server
|
12
|
+
end
|
13
|
+
# reader for the Marshaller. Defaults to ODBA.Marshal
|
14
|
+
def ODBA.marshaller
|
15
|
+
@marshaller ||= ODBA::Marshal
|
16
|
+
end
|
17
|
+
# writer for the Marshaller. Example: override the default Marshaller to
|
18
|
+
# serialize your objects in a custom format (yaml, xml, ...).
|
19
|
+
def ODBA.marshaller=(marshaller)
|
20
|
+
@marshaller = marshaller
|
21
|
+
end
|
22
|
+
# peer two instances of ODBA::Cache
|
23
|
+
def ODBA.peer peer
|
24
|
+
peer.register_peer ODBA.cache
|
25
|
+
ODBA.cache.register_peer peer
|
26
|
+
end
|
27
|
+
# reader for the Storage Server. Defaults to ODBA::Storage.instance
|
28
|
+
def ODBA.storage
|
29
|
+
@storage ||= ODBA::Storage.instance
|
30
|
+
end
|
31
|
+
# writer for the Storage Server. Example: override the default Storage Server
|
32
|
+
# to dump all your data in a flatfile.
|
33
|
+
def ODBA.storage=(storage)
|
34
|
+
@storage = storage
|
35
|
+
end
|
36
|
+
# unpeer two instances of ODBA::Cache
|
37
|
+
def ODBA.unpeer peer
|
38
|
+
peer.unregister_peer ODBA.cache
|
39
|
+
ODBA.cache.unregister_peer peer
|
40
|
+
end
|
41
|
+
# Convenience method. Delegates the transaction-call to the Cache server.
|
42
|
+
def ODBA.transaction(&block)
|
43
|
+
ODBA.cache.transaction(&block)
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- OdbaError -- odba -- 29.04.2004 -- hwyss@ywesee.com rwaltert@ywesee.com mwalder@ywesee.com
|
3
|
+
|
4
|
+
module ODBA
|
5
|
+
class OdbaError < RuntimeError
|
6
|
+
end
|
7
|
+
class OdbaResultLimitError < OdbaError
|
8
|
+
attr_accessor :limit, :size, :index, :search_term, :meta
|
9
|
+
end
|
10
|
+
class OdbaDuplicateIdError < OdbaError
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,621 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#-- Persistable -- odba -- 29.04.2004 -- hwyss@ywesee.com rwaltert@ywesee.com mwalder@ywesee.com
|
3
|
+
|
4
|
+
class Object # :nodoc: all
|
5
|
+
def odba_id
|
6
|
+
end
|
7
|
+
def odba_instance
|
8
|
+
self
|
9
|
+
end
|
10
|
+
def odba_isolated_stub
|
11
|
+
self
|
12
|
+
end
|
13
|
+
def metaclass; class << self; self; end; end
|
14
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
15
|
+
end
|
16
|
+
|
17
|
+
require 'odba/stub'
|
18
|
+
require 'observer'
|
19
|
+
|
20
|
+
module ODBA
|
21
|
+
class Stub; end
|
22
|
+
module Persistable
|
23
|
+
meta = Struct.new(:exact, :limit)
|
24
|
+
Exact = meta.new
|
25
|
+
Exact.exact = true
|
26
|
+
Find = Exact.dup
|
27
|
+
Find.limit = 1
|
28
|
+
# Classes which include Persistable have a class-method 'odba_index'
|
29
|
+
def Persistable.append_features(mod)
|
30
|
+
super
|
31
|
+
mod.module_eval {
|
32
|
+
class << self
|
33
|
+
def odba_index(*keys)
|
34
|
+
require 'odba/index_definition'
|
35
|
+
origin_klass = self
|
36
|
+
resolve_origin = nil
|
37
|
+
resolve_target = :none
|
38
|
+
resolve = {}
|
39
|
+
opts = {}
|
40
|
+
if(keys.size > 1)
|
41
|
+
if(keys.last.is_a?(Hash))
|
42
|
+
opts = keys.pop
|
43
|
+
end
|
44
|
+
if(keys.last.is_a?(Class))
|
45
|
+
origin_klass = keys.pop
|
46
|
+
resolve = keys.pop
|
47
|
+
resolve_origin = keys.pop
|
48
|
+
elsif(keys.last.is_a?(Symbol))
|
49
|
+
keys.each { |key|
|
50
|
+
resolve.store(key, {'resolve' => key})
|
51
|
+
}
|
52
|
+
else
|
53
|
+
resolve = keys.pop
|
54
|
+
end
|
55
|
+
else
|
56
|
+
resolve = keys.first
|
57
|
+
end
|
58
|
+
keys.each { |key|
|
59
|
+
if RUBY_VERSION >= '1.9'
|
60
|
+
key = key.to_sym
|
61
|
+
else
|
62
|
+
key = key.to_s
|
63
|
+
end
|
64
|
+
unless(instance_methods.include?(key))
|
65
|
+
attr_accessor key
|
66
|
+
end
|
67
|
+
}
|
68
|
+
index_prefix = self.name.downcase.gsub(/::/, '_')
|
69
|
+
index_suffix = Persistable.sanitize(keys.join('_and_'))
|
70
|
+
index_name = sprintf("%s_%s", index_prefix, index_suffix)
|
71
|
+
search_name = sprintf("search_by_%s", index_suffix)
|
72
|
+
exact_name = sprintf("search_by_exact_%s", index_suffix)
|
73
|
+
find_name = sprintf("find_by_%s", index_suffix)
|
74
|
+
keys_name = sprintf("%s_keys", index_suffix)
|
75
|
+
index_definition = IndexDefinition.new
|
76
|
+
index_definition.index_name = index_name
|
77
|
+
index_definition.origin_klass = origin_klass
|
78
|
+
index_definition.target_klass = self
|
79
|
+
index_definition.resolve_search_term = resolve
|
80
|
+
index_definition.resolve_origin = resolve_origin.to_s
|
81
|
+
index_definition.resolve_target = resolve_target
|
82
|
+
opts.each { |key, val| index_definition.send "#{key}=", val }
|
83
|
+
ODBA.cache.ensure_index_deferred(index_definition)
|
84
|
+
meta_eval {
|
85
|
+
define_method(search_name) { |*vals|
|
86
|
+
if(vals.size > 1)
|
87
|
+
args = {}
|
88
|
+
vals.each_with_index { |val, idx|
|
89
|
+
cond = case val
|
90
|
+
when Numeric, Date
|
91
|
+
'='
|
92
|
+
else
|
93
|
+
'like'
|
94
|
+
end
|
95
|
+
args.store(keys.at(idx),
|
96
|
+
{ 'value' => val, 'condition' => cond })
|
97
|
+
}
|
98
|
+
ODBA.cache.retrieve_from_index(index_name, args)
|
99
|
+
else
|
100
|
+
ODBA.cache.retrieve_from_index(index_name, vals.first)
|
101
|
+
end
|
102
|
+
}
|
103
|
+
define_method(exact_name) { |*vals|
|
104
|
+
if(vals.size > 1)
|
105
|
+
args = {}
|
106
|
+
vals.each_with_index { |val, idx|
|
107
|
+
args.store(keys.at(idx), val)
|
108
|
+
}
|
109
|
+
ODBA.cache.retrieve_from_index(index_name, args,
|
110
|
+
ODBA::Persistable::Exact)
|
111
|
+
else
|
112
|
+
ODBA.cache.retrieve_from_index(index_name, vals.first,
|
113
|
+
ODBA::Persistable::Exact)
|
114
|
+
end
|
115
|
+
}
|
116
|
+
define_method(find_name) { |*vals|
|
117
|
+
if(vals.size > 1)
|
118
|
+
args = {}
|
119
|
+
vals.each_with_index { |val, idx|
|
120
|
+
cond = case val
|
121
|
+
when Numeric, Date
|
122
|
+
'='
|
123
|
+
else
|
124
|
+
'like'
|
125
|
+
end
|
126
|
+
args.store(keys.at(idx),
|
127
|
+
{ 'value' => val, 'condition' => cond })
|
128
|
+
}
|
129
|
+
ODBA.cache.retrieve_from_index(index_name, args,
|
130
|
+
ODBA::Persistable::Find)
|
131
|
+
else
|
132
|
+
ODBA.cache.retrieve_from_index(index_name, vals.first,
|
133
|
+
ODBA::Persistable::Find)
|
134
|
+
end.first
|
135
|
+
}
|
136
|
+
define_method(keys_name) { |*vals|
|
137
|
+
# TODO fix this for fulltext and condition indices
|
138
|
+
length, = vals
|
139
|
+
ODBA.cache.index_keys(index_name, length)
|
140
|
+
}
|
141
|
+
}
|
142
|
+
index_definition
|
143
|
+
end
|
144
|
+
def odba_extent
|
145
|
+
all = ODBA.cache.extent(self)
|
146
|
+
if(block_given?)
|
147
|
+
all.each { |instance| yield instance }
|
148
|
+
nil
|
149
|
+
else
|
150
|
+
all
|
151
|
+
end
|
152
|
+
end
|
153
|
+
def odba_count
|
154
|
+
ODBA.cache.count(self)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
}
|
158
|
+
end
|
159
|
+
@@sanitize_ptrn = /[^a-z0-9_]/i
|
160
|
+
def Persistable.sanitize(name)
|
161
|
+
name.gsub(@@sanitize_ptrn, '_')
|
162
|
+
end
|
163
|
+
attr_accessor :odba_name, :odba_prefetch
|
164
|
+
# Classes which include Persistable may override ODBA_EXCLUDE_VARS to
|
165
|
+
# prevent data from being stored in the database (e.g. passwords, file
|
166
|
+
# descriptors). Simply redefine: ODBA_EXCLUDE_VARS = ['@foo']
|
167
|
+
ODBA_EXCLUDE_VARS = []
|
168
|
+
ODBA_INDEXABLE = true # :nodoc:
|
169
|
+
# see odba_prefetch?
|
170
|
+
ODBA_PREFETCH = false
|
171
|
+
if RUBY_VERSION >= '1.9'
|
172
|
+
ODBA_PREDEFINE_EXCLUDE_VARS = [:@odba_observers] # :nodoc:
|
173
|
+
ODBA_PREDEFINE_SERIALIZABLE = [:@odba_target_ids] # :nodoc:, legacy
|
174
|
+
else
|
175
|
+
ODBA_PREDEFINE_EXCLUDE_VARS = ['@odba_observers'] # :nodoc:
|
176
|
+
ODBA_PREDEFINE_SERIALIZABLE = ['@odba_target_ids'] # :nodoc:, legacy
|
177
|
+
end
|
178
|
+
# If you want to prevent Persistables from being disconnected and stored
|
179
|
+
# separately (Array and Hash are Persistable by default), redefine:
|
180
|
+
# ODBA_SERIALIZABLE = ['@bar']
|
181
|
+
ODBA_SERIALIZABLE = []
|
182
|
+
def ==(other) # :nodoc:
|
183
|
+
super(other.odba_instance)
|
184
|
+
end
|
185
|
+
@@odba_id_name = RUBY_VERSION >= '1.9' ? :@odba_id : '@odba_id'
|
186
|
+
def dup # :nodoc:
|
187
|
+
twin = super
|
188
|
+
## since twin may not be a Persistable, we need to do some magic here to
|
189
|
+
# ensure that it does not have the same odba_id
|
190
|
+
twin.instance_variable_set(@@odba_id_name, nil)
|
191
|
+
twin
|
192
|
+
end
|
193
|
+
def eql?(other) # :nodoc:
|
194
|
+
(other.is_a?(Stub) && other.odba_id == @odba_id) \
|
195
|
+
|| super(other.odba_instance)
|
196
|
+
end
|
197
|
+
# Add an observer for Cache#store(self), Cache#delete(self) and
|
198
|
+
# Cache#clean removing the object from the Cache
|
199
|
+
def odba_add_observer(obj)
|
200
|
+
odba_observers.push(obj)
|
201
|
+
obj
|
202
|
+
end
|
203
|
+
def odba_collection # :nodoc:
|
204
|
+
[]
|
205
|
+
end
|
206
|
+
# Removes all connections to another persistable. This method is called
|
207
|
+
# by the Cache server when _remove_object_ is deleted from the database
|
208
|
+
def odba_cut_connection(remove_object)
|
209
|
+
odba_potentials.each { |name|
|
210
|
+
var = instance_variable_get(name)
|
211
|
+
if(var.eql?(remove_object))
|
212
|
+
instance_variable_set(name, nil)
|
213
|
+
end
|
214
|
+
}
|
215
|
+
end
|
216
|
+
# Permanently deletes this Persistable from the database and remove
|
217
|
+
# all connections to it
|
218
|
+
def odba_delete
|
219
|
+
ODBA.cache.delete(self)
|
220
|
+
end
|
221
|
+
# Delete _observer_ as an observer on this object.
|
222
|
+
# It will no longer receive notifications.
|
223
|
+
def odba_delete_observer(observer)
|
224
|
+
@odba_observers.delete(observer) if(@odba_observers)
|
225
|
+
end
|
226
|
+
# Delete all observers associated with this object.
|
227
|
+
def odba_delete_observers
|
228
|
+
@odba_observers = nil
|
229
|
+
end
|
230
|
+
def odba_dup #:nodoc:
|
231
|
+
twin = dup
|
232
|
+
twin.extend(Persistable)
|
233
|
+
twin.odba_id = @odba_id
|
234
|
+
odba_potentials.each { |name|
|
235
|
+
var = twin.instance_variable_get(name)
|
236
|
+
if(var.is_a?(ODBA::Stub))
|
237
|
+
stub = var.odba_dup
|
238
|
+
stub.odba_container = twin
|
239
|
+
twin.instance_variable_set(name, stub)
|
240
|
+
end
|
241
|
+
}
|
242
|
+
twin
|
243
|
+
end
|
244
|
+
def odba_exclude_vars # :nodoc:
|
245
|
+
exc = if(defined?(self::class::ODBA_PREDEFINE_EXCLUDE_VARS))
|
246
|
+
self::class::ODBA_PREDEFINE_EXCLUDE_VARS
|
247
|
+
else
|
248
|
+
ODBA_PREDEFINE_EXCLUDE_VARS
|
249
|
+
end
|
250
|
+
if(defined?(self::class::ODBA_EXCLUDE_VARS))
|
251
|
+
exc += self::class::ODBA_EXCLUDE_VARS
|
252
|
+
end
|
253
|
+
exc
|
254
|
+
end
|
255
|
+
# Returns the odba unique id of this Persistable.
|
256
|
+
# If no id had been assigned, this is now done.
|
257
|
+
# No attempt is made to store the Persistable in the db.
|
258
|
+
def odba_id
|
259
|
+
@odba_id ||= ODBA.cache.next_id
|
260
|
+
end
|
261
|
+
def odba_isolated_dump # :nodoc:
|
262
|
+
ODBA.marshaller.dump(odba_isolated_twin)
|
263
|
+
end
|
264
|
+
# Convenience method equivalent to ODBA.cache.store(self)
|
265
|
+
def odba_isolated_store
|
266
|
+
@odba_persistent = true
|
267
|
+
ODBA.cache.store(self)
|
268
|
+
end
|
269
|
+
# Returns a new instance of Stub, which can be used as a stand-in replacement
|
270
|
+
# for this Persistable.
|
271
|
+
def odba_isolated_stub
|
272
|
+
Stub.new(self.odba_id, nil, self)
|
273
|
+
end
|
274
|
+
# Returns a duplicate of this Persistable, for which all connected
|
275
|
+
# Persistables have been replaced by a Stub
|
276
|
+
def odba_isolated_twin
|
277
|
+
# ensure a valid odba_id
|
278
|
+
self.odba_id
|
279
|
+
twin = self.odba_dup
|
280
|
+
twin.odba_replace_persistables
|
281
|
+
twin.odba_replace_excluded!
|
282
|
+
twin
|
283
|
+
end
|
284
|
+
# A Persistable instance can be _prefetchable_. This means that the object
|
285
|
+
# can be loaded at startup by calling ODBA.cache.prefetch, and that it will
|
286
|
+
# never expire from the Cache. The prefetch status can be controlled per
|
287
|
+
# instance by setting the instance variable @odba_prefetch, and per class by
|
288
|
+
# overriding the module constant ODBA_PREFETCH
|
289
|
+
def odba_prefetch?
|
290
|
+
@odba_prefetch \
|
291
|
+
|| (defined?(self::class::ODBA_PREFETCH) && self::class::ODBA_PREFETCH)
|
292
|
+
end
|
293
|
+
def odba_indexable? # :nodoc:
|
294
|
+
@odba_indexable \
|
295
|
+
|| (defined?(self::class::ODBA_INDEXABLE) && self::class::ODBA_INDEXABLE)
|
296
|
+
end
|
297
|
+
# Invoke the update method in each currently associated observer
|
298
|
+
# in turn, passing it the given arguments
|
299
|
+
def odba_notify_observers(*args)
|
300
|
+
odba_observers.each { |obs| obs.odba_update(*args) }
|
301
|
+
end
|
302
|
+
def odba_observers
|
303
|
+
@odba_observers ||= []
|
304
|
+
end
|
305
|
+
def odba_potentials # :nodoc:
|
306
|
+
instance_variables - odba_serializables - odba_exclude_vars
|
307
|
+
end
|
308
|
+
def odba_replace!(obj) # :nodoc:
|
309
|
+
instance_variables.each { |name|
|
310
|
+
instance_variable_set(name, obj.instance_variable_get(name))
|
311
|
+
}
|
312
|
+
end
|
313
|
+
def odba_replace_persistables # :nodoc:
|
314
|
+
odba_potentials.each { |name|
|
315
|
+
var = instance_variable_get(name)
|
316
|
+
if(var.is_a?(ODBA::Stub))
|
317
|
+
var.odba_clear_receiver # ensure we don't leak into the db
|
318
|
+
var.odba_container = self # ensure we don't leak into memory
|
319
|
+
elsif(var.is_a?(ODBA::Persistable))
|
320
|
+
odba_id = var.odba_id
|
321
|
+
stub = ODBA::Stub.new(odba_id, self, var)
|
322
|
+
instance_variable_set(name, stub)
|
323
|
+
end
|
324
|
+
}
|
325
|
+
odba_serializables.each { |name|
|
326
|
+
var = instance_variable_get(name)
|
327
|
+
if(var.is_a?(ODBA::Stub))
|
328
|
+
instance_variable_set(name, var.odba_instance)
|
329
|
+
end
|
330
|
+
}
|
331
|
+
end
|
332
|
+
def odba_replace_stubs(odba_id, substitution) # :nodoc:
|
333
|
+
odba_potentials.each { |name|
|
334
|
+
var = instance_variable_get(name)
|
335
|
+
if(var.is_a?(Stub) && odba_id == var.odba_id)
|
336
|
+
instance_variable_set(name, substitution)
|
337
|
+
end
|
338
|
+
}
|
339
|
+
end
|
340
|
+
def odba_restore(collection=[]) # :nodoc:
|
341
|
+
end
|
342
|
+
def odba_serializables # :nodoc:
|
343
|
+
srs = if(defined?(self::class::ODBA_PREDEFINE_SERIALIZABLE))
|
344
|
+
self::class::ODBA_PREDEFINE_SERIALIZABLE
|
345
|
+
else
|
346
|
+
ODBA_PREDEFINE_SERIALIZABLE
|
347
|
+
end
|
348
|
+
if(defined?(self::class::ODBA_SERIALIZABLE))
|
349
|
+
srs += self::class::ODBA_SERIALIZABLE
|
350
|
+
end
|
351
|
+
srs
|
352
|
+
end
|
353
|
+
def odba_snapshot(snapshot_level) # :nodoc:
|
354
|
+
if(snapshot_level > @odba_snapshot_level.to_i)
|
355
|
+
@odba_snapshot_level = snapshot_level
|
356
|
+
odba_isolated_store
|
357
|
+
end
|
358
|
+
end
|
359
|
+
# Stores this Persistable and recursively all connected unsaved persistables,
|
360
|
+
# until no more direcly connected unsaved persistables can be found.
|
361
|
+
# The optional parameter _name_ can be used later to retrieve this
|
362
|
+
# Persistable using Cache#fetch_named
|
363
|
+
def odba_store(name = nil)
|
364
|
+
begin
|
365
|
+
unless (name.nil?)
|
366
|
+
old_name = @odba_name
|
367
|
+
@odba_name = name
|
368
|
+
end
|
369
|
+
odba_store_unsaved
|
370
|
+
self
|
371
|
+
rescue
|
372
|
+
@odba_name = old_name
|
373
|
+
raise
|
374
|
+
end
|
375
|
+
end
|
376
|
+
def odba_store_unsaved # :nodoc:
|
377
|
+
@odba_persistent = false
|
378
|
+
current_level = [self]
|
379
|
+
while(!current_level.empty?)
|
380
|
+
next_level = []
|
381
|
+
current_level.each { |item|
|
382
|
+
if(item.odba_unsaved?)
|
383
|
+
next_level += item.odba_unsaved_neighbors
|
384
|
+
item.odba_isolated_store
|
385
|
+
end
|
386
|
+
}
|
387
|
+
current_level = next_level
|
388
|
+
end
|
389
|
+
end
|
390
|
+
def odba_stubize(obj, opts={}) # :nodoc:
|
391
|
+
return false if(frozen?)
|
392
|
+
id = obj.odba_id
|
393
|
+
odba_potentials.each { |name|
|
394
|
+
var = instance_variable_get(name)
|
395
|
+
# must not be synchronized because of the following if
|
396
|
+
# statement (if an object has already been replaced by
|
397
|
+
# a stub, it will have the correct id and it
|
398
|
+
# will be ignored)
|
399
|
+
case var
|
400
|
+
when Stub
|
401
|
+
# no need to make a new stub
|
402
|
+
when Persistable
|
403
|
+
if(var.odba_id == id)
|
404
|
+
stub = ODBA::Stub.new(id, self, obj)
|
405
|
+
instance_variable_set(name, stub)
|
406
|
+
end
|
407
|
+
end
|
408
|
+
}
|
409
|
+
odba_notify_observers(:stubize, id, obj.object_id)
|
410
|
+
## allow CacheEntry to retire
|
411
|
+
true
|
412
|
+
end
|
413
|
+
# Recursively stores all connected Persistables.
|
414
|
+
def odba_take_snapshot
|
415
|
+
@odba_snapshot_level ||= 0
|
416
|
+
snapshot_level = @odba_snapshot_level.next
|
417
|
+
current_level = [self]
|
418
|
+
tree_level = 0
|
419
|
+
while(!current_level.empty?)
|
420
|
+
tree_level += 1
|
421
|
+
obj_count = 0
|
422
|
+
next_level = []
|
423
|
+
current_level.each { |item|
|
424
|
+
if(item.odba_unsaved?(snapshot_level))
|
425
|
+
obj_count += 1
|
426
|
+
next_level += item.odba_unsaved_neighbors(snapshot_level)
|
427
|
+
item.odba_snapshot(snapshot_level)
|
428
|
+
end
|
429
|
+
}
|
430
|
+
current_level = next_level #.uniq
|
431
|
+
end
|
432
|
+
end
|
433
|
+
def odba_target_ids # :nodoc:
|
434
|
+
odba_potentials.collect { |name|
|
435
|
+
var = instance_variable_get(name)
|
436
|
+
if(var.is_a?(ODBA::Persistable))
|
437
|
+
var.odba_id
|
438
|
+
end
|
439
|
+
}.compact.uniq
|
440
|
+
end
|
441
|
+
def odba_unsaved_neighbors(snapshot_level = nil) # :nodoc:
|
442
|
+
unsaved = []
|
443
|
+
odba_potentials.each { |name|
|
444
|
+
item = instance_variable_get(name)
|
445
|
+
if(item.is_a?(ODBA::Persistable) \
|
446
|
+
&& item.odba_unsaved?(snapshot_level))
|
447
|
+
unsaved.push(item)
|
448
|
+
end
|
449
|
+
}
|
450
|
+
unsaved
|
451
|
+
end
|
452
|
+
def odba_unsaved?(snapshot_level = nil) # :nodoc:
|
453
|
+
if(snapshot_level.nil?)
|
454
|
+
!@odba_persistent
|
455
|
+
#true
|
456
|
+
else
|
457
|
+
@odba_snapshot_level.to_i < snapshot_level
|
458
|
+
end
|
459
|
+
end
|
460
|
+
protected
|
461
|
+
attr_writer :odba_id
|
462
|
+
def odba_replace_excluded!
|
463
|
+
odba_exclude_vars.each { |name|
|
464
|
+
instance_variable_set(name, nil)
|
465
|
+
}
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
class Array # :nodoc: all
|
470
|
+
include ODBA::Persistable
|
471
|
+
def odba_collection
|
472
|
+
coll = []
|
473
|
+
each_with_index { |item, index|
|
474
|
+
coll.push([index, item])
|
475
|
+
}
|
476
|
+
coll
|
477
|
+
end
|
478
|
+
def odba_cut_connection(remove_object)
|
479
|
+
super(remove_object)
|
480
|
+
delete_if { |val| val.eql?(remove_object) }
|
481
|
+
end
|
482
|
+
def odba_prefetch?
|
483
|
+
super || any? { |item|
|
484
|
+
item.respond_to?(:odba_prefetch?) \
|
485
|
+
&& item.odba_prefetch?
|
486
|
+
}
|
487
|
+
end
|
488
|
+
def odba_replace!(obj) # :nodoc:
|
489
|
+
super
|
490
|
+
replace(obj)
|
491
|
+
end
|
492
|
+
def odba_replace_persistables
|
493
|
+
clear
|
494
|
+
super
|
495
|
+
end
|
496
|
+
def odba_restore(collection=[])
|
497
|
+
collection.each { |key, val|
|
498
|
+
self[key] = val
|
499
|
+
}
|
500
|
+
end
|
501
|
+
def odba_unsaved_neighbors(snapshot_level = nil)
|
502
|
+
unsaved = super
|
503
|
+
each { |item|
|
504
|
+
#odba_extend_enumerable(item)
|
505
|
+
if(item.is_a?(ODBA::Persistable) \
|
506
|
+
&& item.odba_unsaved?(snapshot_level))
|
507
|
+
unsaved.push(item)
|
508
|
+
end
|
509
|
+
}
|
510
|
+
unsaved
|
511
|
+
end
|
512
|
+
def odba_unsaved?(snapshot_level = nil)
|
513
|
+
super || (snapshot_level.nil? && any? { |val|
|
514
|
+
val.is_a?(ODBA::Persistable) && val.odba_unsaved?
|
515
|
+
} )
|
516
|
+
end
|
517
|
+
def odba_stubize(obj, opts={}) # :nodoc:
|
518
|
+
return false if(frozen?)
|
519
|
+
super
|
520
|
+
if opts[:force]
|
521
|
+
id = obj.odba_id
|
522
|
+
collect! do |item|
|
523
|
+
if item.is_a?(ODBA::Persistable) \
|
524
|
+
&& !item.is_a?(ODBA::Stub) && item.odba_id == id
|
525
|
+
ODBA::Stub.new(id, self, obj)
|
526
|
+
else
|
527
|
+
item
|
528
|
+
end
|
529
|
+
end
|
530
|
+
true
|
531
|
+
else
|
532
|
+
false
|
533
|
+
end
|
534
|
+
end
|
535
|
+
def odba_target_ids
|
536
|
+
ids = super
|
537
|
+
self.each { |value|
|
538
|
+
if(value.is_a?(ODBA::Persistable))
|
539
|
+
ids.push(value.odba_id)
|
540
|
+
end
|
541
|
+
}
|
542
|
+
ids.uniq
|
543
|
+
end
|
544
|
+
end
|
545
|
+
class Hash # :nodoc: all
|
546
|
+
include ODBA::Persistable
|
547
|
+
def odba_cut_connection(remove_object)
|
548
|
+
super(remove_object)
|
549
|
+
delete_if { |key, val|
|
550
|
+
key.eql?(remove_object) || val.eql?(remove_object)
|
551
|
+
}
|
552
|
+
end
|
553
|
+
def odba_collection
|
554
|
+
self.to_a
|
555
|
+
end
|
556
|
+
def odba_prefetch?
|
557
|
+
super || any? { |item|
|
558
|
+
item.respond_to?(:odba_prefetch?) \
|
559
|
+
&& item.odba_prefetch?
|
560
|
+
}
|
561
|
+
end
|
562
|
+
def odba_replace!(obj) # :nodoc:
|
563
|
+
super
|
564
|
+
replace(obj)
|
565
|
+
end
|
566
|
+
def odba_replace_persistables
|
567
|
+
clear
|
568
|
+
super
|
569
|
+
end
|
570
|
+
def odba_restore(collection=[])
|
571
|
+
collection.each { |key, val|
|
572
|
+
self[key] = val
|
573
|
+
}
|
574
|
+
end
|
575
|
+
def odba_unsaved?(snapshot_level = nil)
|
576
|
+
super || (snapshot_level.nil? && any? { |key, val|
|
577
|
+
val.is_a?(ODBA::Persistable) && val.odba_unsaved? \
|
578
|
+
|| key.is_a?(ODBA::Persistable) && key.odba_unsaved?
|
579
|
+
})
|
580
|
+
end
|
581
|
+
def odba_unsaved_neighbors(snapshot_level = nil)
|
582
|
+
unsaved = super
|
583
|
+
each { |pair|
|
584
|
+
pair.each { |item|
|
585
|
+
#odba_extend_enumerable(item)
|
586
|
+
if(item.is_a?(ODBA::Persistable)\
|
587
|
+
&& item.odba_unsaved?(snapshot_level))
|
588
|
+
unsaved.push(item)
|
589
|
+
end
|
590
|
+
}
|
591
|
+
}
|
592
|
+
unsaved
|
593
|
+
end
|
594
|
+
def odba_stubize(obj, opts={}) # :nodoc:
|
595
|
+
return false if(frozen?)
|
596
|
+
super
|
597
|
+
if opts[:force]
|
598
|
+
dup = {}
|
599
|
+
each do |pair|
|
600
|
+
pair.odba_stubize(obj)
|
601
|
+
dup.store *pair
|
602
|
+
end
|
603
|
+
replace dup
|
604
|
+
true
|
605
|
+
else
|
606
|
+
false
|
607
|
+
end
|
608
|
+
end
|
609
|
+
def odba_target_ids
|
610
|
+
ids = super
|
611
|
+
self.each { |key, value|
|
612
|
+
if(value.is_a?(ODBA::Persistable))
|
613
|
+
ids.push(value.odba_id)
|
614
|
+
end
|
615
|
+
if(key.is_a?(ODBA::Persistable))
|
616
|
+
ids.push(key.odba_id)
|
617
|
+
end
|
618
|
+
}
|
619
|
+
ids.uniq
|
620
|
+
end
|
621
|
+
end
|