hyperactive 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README +12 -5
- data/lib/hyperactive.rb +1 -1
- data/lib/hyperactive/hash.rb +172 -0
- data/lib/hyperactive/list.rb +128 -43
- data/lib/hyperactive/record.rb +159 -41
- data/tests/{tree_benchmark.rb → hash_benchmark.rb} +6 -6
- data/tests/{tree_test.rb → hash_test.rb} +17 -16
- data/tests/list_benchmark.rb +48 -0
- data/tests/list_test.rb +69 -11
- data/tests/record_test.rb +25 -31
- metadata +7 -6
- data/lib/hyperactive/tree.rb +0 -241
data/lib/hyperactive/record.rb
CHANGED
@@ -61,7 +61,7 @@ module Hyperactive
|
|
61
61
|
@attributes = attributes
|
62
62
|
end
|
63
63
|
#
|
64
|
-
# Get the
|
64
|
+
# Get the key for the given +record+.
|
65
65
|
#
|
66
66
|
def get_key_for(record)
|
67
67
|
values = @attributes.collect do |att|
|
@@ -92,12 +92,12 @@ module Hyperactive
|
|
92
92
|
old_key = get_key_for(old_record)
|
93
93
|
new_key = get_key_for(record)
|
94
94
|
if old_key != new_key
|
95
|
-
(CAPTAIN[old_key] ||= Hyperactive::
|
96
|
-
(CAPTAIN[new_key] ||= Hyperactive::
|
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
97
|
end
|
98
98
|
else
|
99
99
|
record = argument
|
100
|
-
(CAPTAIN[get_key_for(record)] ||= Hyperactive::
|
100
|
+
(CAPTAIN[get_key_for(record)] ||= Hyperactive::Hash::Head.get_instance_with_transaction(record.transaction)).delete(record.record_id)
|
101
101
|
end
|
102
102
|
end
|
103
103
|
end
|
@@ -130,23 +130,23 @@ module Hyperactive
|
|
130
130
|
case @mode
|
131
131
|
when :select
|
132
132
|
if @matcher.call(record)
|
133
|
-
CAPTAIN[@key][record.record_id] = record
|
133
|
+
CAPTAIN[@key, record.transaction][record.record_id] = record
|
134
134
|
else
|
135
|
-
CAPTAIN[@key].delete(record.record_id)
|
135
|
+
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
136
136
|
end
|
137
137
|
when :reject
|
138
138
|
if @matcher.call(record)
|
139
|
-
CAPTAIN[@key].delete(record.record_id)
|
139
|
+
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
140
140
|
else
|
141
|
-
CAPTAIN[@key][record.record_id] = record
|
141
|
+
CAPTAIN[@key, record.transaction][record.record_id] = record
|
142
142
|
end
|
143
143
|
when :delete_if_match
|
144
144
|
if @matcher.call(record)
|
145
|
-
CAPTAIN[@key].delete(record.record_id)
|
145
|
+
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
146
146
|
end
|
147
147
|
when :delete_unless_match
|
148
148
|
unless @matcher.call(record)
|
149
|
-
CAPTAIN[@key].delete(record.record_id)
|
149
|
+
CAPTAIN[@key, record.transaction].delete(record.record_id)
|
150
150
|
end
|
151
151
|
end
|
152
152
|
end
|
@@ -156,20 +156,19 @@ module Hyperactive
|
|
156
156
|
# A convenient base class to inherit when you want the basic utility methods
|
157
157
|
# provided by for example ActiveRecord::Base *hint hint*.
|
158
158
|
#
|
159
|
-
# NB:
|
160
|
-
# which is not what
|
159
|
+
# NB: When an instance is created you will actually have a copy <b>within your local machine</b>
|
160
|
+
# which is not what you usually want. Every other time you fetch it using a select or other
|
161
161
|
# method you will instead receive a proxy object to the database. This means that nothing you
|
162
162
|
# do to it at that point will be persistent or even necessarily have a defined result.
|
163
|
-
# Therefore: do not use
|
164
|
-
#
|
165
|
-
# instead of the devious original:
|
166
|
-
# my_instance = MyRecordSubclass.get_instance(*the_same_arguments_as_to_initialize)
|
163
|
+
# Therefore: do not use the instantiated object, instead call <b>my_instance.save</b>
|
164
|
+
# to get a proxy to the object stored into the database.
|
167
165
|
#
|
168
166
|
class Bass
|
169
167
|
|
170
168
|
@@create_hooks_by_class = {}
|
171
169
|
@@destroy_hooks_by_class = {}
|
172
170
|
@@save_hooks_by_class = {}
|
171
|
+
@@load_hooks_by_class = {}
|
173
172
|
|
174
173
|
#
|
175
174
|
# The host we are running on.
|
@@ -184,6 +183,50 @@ module Hyperactive
|
|
184
183
|
CAPTAIN.setup(options[:pirate_options])
|
185
184
|
end
|
186
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
|
+
|
187
230
|
#
|
188
231
|
# Return our create_hooks, which can then be treated as any old Array.
|
189
232
|
#
|
@@ -218,6 +261,23 @@ module Hyperactive
|
|
218
261
|
self.get_hook_array_by_class(@@destroy_hooks_by_class)
|
219
262
|
end
|
220
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
|
+
|
221
281
|
#
|
222
282
|
# Return our save_hooks, which can then be treated as any old Array.
|
223
283
|
#
|
@@ -263,7 +323,7 @@ END
|
|
263
323
|
#
|
264
324
|
def self.select(name, &block)
|
265
325
|
key = self.collection_key(name)
|
266
|
-
CAPTAIN[key] ||= Hyperactive::
|
326
|
+
CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
|
267
327
|
self.class_eval <<END
|
268
328
|
def self.#{name}
|
269
329
|
CAPTAIN["#{key}"]
|
@@ -283,9 +343,9 @@ END
|
|
283
343
|
# does not return true. Will only return instances saved after this
|
284
344
|
# rejector is defined.
|
285
345
|
#
|
286
|
-
|
346
|
+
def self.reject(name, &block)
|
287
347
|
key = self.collection_key(name)
|
288
|
-
CAPTAIN[key] ||= Hyperactive::
|
348
|
+
CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
|
289
349
|
self.class_eval <<END
|
290
350
|
def self.#{name}
|
291
351
|
CAPTAIN["#{key}"]
|
@@ -300,44 +360,90 @@ END
|
|
300
360
|
end
|
301
361
|
|
302
362
|
#
|
303
|
-
# Return the record with +record_id+.
|
363
|
+
# Return the record with +record_id+, optionally within a +transaction+.
|
304
364
|
#
|
305
|
-
def self.find(record_id)
|
306
|
-
CAPTAIN[record_id]
|
365
|
+
def self.find(record_id, transaction = nil)
|
366
|
+
CAPTAIN[record_id, transaction]
|
367
|
+
end
|
368
|
+
|
369
|
+
#
|
370
|
+
# Utility method to get a proxy to a newly saved instance of this class in one call.
|
371
|
+
#
|
372
|
+
def self.get_instance(*args)
|
373
|
+
instance = self.new(*args)
|
374
|
+
return instance.create
|
307
375
|
end
|
308
376
|
|
309
377
|
#
|
310
|
-
#
|
378
|
+
# Utility method to get a proxy to a within a transaction newly saved instance of this class in one call.
|
311
379
|
#
|
312
|
-
def self.transaction
|
313
|
-
|
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
|
314
387
|
end
|
388
|
+
|
389
|
+
#
|
390
|
+
# Our semi-unique id.
|
391
|
+
#
|
392
|
+
attr_reader :record_id, :transaction
|
315
393
|
|
316
394
|
#
|
317
|
-
#
|
318
|
-
# make sure it both resides in the database and return a proxy to the remote
|
319
|
-
# object.
|
395
|
+
# Utility compare method. Override as you please.
|
320
396
|
#
|
321
|
-
def
|
322
|
-
|
323
|
-
|
324
|
-
|
397
|
+
def <=>(o)
|
398
|
+
if Record === o
|
399
|
+
@record_id <=> o.record_id
|
400
|
+
else
|
401
|
+
0
|
325
402
|
end
|
403
|
+
end
|
404
|
+
|
405
|
+
#
|
406
|
+
# Save this Record instance into the distributed database and return a proxy to the saved object.
|
407
|
+
#
|
408
|
+
# This will also wrap the actual insertion within the create_hooks you have defined for this class.
|
409
|
+
#
|
410
|
+
def create
|
411
|
+
@record_id ||= Digest::SHA1.hexdigest("#{HOST}:#{Time.new.to_f}:#{self.object_id}:#{rand(1 << 32)}").to_s
|
412
|
+
@transaction ||= nil
|
326
413
|
|
327
|
-
Hyperactive::Hooker.call_with_hooks(
|
328
|
-
CAPTAIN[
|
414
|
+
Hyperactive::Hooker.call_with_hooks(self, *self.class.create_hooks) do
|
415
|
+
CAPTAIN[self.record_id, @transaction] = self
|
329
416
|
end
|
330
417
|
|
331
|
-
proxy = CAPTAIN[
|
418
|
+
proxy = CAPTAIN[@record_id, @transaction]
|
332
419
|
|
333
420
|
return proxy
|
334
421
|
end
|
335
|
-
|
422
|
+
|
336
423
|
#
|
337
|
-
#
|
424
|
+
# Will execute +block+ within a transaction.
|
338
425
|
#
|
339
|
-
|
340
|
-
|
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
|
+
|
341
447
|
#
|
342
448
|
# This will allow us to wrap any write of us to persistent storage
|
343
449
|
# in the @@save_hooks as long as the Archipelago::Hashish provider
|
@@ -350,6 +456,18 @@ END
|
|
350
456
|
end
|
351
457
|
end
|
352
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
|
+
|
353
471
|
#
|
354
472
|
# Remove this instance from the database calling all the right hooks.
|
355
473
|
#
|
@@ -360,9 +478,9 @@ END
|
|
360
478
|
#
|
361
479
|
# Returns true otherwise.
|
362
480
|
#
|
363
|
-
def destroy
|
481
|
+
def destroy!
|
364
482
|
Hyperactive::Hooker.call_with_hooks(self, *self.class.destroy_hooks) do
|
365
|
-
CAPTAIN.delete(@record_id)
|
483
|
+
CAPTAIN.delete(@record_id, @transaction)
|
366
484
|
self.freeze
|
367
485
|
end
|
368
486
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
require File.join(File.dirname(__FILE__), 'test_helper')
|
3
3
|
|
4
|
-
class
|
4
|
+
class HashBenchmark < Test::Unit::TestCase
|
5
5
|
|
6
6
|
def setup
|
7
7
|
@c = TestChest.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("chest.db")))
|
@@ -22,17 +22,17 @@ class TreeBenchmark < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def teardown
|
24
24
|
@c.stop!
|
25
|
-
@c.persistence_provider.unlink
|
25
|
+
@c.persistence_provider.unlink!
|
26
26
|
@c2.stop!
|
27
|
-
@c2.persistence_provider.unlink
|
27
|
+
@c2.persistence_provider.unlink!
|
28
28
|
@tm.stop!
|
29
|
-
@tm.persistence_provider.unlink
|
29
|
+
@tm.persistence_provider.unlink!
|
30
30
|
end
|
31
31
|
|
32
32
|
def test_set_get
|
33
|
-
h = Hyperactive::
|
33
|
+
h = Hyperactive::Hash::Head.get_instance
|
34
34
|
r = Hyperactive::Record::Bass.get_instance
|
35
|
-
hash_test("
|
35
|
+
hash_test("Hyperactive::Hash", h, r, 100)
|
36
36
|
end
|
37
37
|
|
38
38
|
def test_regular_hash_set_get
|
@@ -10,7 +10,7 @@ class RecordMatcher
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
class
|
13
|
+
class HashTest < Test::Unit::TestCase
|
14
14
|
|
15
15
|
def setup
|
16
16
|
@c = TestChest.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("chest.db")))
|
@@ -32,33 +32,34 @@ class TreeTest < Test::Unit::TestCase
|
|
32
32
|
|
33
33
|
def teardown
|
34
34
|
@c.stop!
|
35
|
-
@c.persistence_provider.unlink
|
35
|
+
@c.persistence_provider.unlink!
|
36
36
|
@c2.stop!
|
37
|
-
@c2.persistence_provider.unlink
|
37
|
+
@c2.persistence_provider.unlink!
|
38
38
|
@tm.stop!
|
39
|
-
@tm.persistence_provider.unlink
|
40
|
-
Archipelago::Disco::MC.clear!
|
41
|
-
Hyperactive::Record::CAPTAIN.update_services!
|
42
|
-
assert_within(10) do
|
43
|
-
Hyperactive::Record::CAPTAIN.chests.empty?
|
44
|
-
end
|
45
|
-
assert_within(10) do
|
46
|
-
Hyperactive::Record::CAPTAIN.trannies.empty?
|
47
|
-
end
|
39
|
+
@tm.persistence_provider.unlink!
|
48
40
|
end
|
49
41
|
|
50
42
|
def test_select_reject
|
51
|
-
h = Hyperactive::
|
43
|
+
h = Hyperactive::Hash::Head.get_instance
|
52
44
|
r1 = Hyperactive::Record::Bass.get_instance
|
53
45
|
r2 = Hyperactive::Record::Bass.get_instance
|
54
46
|
h[r1.record_id] = r1
|
55
47
|
h[r2.record_id] = r2
|
56
|
-
assert_equal(r1.record_id, h.
|
57
|
-
assert_equal(r1.record_id, h.
|
48
|
+
assert_equal(r1.record_id, h.t_select(RecordMatcher.new(r1.record_id)).first.first)
|
49
|
+
assert_equal(r1.record_id, h.t_reject(RecordMatcher.new(r2.record_id)).first.first)
|
58
50
|
end
|
59
51
|
|
52
|
+
def test_delete
|
53
|
+
h = Hyperactive::Hash::Head.get_instance
|
54
|
+
r = Hyperactive::Record::Bass.get_instance
|
55
|
+
h[r.record_id] = r
|
56
|
+
assert(h.include?(r.record_id))
|
57
|
+
h.delete(r.record_id)
|
58
|
+
assert(!h.include?(r.record_id))
|
59
|
+
end
|
60
|
+
|
60
61
|
def test_set_get
|
61
|
-
h = Hyperactive::
|
62
|
+
h = Hyperactive::Hash::Head.get_instance
|
62
63
|
h2 = {}
|
63
64
|
10.times do
|
64
65
|
r = Hyperactive::Record::Bass.get_instance
|
@@ -0,0 +1,48 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
3
|
+
|
4
|
+
class ListBenchmark < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@c = TestChest.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("chest.db")))
|
8
|
+
@c.publish!
|
9
|
+
@c2 = TestChest.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("chest2.db")))
|
10
|
+
@c2.publish!
|
11
|
+
@tm = TestManager.new(:persistence_provider => Archipelago::Hashish::BerkeleyHashishProvider.new(Pathname.new(__FILE__).parent.join("tranny1.db")))
|
12
|
+
@tm.publish!
|
13
|
+
Hyperactive::Record::CAPTAIN.setup(:chest_description => {:class => 'TestChest'},
|
14
|
+
:tranny_description => {:class => 'TestManager'})
|
15
|
+
assert_within(20) do
|
16
|
+
Set.new(Hyperactive::Record::CAPTAIN.chests.keys) == Set.new([@c.service_id, @c2.service_id])
|
17
|
+
end
|
18
|
+
assert_within(20) do
|
19
|
+
Hyperactive::Record::CAPTAIN.trannies.keys == [@tm.service_id]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def teardown
|
24
|
+
@c.stop!
|
25
|
+
@c.persistence_provider.unlink!
|
26
|
+
@c2.stop!
|
27
|
+
@c2.persistence_provider.unlink!
|
28
|
+
@tm.stop!
|
29
|
+
@tm.persistence_provider.unlink!
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_set_get
|
33
|
+
l = Hyperactive::List::Head.get_instance
|
34
|
+
r = Hyperactive::Record::Bass.get_instance
|
35
|
+
bm("List::Head#push/pop", :n => 1000) do
|
36
|
+
l << r
|
37
|
+
l.pop
|
38
|
+
end
|
39
|
+
bm("List::Head#push", :n => 1000) do
|
40
|
+
l << r
|
41
|
+
end
|
42
|
+
bm("List::Head#push/pop (big)", :n => 1000) do
|
43
|
+
l << r
|
44
|
+
l.pop
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|