zoer/xmlable

View on GitHub
lib/xmlable/mixins/attributes_storage.rb

Summary

Maintainability
A
0 mins
Test Coverage
module XMLable
  module Mixins
    #
    # AttributesStorage module contains the logic for object that able to
    #   store XML attributes
    #
    module AttributesStorage
      def self.included(base)
        base.send(:extend, ClassMethods)
      end

      #
      # Set XML attribute
      #
      # @param [Nokogiri::XML::Attr, nil] att
      # @param [Hash] opts
      # @option opts [String] :tag attribute's name
      # @option opts [String] :namespace attribute's namespace
      #
      # @api private
      #
      def __set_attribute(att, opts = {})
        unless att
          @__node[opts[:tag]] = att
          att = @__node.attributes[opts[:tag]]
          if opts[:namespace]
            att.namespace = att.namespace_scopes.find { |n| n.prefix == opts[:namespace] }
          end
        end
        h = __attributes_handlers.for_xml_object(att)
        att.instance_variable_set(:@__handler, h)
        __attributes[h.key] = h.from_xml_attribute(att)
      end

      #
      # Attributes which current object holds
      #
      # @api private
      #
      # @return [Hash(String => Array<XMLable::Mixins::Object>)]
      #
      def __attributes
        @__attributes ||= {}
      end

      #
      # Is this object empty?
      #
      # @api private
      #
      # @return [Hash(String => Array<XMLable::Mixins::Object>)]
      #
      def __empty?
        return false unless super
        __attributes.values.all?(&:__empty?)
      end

      def method_missing(name, *args, &block)
        h = __has_attribute_handler?(name)
        return super unless h

        if name.to_s.end_with?('=')
          __attribute_object_set(h, args.first)
        else
          __attribute_object_get(h)
        end
      end

      #
      # Does this object contain attribute with given key?
      #
      # @return [XMLable::Mixins::Object, false]
      #
      def key?(key)
        super || __has_attribute_handler?(key)
      end

      #
      # Get attribute object by its key
      #
      # @param [String] key attribute key
      #
      # @return [XMLable::Mixins::Object, nil]
      #
      def [](key)
        h = __has_attribute_handler?(key)
        h ? __attribute_object_get(h) : super
      end

      #
      # Set attribute value
      #
      # @param [String] key attribute key
      # @param [Object] val new value
      #
      # @return [XMLable::Mixins::Object, nil]
      #
      def []=(key, val)
        h = __has_attribute_handler?(key)
        h ? __attribute_object_set(h, val) : super
      end

      #
      # Find attribute handler by its key
      #
      # @param [Symbol, String] key
      #
      # @api private
      #
      # @return [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil]
      #
      def __has_attribute_handler?(key)
        key = key.to_s.gsub(/[=!]$/, '')
        __attributes_handlers.storage.find { |h| h.method_name == key }
      end

      #
      # Get attribute object
      #
      # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone] h
      #
      # @api private
      #
      # @return [XMLable::Mixins::Object]
      #
      def __attribute_object_get(h)
        unless __attributes.key?(h.key)
          __set_attribute(nil, tag: h.tag, namespace: h.namespace_prefix)
        end
        __attributes[h.key]
      end

      #
      # Set attribute object value
      #
      # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil] h
      # @param [Object] val new value
      #
      # @api private
      #
      def __attribute_object_set(h, val)
        __attribute_object_get(h).__overwrite_value(val)
      end

      #
      # Initialize attribute object with value
      #
      # @param [XMLable::Handlers::Attribute, XMLable::Handlers::AttributeNone, nil] h
      # @param [Object] val
      #
      # @api private
      #
      def __attribute_object_initialize(h, value)
        __attribute_object_get(h).__overwrite_value(value)
      end

      #
      # Attributes handlers storage
      #
      # @return [XMLable::Handlers::Storage]
      #
      def __attributes_handlers
        @__attributes_handler ||= self.class.__attributes_handlers.clone
      end

      module ClassMethods
        #
        # Attributes handlers storage
        #
        # @return [XMLable::Handlers::Storage]
        #
        def __attributes_handlers
          @__attributes_handlers ||=
            __nested(:@__attributes_handlers) || Handlers::Storage.new(default: Handlers::AttributeNone)
        end

        #
        # Describe object attribute
        #
        # @see XMLable::Handler::Base#build
        #
        # @return [XMLable::Handlers::Storage]
        #
        def attribute(*args, &block)
          opts = args.last.is_a?(Hash) ? args.pop : {}
          if __default_namespace && !opts.key?(:namespace)
            opts[:namespace] = __default_namespace
          end
          h = Handlers::Attribute.build(*args, opts, &block)
          __attributes_handlers << h
        end
      end
    end
  end
end