# PersistedHash Code: module GxG module Database # class DetachedHash # public # ### OpenStruct Integration def as_structure() ::OpenStruct.new(self) end # def uuid() @uuid.clone end # def uuid=(the_uuid=nil) if GxG::valid_uuid?(the_uuid) @uuid = the_uuid.to_s.to_sym end end # def title() @title.clone end # def title=(the_title=nil) if the_title @title = the_title.to_s[0..255] self.increment_version end end # def version() @version.clone end # def version=(the_version=nil) if the_version.is_a?(::Numeric) @version = the_version.to_s("F").to_d end end # def element_version(key=nil) result = 0.0 if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end # if @property_links[(key.to_s.to_sym)] result = @property_links[(key.to_s.to_sym)][:record][:version] else result = 0.0 end # end result end # def set_element_version(element_key, the_version=nil) result = false if @property_links[(element_key)] if the_version.is_a?(::Numeric) @property_links[(element_key)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) result = true else log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for #{element_key.inspect} on Object #{@uuid.inspect}") end else log_warning("Attempted to set version with an invalid key #{element_key.inspect} on Object #{@uuid.inspect}") end result end # def format() @format.clone end # def format=(the_format=nil) if GxG::valid_uuid?(the_format) @format = the_format.to_s.to_sym end end # def parent() @parent end # def parent=(object=nil) if object.is_any?([::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray]) # Review : parent can only be set once -- is this best?? unless @parent @parent = object end end end # def write_reserved?() true end # def release_reservation() true end # def get_reservation() true end # def wait_for_reservation(timeout=nil) true end # ### Review: move to Database class as generic toolbox method at some point: # def self.import(import_record=nil) result = nil unless import_record.is_any?(::Hash, ::GxG::Database::DetachedHash) raise Exception, "Malformed import record passed." end # {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.clone, :format => @format.clone, :content => {}} import_db = [{:target => ::GxG::Database::DetachedHash.new, :record => import_record}] result = import_db[0][:target] while import_db.size > 0 do entry = import_db.shift case entry[:record][:type].to_s.to_sym when :element_hash entry[:target].uuid = entry[:record][:uuid].to_s.to_sym entry[:target].title = entry[:record][:title].to_s entry[:record][:content].keys.each do |the_key| case entry[:record][:content][(the_key)][:type].to_s.to_sym when :element_hash new_target = GxG::Database::DetachedHash.new entry[:target][(the_key)] = new_target import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} entry[:target][(the_key)].parent = (entry[:target]) when :element_array new_target = GxG::Database::DetachedArray.new entry[:target][(the_key)] = new_target import_db << {:target => new_target, :record => (entry[:record][:content][(the_key)])} entry[:target][(the_key)].parent = (entry[:target]) when :element_boolean, :element_integer, :element_float, :element_text entry[:target][(the_key)] = entry[:record][:content][(the_key)][:content] when :element_bigdecimal entry[:target][(the_key)] = ::BigDecimal.new(entry[:record][:content][(the_key)][:content].to_s) when :element_datetime entry[:target][(the_key)] = ::Chronic::parse(entry[:record][:content][(the_key)][:content].to_s) when :element_binary entry[:target][(the_key)] = ::GxG::ByteArray.new(entry[:record][:content][(the_key)][:content].to_s.decode64) end end entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) entry[:target].format = entry[:record][:format].to_s.to_sym when :element_array entry[:target].uuid = entry[:record][:uuid].to_s.to_sym entry[:target].title = entry[:record][:title].to_s entry[:record][:content].each_index do |indexer| case entry[:record][:content][(indexer)][:type].to_s.to_sym when :element_hash new_target = GxG::Database::DetachedHash.new entry[:target][(indexer)] = new_target import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} entry[:target][(indexer)].parent =(entry[:target]) when :element_array new_target = GxG::Database::DetachedArray.new entry[:target][(indexer)] = new_target import_db << {:target => new_target, :record => (entry[:record][:content][(indexer)])} entry[:target][(indexer)].parent = (entry[:target]) when :element_boolean, :element_integer, :element_float, :element_text entry[:target][(indexer)] = entry[:record][:content][(indexer)][:content] when :element_bigdecimal entry[:target][(indexer)] = ::BigDecimal.new(entry[:record][:content][(indexer)][:content].to_s) when :element_datetime entry[:target][(indexer)] = ::Chronic::parse(entry[:record][:content][(indexer)][:content].to_s) when :element_binary entry[:target][(indexer)] = ::GxG::ByteArray.new(entry[:record][:content][(indexer)][:content].to_s.decode64) end end entry[:target].version = ::BigDecimal.new(entry[:record][:version].to_s) entry[:target].constraint = entry[:record][:constraint].to_s.to_sym end end # result end # def self.create_from_format(the_format=nil) result = nil format_record = nil if ::GxG::valid_uuid?(the_format) format_record = ::GxG::DB[:roles][:formats].format_load({:uuid => the_format}) else format_record = ::GxG::DB[:roles][:formats].format_load({:ufs => the_format}) end if format_record # Review : ?? the ability to ingest a detached_object's data while retaining self's identity data. worth it ?? new_object = ::GxG::Database::DetachedHash.new new_object.uuid = ::GxG::uuid_generate.to_sym new_object.title = "Untitled #{new_object.uuid.to_s}" object_db = [{:destination => new_object, :source => format_record[:content]}] while object_db.size > 0 do pair = object_db.shift if pair pair[:source].search do |item, selector, container| if item.is_any?(::Array, ::Hash) if item.is_a?(::Hash) unless pair[:destination][(selector)].is_a?(::GxG::Database::DetachedHash) new_item = ::GxG::Database::DetachedHash.new new_item.uuid = ::GxG::uuid_generate.to_sym new_item.title = "Untitled #{new_object.uuid.to_s}" pair[:destination][(selector)] = new_item end object_db << [{:destination => pair[:destination][(selector)], :source => item}] else if item.is_a?(::Array) unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedArray) new_item = ::GxG::Database::DetachedArray.new new_item.uuid = ::GxG::uuid_generate.to_sym new_item.title = "Untitled #{new_object.uuid.to_s}" pair[:destination][(selector)] = new_item end object_db << [{:destination => pair[:destination][(selector)], :source => item}] end end # else pair[:destination][(selector)] = item end end end end new_object.format = format_record[:uuid].to_s.to_sym # unless ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] } ::GxG::DB_SAFETY.synchronize { GxG::DB[:formats][(format_record[:uuid].to_s.to_sym)] = format_record } end # result = new_object else result = ::GxG::Database::DetachedHash.new result.uuid = ::GxG::uuid_generate.to_sym result.title = "Untitled #{new_object.uuid.to_s}" end # result end # def self.create() ::GxG::Database::DetachedHash::create_from_format(nil) end # def ufs() if @format record = GxG::DB[:formats][(@format.to_s.to_sym)] if record record[:ufs].to_s.to_sym else "" end else "" end end # def increment_version() @version += 0.0001 end # def initialize() @active = true @uuid = nil @title = nil @version = ::BigDecimal.new("0.0") @format = nil @parent = nil @property_links = {} self end # def inspect() # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with DetachedHash. # TODO: make interative instead of re-entrant. last_key = @property_links.keys.last result = "{" @property_links.keys.each do |element_key| result = result + (":#{element_key.to_s} => " + @property_links[(element_key)][:content].inspect) # if @property_links[(element_key)][:loaded] == true # result = result + (":#{element_key.to_s} => " + @property_links[(element_key)][:content].inspect) # else # result = result + (":#{element_key.to_s} => (Not Loaded)") # end unless last_key == element_key result = result + ", " end end result = result + "}" result end # def alive?() @active end # def save() result = false if self.alive? begin # Review : expand to save to server result = true rescue Exception => the_error log_error({:error => the_error}) end end result end # def deactivate() ::GxG::DB_SAFETY.synchronize { ::GxG::DB[:cache].delete(@uuid) } @active = false true end # def destroy() result = false if self.alive? result = self.deactivate end result end # def size() @property_links.size end # def keys() @property_links.keys end # def []=(key=nil, value=nil) # Only works with Symbols. unless self.alive?() raise Exception, "Attempted to alter a defunct structure" end result = nil if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end property_key = key.to_s.to_sym # Review : rewrite ???? if value.is_any?(::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::BigDecimal, ::String, ::Time, ::GxG::ByteArray, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray, ::Hash, ::Array) unless self.write_reserved?() self.get_reservation() end if self.write_reserved?() # Further screen value provided: # ### Check provided DetachedHashes and DetachedArrays # ### Check provided Hashes and Arrays # ### Property Exists? if @property_links[(key.to_s.to_sym)] # set property to value operation = :set_value else if @format raise Exception, "Formatted - the structure cannot be altered" else # add property : value pair operation = :add_value end end # ### Prepare new value new_value = { :linkid => nil, :content => nil, :loaded => true, :state => 0, :record => { :parent_uuid => @uuid.to_s, :property => key.to_s, :ordinal => 0, :version => ::BigDecimal.new("0.0"), :element => "element_boolean", :element_boolean => -1, :element_integer => 0, :element_float => 0.0, :element_bigdecimal => ::BigDecimal.new("0.0"), :element_datetime => ::Time.now, :time_offset => 0.0, :time_prior => 0.0, :time_after => 0.0, :length => 0, :element_text => "", :element_binary => nil, :element_array => nil, :element_hash => nil } } # if value.is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) # ### Assimilate DetachedHash or DetachedArray new_value[:content] = value new_value[:loaded] = true new_value[:state] = new_value[:content].hash # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. new_value[:record][:version] = new_value[:content].version() if value.is_a?(::GxG::Database::DetachedHash) new_value[:record][:element] = "element_hash" new_value[:record][:element_hash] = value else new_value[:record][:element] = "element_array" new_value[:record][:element_array] = value end else # ### Persist Hashes and Arrays if value.is_any?(::Hash, ::Array) new_value[:content] = ::GxG::Database::iterative_detached_persist(value) new_value[:loaded] = true new_value[:state] = new_value[:content].hash # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. new_value[:record][:version] = new_value[:content].version() if value.is_a?(::Hash) new_value[:record][:element] = "element_hash" new_value[:record][:element_hash] = new_value[:content] else new_value[:record][:element] = "element_array" new_value[:record][:element_array] = new_value[:content] end else # ### Persist Base Element Values new_value[:record][:element] = ::GxG::Database::element_table_for_instance(value).to_s case new_value[:record][:element] when "element_boolean" if value.class == ::NilClass new_value[:record][:element_boolean] = -1 new_value[:content] = nil end if value.class == ::FalseClass new_value[:record][:element_boolean] = 0 new_value[:content] = false end if value.class == ::TrueClass new_value[:record][:element_boolean] = 1 new_value[:content] = true end when "element_integer" new_value[:record][:element_integer] = value new_value[:content] = value when "element_float" new_value[:record][:element_float] = value new_value[:content] = value when "element_bigdecimal" new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) new_value[:content] = new_value[:record][:element_bigdecimal] when "element_datetime" new_value[:record][:element_datetime] = value new_value[:content] = value when "element_text" new_value[:record][:element_text] = value new_value[:record][:length] = value.size new_value[:content] = value when "element_binary" # Note: be sure to keep version & length sync'd with linked binary element record if operation == :set_value new_value[:record][:version] = @property_links[(property_key)][:record][:version] new_value[:record][:element_binary] = @property_links[(property_key)][:record][:element_binary] end new_value[:record][:length] = value.size new_value[:content] = value else raise Exception, "Unable to map an element type for value: #{value.inspect}" end end end # ### Commit Changes case operation when :set_value # Note: Set In-memory value only, don't save unless you have to. if new_value[:content].is_a?(@property_links[(property_key)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@property_links[(property_key)][:content])) # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). new_value[:linkid] = @property_links[(property_key)][:linkid] if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) end new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] new_value[:loaded] = true new_value[:state] = @property_links[(property_key)][:state] # @property_links[(property_key)] = new_value # else # Other class value being substituted. if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = (((@property_links[(property_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) end new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] new_value[:loaded] = true # @property_links[(property_key)] = new_value end result = @property_links[(property_key)][:content] # when :add_value # Note: set in-memory value and save. if new_value[:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = ::BigDecimal.new("0.0") end new_value[:record][:ordinal] = @property_links.keys.size new_value[:loaded] = true # @property_links[(property_key)] = new_value # Review : is this wise to do this here?? # property_write(property_key) # refresh_ordinals end # ### Handle coordination between persisted objects: if @property_links[(property_key)][:content].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) @property_links[(property_key)][:content].parent = (self) end # self.increment_version() result = @property_links[(property_key)][:content] # else raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" end else raise Exception, "The value is not persistable." end else raise Exception, "You must provide a property key in the form of a Symbol." end result end # def [](key=nil) result = nil # if key exists if self.alive?() if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end property_key = key.to_s.to_sym if @property_links[(property_key)] result = @property_links[(property_key)][:content] end else raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" end else raise Exception, "Attempted to access a defunct structure" end result end # def include?(the_key) @property_links.keys.include?(the_key) end # def delete(key=nil) result = nil if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end if @format raise Exception, "Formatted - the structure cannot be altered" else if @property_links[(key)] result = @property_links[(key)][:content] @property_links.delete(key) # Review : send a 'delete_element' to server with the uuid of the DetachedHash and the key name. # ### -OR- develop @ the server: an 'overwrite' push call that will trim missing elements from stored copy. end end else raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" end # result end # def unpersist() result = {} if self.alive? # export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] children_of = Proc.new do |the_parent=nil| list = [] export_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # Build up export_db: self.search do |the_value, the_selector, the_container| if the_value.is_a?(::GxG::Database::DetachedHash) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} else if the_value.is_a?(::GxG::Database::DetachedArray) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} end end end # Collect children export content: link_db =[(export_db[0])] while link_db.size > 0 do entry = link_db.shift children_of.call(entry[:object]).each do |the_child| entry[:record][(the_child[:parent_selector])] = the_child[:record] if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) link_db << the_child end end end # end result end # def export(options={:exclude_file_segments=>false}) exclude_file_segments = (options[:exclude_file_segments] || false) if options[:clone] == true # Review : why are cloned objects unformatted? sync issues?? result = {:type => :element_hash, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => @version.to_s("F"), :content => {}} else result = {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => @version.to_s("F"), :format => @format.clone, :content => {}} end export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] children_of = Proc.new do |the_parent=nil| list = [] export_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # Build up export_db: self.search do |the_value, the_selector, the_container| if the_value.is_a?(::GxG::Database::DetachedHash) if options[:clone] == true the_uuid = GxG::uuid_generate.to_s.to_sym else the_uuid = the_value.uuid().clone end if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :format => the_value.format().clone, :content => {}}} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :format => the_value.format().clone, :content => {}}} end end if the_value.is_a?(::GxG::Database::DetachedArray) if options[:clone] == true the_uuid = GxG::uuid_generate.to_s.to_sym else the_uuid = the_value.uuid().clone end if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :constraint => the_value.constraint().clone, :content => []}} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => the_value.version().to_s("F"), :constraint => the_value.constraint().clone, :content => []}} end end if the_value.is_any?(::NilClass, ::TrueClass, ::FalseClass, ::Integer, ::Float, ::BigDecimal, ::String, ::Time, ::GxG::ByteArray) data_type = GxG::Database::element_table_for_instance(the_value) # case data_type when :element_bigdecimal data = the_value.to_s when :element_datetime data = the_value.iso8601.to_s when :element_binary data = the_value.to_s.encode64 when :element_text data = the_value.to_s else data = the_value end # export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (the_container.version(the_selector) || ::BigDecimal.new("0.0")).to_s("F"), :content => data}} end end # Collect children export content: link_db = [(export_db[0])] while link_db.size > 0 do entry = link_db.shift children_of.call(entry[:object]).each do |the_child| entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] if the_child[:object].is_any?(::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) link_db << the_child end end end # result end # def export_package(options={:exclude_file_segments=>false}) result = {:formats => {}, :records => []} object_record = self.export(options) object_record.search do |item,selector,container| if selector == :format || selector == :constraint if item.to_s.size > 0 format_uuid = item unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) format_sample = ::GxG::DB[:roles][:formats].format_load({:uuid => format_uuid.to_s.to_sym}) format_sample[:content] = format_sample[:content].gxg_export() result[:formats][(format_uuid.to_s.to_sym)] = format_sample end end end end result[:records] << object_record result end # # def each_pair(&block) collection = {} @property_links.keys.each do |key| collection[(key)] = (self[(key)]) end if block.respond_to?(:call) collection.to_enum(:each_pair).each do |key,value| block.call(key,value) end else collection.to_enum(:each_pair) end end # def iterative(options={:include_inactive => true}, &block) result = [] visit = Proc.new do |the_node=nil, accumulator=[]| node_stack = [] if the_node node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) while (node_stack.size > 0) do a_node = node_stack.shift # if a_node[:object].is_a?(::GxG::Database::DetachedHash) if a_node[:object].alive? a_node[:object].each_pair do |the_key, the_value| node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) end else if options[:include_inactive] accumulator << a_node end end end if a_node[:object].is_a?(::GxG::Database::DetachedArray) if a_node[:object].alive? a_node[:object].each_with_index do |the_value, the_index| node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) end else if options[:include_inactive] accumulator << a_node end end end end end accumulator end # children_of = Proc.new do |the_db=[], the_parent=nil| list = [] the_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # begin database = visit.call(self,[]) link_db = children_of.call(database, self) if block.respond_to?(:call) while (link_db.size > 0) do entry = link_db.shift unless entry[:object].object_id == self.object_id # calls with parameters: the_value, the_key/the_index (the_selector), the_container raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) if raw_result result << raw_result end end if entry[:object].object_id != nil.object_id children = children_of.call(database, entry[:object]) children.each do |child| link_db << child end end end end # rescue Exception => the_error log_error({:error => the_error, :parameters => {}}) end # result end # def process!(options={:include_inactive => true}, &block) self.iterative(options, &block) self end # def search(options={:include_inactive => true}, &block) results = [] if block.respond_to?(:call) results = self.iterative(options, &block) end results end # def paths_to(the_object=nil,base_path="") # new idea here: search_results = [] unless base_path[0] == "/" base_path = ("/" << base_path) end if base_path.size > 1 path_stack = base_path.split("/")[1..-1].reverse else path_stack = [] end origin = self.get_at_path(base_path) container_stack = [{:selector => nil, :container => origin}] find_container = Proc.new do |the_container| result = nil container_stack.each_with_index do |entry, index| if entry[:container] == the_container result = entry break end end result end last_container = origin found = false # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) origin.process! do |the_value, selector, container| if last_container.object_id != container.object_id container_record = find_container.call(container) if container_record path_stack = container_record[:prefix].split("/").reverse if path_stack.size == 0 path_stack << "" end end last_container = container end safe_key = selector.to_s # xxx # if selector.is_a?(Symbol) # safe_key = (":" + selector.to_s) # else # safe_key = selector.to_s # end # xxx safe_key.gsub!("/","%2f") path_stack.unshift(safe_key) # compare the_value found = false if (the_value == the_object) found = true end if found search_results << ("/" << path_stack.reverse.join("/")) end # if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::DetachedHash, ::GxG::Database::DetachedArray) container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) end path_stack.shift # nil end else search_results << ("/" << path_stack.join("/")) end search_results end # def get_at_path(the_path="/") result = nil if the_path == "/" result = self else object_stack = [(self)] path_stack = the_path.split("/") path_stack.to_enum.each do |path_element| element = nil if path_element.size > 0 if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 element = path_element.to_i else element = path_element element.gsub!("%2f","/") element = element.to_sym end end if element result = object_stack.first[(element)] if result.is_a?(NilClass) break else object_stack.unshift(result) end else # ignore double slashes? '//' # break end end end result end # def set_at_path(the_path="/",the_value=nil) result = nil if the_path != "/" container = self.get_at_path(::File::dirname(the_path)) if container raw_selector = ::File::basename(the_path) selector = nil if raw_selector.size > 0 if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 selector = raw_selector.to_i else selector = raw_selector selector.gsub!("%2f","/") selector = selector.to_sym end end if selector container[(selector)] = the_value result = container[(selector)] else # ignore double slashes? '//' # break end # end end result end # def merge!(input_object=nil, options={}) result = self if input_object.is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) # if @format # merge only keys common to both input_object.keys.each do |import_key| if result.keys.include?(import_key) result[(import_key)] = input_object[(import_key)] end end else input_object.keys.each do |import_key| result[(import_key)] = input_object[(import_key)] end end # end result end # def merge(input_object=nil, options={}) result = self if input_object.is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) result = ::GxG::Database::DetachedHash::iterative_detached_persist(self.unpersist) # if @format # merge only keys common to both input_object.keys.each do |import_key| if result.keys.include?(import_key) result[(import_key)] = input_object[(import_key)] end end result.format = @format else input_object.keys.each do |import_key| result[(import_key)] = input_object[(import_key)] end end # end result end # end # class PersistedHash # private # def property_destroy(the_key=nil) # Destroy database property record, but leave in-memory property intact - for now. (preserves key-ordinal position) result = false if the_key if @property_links[(key.to_s.to_sym)] property = @property_links[(key.to_s.to_sym)] # ### Delete sub-object linked to. the_table = nil the_uuid = nil if property[:record][:element_text_uuid].size > 0 the_table = :element_text the_uuid = property[:record][:element_text_uuid] end if property[:record][:element_binary_uuid].size > 0 the_table = :element_binary the_uuid = property[:record][:element_binary_uuid] end if property[:record][:element_array_uuid].size > 0 the_table = :element_array the_uuid = property[:record][:element_array_uuid] end if property[:record][:element_hash_uuid].size > 0 the_table = :element_hash the_uuid = property[:record][:element_hash_uuid] end case the_table when :element_text the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_uuid}).first if the_temp_record @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) end when :element_binary the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_uuid}).first if the_temp_record @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) end when :element_array, :element_hash @db_address[:database].destroy_by_uuid(@credential, the_uuid) end # ### Delete property record. @db_address[:database].connector()[:hash_properties].filter({:dbid => property[:linkid]}).delete result = true end end result end # Note: property_read handled by load_property_links def property_write(the_key=nil) result = false if the_key if @property_links[(the_key.to_s.to_sym)] the_property = @property_links[(the_key.to_s.to_sym)] # Save out text and binary pages if needed. if the_property[:content].is_a?(::String) if the_property[:content].size > 256 unless the_property[:record][:element_text_uuid].size > 0 # create text header the_uuid = ::GxG::uuid_generate while @db_address[:database].connector()[:element_text].filter({:uuid => the_uuid.to_s}).count > 0 do the_uuid = ::GxG::uuid_generate end the_property[:record][:length] = the_property[:content].size the_dbid = @db_address[:database].connector()[:element_text].insert({:uuid => the_uuid.to_s, :version => the_property[:record][:version], :length => the_property[:record][:length]}) # the_property[:record][:element_text_uuid] = the_uuid end end else if the_property[:content].is_a?(::GxG::ByteArray) unless the_property[:record][:element_binary_uuid].size > 0 # create binary header the_uuid = ::GxG::uuid_generate while @db_address[:database].connector()[:element_binary].filter({:uuid => the_uuid.to_s}).count > 0 do the_uuid = ::GxG::uuid_generate end the_property[:record][:length] = the_property[:content].size # format field?? the_dbid = @db_address[:database].connector()[:element_binary].insert({:uuid => the_uuid.to_s, :version => the_property[:record][:version], :length => the_property[:record][:length]}) # the_property[:record][:element_binary_uuid] = the_uuid end end end # Store text pages if the_property[:record][:element_text_uuid].size > 0 header_record = @db_address[:database].connector()[:element_text].filter({:uuid => the_property[:record][:element_text_uuid].to_s}).first if header_record old_segments = [] @db_address[:database].connector()[:text_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| old_segments << entry[:dbid] end if the_property[:content].size == 0 # delete all text_pages if count > 0 if old_segments.size > 0 @db_address[:database].connector()[:text_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete end else new_segments = ::GxG::apportioned_ranges(the_property[:content].size, 4096) if old_segments.size > new_segments.size the_count = old_segments.size else the_count = new_segments.size end manifest = [] (0..(the_count - 1)).each do |the_ordinal| if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] #overwrite with new segment range manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} else if new_segments[(the_ordinal)] # create new segment at this ordinal manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} else # delete old segment manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} end end end # manifest.each do |action| case action[:operation] when :overwrite @db_address[:database].element_update(:text_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) when :create new_dbid = @db_address[:database].element_create(:text_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) when :delete @db_address[:database].element_destroy(@credential,:text_page,action[:dbid]) # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete end end # end end else if the_property[:record][:element_binary_uuid].size > 0 header_record = @db_address[:database].connector()[:element_binary].filter({:uuid => the_property[:record][:element_binary_uuid].to_s}).first if header_record old_segments = [] @db_address[:database].connector()[:binary_page].filter({:parent_uuid => header_record[:uuid].to_s}).order(:ordinal).each do |entry| old_segments << entry[:dbid] end if the_property[:content].size == 0 # delete all text_pages if count > 0 if old_segments.size > 0 @db_address[:database].connector()[:binary_page].filter({:parent_uuid => (header_record[:uuid])}).order(:ordinal).delete end else new_segments = ::GxG::apportioned_ranges(the_property[:content].size, 65536) if old_segments.size > new_segments.size the_count = old_segments.size else the_count = new_segments.size end manifest = [] (0..(the_count - 1)).each do |the_ordinal| if new_segments[(the_ordinal)] and old_segments[(the_ordinal)] #overwrite with new segment range manifest << {:operation => :overwrite, :dbid => (old_segments[(the_ordinal)]), :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} else if new_segments[(the_ordinal)] # create new segment at this ordinal manifest << {:operation => :create, :ordinal => (the_ordinal), :portion => (new_segments[(the_ordinal)])} else # delete old segment manifest << {:operation => :delete, :dbid => (old_segments[(the_ordinal)])} end end end # manifest.each do |action| case action[:operation] when :overwrite @db_address[:database].element_update(:binary_page,{:dbid => action[:dbid]},{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).update({:ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) when :create new_dbid = @db_address[:database].element_create(:binary_page,{:parent_uuid => header_record[:uuid], :ordinal => action[:ordinal], :length => the_property[:content][(action[:portion])].size, :content => the_property[:content][(action[:portion])]}) # @db_address[:database].connector()[:text_page].insert({:parent_uuid => (record[:uuid]), :ordinal => (action[:ordinal]), :length => (@content[(action[:portion])].size), :content => (@content[(action[:portion])])}) when :delete @db_address[:database].element_destroy(@credential,:binary_page,action[:dbid]) # @db_address[:database].connector()[:text_page].filter({:dbid => (action[:dbid])}).delete end end # end end end end # ### Create new property record if missing: otherwise just update it. if the_property[:linkid] == nil || the_property[:linkid] == 0 the_property[:linkid] = @db_address[:database].connector()[:hash_properties].insert(the_property[:record]) else @db_address[:database].connector()[:hash_properties].filter({:dbid => the_property[:linkid]}).update(the_property[:record]) end # the_property[:state] = the_property[:content].hash result = true end end result end # def load_property_links # result = true begin if @db_address if @db_address[:database].is_a?(::GxG::Database::Database) if @db_address[:database].open?() new_links = {} the_records = @db_address[:database].connector()[:hash_properties].filter({:parent_uuid => @uuid.to_s}).order(:ordinal) the_records.each do |entry| # :property_name => {:linkid => 0, :content => nil, :loaded => false, :state => 0, :record => {}} the_key = entry[:property].to_sym new_links[(the_key)] = {:linkid => entry.delete(:dbid), :content => nil, :loaded => false, :state => 0, :record => entry} # Load indicated :content case new_links[(the_key)][:record][:element] when "element_boolean" case new_links[(the_key)][:record][:element_boolean] when -1 new_links[(the_key)][:content] = nil new_links[(the_key)][:loaded] = true when 0 new_links[(the_key)][:content] = false new_links[(the_key)][:loaded] = true when 1 new_links[(the_key)][:content] = true new_links[(the_key)][:loaded] = true end when "element_integer" new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_integer] new_links[(the_key)][:loaded] = true when "element_float" new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_float] new_links[(the_key)][:loaded] = true when "element_bigdecimal" new_links[(the_key)][:content] = ::BigDecimal.new(new_links[(the_key)][:record][:element_bigdecimal].to_s) new_links[(the_key)][:loaded] = true when "element_datetime" new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_datetime] if new_links[(the_key)][:content].is_a?(::Time) new_links[(the_key)][:content] = new_links[(the_key)][:content].to_datetime() end new_links[(the_key)][:loaded] = true when "element_text" if new_links[(the_key)][:record][:element_text_uuid].size > 0 # Load full text content from pages new_links[(the_key)][:content] = "" @db_address[:database].connector()[:text_page].filter({:parent_uuid => new_links[(the_key)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| new_links[(the_key)][:content] << text_page[:content] end else new_links[(the_key)][:content] = new_links[(the_key)][:record][:element_text] end new_links[(the_key)][:loaded] = true when "element_binary" # Load binary data new_links[(the_key)][:content] = ::GxG::ByteArray.new new_links[(the_key)][:loaded] = false # Review : support on-read segment loading and property unloading. (thinking of large binary files here) when "element_array" # Load PersistedArray new_links[(the_key)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(the_key)][:record][:element_array_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) if new_links[(the_key)][:content].is_a?(::GxG::Database::PersistedArray) new_links[(the_key)][:loaded] = true new_links[(the_key)][:state] = new_links[(the_key)][:content].hash new_links[(the_key)][:content].set_parent(self) else new_links[(the_key)][:loaded] = false end when "element_hash" # Load PersistedHash # Review : HOW to keep the version info updated when the sub-object changes happen?? objects have ZERO knowledge of thier parent properties. # Maybe: @ object --> @parent.update_property_version(self,version) : it can match on :content and property_write(key) of found item. new_links[(the_key)][:content] = @db_address[:database].retrieve_by_uuid(new_links[(the_key)][:record][:element_hash_uuid].to_s.to_sym, @credential, (@reservation || @delegate)) if new_links[(the_key)][:content].is_a?(::GxG::Database::PersistedHash) new_links[(the_key)][:loaded] = true new_links[(the_key)][:state] = new_links[(the_key)][:content].hash new_links[(the_key)][:content].set_parent(self) else new_links[(the_key)][:loaded] = false end end end # xxx # :property_name => {:linkid => 0, :table => :unspecified, :dbid => 0, :ordinal => 0, :element => nil} # link_record => {:dbid => 0, :parent_uuid => "", :property => "", :ordinal => 0, :element => 0, :elementid => 0} # the_links = @db_address[:database].connector()[:hash_links].filter({:parent_uuid => @uuid.to_s}).order(:ordinal) # the_links.each do |entry| # # # the_key = entry[:property].to_sym # new_links[(the_key)] = {:linkid => (entry[:dbid])} # new_links[(the_key)][:table] = ::GxG::Database::Database::element_table_by_index(entry[:element]) # new_links[(the_key)][:dbid] = entry[:elementid] # new_links[(the_key)][:ordinal] = entry[:ordinal] # # TODO: load element based upon setting in Database: :immediate or :lazy (default) # new_links[(the_key)][:element] = nil # if @property_links[(the_key)] # if @property_links[(the_key)][:element] # new_links[(the_key)][:element] = @property_links[(the_key)][:element] # end # end # end # xxx @property_links = new_links else raise Exception, "Database not available" end else raise Exception, "Invalid Database object" end else # Deactivated result = false end rescue Exception => the_error result = false log_error({:error => the_error}) end result end # def refresh_ordinals() if self.alive? if @db_address[:database].open? @property_links.keys.each_with_index do |the_key, ordinal| @property_links[(the_key)][:record][:ordinal] = ordinal @db_address[:database].connector()[:hash_properties].filter({:dbid => @property_links[(the_key)][:linkid]}).update({:ordinal => (ordinal)}) end end end end # public # def assimilate_detached(detached_object=nil) result = false if detached_object.is_a?(::GxG::Database::DetachedHash) # Review : ?? the ability to ingest a detached_object's data while retaining self's identity data. worth it ?? object_db = [{:destination => self, :source => detached_object}] while object_db.size > 0 do pair = object_db.shift if pair pair[:source].search do |item, selector, container| if item.is_any?(::GxG::Database::DetachedArray, ::GxG::Database::DetachedHash) if item.is_a?(::GxG::Database::DetachedHash) unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedHash) pair[:destination][(selector)] = @db_address[:database].try_persist({}, @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_format => item.format}) end object_db << [{:destination => pair[:destination][(selector)], :source => item}] else if item.is_a?(::GxG::Database::DetachedArray) unless pair[:destination][(selector)].is_a?(::GxG::Database::PersistedArray) pair[:destination][(selector)] = @db_address[:database].try_persist([], @credential, {:with_uuid => item.uuid.to_s, :with_title => item.title.to_s, :with_version => item.version, :with_constraint => item.constraint}) end object_db << [{:destination => pair[:destination][(selector)], :source => item}] end end # else pair[:destination][(selector)] = item end end end end # result = true end result end # def detach() ::GxG::Database::detached_import(self.export()) end # ### OpenStruct Integration def as_structure() ::OpenStruct.new(self) end # Review : favor the following more Ruby-like accessors over the set_xxx style methods, eliminate older duplicate methods. def uuid() @uuid.clone end # def uuid=(the_uuid=nil) if GxG::valid_uuid?(the_uuid) @uuid = the_uuid.to_s.to_sym end end # def title() @title.clone end # def title=(the_title=nil) if the_title @title = the_title.to_s[0..255] self.increment_version end end # def version() @version.clone end # def version=(the_version=nil) if the_version.is_a?(::Numeric) @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) end end # def element_version(key=nil) result = 0.0 if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end # if @property_links[(key.to_s.to_sym)] result = @property_links[(key.to_s.to_sym)][:record][:version] else result = 0.0 end # end result end # def set_element_version(element_key, the_version=nil) result = false if @property_links[(element_key)] if the_version.is_a?(::Numeric) @property_links[(element_key)][:record][:version] = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) result = true else log_warning("Attempted to set version to an invalid version value #{the_version.inspect} for #{element_key.inspect} on Object #{@uuid.inspect}") end else log_warning("Attempted to set version with an invalid key #{element_key.inspect} on Object #{@uuid.inspect}") end result end # def format() @format.clone end # def format=(the_format=nil) if GxG::valid_uuid?(the_format) @format = the_format.to_s.to_sym end end # def parent() @parent end # def parent=(object=nil) if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) # Review : parent can only be set once -- is this best?? unless @parent @parent = object end end end # ### Review: move to Database class as generic toolbox method at some point: # def get_permissions() @permission.clone end # def set_permissions(credential=nil, the_permission=nil) if self.alive? if credential.is_a?(::Hash) && the_permission.is_a?(::Hash) if credential[:user] if credential[:user] == "public" || credential[:user] == "Public" credential = "00000000-0000-4000-0000-000000000000" else credential = (@db_address[:database].user_fetch({:user_id => credential[:user].to_s}) || {})[:uuid] end if credential credential = credential.to_s.to_sym end else if credential[:group] credential = @db_address[:database].group_credential(credential) else credential = nil end end else if credential.is_a?(::Hash) && the_permission == nil the_permission = credential credential = @credential else unless credential credential = @credential end end end if the_permission.is_a?(::Hash) && ::GxG::valid_uuid?(credential) manifest = [] structures = [{:table => @db_address[:table], :dbid => @db_address[:dbid]}] while structures.size > 0 do entry = structures[0] @db_address[:database].element_manifest(entry[:table],entry[:dbid]).each do |record| if [:element_hash, :element_array].include?(record[:table]) unless structures.include?(record) structures << record end else # Review - only set/get/check permissions on persisted arrays/hashes # unless manifest.include?(record) # manifest << record # end end end manifest << structures.shift end manifest.each do |record| @db_address[:database].assign_element_permission(record[:table], record[:dbid], credential, the_permission) end # true else log_error({:error => Exception.new("You MUST provide a valid permissions Hash."), :parameters => {:credential => credential, :permission => the_permission}}) false end end end # def reservation() if self.alive? if @reservation.is_any?(::String, ::Symbol) @reservation.clone end end end # def write_permission?() @permission[:write] end # def write_reserved?() if @reservation or @delegate true else false end end # def release_reservation() if self.alive?() if @reservation @db_address[:database].release_element_locks(@reservation) @reservation = nil end if @delegate @delegate = nil end # release write-locks of all sub-items @property_links.keys.each do |the_key| if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) @property_links[(the_key)][:content].release_reservation end end end true end # def get_reservation() result = false # Review : Experimental design change - deprecate @delegate use, require entire structure (and sub-structures) to get solid @reservation (all of them) or this fails. # Note : keep your structures small as this opens the door for stack-depth exceptions if it is nested too deep. (say 256 or less layers deep??) if @reservation result = true else if self.alive? if @permission[:write] clean_up_list = [] # Review : efficiency - write locks only apply to PersistedHashes and PersistedArrays so do we need a full manifest?? the_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) if the_reservation @reservation = the_reservation result = true # # @property_links.keys.each do |the_key| # if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) # clean_up_list << @property_links[(the_key)][:content] # unless @property_links[(the_key)][:content].get_reservation() # @db_address[:database].release_element_locks(the_reservation) # the_reservation = nil # break # end # end # end # end # if the_reservation # result = true # else # clean_up_list.each do |the_object| # the_object.release_reservation # end # end end end end # xxx # if (@reservation || @delegate) # result = true # else # if self.alive? # if @permission[:write] # @reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :write) # if @reservation # @property_links.keys.each do |the_key| # if @property_links[(the_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) # @property_links[(the_key)][:content].set_delegate((@reservation || @delegate)) # end # end # result = true # end # # # end # end # end # xxx result end # def wait_for_reservation(timeout=nil) result = true the_timeout = Time.now.to_f + (timeout || 30.0).to_f until self.get_reservation() == true do sleep 1.0 if Time.now.to_f >= the_timeout result = false break end end result end # def get_delegate() if self.alive? if @delegate.is_any?(::String, ::Symbol) @delegate.clone end end end # def set_delegate(the_delegate=nil) if self.alive? if the_delegate.is_any?([::String, ::Symbol]) @delegate = the_delegate.to_sym end end end # def set_format(format_uuid=nil) if self.alive? unless @format if ::GxG::valid_uuid?(format_uuid) @format = format_uuid.to_sym @db_address[:database].connector()[:element_hash].filter({:dbid => (@db_address[:dbid])}).update({:format => (@format.to_s)}) end end end end # def format() @format.clone end # def ufs() if @format record = @db_address[:database].format_load({:uuid => @format.to_s}) if record record[:ufs] else "" end else "" end end # def version(key=nil) result = @version if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end # if @property_links[(key.to_s.to_sym)] result = @property_links[(key.to_s.to_sym)][:record][:version] else result = 0.0 end # end result end # def version=(the_version=nil) if the_version.is_a?(::Numeric) @version = ::BigDecimal.new(the_version.to_s) end end # # def parent() @parent end # def set_parent(object=nil) if self.alive? if object.is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) unless @parent @parent = object end end end end # def refresh_links() if self.alive?() unless @db_address[:database].element_in_use?(@db_address[:table], @db_address[:dbid]) @parent = nil end end end # def increment_version() if self.alive? if @db_address[:database].open? if self.write_reserved?() @version += 0.0001 end end end end # def save_version() @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:version => @version}) end # def set_version(the_version=0.0) if self.write_reserved?() @version = (((the_version.to_f) * 10000.0).to_i.to_f / 10000.0) self.save_version end end # def set_title(the_title=nil) if self.alive? if @db_address[:database].open? if self.write_reserved?() if the_title @title = the_title.to_s[0..255] @db_address[:database].connector()[:element_hash].filter({:dbid => @db_address[:dbid]}).update({:title => @title}) self.increment_version end end end end end # def initialize(settings = {}, options={}) # mount element: {:database => , :uuid => , :credential => , :delegate => nil/, :parent => nil/} # create element: {:database => , :credential => , :delegate => nil/, :parent => nil/} # if settings.is_a?(::Hash) unless ::GxG::valid_uuid?(settings[:credential]) raise ArgumentError, "You must supply a valid credential UUID (37 character limit) as a String or Symbol" end @quick = settings[:quick] @credential = settings[:credential].to_sym @reservation = nil @delegate = settings[:delegate] if settings[:parent].is_any?([::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray]) @parent = settings[:parent] else @parent = nil end if settings[:database].is_a?(::GxG::Database::Database) if settings[:database].open?() # note - rewrite. if create then as subset, eventually will always mount if all checks out. the_uuid = settings[:uuid] if the_uuid.is_a?(::Symbol) the_uuid = the_uuid.to_s end # TODO: validate uuid pattern if settings[:title].is_a?(::String) the_title = settings[:title] else if options[:with_title] the_title = options[:with_title] else the_title = ("Untitled PersistedHash " << (settings[:database].connector()[:element_hash].count.to_s)) end end @format = settings[:format] unless @format if options[:with_format] @format = options[:with_format] end end unless the_uuid # without specifying a uuid, the implied intent is to create a new PersistedHash if ::GxG::valid_uuid?(options[:with_uuid]) the_uuid = options[:with_uuid].to_s else the_uuid = ::GxG::uuid_generate() while ((settings[:database].connector()[:element_hash].filter({:uuid => the_uuid}).count > 0) or (settings[:database].connector()[:element_array].filter({:uuid => the_uuid}).count > 0)) the_uuid = ::GxG::uuid_generate() end end # creat blank hash record new_dbid = settings[:database].connector()[:element_hash].insert({:uuid => (the_uuid), :title => (the_title), :format => (@format.to_s), :version => (options[:with_version] || BigDecimal.new("0.0"))}) # @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (new_dbid)} if @parent @permission = @parent.get_permissions() # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => settings[:parent], :destination => self}) # settings[:database].extend_element_permissions(settings[:parent].db_address()[:table], settings[:parent].db_address()[:dbid], :element_hash, new_dbid) else # create raw permissions for this element @permission = {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true} # settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, {:execute => false, :rename => true, :move => true, :destroy => true, :create => true, :write => true, :read => true}) # settings[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => self}) end settings[:database].assign_element_permission(:element_hash, new_dbid, @credential, @permission) end # mount according to uuid if the_uuid # mount record = settings[:database].connector()[:element_hash].filter({:uuid => (the_uuid)}).first if record @uuid = the_uuid.to_sym @title = record[:title].to_s @version = record[:version] @db_address = {:database => (settings[:database]), :table => :element_hash, :dbid => (record[:dbid])} if record[:format].to_s.size > 0 @format = record[:format].to_sym end @property_links = {} # unless @permission @permission = @db_address[:database].effective_element_permission(@db_address[:table], @db_address[:dbid], @credential) end # @read_reservation = @db_address[:database].reserve_element_locks(@credential, (@db_address[:database].element_manifest(@db_address[:table], @db_address[:dbid])), :read) # load_property_links # else raise Exception, "Unable to mount the PersistedHash record" end else raise Exception, "Unable to mount the PersistedHash record" end # else raise Exception, "Database not available for storage" end # else raise Exception, "Invalid Database object" end # else raise ArgumentError, "You must supply a hash of details" end # end # def inspect() # FORNOW: make re-entrant (yes, I know!) Fortunately, circular links are impossible with PersistedHash. # TODO: make interative instead of re-entrant. result = "(Not Loaded)" if @db_address if @db_address[:database].element_byte_size(@db_address[:table], @db_address[:dbid]) <= 10485760 # smaller than 10MB inspection_db = [] last_key = @property_links.keys.last result = "{" @property_links.keys.each do |element_key| if @property_links[(element_key)][:loaded] == true result << (":#{element_key.to_s} => " << @property_links[(element_key)][:content].inspect) else result << (":#{element_key.to_s} => (Not Loaded)") end unless last_key == element_key result << ", " end end result << "}" else result = "(Too Damned Big)" end # # # end result end # def alive?() if @db_address true else false end end # def db_address() @db_address end # def deactivate() if self.alive?() @property_links.keys.each do |key| element = @property_links[(key)][:content] if element.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) containers = [(element)] element.search({:include_inactive => false}) do |the_value, the_selector, the_container| if the_value.alive? && the_value.respond_to?(:deactivate) the_value.deactivate end if the_container.alive? unless containers.include?(the_container) containers.unshift(the_container) end end nil end containers.each do |the_container| the_container.deactivate end end end if self.write_reserved?() self.release_reservation() end @property_links = {} @db_address = nil end true end # def save() result = false if self.alive? begin if @db_address[:database].open?() @property_links.keys.each do |the_key| # Note: only save property data and header if: a) data is still loaded and b) state has changed. if @property_links[(the_key)][:loaded] == true if @property_links[(the_key)][:state] != @property_links[(the_key)][:content].hash @property_links[(the_key)][:record][:version] = (((@property_links[(the_key)][:record][:version].to_f + 0.0001) * 10000.0).to_i.to_f / 10000.0) property_write(the_key) end end # end else raise Exception, "Database not available" end self.save_version result = true rescue Exception => the_error log_error({:error => the_error}) end end result end # def destroy() result = false if self.alive? begin unless self.write_reserved?() raise Exception, "You do not have a write-lock for this element" end unless @permission[:destroy] raise Exception, "You do not have permission to destory this element" end if @db_address[:database].open?() if @db_address[:database].structure_attached?(@uuid) raise Exception, "Element is still linked to a structure. Use .delete().destroy or .delete_at().destroy" else if self.write_reserved?() address = @db_address self.deactivate address[:database].element_destroy(@credential, address[:table],address[:dbid]) # Review : consider either emptying the db trash now, or adding a flag to manifest records to prevent future loading. result = true else raise Exception, "You do not have sufficient privileges to make this change" end end else raise Exception, "Database not available" end rescue Exception => the_error log_error({:error => the_error}) end end result end # def size() @property_links.size end # def structure_attached?() @db_address[:database].structure_attached?(@uuid.to_s) end def structure_detach() @db_address[:database].structure_detach(@uuid.to_s) end # def keys() @property_links.keys end # def []=(key=nil, value=nil) # Only works with Symbols. unless self.alive?() raise Exception, "Attempted to alter a defunct structure" end unless @db_address[:database].open?() raise Exception, "Database not available" end result = nil if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end property_key = key.to_s.to_sym # Review : rewrite ???? if value.is_any?(::GxG::Database::Database::valid_field_classes()) || value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) unless self.write_reserved?() self.get_reservation() end if self.write_reserved?() # Further screen value provided: # ### Check provided PersistedHashes and PersistedArrays if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) unless value.db_address() raise Exception, "The structure you are attaching is Deactivated (not supported)." end if value.structure_attached?() # Review : experimental - auto-detach from prior structural relationship and reassign here. # raise Exception, "Cross linked structures not supported" unless value.write_reserved?() value.get_reservation() end if value.write_reserved?() value.structure_detach else raise Exception, "Unable to secure write-reservation for this structural change." end end value.search do |the_value, the_selector, the_container| if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) unless the_value.db_address() raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." end end end end # ### Check provided Hashes and Arrays if value.is_any?(::Hash, ::Array) value.search do |the_value, the_selector, the_container| if the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) unless the_value.db_address() raise Exception, "The structure you are attaching has a Deactivated item within it (not supported)." end if @db_address[:database].structure_attached?(the_value.uuid.to_s) && the_container.is_any?(::Hash, ::Array) raise Exception, "The structure you are attaching has a cross-linked structure within it (cross linked structures not supported)." end end unless the_value.is_any?(::GxG::Database::Database::valid_field_classes()) || the_value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::Hash, ::Array) raise Exception, "The structure you are attaching has an unpersistable item within it (not supported)." end end end # ### Property Exists? if @property_links[(key.to_s.to_sym)] # set property to value operation = :set_value else if @format raise Exception, "Formatted - the structure cannot be altered" else # add property : value pair operation = :add_value end end # ### Prepare new value new_value = { :linkid => nil, :content => nil, :loaded => false, :state => 0, :record => { :parent_uuid => @uuid.to_s, :property => key.to_s, :ordinal => 0, :version => ::BigDecimal.new("0.0"), :element => "element_boolean", :element_boolean => -1, :element_integer => 0, :element_float => 0.0, :element_bigdecimal => ::BigDecimal.new("0.0"), :element_datetime => ::DateTime.now, :time_offset => 0.0, :time_prior => 0.0, :time_after => 0.0, :length => 0, :element_text => "", :element_text_uuid => "", :element_binary_uuid => "", :element_array_uuid => "", :element_hash_uuid => "" } } # if value.is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) # ### Assimilate PersistedHash or PersistedArray new_value[:content] = value new_value[:loaded] = true new_value[:state] = new_value[:content].hash # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. new_value[:record][:version] = new_value[:content].version() if value.is_a?(::GxG::Database::PersistedHash) new_value[:record][:element] = "element_hash" new_value[:record][:element_hash_uuid] = value.uuid.to_s else new_value[:record][:element] = "element_array" new_value[:record][:element_array_uuid] = value.uuid.to_s end else # ### Persist Hashes and Arrays if value.is_any?(::Hash, ::Array) new_value[:content] = @db_address[:database].iterative_persist(value,@credential) new_value[:loaded] = true new_value[:state] = new_value[:content].hash # Note: version is sync'd here, but don't rely upon property version with linked structures, but use structure's version method. new_value[:record][:version] = new_value[:content].version() if value.is_a?(::Hash) new_value[:record][:element] = "element_hash" new_value[:record][:element_hash_uuid] = new_value[:content].uuid.to_s else new_value[:record][:element] = "element_array" new_value[:record][:element_array_uuid] = new_value[:content].uuid.to_s end else # ### Persist Base Element Values new_value[:record][:element] = ::GxG::Database::Database::element_table_for_instance(value).to_s case new_value[:record][:element] when "element_boolean" if value.class == ::NilClass new_value[:record][:element_boolean] = -1 new_value[:content] = nil end if value.class == ::FalseClass new_value[:record][:element_boolean] = 0 new_value[:content] = false end if value.class == ::TrueClass new_value[:record][:element_boolean] = 1 new_value[:content] = true end when "element_integer" new_value[:record][:element_integer] = value new_value[:content] = value when "element_float" new_value[:record][:element_float] = value new_value[:content] = value when "element_bigdecimal" new_value[:record][:element_bigdecimal] = ::BigDecimal.new(value.to_s) new_value[:content] = new_value[:record][:element_bigdecimal] when "element_datetime" new_value[:record][:element_datetime] = value new_value[:content] = value when "element_text" if value.size > 256 # Note: be sure to keep text version & length sync'd with linked text element record if operation == :set_value new_value[:record][:version] = @property_links[(property_key)][:record][:version] new_value[:record][:element_text_uuid] = @property_links[(property_key)][:record][:element_text_uuid] end new_value[:record][:length] = value.size else new_value[:record][:element_text] = value new_value[:record][:length] = value.size end new_value[:content] = value when "element_binary" # Note: be sure to keep version & length sync'd with linked binary element record if operation == :set_value new_value[:record][:version] = @property_links[(property_key)][:record][:version] new_value[:record][:element_binary_uuid] = @property_links[(property_key)][:record][:element_binary_uuid] end new_value[:record][:length] = value.size new_value[:content] = value else raise Exception, "Unable to map an element type for value: #{value.inspect}" end end end # ### Commit Changes case operation when :set_value # Note: Set In-memory value only, don't save unless you have to. if new_value[:content].is_a?(@property_links[(property_key)][:content].class) || ([true, false, nil].include?(new_value[:content]) && [true, false, nil].include?(@property_links[(property_key)][:content])) # Replace value directly, but don't save yet unless you have to (:state refers to the last 'loaded-in-from-db' state of the data). new_value[:linkid] = @property_links[(property_key)][:linkid] if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) end new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] new_value[:loaded] = true new_value[:state] = @property_links[(property_key)][:state] # ### Did string grow or shrink crossing the threshold?? if new_value[:content].is_a?(::String) if (@property_links[(property_key)][:content].size > 256 && new_value[:content].size <= 256) # shrank - delete text object and pages as well as property record (slow) property_destroy(property_key) @property_links[(property_key)] = new_value property_write(property_key) else if (@property_links[(property_key)][:content].size <= 256 && new_value[:content].size > 256) # grew - create text object and pages (a little less slow) @property_links[(property_key)] = new_value property_write(property_key) else # N/A @property_links[(property_key)] = new_value end end else # N/A @property_links[(property_key)] = new_value end if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) # Note: this ensures attachment to this object property_write(property_key) end # else # Other class value being substituted. if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = (@property_links[(property_key)][:record][:version] + 0.0001) end new_value[:record][:ordinal] = @property_links[(property_key)][:record][:ordinal] new_value[:loaded] = true # Destroy old property record on db, and associated linked objects. # Review : this is slow - look into optimizations here. if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray, ::GxG::ByteArray, ::String) if @property_links[(property_key)][:content].is_a?(::String) if (@property_links[(property_key)][:content].size > 256 && new_value[:content].to_s.size <= 256) || (@property_links[(property_key)][:content].size <= 256 && new_value[:content].to_s.size > 256) property_destroy(property_key) end else property_destroy(property_key) end end # @property_links[(property_key)] = new_value property_write(property_key) end # when :add_value # Note: set in-memory value and save. if new_value[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) new_value[:record][:version] = new_value[:content].version() else new_value[:record][:version] = ::BigDecimal.new("0.0") end new_value[:record][:ordinal] = @property_links.keys.size new_value[:loaded] = true # @property_links[(property_key)] = new_value # Review : is this wise to do this here?? property_write(property_key) # self.refresh_ordinals ?? end # ### Handle coordination between persisted objects: if @property_links[(property_key)][:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) # ### Extend Reservation to persisted objects ( Review : does this slow it down too much?? ) @property_links[(property_key)][:content].release_reservation @db_address[:database].reservation_add_element((@reservation || @delegate), (@property_links[(property_key)][:content].db_address()[:table]), @property_links[(property_key)][:content].db_address()[:dbid]) @property_links[(property_key)][:content].set_delegate((@reservation || @delegate)) # ### Extend Permissions to persisted object @db_address[:database].extend_element_permissions(@db_address[:table],@db_address[:dbid],@property_links[(property_key)][:content].db_address()[:table],@property_links[(property_key)][:content].db_address()[:dbid]) @db_address[:database].enforce_permission_policy({:action => :extend, :credential => @credential, :source => self, :destination => @property_links[(property_key)][:content]}) # @property_links[(property_key)][:content].set_parent(self) end # self.increment_version() result = @property_links[(property_key)][:content] # else raise Exception, "You do not have sufficient privileges to make this change. (write-reservation)" end else raise Exception, "The value is not persistable." end else raise Exception, "You must provide a property key in the form of a Symbol." end result end # def unload(key=nil) result = false # if key exists # if key is loaded, unload element (if String or ByteArray) if self.alive?() if @db_address[:database].open?() if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end property_key = key.to_s.to_sym if @property_links[(property_key)] if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray) if @property_links[(property_key)][:loaded] == true # ### Unload Base Element contents from memory, leaving reference harness intact and active. @property_links[(property_key)][:content].clear @property_links[(property_key)][:loaded] = false end end result = true end else raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" end else raise Exception, "Database not available" end else raise Exception, "Attempted to access a defunct structure" end result end # def [](key=nil) result = nil # if key exists # if key is not loaded, load element (if String or ByteArray) if self.alive?() if @db_address[:database].open?() if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end property_key = key.to_s.to_sym if @property_links[(property_key)] if @property_links[(property_key)][:content].is_any?(::String, ::GxG::ByteArray) unless @property_links[(property_key)][:loaded] == true # ### Load Base Element contents from the database. if @property_links[(property_key)][:content].is_a?(::String) if @property_links[(property_key)][:record][:element_text_uuid].to_s.size > 0 @db_address[:database].connector()[:text_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_text_uuid].to_s}).order(:ordinal).each do |text_page| @property_links[(property_key)][:content] << text_page[:content] end else @property_links[(property_key)][:content] = @property_links[(property_key)][:record][:element_text].to_s end else @db_address[:database].connector()[:binary_page].filter({:parent_uuid => @property_links[(property_key)][:record][:element_binary_uuid].to_s}).order(:ordinal).each do |binary_page| @property_links[(property_key)][:content] << binary_page[:content] end end @property_links[(property_key)][:loaded] = true end end result = @property_links[(property_key)][:content] end else raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" end else raise Exception, "Database not available" end else raise Exception, "Attempted to access a defunct structure" end result end # def include?(the_key) @property_links.include?(the_key) end # def delete(key=nil) result = nil # if key exists # if key is not loaded, load element if self.alive?() if @db_address[:database].open?() if key.is_a?(::Symbol) if key.to_s.size > 256 log_warn({:warning => "Attempted oversized key usage (limited to 256 characters), truncated #{key.inspect} to #{key.to_s[(0..255)].to_sym.inspect}"}) key = key.to_s[(0..255)].to_sym end if @format raise Exception, "Formatted - the structure cannot be altered" else if self.write_reserved?() property_key = key.to_s.to_sym # if @property_links[(property_key)] # This will load the unloaded prior to separation. result = self[(property_key)] the_link = @property_links.delete(property_key) if the_link[:content].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) the_link[:content].save the_link[:content].release_reservation() @db_address[:database].reservation_remove_element((@reservation || @delegate), the_link[:content].db_address()[:table], the_link[:content].db_address()[:dbid]) else # Eliminate orphaned text and binary objects in the database. They will exist only in memory. if the_link[:content].is_any?(::String, ::GxG::ByteArray) if the_link[:content].is_a?(::String) if the_link[:content].size > 256 # eliminate text object and pages. the_temp_record = @db_address[:database].connector()[:element_text].select(:dbid).where({:uuid => the_link[:record][:element_text_uuid]}).first if the_temp_record @db_address[:database].element_destroy(@credential, :element_text, the_temp_record[:dbid]) end end end if the_link[:content].is_a?(::GxG::ByteArray) # eliminate binary object and pages. the_temp_record = @db_address[:database].connector()[:element_binary].select(:dbid).where({:uuid => the_link[:record][:element_binary_uuid]}).first if the_temp_record @db_address[:database].element_destroy(@credential, :element_binary, the_temp_record[:dbid]) end end end end @db_address[:database].connector()[:hash_properties].filter({:dbid => the_link[:linkid]}).delete refresh_ordinals self.increment_version() end # else raise Exception, "You do not have sufficient privileges to make this change" end end else raise ArgumentError, "You must specify with a Symbol, not a #{key.class.inspect}" end else raise Exception, "Database not available" end else raise Exception, "Attempted to access a defunct structure" end result end # def unpersist() result = {} if self.alive? # export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] children_of = Proc.new do |the_parent=nil| list = [] export_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # Build up export_db: self.search do |the_value, the_selector, the_container| if the_value.is_a?(::GxG::Database::PersistedHash) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {}} else if the_value.is_a?(::GxG::Database::PersistedArray) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => []} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => the_value} end end end # Collect children export content: link_db =[(export_db[0])] while link_db.size > 0 do entry = link_db.shift children_of.call(entry[:object]).each do |the_child| entry[:record][(the_child[:parent_selector])] = the_child[:record] if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) link_db << the_child end end end # end result end # def import_update(the_record=nil) if self.alive? if the_record.is_a?(::Hash) if @uuid == the_record[:uuid].to_s.to_sym if @reservation had_reservation = true self.release_reservation else had_reservation = false end @db_address[:database].synchronize_records([{:operation => :merge, :data => the_record}],@credential) @property_links = {} load_property_links if had_reservation self.get_reservation end end end end self end # def export(options={:exclude_file_segments=>false}) if self.alive? exclude_file_segments = (options[:exclude_file_segments] || false) if options[:clone] == true # Review : why are cloned objects unformatted? sync issues?? result = {:type => :element_hash, :uuid => GxG::uuid_generate.to_s.to_sym, :title => @title.clone, :version => ::BigDecimal.new(@version.to_s).to_s("F"), :content => {}} else result = {:type => :element_hash, :uuid => @uuid.clone, :title => @title.clone, :version => ::BigDecimal.new(@version.to_s).to_s("F"), :format => @format.clone, :content => {}} end export_db = [{:parent => nil, :parent_selector => nil, :object => self, :record => result}] children_of = Proc.new do |the_parent=nil| list = [] export_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # Build up export_db: self.search do |the_value, the_selector, the_container| if the_value.is_a?(::GxG::Database::PersistedHash) if options[:clone] == true the_uuid = GxG::uuid_generate.to_s.to_sym else the_uuid = the_value.uuid().clone end if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => {}, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_hash, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :format => the_value.format().clone, :content => {}}} end end if the_value.is_a?(::GxG::Database::PersistedArray) if options[:clone] == true the_uuid = GxG::uuid_generate.to_s.to_sym else the_uuid = the_value.uuid().clone end if (exclude_file_segments == true) && (the_selector == :file_segments || the_selector == :segments || the_selector == :portions) export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => [], :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} else export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => :element_array, :uuid => the_uuid, :title => the_value.title().clone, :version => ::BigDecimal.new(the_value.version().to_s).to_s("F"), :constraint => the_value.constraint().clone, :content => []}} end end if the_value.is_any?(::GxG::Database::Database::valid_field_classes()) data_type = GxG::Database::Database::element_table_for_instance(the_value) case data_type when :element_bigdecimal data = the_value.to_s("F") when :element_datetime data = the_value.to_s when :element_binary data = the_value.to_s.encode64 when :element_text data = the_value.to_s else data = the_value end export_db << {:parent => the_container, :parent_selector => the_selector.clone, :object => the_value, :record => {:type => data_type, :version => (::BigDecimal.new(the_container.element_version(the_selector).to_s) || ::BigDecimal.new("0.0")).to_s("F"), :content => data}} end end # Collect children export content: link_db = [(export_db[0])] while link_db.size > 0 do entry = link_db.shift children_of.call(entry[:object]).each do |the_child| entry[:record][:content][(the_child[:parent_selector])] = the_child[:record] if the_child[:object].is_any?(::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) link_db << the_child end end end else result = nil end # result end # def export_package(options={:exclude_file_segments=>false}) self.save result = {:formats => {}, :records => []} object_record = self.export(options) object_record.search do |item,selector,container| if selector == :format || selector == :constraint if item.to_s.size > 0 format_uuid = item unless result[:formats][(format_uuid.to_s.to_sym)].is_a?(::Hash) format_sample = @db_address[:database].format_load({:uuid => format_uuid.to_s.to_sym}) format_sample[:content] = format_sample[:content].gxg_export() result[:formats][(format_uuid.to_s.to_sym)] = format_sample end end end end result[:records] << object_record result end # def each_pair(&block) collection = {} @property_links.keys.each do |key| collection[(key)] = (self[(key)]) end if block.respond_to?(:call) collection.to_enum(:each_pair).each do |key,value| block.call(key,value) end else collection.to_enum(:each_pair) end end # def iterative(options={:include_inactive => true}, &block) result = [] visit = Proc.new do |the_node=nil, accumulator=[]| node_stack = [] if the_node node_stack << ({:parent => nil, :parent_selector => nil, :object => (the_node)}) while (node_stack.size > 0) do a_node = node_stack.shift # if a_node[:object].is_a?(::GxG::Database::PersistedHash) if a_node[:object].alive? a_node[:object].each_pair do |the_key, the_value| node_stack << ({:parent => a_node[:object], :parent_selector => the_key, :object => the_value}) end else if options[:include_inactive] accumulator << a_node end end end if a_node[:object].is_a?(::GxG::Database::PersistedArray) if a_node[:object].alive? a_node[:object].each_with_index do |the_value, the_index| node_stack << ({:parent => a_node[:object], :parent_selector => the_index, :object => the_value}) end else if options[:include_inactive] accumulator << a_node end end end # Review : is this for a Field?? if a_node.alive? || options[:include_inactive] accumulator << a_node end end end accumulator end # children_of = Proc.new do |the_db=[], the_parent=nil| list = [] the_db.each do |node| if node[:parent].object_id == the_parent.object_id list << node end end list end # begin database = visit.call(self,[]) link_db = children_of.call(database, self) if block.respond_to?(:call) while (link_db.size > 0) do entry = link_db.shift unless entry[:object].object_id == self.object_id # calls with parameters: the_value, the_key/the_index (the_selector), the_container raw_result = block.call(entry[:object], entry[:parent_selector], entry[:parent]) if raw_result result << raw_result end end if entry[:object].object_id != nil.object_id children = children_of.call(database, entry[:object]) children.each do |child| link_db << child end end end end # rescue Exception => the_error log_error({:error => the_error, :parameters => {}}) end # result end alias :process :iterative # def process!(options={:include_inactive => true}, &block) self.iterative(options, &block) self end # def search(options={:include_inactive => true}, &block) results = [] if block.respond_to?(:call) results = self.iterative(options, &block) end results end # def paths_to(the_object=nil,base_path="") # new idea here: search_results = [] unless base_path[0] == "/" base_path = ("/" << base_path) end if base_path.size > 1 path_stack = base_path.split("/")[1..-1].reverse else path_stack = [] end origin = self.get_at_path(base_path) container_stack = [{:selector => nil, :container => origin}] find_container = Proc.new do |the_container| result = nil container_stack.each_with_index do |entry, index| if entry[:container] == the_container result = entry break end end result end last_container = origin found = false # tester = {:a=>1, :b=>2, :c=>[0, 5], :testing=>{:d=>4.0, :e=>0.9, :f => nil}} if origin.is_any?(::Hash, ::Array, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) origin.process! do |the_value, selector, container| if last_container.object_id != container.object_id container_record = find_container.call(container) if container_record path_stack = container_record[:prefix].split("/").reverse if path_stack.size == 0 path_stack << "" end end last_container = container end if selector.is_a?(Symbol) safe_key = (":" + selector.to_s) else safe_key = selector.to_s end safe_key.gsub!("/","%2f") path_stack.unshift(safe_key) # compare the_value found = false if (the_value == the_object) found = true end if found search_results << ("/" << path_stack.reverse.join("/")) end # if the_value.is_any?(::Array, ::Hash, ::Struct, ::GxG::Database::PersistedHash, ::GxG::Database::PersistedArray) container_stack.unshift({:selector => selector, :container => the_value, :prefix => (path_stack.reverse.join("/"))}) end path_stack.shift # nil end else search_results << ("/" << path_stack.join("/")) end search_results end # def get_at_path(the_path="/") result = nil if the_path == "/" result = self else object_stack = [(self)] path_stack = the_path.split("/") path_stack.to_enum.each do |path_element| element = nil if path_element.size > 0 if (path_element =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 element = path_element.to_i else element = path_element element.gsub!("%2f","/") element = element.to_sym end end if element result = object_stack.first[(element)] if result.is_a?(NilClass) break else object_stack.unshift(result) end else # ignore double slashes? '//' # break end end end result end # def set_at_path(the_path="/",the_value=nil) result = nil if the_path != "/" container = self.get_at_path(::File::dirname(the_path)) if container raw_selector = ::File::basename(the_path) selector = nil if raw_selector.size > 0 if (raw_selector =~ /^(?:[0-9])*[0-9](?:[0-9])*$/) == 0 selector = raw_selector.to_i else selector = raw_selector selector.gsub!("%2f","/") selector = selector.to_sym end end if selector container[(selector)] = the_value result = container[(selector)] else # ignore double slashes? '//' # break end # end end result end # def merge!(input_object=nil, options={}) result = self if input_object.is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) # if @format # merge only keys common to both input_object.keys.each do |import_key| if result.keys.include?(import_key) result[(import_key)] = input_object[(import_key)] end end else input_object.keys.each do |import_key| result[(import_key)] = input_object[(import_key)] end end # end result end # def merge(input_object=nil, options={:role => :data, :credential => ::GxG::DB[:administrator]}) result = nil if input_object.is_any?(::Hash, ::GxG::Database::DetachedHash, ::GxG::Database::PersistedHash) # database = ::GxG::DB[:roles][(options[:role] || :data)] if database.is_a?(::GxG::Database::Database) result = database.try_persist({}, (options[:credential] || ::GxG::DB[:administrator])) # if @format # merge only keys common to both self.keys.each do |import_key| if input_object.keys.include?(import_key) result[(import_key)] = input_object[(import_key)] end end result.format = @format else input_object.keys.each do |import_key| result[(import_key)] = input_object[(import_key)] end end # end end result end # end end end