hyperactive 0.2.2 → 0.2.3
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 +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
|