hyperactive 0.2.3 → 0.2.4
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/README +9 -3
- data/TODO +4 -0
- data/lib/hyperactive/hash.rb +14 -6
- data/lib/hyperactive/hooker.rb +157 -47
- data/lib/hyperactive/index.rb +210 -0
- data/lib/hyperactive/list.rb +8 -0
- data/lib/hyperactive/record.rb +20 -407
- data/lib/hyperactive/transactions.rb +154 -0
- data/lib/hyperactive.rb +2 -0
- data/tests/hash_benchmark.rb +5 -5
- data/tests/hash_test.rb +5 -5
- data/tests/list_benchmark.rb +3 -3
- data/tests/list_test.rb +6 -6
- data/tests/record_test.rb +8 -8
- metadata +5 -2
data/lib/hyperactive/record.rb
CHANGED
@@ -25,133 +25,17 @@ require 'archipelago'
|
|
25
25
|
#
|
26
26
|
module Hyperactive
|
27
27
|
|
28
|
+
#
|
29
|
+
# The default database connector.
|
30
|
+
#
|
31
|
+
CAPTAIN = Archipelago::Pirate::Captain.new
|
32
|
+
|
28
33
|
#
|
29
34
|
# The package containing the base class Bass that simplifies
|
30
35
|
# using archipelago for generic database stuff.
|
31
36
|
#
|
32
37
|
module Record
|
33
38
|
|
34
|
-
#
|
35
|
-
# The default database connector.
|
36
|
-
#
|
37
|
-
CAPTAIN = Archipelago::Pirate::Captain.new
|
38
|
-
|
39
|
-
#
|
40
|
-
# A tiny <b>call</b>able class that saves stuff in
|
41
|
-
# indexes depending on certain attributes.
|
42
|
-
#
|
43
|
-
class IndexBuilder
|
44
|
-
#
|
45
|
-
# Get the first part of the key, that depends on the +attributes+.
|
46
|
-
#
|
47
|
-
def self.get_attribute_key_part(attributes)
|
48
|
-
"Hyperactive::IndexBuilder::#{attributes.join(",")}"
|
49
|
-
end
|
50
|
-
#
|
51
|
-
# Get the last part of the key, that depends on the +values+.
|
52
|
-
#
|
53
|
-
def self.get_value_key_part(values)
|
54
|
-
"#{values.join(",")}"
|
55
|
-
end
|
56
|
-
#
|
57
|
-
# Initialize an IndexBuilder giving it an array of +attributes+
|
58
|
-
# that will be indexed.
|
59
|
-
#
|
60
|
-
def initialize(attributes)
|
61
|
-
@attributes = attributes
|
62
|
-
end
|
63
|
-
#
|
64
|
-
# Get the key for the given +record+.
|
65
|
-
#
|
66
|
-
def get_key_for(record)
|
67
|
-
values = @attributes.collect do |att|
|
68
|
-
if record.respond_to?(att)
|
69
|
-
record.send(att)
|
70
|
-
else
|
71
|
-
nil
|
72
|
-
end
|
73
|
-
end
|
74
|
-
key = "#{self.class.get_attribute_key_part(@attributes)}::#{self.class.get_value_key_part(values)}"
|
75
|
-
end
|
76
|
-
#
|
77
|
-
# Call this IndexBuilder and pass it a +block+.
|
78
|
-
#
|
79
|
-
# If the +argument+ is an Array then we know we are a save hook,
|
80
|
-
# otherwise we are a destroy hook.
|
81
|
-
#
|
82
|
-
def call(argument, &block)
|
83
|
-
yield
|
84
|
-
|
85
|
-
#
|
86
|
-
# If the argument is an Array (of old value, new value)
|
87
|
-
# then we are a save hook, otherwise a destroy hook.
|
88
|
-
#
|
89
|
-
if Array === argument
|
90
|
-
record = argument.last
|
91
|
-
old_record = argument.first
|
92
|
-
old_key = get_key_for(old_record)
|
93
|
-
new_key = get_key_for(record)
|
94
|
-
if old_key != new_key
|
95
|
-
(CAPTAIN[old_key] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction)).delete(record.record_id)
|
96
|
-
(CAPTAIN[new_key] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction))[record.record_id] = record
|
97
|
-
end
|
98
|
-
else
|
99
|
-
record = argument
|
100
|
-
(CAPTAIN[get_key_for(record)] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction)).delete(record.record_id)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
#
|
106
|
-
# A tiny <b>call</b>able class that saves stuff inside containers
|
107
|
-
# if they match certain criteria.
|
108
|
-
#
|
109
|
-
class MatchSaver
|
110
|
-
#
|
111
|
-
# Initialize this MatchSaver with a +key+, a <b>call</b>able +matcher+
|
112
|
-
# and a +mode+ (:select, :reject, :delete_if_match or :delete_unless_match).
|
113
|
-
#
|
114
|
-
def initialize(key, matcher, mode)
|
115
|
-
@key = key
|
116
|
-
@matcher = matcher
|
117
|
-
@mode = mode
|
118
|
-
end
|
119
|
-
#
|
120
|
-
# Depending on <i>@mode</i> and return value of <i>@matcher</i>.call
|
121
|
-
# may save record in the Hash-like container named <i>@key</i> in
|
122
|
-
# the main database after having yielded to +block+.
|
123
|
-
#
|
124
|
-
def call(argument, &block)
|
125
|
-
yield
|
126
|
-
|
127
|
-
record = argument
|
128
|
-
record = argument.last if Array === argument
|
129
|
-
|
130
|
-
case @mode
|
131
|
-
when :select
|
132
|
-
if @matcher.call(record)
|
133
|
-
CAPTAIN[@key, record.transaction][record.record_id] = record
|
134
|
-
else
|
135
|
-
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
136
|
-
end
|
137
|
-
when :reject
|
138
|
-
if @matcher.call(record)
|
139
|
-
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
140
|
-
else
|
141
|
-
CAPTAIN[@key, record.transaction][record.record_id] = record
|
142
|
-
end
|
143
|
-
when :delete_if_match
|
144
|
-
if @matcher.call(record)
|
145
|
-
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
146
|
-
end
|
147
|
-
when :delete_unless_match
|
148
|
-
unless @matcher.call(record)
|
149
|
-
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
39
|
#
|
156
40
|
# A convenient base class to inherit when you want the basic utility methods
|
157
41
|
# provided by for example ActiveRecord::Base *hint hint*.
|
@@ -164,201 +48,17 @@ module Hyperactive
|
|
164
48
|
# to get a proxy to the object stored into the database.
|
165
49
|
#
|
166
50
|
class Bass
|
167
|
-
|
168
|
-
@@create_hooks_by_class = {}
|
169
|
-
@@destroy_hooks_by_class = {}
|
170
|
-
@@save_hooks_by_class = {}
|
171
|
-
@@load_hooks_by_class = {}
|
172
51
|
|
52
|
+
include Hyperactive::Index::Indexable
|
53
|
+
include Hyperactive::Transactions::Accessors
|
54
|
+
include Hyperactive::Transactions::Participant
|
55
|
+
include Hyperactive::Hooker::Pimp
|
56
|
+
|
173
57
|
#
|
174
58
|
# The host we are running on.
|
175
59
|
#
|
176
60
|
HOST = "#{Socket::gethostbyname(Socket::gethostname)[0]}" rescue "localhost"
|
177
61
|
|
178
|
-
#
|
179
|
-
# Call this if you want to change the default database connector
|
180
|
-
# to something else.
|
181
|
-
#
|
182
|
-
def self.setup(options = {})
|
183
|
-
CAPTAIN.setup(options[:pirate_options])
|
184
|
-
end
|
185
|
-
|
186
|
-
#
|
187
|
-
# Works like normal attr_reader but with transactional awareness.
|
188
|
-
#
|
189
|
-
def self.attr_reader(*attributes)
|
190
|
-
attributes.each do |attribute|
|
191
|
-
define_method(attribute) do
|
192
|
-
value = instance_variable_get("@#{attribute}")
|
193
|
-
if Archipelago::Treasure::Dubloon === value
|
194
|
-
if @transaction ||= nil
|
195
|
-
return value.join(@transaction)
|
196
|
-
else
|
197
|
-
return value
|
198
|
-
end
|
199
|
-
else
|
200
|
-
return value
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
#
|
207
|
-
# Works like normal attr_writer but with transactional awareness.
|
208
|
-
#
|
209
|
-
def self.attr_writer(*attributes)
|
210
|
-
attributes.each do |attribute|
|
211
|
-
define_method("#{attribute}=") do |new_value|
|
212
|
-
if Archipelago::Treasure::Dubloon === new_value
|
213
|
-
new_value.assert_transaction(@transaction) if @transaction ||= nil
|
214
|
-
instance_variable_set("@#{attribute}", new_value)
|
215
|
-
else
|
216
|
-
instance_variable_set("@#{attribute}", new_value)
|
217
|
-
end
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
#
|
223
|
-
# Works like normal attr_accessor but with transactional awareness.
|
224
|
-
#
|
225
|
-
def self.attr_accessor(*attributes)
|
226
|
-
attr_reader(*attributes)
|
227
|
-
attr_writer(*attributes)
|
228
|
-
end
|
229
|
-
|
230
|
-
#
|
231
|
-
# Return our create_hooks, which can then be treated as any old Array.
|
232
|
-
#
|
233
|
-
# These must be <b>call</b>able objects with an arity of 1
|
234
|
-
# that will be sent the instance about to be created (initial
|
235
|
-
# insertion into the database system) that take a block argument.
|
236
|
-
#
|
237
|
-
# The block argument will be a Proc that actually injects the
|
238
|
-
# instance into the database system.
|
239
|
-
#
|
240
|
-
# Use this to preprocess, validate and/or postprocess your
|
241
|
-
# instances upon creation.
|
242
|
-
#
|
243
|
-
def self.create_hooks
|
244
|
-
self.get_hook_array_by_class(@@create_hooks_by_class)
|
245
|
-
end
|
246
|
-
|
247
|
-
#
|
248
|
-
# Return our destroy_hooks, which can then be treated as any old Array.
|
249
|
-
#
|
250
|
-
# These must be <b>call</b>able objects with an arity of 1
|
251
|
-
# that will be sent the instance about to be destroyed (removal
|
252
|
-
# from the database system) that take a block argument.
|
253
|
-
#
|
254
|
-
# The block argument will be a Proc that actually removes the
|
255
|
-
# instance from the database system.
|
256
|
-
#
|
257
|
-
# Use this to preprocess, validate and/or postprocess your
|
258
|
-
# instances upon destruction.
|
259
|
-
#
|
260
|
-
def self.destroy_hooks
|
261
|
-
self.get_hook_array_by_class(@@destroy_hooks_by_class)
|
262
|
-
end
|
263
|
-
|
264
|
-
#
|
265
|
-
# Return our load_hooks, which can then be treated as any old Array.
|
266
|
-
#
|
267
|
-
# These must be <b>call</b>able objects with an arity of 1
|
268
|
-
# that will be sent the instance about to be loaded (insertion
|
269
|
-
# into the live hash of the database in question) that take a block argument.
|
270
|
-
#
|
271
|
-
# The block argument will be a Proc that actually puts the
|
272
|
-
# instance into the live hash.
|
273
|
-
#
|
274
|
-
# Use this to preprocess, validate and/or postprocess your
|
275
|
-
# instances upon loading.
|
276
|
-
#
|
277
|
-
def self.load_hooks
|
278
|
-
self.get_hook_array_by_class(@@load_hooks_by_class)
|
279
|
-
end
|
280
|
-
|
281
|
-
#
|
282
|
-
# Return our save_hooks, which can then be treated as any old Array.
|
283
|
-
#
|
284
|
-
# These must be <b>call</b>able objects with an arity of 1
|
285
|
-
# that will be sent [the old version, the new version] of the
|
286
|
-
# instance about to be saved (storage into the database system)
|
287
|
-
# along with a block argument.
|
288
|
-
#
|
289
|
-
# The block argument will be a Proc that actually saves the
|
290
|
-
# instance into the database system.
|
291
|
-
#
|
292
|
-
# Use this to preprocess, validate and/or postprocess your
|
293
|
-
# instances upon saving.
|
294
|
-
#
|
295
|
-
def self.save_hooks
|
296
|
-
self.get_hook_array_by_class(@@save_hooks_by_class)
|
297
|
-
end
|
298
|
-
|
299
|
-
#
|
300
|
-
# Create an index for this class.
|
301
|
-
#
|
302
|
-
# Will create a method find_by_#{attributes.join("_and_")} for this
|
303
|
-
# class that will return what you expect.
|
304
|
-
#
|
305
|
-
def self.index_by(*attributes)
|
306
|
-
attribute_key_part = IndexBuilder.get_attribute_key_part(attributes)
|
307
|
-
self.class_eval <<END
|
308
|
-
def self.find_by_#{attributes.join("_and_")}(*args)
|
309
|
-
key = "#{attribute_key_part}::" + IndexBuilder.get_value_key_part(args)
|
310
|
-
CAPTAIN[key]
|
311
|
-
end
|
312
|
-
END
|
313
|
-
index_builder = IndexBuilder.new(attributes)
|
314
|
-
self.save_hooks << index_builder
|
315
|
-
self.destroy_hooks << index_builder
|
316
|
-
end
|
317
|
-
|
318
|
-
#
|
319
|
-
# Will define a method called +name+ that will include all
|
320
|
-
# existing instances of this class that when sent to +matcher+.call
|
321
|
-
# return true. Will only return instances saved after this selector
|
322
|
-
# is defined.
|
323
|
-
#
|
324
|
-
def self.select(name, &block)
|
325
|
-
key = self.collection_key(name)
|
326
|
-
CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
|
327
|
-
self.class_eval <<END
|
328
|
-
def self.#{name}
|
329
|
-
CAPTAIN["#{key}"]
|
330
|
-
end
|
331
|
-
END
|
332
|
-
self.save_hooks << MatchSaver.new(key, Proc.new do |arg|
|
333
|
-
yield(arg)
|
334
|
-
end, :select)
|
335
|
-
self.destroy_hooks << MatchSaver.new(key, Proc.new do |arg|
|
336
|
-
yield(arg)
|
337
|
-
end, :delete_if_match)
|
338
|
-
end
|
339
|
-
|
340
|
-
#
|
341
|
-
# Will define a method called +name+ that will include all
|
342
|
-
# existing instances of this class that when sent to +matcher+.call
|
343
|
-
# does not return true. Will only return instances saved after this
|
344
|
-
# rejector is defined.
|
345
|
-
#
|
346
|
-
def self.reject(name, &block)
|
347
|
-
key = self.collection_key(name)
|
348
|
-
CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
|
349
|
-
self.class_eval <<END
|
350
|
-
def self.#{name}
|
351
|
-
CAPTAIN["#{key}"]
|
352
|
-
end
|
353
|
-
END
|
354
|
-
self.save_hooks << MatchSaver.new(key, Proc.new do |arg|
|
355
|
-
yield(arg)
|
356
|
-
end, :reject)
|
357
|
-
self.destroy_hooks << MatchSaver.new(key, Proc.new do |arg|
|
358
|
-
yield(arg)
|
359
|
-
end, :delete_unless_match)
|
360
|
-
end
|
361
|
-
|
362
62
|
#
|
363
63
|
# Return the record with +record_id+, optionally within a +transaction+.
|
364
64
|
#
|
@@ -374,22 +74,10 @@ END
|
|
374
74
|
return instance.create
|
375
75
|
end
|
376
76
|
|
377
|
-
#
|
378
|
-
# Utility method to get a proxy to a within a transaction newly saved instance of this class in one call.
|
379
|
-
#
|
380
|
-
def self.get_instance_with_transaction(transaction, *args)
|
381
|
-
instance = self.new(*args)
|
382
|
-
return_value = nil
|
383
|
-
instance.with_transaction(transaction) do
|
384
|
-
return_value = instance.create
|
385
|
-
end
|
386
|
-
return return_value
|
387
|
-
end
|
388
|
-
|
389
77
|
#
|
390
78
|
# Our semi-unique id.
|
391
79
|
#
|
392
|
-
attr_reader :record_id
|
80
|
+
attr_reader :record_id
|
393
81
|
|
394
82
|
#
|
395
83
|
# Utility compare method. Override as you please.
|
@@ -401,6 +89,11 @@ END
|
|
401
89
|
0
|
402
90
|
end
|
403
91
|
end
|
92
|
+
|
93
|
+
def initialize
|
94
|
+
@record_id = nil
|
95
|
+
@transaction = nil
|
96
|
+
end
|
404
97
|
|
405
98
|
#
|
406
99
|
# Save this Record instance into the distributed database and return a proxy to the saved object.
|
@@ -410,8 +103,8 @@ END
|
|
410
103
|
def create
|
411
104
|
@record_id ||= Digest::SHA1.hexdigest("#{HOST}:#{Time.new.to_f}:#{self.object_id}:#{rand(1 << 32)}").to_s
|
412
105
|
@transaction ||= nil
|
413
|
-
|
414
|
-
|
106
|
+
|
107
|
+
self.class.with_hooks(:instance => self, :hooks => self.class.create_hooks) do
|
415
108
|
CAPTAIN[self.record_id, @transaction] = self
|
416
109
|
end
|
417
110
|
|
@@ -421,97 +114,17 @@ END
|
|
421
114
|
end
|
422
115
|
|
423
116
|
#
|
424
|
-
#
|
425
|
-
#
|
426
|
-
# What it does is just set the @transaction instance variable
|
427
|
-
# before calling the block, and unsetting it after.
|
428
|
-
#
|
429
|
-
# This means that any classes that want to be transaction sensitive
|
430
|
-
# need to take heed regarding the @transaction instance variable.
|
431
|
-
#
|
432
|
-
# For example, when creating new Record instances you may want to use
|
433
|
-
# get_instance_with_transaction(@transaction, *args) to ensure that the
|
434
|
-
# new instance exists within the same transaction as yourself.
|
435
|
-
#
|
436
|
-
# See Hyperactive::List::Head and Hyperactive::Hash::Head for examples of this behaviour.
|
437
|
-
#
|
438
|
-
def with_transaction(transaction, &block)
|
439
|
-
@transaction = transaction
|
440
|
-
begin
|
441
|
-
yield
|
442
|
-
ensure
|
443
|
-
@transaction = nil
|
444
|
-
end
|
445
|
-
end
|
446
|
-
|
447
|
-
#
|
448
|
-
# This will allow us to wrap any write of us to persistent storage
|
449
|
-
# in the @@save_hooks as long as the Archipelago::Hashish provider
|
450
|
-
# supports it. See Archipelago::Hashish::BerkeleyHashish for an example
|
451
|
-
# of Hashish providers that do this.
|
452
|
-
#
|
453
|
-
def save_hook(old_value, &block)
|
454
|
-
Hyperactive::Hooker.call_with_hooks([old_value, self], *self.class.save_hooks) do
|
455
|
-
yield
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
|
-
#
|
460
|
-
# This will allow us to wrap any load of us from persistent storage
|
461
|
-
# in the @@load_hooks as long as the Archipelago::Hashish provider
|
462
|
-
# supports it. See Archipelago::Hashish::BerkeleyHashish for an example
|
463
|
-
# of Hashish providers that do this.
|
464
|
-
#
|
465
|
-
def load_hook(&block)
|
466
|
-
Hyperactive::Hooker.call_with_hooks(self, *self.class.load_hooks) do
|
467
|
-
yield
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
#
|
472
|
-
# Remove this instance from the database calling all the right hooks.
|
117
|
+
# Remove this instance from the database wrapped in our destroy_hooks.
|
473
118
|
#
|
474
119
|
# Freezes this instance after having deleted it.
|
475
120
|
#
|
476
|
-
# Returns false without destroying anything if any of the @@pre_destroy_hooks
|
477
|
-
# returns false.
|
478
|
-
#
|
479
|
-
# Returns true otherwise.
|
480
|
-
#
|
481
121
|
def destroy!
|
482
|
-
|
122
|
+
self.class.with_hooks(:instance => self, :hooks => self.class.destroy_hooks) do
|
483
123
|
CAPTAIN.delete(@record_id, @transaction)
|
484
124
|
self.freeze
|
485
125
|
end
|
486
126
|
end
|
487
127
|
|
488
|
-
private
|
489
|
-
|
490
|
-
#
|
491
|
-
# The key used to store the collection with the given +sym+ as name.
|
492
|
-
#
|
493
|
-
def self.collection_key(sym)
|
494
|
-
"Hyperactive::Record::Bass::collection_key::#{sym}"
|
495
|
-
end
|
496
|
-
|
497
|
-
#
|
498
|
-
# Get an Array from +hash+ using <i>self</i> as
|
499
|
-
# key. If <i>self</i> doesnt exist in the +hash+
|
500
|
-
# it will recurse by calling the same method in the
|
501
|
-
# superclass until it has been called in Hyperactive::Record::Base.
|
502
|
-
#
|
503
|
-
def self.get_hook_array_by_class(hash)
|
504
|
-
return hash[self] if hash.include?(self)
|
505
|
-
|
506
|
-
if self == Bass
|
507
|
-
hash[self] = []
|
508
|
-
return self.get_hook_array_by_class(hash)
|
509
|
-
else
|
510
|
-
hash[self] = self.superclass.get_hook_array_by_class(hash).clone
|
511
|
-
return self.get_hook_array_by_class(hash)
|
512
|
-
end
|
513
|
-
end
|
514
|
-
|
515
128
|
end
|
516
129
|
end
|
517
130
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# Archipelago - a distributed computing toolkit for ruby
|
2
|
+
# Copyright (C) 2006 Martin Kihlgren <zond at troja dot ath dot cx>
|
3
|
+
#
|
4
|
+
# This program is free software; you can redistribute it and/or
|
5
|
+
# modify it under the terms of the GNU General Public License
|
6
|
+
# as published by the Free Software Foundation; either version 2
|
7
|
+
# of the License, or (at your option) any later version.
|
8
|
+
#
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
#
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program; if not, write to the Free Software
|
16
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
17
|
+
|
18
|
+
module Hyperactive
|
19
|
+
|
20
|
+
#
|
21
|
+
# A utility package of transaction handling methods.
|
22
|
+
#
|
23
|
+
module Transactions
|
24
|
+
|
25
|
+
#
|
26
|
+
# Include this to get methods to do with participating in a transaction.
|
27
|
+
#
|
28
|
+
module Participant
|
29
|
+
|
30
|
+
#
|
31
|
+
# The transaction we are currently in.
|
32
|
+
#
|
33
|
+
attr_reader :transaction
|
34
|
+
|
35
|
+
#
|
36
|
+
# Will execute +block+ within a transaction.
|
37
|
+
#
|
38
|
+
# What it does is just set the @transaction instance variable
|
39
|
+
# before calling the block, and unsetting it after.
|
40
|
+
#
|
41
|
+
# This means that any classes that want to be transaction sensitive
|
42
|
+
# need to take heed regarding the @transaction instance variable.
|
43
|
+
#
|
44
|
+
# For example, when creating new Record instances you may want to use
|
45
|
+
# get_instance_with_transaction(@transaction, *args) to ensure that the
|
46
|
+
# new instance exists within the same transaction as yourself.
|
47
|
+
#
|
48
|
+
# See Hyperactive::List::Head and Hyperactive::Hash::Head for examples of this behaviour.
|
49
|
+
#
|
50
|
+
def with_transaction(transaction, &block)
|
51
|
+
@transaction = transaction
|
52
|
+
begin
|
53
|
+
yield
|
54
|
+
ensure
|
55
|
+
@transaction = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Add our ClassMethods to anyone that includes us.
|
61
|
+
#
|
62
|
+
def self.append_features(base)
|
63
|
+
super
|
64
|
+
base.extend(ClassMethods)
|
65
|
+
end
|
66
|
+
|
67
|
+
#
|
68
|
+
# Class methods for transaction participants.
|
69
|
+
#
|
70
|
+
module ClassMethods
|
71
|
+
#
|
72
|
+
# Utility method to get a proxy to a within a transaction newly saved instance of this class in one call.
|
73
|
+
#
|
74
|
+
def get_instance_with_transaction(transaction, *args)
|
75
|
+
instance = self.new(*args)
|
76
|
+
return_value = nil
|
77
|
+
instance.with_transaction(transaction) do
|
78
|
+
return_value = instance.create
|
79
|
+
end
|
80
|
+
return return_value
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
#
|
88
|
+
# Include this to get the default attr_reader at al redefined to be transactionally aware.
|
89
|
+
#
|
90
|
+
module Accessors
|
91
|
+
|
92
|
+
#
|
93
|
+
# The +base+ class including this will get a our ClassMethods as well.
|
94
|
+
#
|
95
|
+
def self.append_features(base)
|
96
|
+
super
|
97
|
+
base.extend(ClassMethods)
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# The class methods that help us set/get instance variables in a transactionally aware way.
|
102
|
+
#
|
103
|
+
module ClassMethods
|
104
|
+
#
|
105
|
+
# Works like normal attr_reader but with transactional awareness.
|
106
|
+
#
|
107
|
+
def attr_reader(*attributes)
|
108
|
+
attributes.each do |attribute|
|
109
|
+
define_method(attribute) do
|
110
|
+
value = instance_variable_get("@#{attribute}")
|
111
|
+
if Archipelago::Treasure::Dubloon === value
|
112
|
+
if @transaction ||= nil
|
113
|
+
return value.join(@transaction)
|
114
|
+
else
|
115
|
+
return value
|
116
|
+
end
|
117
|
+
else
|
118
|
+
return value
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Works like normal attr_writer but with transactional awareness.
|
126
|
+
#
|
127
|
+
def attr_writer(*attributes)
|
128
|
+
attributes.each do |attribute|
|
129
|
+
define_method("#{attribute}=") do |new_value|
|
130
|
+
if Archipelago::Treasure::Dubloon === new_value
|
131
|
+
new_value.assert_transaction(@transaction) if @transaction ||= nil
|
132
|
+
instance_variable_set("@#{attribute}", new_value)
|
133
|
+
else
|
134
|
+
instance_variable_set("@#{attribute}", new_value)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Works like normal attr_accessor but with transactional awareness.
|
142
|
+
#
|
143
|
+
def attr_accessor(*attributes)
|
144
|
+
attr_reader(*attributes)
|
145
|
+
attr_writer(*attributes)
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
end
|
data/lib/hyperactive.rb
CHANGED
data/tests/hash_benchmark.rb
CHANGED
@@ -10,13 +10,13 @@ class HashBenchmark < Test::Unit::TestCase
|
|
10
10
|
@c2.publish!
|
11
11
|
@tm = TestManager.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("tranny1.db")))
|
12
12
|
@tm.publish!
|
13
|
-
Hyperactive::
|
13
|
+
Hyperactive::CAPTAIN.setup(:chest_description => {:class => 'TestChest'},
|
14
14
|
:tranny_description => {:class => 'TestManager'})
|
15
15
|
assert_within(20) do
|
16
|
-
Set.new(Hyperactive::
|
16
|
+
Set.new(Hyperactive::CAPTAIN.chests.keys) == Set.new([@c.service_id, @c2.service_id])
|
17
17
|
end
|
18
18
|
assert_within(20) do
|
19
|
-
Hyperactive::
|
19
|
+
Hyperactive::CAPTAIN.trannies.keys == [@tm.service_id]
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
@@ -36,8 +36,8 @@ class HashBenchmark < Test::Unit::TestCase
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_regular_hash_set_get
|
39
|
-
Hyperactive::
|
40
|
-
h = Hyperactive::
|
39
|
+
Hyperactive::CAPTAIN["h"] = {}
|
40
|
+
h = Hyperactive::CAPTAIN["h"]
|
41
41
|
r = Hyperactive::Record::Bass.get_instance
|
42
42
|
hash_test("Hash", h, r, 100)
|
43
43
|
end
|
data/tests/hash_test.rb
CHANGED
@@ -19,14 +19,14 @@ class HashTest < Test::Unit::TestCase
|
|
19
19
|
@c2.publish!
|
20
20
|
@tm = TestManager.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("tranny1.db")))
|
21
21
|
@tm.publish!
|
22
|
-
Hyperactive::
|
22
|
+
Hyperactive::CAPTAIN.setup(:chest_description => {:class => 'TestChest'},
|
23
23
|
:tranny_description => {:class => 'TestManager'})
|
24
|
-
Hyperactive::
|
24
|
+
Hyperactive::CAPTAIN.update_services!
|
25
25
|
assert_within(10) do
|
26
|
-
Hyperactive::
|
26
|
+
Hyperactive::CAPTAIN.chests.keys.sort == [@c.service_id, @c2.service_id].sort
|
27
27
|
end
|
28
28
|
assert_within(10) do
|
29
|
-
Hyperactive::
|
29
|
+
Hyperactive::CAPTAIN.trannies.keys == [@tm.service_id]
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -69,7 +69,7 @@ class HashTest < Test::Unit::TestCase
|
|
69
69
|
|
70
70
|
h2.each do |k,v|
|
71
71
|
assert_equal(v, h[k])
|
72
|
-
assert_equal(v, Hyperactive::
|
72
|
+
assert_equal(v, Hyperactive::CAPTAIN[h.record_id][k])
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|