hyperactive 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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