You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: declared_properties.rb
    path of file: ./repos/activegraph/lib/active_graph/shared
    
    the code of the file until where you have to start completion: module ActiveGraph::Shared
  # The DeclaredPropertyuManager holds details about objects created as a result of calling the #property
  # class method on a class that includes ActiveGraph::Node or ActiveGraph::Relationship. There are many options
  # that are referenced frequently, particularly during load and save, so this provides easy access and
  # a way of separating behavior from the general Active{obj} modules.
  #
  # See ActiveGraph::Shared::DeclaredProperty for definitions of the property objects themselves.
  class DeclaredProperties
    include ActiveGraph::Shared::TypeConverters

    attr_reader :klass
    delegate :each, :each_pair, :each_key, :each_value, to: :registered_properties

    # Each class that includes ActiveGraph::Node or ActiveGraph::Relationship gets one instance of this class.
    # @param [#declared_properties] klass An object that has the #declared_properties method.
    def initialize(klass)
      @klass = klass
    end

    def [](key)
      registered_properties[key.to_sym]
    end

    def property?(key)
      registered_properties.key?(key.to_sym)
    end

    # @param [ActiveGraph::Shared::DeclaredProperty] property An instance of DeclaredProperty, created as the result of calling
    # #property on an Node or Relationship class. The DeclaredProperty has specifics about the property, but registration
    # makes the management object aware of it. This is necessary for type conversion, defaults, and inclusion in the nil and string hashes.
    def register(property)
      @_attributes_nil_hash = nil
      @_attributes_string_map = nil
      registered_properties[property.name] = property
      register_magic_typecaster(property) if property.magic_typecaster
      declared_property_defaults[property.name] = property.default_value if !property.default_value.nil?
    end

    def index_or_fail!(key, id_property_name, type = :exact)
      return if key == id_property_name
      fail "Cannot index undeclared property #{key}" unless property?(key)
      registered_properties[key].index!(type)
    end

    def constraint_or_fail!(key, id_property_name, type = :unique)
      return if key == id_property_name
      fail "Cannot constraint undeclared property #{property}" unless property?(key)
      registered_properties[key].constraint!(type)
    end

    # The :default option in ActiveGraph::Node#property class method allows for setting a default value instead of
    # nil on declared properties. This holds those values.
    def declared_property_defaults
      @_default_property_values ||= {}
    end

    def registered_properties
      @_registered_properties ||= {}
    end

    def indexed_properties
      registered_properties.select { |_, p| p.index_or_constraint? }
    end

    # During object wrap, a hash is needed that contains each declared property with a nil value.
    # The active_attr dependency is capable of providing this but it is expensive and calculated on the fly
    # each time it is called. Rather than rely on that, we build this progressively as properties are registered.
    # When the node or rel is loaded, this is used as a template.
    def attributes_nil_hash
      @_attributes_nil_hash ||= {}.tap do |attr_hash|
        registered_properties.each_pair do |k, prop_obj|
          val = prop_obj.default_value
          attr_hash[k.to_s] = val
        end
      end.freeze
    end

    # During object wrapping, a props hash is built with string keys but ActiveGraph::Core provides symbols.
    # Rather than a `to_s` or `symbolize_keys` during every load, we build a map of symbol-to-string
    # to speed up the process. This increases memory used by the gem but reduces object allocation and GC, so it is faster
    # in practice.
    def attributes_string_map
      @_attributes_string_map ||= {}.tap do |attr_hash|
        attributes_nil_hash.each_key { |k| attr_hash[k.to_sym] = k }
      end.freeze
    end

    # @param [Symbol] k A symbol for which the String representation is sought. This might seem silly -- we could just call #to_s --
    # but when this happens many times while loading many objects, it results in a surprisingly significant slowdown.
    # The branching logic handles what happens if a property can't be found.
    # The first option a
    
    def attributes_nil_hash
      @_attributes_nil_hash ||= {}.tap do |attr_hash|
        registered_properties.each_pair do |k, prop_obj|
          val = prop_obj.default_value
          attr_hash[k.to_s] = val
        end
      end.freeze
    end

    # During object wrapping, a props hash is built with string keys but ActiveGraph::Core provides symbols.
    # Rather than a `to_s` or `symbolize_keys` during every load, we build a map of symbol-to-string
    # to speed up the process. This increases memory used by the gem but reduces object allocation and GC, so it is faster
    # in practice.
    def attributes_string_map
      @_attributes_string_map ||= {}.tap do |attr_hash|
        attributes_nil_hash.each_key { |k| attr_hash[k.to_sym] = k }
      end.freeze
    end

    # @param [Symbol] k A symbol for which the String representation is sought. This might seem silly -- we could just call #to_s --
    # but when this happens many times while loading many objects, it results in a surprisingly significant slowdown.
    # The branching logic handles what happens if a property can't be found.
    # The first option attempts to find it in the existing hash.
    # The second option checks whether the key is the class's id property and, if it is, the string hash is rebuilt with it to prevent
    # future lookups.
    # The third calls `to_s`. This would happen if undeclared properties are found on the object. We could add them to the string map
    # but that would result in unchecked, un-GCed memory consumption. In the event that someone is adding properties dynamically,
    # maybe through user input, this would be bad.
    def string_key(k)
      attributes_string_map[k] || string_map_id_property(k) || k.to_s
    end

    def unregister(name)
      # might need to be include?(name.to_s)
      fail ArgumentError, "Argument `#{name}` not an attribute" if not registered_properties[name]
      registered_properties.delete(name)
      unregister_magic_typecaster(name)
      unregister_property_default(name)
    end

    def serialize(name, coder = JSON)
      @serialize ||= {}
      @serialize[name] = coder
    end

    def serialized_properties=(serialize_hash)
      @serialized_property_keys = nil
      @serialize = serialize_hash.clone
    end

    def serialized_properties
      @serialize ||= {}
    end

    def serialized_properties_keys
      @serialized_property_keys ||= serialized_properties.keys
    end

    def magic_typecast_properties_keys
      @magic_typecast_properties_keys ||= magic_typecast_properties.keys
    end

    def magic_typecast_properties
      @magic_typecast_properties ||= {}
    end

    EXCLUDED_TYPES = [Array, Range, Regexp]
    def value_for_where(key, value)
      return value unless prop = registered_properties[key]
      return value_for_db(key, value) if prop.typecaster && prop.typecaster.convert_type == value.class

      if should_convert_for_where?(key, value)
        value_for_db(key, value)
      else
        value
      end
    end

    def value_for_db(key, value)
      return value unless registered_properties[key]
      convert_property(key, value, :to_db)
    end

    def value_for_ruby(key, value)
      return unless registered_properties[key]
      convert_property(key, value, :to_ruby)
    end

    def inject_defaults!(object, props)
      declared_property_defaults.each_pair do |k, v|
        props[k.to_sym] = v.respond_to?(:call) ? v.call : v if object.send(k).nil? && props[k.to_sym].nil?
      end
      props
    end

    protected

    # Prevents repeated calls to :_attribute_type, which isn't free and never changes.
    def fetch_upstream_primitive(attr)
      registered_properties[attr].type
    end

    private

    def should_convert_for_where?(key, value)
      (value.is_a?(Array) && supports_array?(key)) || !EXCLUDED_TYPES.include?(value.class)
    end

    # @param [Symbol] key An undeclared property value found in the _persisted_obj.properties hash.
    # Typically, this is a node's id property, which will not be registered as other properties are.
    # In the future, this should probably be reworked a bit. This class should either not know or care
    # about the id property or it should be in charge of it. In the meantime, this improves
    # node load performance.
    def string_map_id_property(key)
      return unless klass.id_property_name == key
      key.to_s.tap do |string_key|
        @_attributes_string_map = attributes_string_map.dup.tap { |h| h[key] = string_key }.freeze
      end
    end

    def unregister_magic_typecaster(property)
      magic_typecast_properties.delete(property)
      @magic_typecast_properties_keys = nil
    end

    def unregister_property_default(property)
      declared_property_defaults.delete(property)
      @_default_property_values = nil
    end

    def register_magic_typecaster(property)
      magic_typecast_properties[property.name] = property.magic_typecaster
      @magic_typecast_properties_keys = nil
    end
  end