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.
@@ -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
@@ -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
@@ -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