def define(name, attr_type = nil, **opts, &block)
      example_given = opts.key? :example

      # add to existing attribute if present
      if (existing_attribute = attributes[name])
        if existing_attribute.attributes
          existing_attribute.type.attributes(&block)
          return existing_attribute
        end
      end

      # determine inherited type (giving preference to the direct attribute options)
      inherited_type = opts[:reference]
      unless inherited_type
        reference = options[:reference]
        if reference && reference.respond_to?(:attributes) && reference.attributes.key?(name)
          inherited_attribute = reference.attributes[name]
          opts = inherited_attribute.options.merge(opts) unless attr_type
          inherited_type = inherited_attribute.type
          opts[:reference] = inherited_type if block_given?
        end
      end

      # determine attribute type to use
      if attr_type.nil?
        if block_given?
          # Don't inherit explicit examples if we've redefined the structure 
          # (but preserve the direct example if given here)
          opts.delete :example unless  example_given
          attr_type = if inherited_type && inherited_type < Attributor::Collection
                        # override the reference to be the member_attribute's type for collections
                        opts[:reference] = inherited_type.member_attribute.type
                        Attributor::Collection.of(Struct)
                      else
                        Attributor::Struct
                      end
        elsif inherited_type
          attr_type = inherited_type
        else
          raise AttributorException, "type for attribute with name: #{name} could not be determined"
        end
      end

      Attributor::Attribute.new(attr_type, opts, &block)
    end
def define(name, attr_type = nil, **opts, &block)
      example_given = opts.key? :example

      # add to existing attribute if present
      if (existing_attribute = attributes[name])
        if existing_attribute.attributes
          existing_attribute.type.attributes(&block)
          return existing_attribute
        end
      end

      # determine inherited type (giving preference to the direct attribute options)
      inherited_type = opts[:reference]
      unless inherited_type
        reference = options[:reference]
        if reference && reference.respond_to?(:attributes) && reference.attributes.key?(name)
          inherited_attribute = reference.attributes[name]
          opts = inherited_attribute.options.merge(opts) unless attr_type
          inherited_type = inherited_attribute.type
          opts[:reference] = inherited_type if block_given?
        end
      end

      # determine attribute type to use
      if attr_type.nil?
        if block_given?
          # Don't inherit explicit examples if we've redefined the structure 
          # (but preserve the direct example if given here)
          opts.delete :example unless  example_given
          attr_type = if inherited_type && inherited_type < Attributor::Collection
                        # override the reference to be the member_attribute's type for collections
                        opts[:reference] = inherited_type.member_attribute.type
                        Attributor::Collection.of(Struct)
                      else
                        Attributor::Struct
                      end
        elsif inherited_type
          attr_type = inherited_type
        else
          raise AttributorException, "type for attribute with name: #{name} could not be determined"
        end
      end

      Attributor::Attribute.new(attr_type, opts, &block)
    end
Creates an Attributor:Attribute with given definition.

 @overload define(name, type, opts, &block)
   With an explicit type.
   @param [symbol] name describe name param
   @param [Attributor::Type] type describe type param
   @param [Hash] opts describe opts param
   @param [Block] block describe block param
   @example
     attribute :email, String, example: Randgen.email
 @overload define(name, opts, &block)
   Assume a type of Attributor::Struct
   @param [symbol] name describe name param
   @param [Hash] opts describe opts param
   @param [Block] block describe block param
   @example
     attribute :address do
       attribute :number, String
       attribute :street, String
       attribute :city, String
       attribute :state, String
     end
 @api semiprivate