module CouchbaseId::Generator

NOTE

incr, decr, append, prepend == atomic

Public Class Methods

default_class_id_generator(overflow, count) click to toggle source
# File lib/couchbase-id/generator.rb, line 96
def self.default_class_id_generator(overflow, count)
    id = Radix.convert(overflow, B10, B64) + Radix.convert(count, B10, B64)
    "#{self.design_document}_#{CLUSTER_ID}-#{id}"
end
included(base) click to toggle source
# File lib/couchbase-id/generator.rb, line 80
def self.included(base)
    class << base
        attr_accessor :__overflow__
        attr_accessor :__class_id_generator__
    end


    base.class_eval do
        #
        # Best case we have 18446744073709551615 * 18446744073709551615 model entries for each database cluster
        #  and we can always change the cluster id if this limit is reached
        #
        define_model_callbacks :save, :create
        before_save :generate_id
        before_create :generate_id

        def self.default_class_id_generator(overflow, count)
            id = Radix.convert(overflow, B10, B64) + Radix.convert(count, B10, B64)
            "#{self.design_document}_#{CLUSTER_ID}-#{id}"
        end

        #
        # Override the default hashing function
        #
        def self.set_class_id_generator(callback = nil, &block)
            callback ||= block
            self.__class_id_generator__ = callback
        end

        #
        # Configure class level variables
        base.__overflow__ = nil
        base.__class_id_generator__ = method(:default_class_id_generator)
    end
end
set_class_id_generator(callback = nil, &block) click to toggle source

Override the default hashing function

# File lib/couchbase-id/generator.rb, line 104
def self.set_class_id_generator(callback = nil, &block)
    callback ||= block
    self.__class_id_generator__ = callback
end

Public Instance Methods

generate_id() click to toggle source

instance method

# File lib/couchbase-id/generator.rb, line 36
def generate_id
    if self.id.nil?                
        #
        # Generate the id (incrementing values as required)
        #
        overflow = self.class.__overflow__ ||= self.class.bucket.get("#{self.class.design_document}:#{CLUSTER_ID}:overflow", :quiet => true) # Don't error if not there
        count = self.class.bucket.incr("#{self.class.design_document}:#{CLUSTER_ID}:count", :create => true)     # This models current id count
        if count == 0 || overflow.nil?
            overflow ||= 0
            overflow += 1
            # We shouldn't need to worry about concurrency here due to the size of count
            # Would require ~18446744073709551615 concurrent writes
            self.class.bucket.set("#{self.class.design_document}:#{CLUSTER_ID}:overflow", overflow)
            self.class.__overflow__ = overflow
        end
        
        self.id = self.class.__class_id_generator__.call(overflow, count)
        
        
        #
        # So an existing id would only be present if:
        # => something crashed before incrementing the overflow
        # => this is another request was occurring before the overflow is incremented
        #
        # Basically only the overflow should be able to cause issues, we'll increment the count just to be sure
        # One would hope this code only ever runs under high load during an overflow event
        #
        while self.class.bucket.get(self.id, :quiet => true).present?
            # Set in-case we are here due to a crash (concurrency is not an issue)
            # Note we are not incrementing the @__overflow__ variable
            self.class.bucket.set("#{self.class.design_document}:#{CLUSTER_ID}:overflow", overflow + 1)
            count = self.class.bucket.incr("#{self.class.design_document}:#{CLUSTER_ID}:count")               # Increment just in case (attempt to avoid infinite loops)
            
            # Reset the overflow
            if self.class.__overflow__ == overflow
                self.class.__overflow__ = nil
            end

            # Generate the new id
            self.id = self.class.__class_id_generator__.call(overflow + 1, count)
        end
    end
end