Class: Hyperactive::Record::Bass

Inherits:
Object
  • Object
show all
Defined in:
lib/hyperactive/record.rb

Overview

A convenient base class to inherit when you want the basic utility methods provided by for example ActiveRecord::Base *hint hint*.

NB: When an instance is created you will actually have a copy within your local machine which is not what you usually want. Every other time you fetch it using a select or other method you will instead receive a proxy object to the database. This means that nothing you do to it at that point will be persistent or even necessarily have a defined result. Therefore: do not use the instantiated object, instead call my_instance.save to get a proxy to the object stored into the database.

Direct Known Subclasses

Hash::Element, Hash::Head, List::Element, List::Head

Constant Summary collapse

HOST =

The host we are running on.

"#{Socket::gethostbyname(Socket::gethostname)[0]}" rescue "localhost"
@@create_hooks_by_class =
{}
@@destroy_hooks_by_class =
{}
@@save_hooks_by_class =
{}
@@load_hooks_by_class =
{}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#record_idObject (readonly)

Our semi-unique id.



392
393
394
# File 'lib/hyperactive/record.rb', line 392

def record_id
  @record_id
end

#transactionObject (readonly)

Our semi-unique id.



392
393
394
# File 'lib/hyperactive/record.rb', line 392

def transaction
  @transaction
end

Class Method Details

.attr_accessor(*attributes) ⇒ Object

Works like normal attr_accessor but with transactional awareness.



225
226
227
228
# File 'lib/hyperactive/record.rb', line 225

def self.attr_accessor(*attributes)
  attr_reader(*attributes)
  attr_writer(*attributes)
end

.attr_reader(*attributes) ⇒ Object

Works like normal attr_reader but with transactional awareness.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/hyperactive/record.rb', line 189

def self.attr_reader(*attributes)
  attributes.each do |attribute|
    define_method(attribute) do
      value = instance_variable_get("@#{attribute}")
      if Archipelago::Treasure::Dubloon === value
        if @transaction ||= nil
          return value.join(@transaction)
        else
          return value
        end
      else
        return value
      end
    end
  end
end

.attr_writer(*attributes) ⇒ Object

Works like normal attr_writer but with transactional awareness.



209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/hyperactive/record.rb', line 209

def self.attr_writer(*attributes)
  attributes.each do |attribute|
    define_method("#{attribute}=") do |new_value|
      if Archipelago::Treasure::Dubloon === new_value
        new_value.assert_transaction(@transaction) if @transaction ||= nil
        instance_variable_set("@#{attribute}", new_value)
      else
        instance_variable_set("@#{attribute}", new_value)
      end
    end
  end
end

.create_hooksObject

Return our create_hooks, which can then be treated as any old Array.

These must be callable objects with an arity of 1 that will be sent the instance about to be created (initial insertion into the database system) that take a block argument.

The block argument will be a Proc that actually injects the instance into the database system.

Use this to preprocess, validate and/or postprocess your instances upon creation.



243
244
245
# File 'lib/hyperactive/record.rb', line 243

def self.create_hooks
  self.get_hook_array_by_class(@@create_hooks_by_class)
end

.destroy_hooksObject

Return our destroy_hooks, which can then be treated as any old Array.

These must be callable objects with an arity of 1 that will be sent the instance about to be destroyed (removal from the database system) that take a block argument.

The block argument will be a Proc that actually removes the instance from the database system.

Use this to preprocess, validate and/or postprocess your instances upon destruction.



260
261
262
# File 'lib/hyperactive/record.rb', line 260

def self.destroy_hooks
  self.get_hook_array_by_class(@@destroy_hooks_by_class)
end

.find(record_id, transaction = nil) ⇒ Object

Return the record with record_id, optionally within a transaction.



365
366
367
# File 'lib/hyperactive/record.rb', line 365

def self.find(record_id, transaction = nil)
  CAPTAIN[record_id, transaction]
end

.get_instance(*args) ⇒ Object

Utility method to get a proxy to a newly saved instance of this class in one call.



372
373
374
375
# File 'lib/hyperactive/record.rb', line 372

def self.get_instance(*args)
  instance = self.new(*args)
  return instance.create
end

.get_instance_with_transaction(transaction, *args) ⇒ Object

Utility method to get a proxy to a within a transaction newly saved instance of this class in one call.



380
381
382
383
384
385
386
387
# File 'lib/hyperactive/record.rb', line 380

def self.get_instance_with_transaction(transaction, *args)
  instance = self.new(*args)
  return_value = nil
  instance.with_transaction(transaction) do
    return_value = instance.create
  end
  return return_value
end

.index_by(*attributes) ⇒ Object

Create an index for this class.

Will create a method find_by_#attributesattributes.join(“<em>and</em>”) for this class that will return what you expect.



305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/hyperactive/record.rb', line 305

def self.index_by(*attributes)
  attribute_key_part = IndexBuilder.get_attribute_key_part(attributes)
  self.class_eval <<END
def self.find_by_#{attributes.join("_and_")}(*args)
key = "#{attribute_key_part}::" + IndexBuilder.get_value_key_part(args)
CAPTAIN[key]
end
END
  index_builder = IndexBuilder.new(attributes)
  self.save_hooks << index_builder
  self.destroy_hooks << index_builder
end

.load_hooksObject

Return our load_hooks, which can then be treated as any old Array.

These must be callable objects with an arity of 1 that will be sent the instance about to be loaded (insertion into the live hash of the database in question) that take a block argument.

The block argument will be a Proc that actually puts the instance into the live hash.

