odba 1.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.
- 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
|