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.
@@ -61,7 +61,7 @@ module Hyperactive
61
61
  @attributes = attributes
62
62
  end
63
63
  #
64
- # Get the Tree for the given +record+.
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::Tree::Root.get_instance).delete(record.record_id)
96
- (CAPTAIN[new_key] ||= Hyperactive::Tree::Root.get_instance)[record.record_id] = record
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::Tree::Root.get_instance).delete(record.record_id)
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: After an instance is created, it will actually return a copy <b>within your local machine</b>
160
- # which is not what is usually the case. Every other time you fetch it using a select or other
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 <b>MyRecordSubclass.new</b> to <b>MyRecordSubclass#initialize</b> objects,
164
- # instead use <b>MyRecordSubclass.get_instance</b>, since it will return a fresh proxy object
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::Tree::Root.get_instance
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
- def self.reject(name, &block)
346
+ def self.reject(name, &block)
287
347
  key = self.collection_key(name)
288
- CAPTAIN[key] ||= Hyperactive::Tree::Root.get_instance
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
- # Will execute +block+ within a transaction.
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(&block)
313
- CAPTAIN.transaction(&block)
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
- # Use this method to get new instances of this class, since it will actually
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 self.get_instance(*arguments)
322
- instance = self.new(*arguments)
323
- instance.instance_eval do
324
- @record_id = Digest::SHA1.hexdigest("#{HOST}:#{Time.new.to_f}:#{self.object_id}:#{rand(1 << 32)}")
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(instance, *self.create_hooks) do
328
- CAPTAIN[instance.record_id] = instance
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[instance.record_id]
418
+ proxy = CAPTAIN[@record_id, @transaction]
332
419
 
333
420
  return proxy
334
421
  end
335
-
422
+
336
423
  #
337
- # Our semi-unique id.
424
+ # Will execute +block+ within a transaction.
338
425
  #
339
- attr_reader :record_id
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 TreeBenchmark < Test::Unit::TestCase
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::Tree::Root.get_instance
33
+ h = Hyperactive::Hash::Head.get_instance
34
34
  r = Hyperactive::Record::Bass.get_instance
35
- hash_test("Tree", h, r, 100)
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 TreeTest < Test::Unit::TestCase
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::Tree::Root.get_instance
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.select(RecordMatcher.new(r1.record_id)).first.first)
57
- assert_equal(r1.record_id, h.reject(RecordMatcher.new(r2.record_id)).keys.first)
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::Tree::Root.get_instance
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