Use this to preprocess, validate and/or postprocess your instances upon loading.



277
278
279
# File 'lib/hyperactive/record.rb', line 277

def self.load_hooks
  self.get_hook_array_by_class(@@load_hooks_by_class)
end

.reject(name, &block) ⇒ Object

Will define a method called name that will include all existing instances of this class that when sent to matcher.call does not return true. Will only return instances saved after this rejector is defined.



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/hyperactive/record.rb', line 346

def self.reject(name, &block)
  key = self.collection_key(name)
  CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
  self.class_eval <<END
def self.#{name}
    CAPTAIN["#{key}"]
end
END
  self.save_hooks << MatchSaver.new(key, Proc.new do |arg|
                                      yield(arg)
                                    end, :reject)
  self.destroy_hooks << MatchSaver.new(key, Proc.new do |arg|
                                         yield(arg)
                                       end, :delete_unless_match)
end

.save_hooksObject

Return our save_hooks, which can then be treated as any old Array.

These must be callable objects with an arity of 1 that will be sent [the old version, the new version] of the instance about to be saved (storage into the database system) along with a block argument.

The block argument will be a Proc that actually saves the instance into the database system.

Use this to preprocess, validate and/or postprocess your instances upon saving.



295
296
297
# File 'lib/hyperactive/record.rb', line 295

def self.save_hooks
  self.get_hook_array_by_class(@@save_hooks_by_class)
end

.select(name, &block) ⇒ Object

Will define a method called name that will include all existing instances of this class that when sent to matcher.call return true. Will only return instances saved after this selector is defined.



324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/hyperactive/record.rb', line 324

def self.select(name, &block)
  key = self.collection_key(name)
  CAPTAIN[key] ||= Hyperactive::Hash::Head.get_instance
  self.class_eval <<END
def self.#{name}
    CAPTAIN["#{key}"]
end
END
  self.save_hooks << MatchSaver.new(key, Proc.new do |arg|
                                      yield(arg)
                                    end, :select)
  self.destroy_hooks << MatchSaver.new(key, Proc.new do |arg|
                                         yield(arg)
                                       end, :delete_if_match)
end

.setup(options = {}) ⇒ Object

Call this if you want to change the default database connector to something else.



182
183
184
# File 'lib/hyperactive/record.rb', line 182

def self.setup(options = {})
  CAPTAIN.setup(options[:pirate_options])
end

Instance Method Details

#<=>(o) ⇒ Object

Utility compare method. Override as you please.



397
398
399
400
401
402
403
# File 'lib/hyperactive/record.rb', line 397

def <=>(o)
  if Record === o
    @record_id <=> o.record_id 
  else
    0
  end
end

#createObject

Save this Record instance into the distributed database and return a proxy to the saved object.

This will also wrap the actual insertion within the create_hooks you have defined for this class.



410
411
412
413
414
415
416
417
418
419
420
421
# File 'lib/hyperactive/record.rb', line 410

def create
  @record_id ||= Digest::SHA1.hexdigest("#{HOST}:#{Time.new.to_f}:#{self.object_id}:#{rand(1 << 32)}").to_s
  @transaction ||= nil
  
  Hyperactive::Hooker.call_with_hooks(self, *self.class.create_hooks) do
    CAPTAIN[self.record_id, @transaction] = self
  end
  
  proxy = CAPTAIN[@record_id, @transaction]
  
  return proxy
end

#destroy!Object

Remove this instance from the database calling all the right hooks.

Freezes this instance after having deleted it.

Returns false without destroying anything if any of the @@pre_destroy_hooks returns false.

Returns true otherwise.



481
482
483
484
485
486
# File 'lib/hyperactive/record.rb', line 481

def destroy!
  Hyperactive::Hooker.call_with_hooks(self, *self.class.destroy_hooks) do
    CAPTAIN.delete(@record_id, @transaction)
    self.freeze
  end
end

#load_hook(&block) ⇒ Object

This will allow us to wrap any load of us from persistent storage in the @@load_hooks as long as the Archipelago::Hashish provider supports it. See Archipelago::Hashish::BerkeleyHashish for an example of Hashish providers that do this.



465
466
467
468
469
# File 'lib/hyperactive/record.rb', line 465

def load_hook(&block)
  Hyperactive::Hooker.call_with_hooks(self, *self.class.load_hooks) do
    yield
  end
end

#save_hook(old_value, &block) ⇒ Object

This will allow us to wrap any write of us to persistent storage in the @@save_hooks as long as the Archipelago::Hashish provider supports it. See Archipelago::Hashish::BerkeleyHashish for an example of Hashish providers that do this.



453
454
455
456
457
# File 'lib/hyperactive/record.rb', line 453

def save_hook(old_value, &block)
  Hyperactive::Hooker.call_with_hooks([old_value, self], *self.class.save_hooks) do
    yield
  end
end

#with_transaction(transaction, &block) ⇒ Object

Will execute block within a transaction.

What it does is just set the @transaction instance variable before calling the block, and unsetting it after.

This means that any classes that want to be transaction sensitive need to take heed regarding the @transaction instance variable.

For example, when creating new Record instances you may want to use get_instance_with_transaction(@transaction, *args) to ensure that the new instance exists within the same transaction as yourself.

See Hyperactive::List::Head and Hyperactive::Hash::Head for examples of this behaviour.



438
439
440
441
442
443
444
445
# File 'lib/hyperactive/record.rb', line 438

def with_transaction(transaction, &block)
  @transaction = transaction
  begin
    yield
  ensure
    @transaction = nil
  end
end