CDLUC3/datacite-mapping

View on GitHub
lib/datacite/mapping/resource.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true

require 'xml/mapping_extensions'

require 'datacite/mapping/identifier'
require 'datacite/mapping/creator'
require 'datacite/mapping/title'
require 'datacite/mapping/subject'
require 'datacite/mapping/contributor'
require 'datacite/mapping/date'
require 'datacite/mapping/resource_type'
require 'datacite/mapping/alternate_identifier'
require 'datacite/mapping/rights'
require 'datacite/mapping/geo_location'
require 'datacite/mapping/read_only_nodes'

module Datacite
  module Mapping

    # A collection of metadata properties chosen for the accurate and consistent identification
    # of a resource for citation and retrieval purposes, along with recommended use instructions.
    # The resource that is being identified can be of any kind, but it is typically a dataset.
    class Resource # rubocop:disable Metrics/ClassLength
      include XML::MappingExtensions::Namespaced

      # Shadows Namespaced::ClassMethods.namespace
      def namespace
        @namespace ||= DATACITE_4_NAMESPACE
      end

      # Overrides Namespaced::InstanceMethods.fill_into_xml to check mapping
      def fill_into_xml(xml, options = { mapping: :_default })
        current_namespace = namespace
        return super if options[:mapping] != :datacite_3 || current_namespace.uri == DATACITE_3_NAMESPACE.uri

        begin
          @namespace = DATACITE_3_NAMESPACE.with_prefix(current_namespace.prefix)
          super
        ensure
          @namespace = current_namespace
        end
      end

      # Initialies a new {Resource}
      #
      # @param identifier [Identifier] a persistent identifier that identifies a resource.
      # @param creators [Array<Creator>] the main researchers involved working on the data, or the authors of the publication in priority order.
      # @param titles [Array<Title>] the names or titles by which a resource is known.
      # @param publisher [Publisher] the name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource.
      # @param publication_year [Integer] year when the resource is made publicly available.
      # @param subjects [Array<Subject>] subjects, keywords, classification codes, or key phrases describing the resource.
      # @param funding_references [Array<FundingReference>] information about financial support (funding) for the resource being registered.
      # @param contributors [Array<Contributor>] institutions or persons responsible for collecting, creating, or otherwise contributing to the developement of the dataset.
      # @param dates [Array<Date>] different dates relevant to the work.
      # @param language [String, nil] Primary language of the resource: an IETF BCP 47, ISO 639-1 language code.
      # @param resource_type [ResourceType, nil] the type of the resource
      # @param alternate_identifiers [Array<AlternateIdentifier>] an identifier or identifiers other than the primary {Identifier} applied to the resource being registered.
      # @param related_identifiers [Array<RelatedIdentifier>] identifiers of related resources.
      # @param sizes [Array<String>] unstructured size information about the resource.
      # @param formats [Array<String>] technical format of the resource, e.g. file extension or MIME type.
      # @param version [String] version number of the resource.
      # @param rights_list [Array<Rights>] rights information for this resource.
      # @param descriptions [Array<Description>] all additional information that does not fit in any of the other categories.
      # @param geo_locations [Array<GeoLocations>] spatial region or named place where the data was gathered or about which the data is focused.
      def initialize(identifier:, creators:, titles:, publisher:, publication_year:, subjects: [], contributors: [], dates: [], language: nil, funding_references: [], resource_type: nil, alternate_identifiers: [], related_identifiers: [], sizes: [], formats: [], version: nil, rights_list: [], descriptions: [], geo_locations: []) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
        self.identifier = identifier
        self.creators = creators
        self.titles = titles
        self.publisher = publisher
        self.publication_year = publication_year
        self.subjects = subjects
        self.funding_references = funding_references
        self.contributors = contributors
        self.dates = dates
        self.language = language
        self.resource_type = resource_type
        self.alternate_identifiers = alternate_identifiers
        self.related_identifiers = related_identifiers
        self.sizes = sizes
        self.formats = formats
        self.version = version
        self.rights_list = rights_list
        self.descriptions = descriptions
        self.geo_locations = geo_locations
      end

      # Sets the namespace prefix to be used when writing out XML (defaults to nil)
      # @param prefix [String, nil] The new prefix, or nil to use the default,
      #   unprefixed namespace
      def namespace_prefix=(prefix)
        old_namespace = namespace
        @namespace = ::XML::MappingExtensions::Namespace.new(uri: old_namespace.uri, schema_location: old_namespace.schema_location, prefix: prefix)
      end

      def identifier=(value)
        raise ArgumentError, 'Resource must have an identifier' unless value

        @identifier = value
      end

      def creators=(value)
        raise ArgumentError, 'Resource must have at least one creator' unless value && !value.empty?

        @creators = value
      end

      def titles=(value)
        raise ArgumentError, 'Resource must have at least one title' unless value && !value.empty?

        @titles = value
      end

      # publisher can be entered as a string or a Publisher object, but it will be stored
      # as a Publisher object
      def publisher=(value)
        raise ArgumentError, 'Publisher must have a value' unless value

        @publisher = if value.is_a?(Publisher)
                       value
                     else
                       Publisher.new(value: value)
                     end
      end

      def publication_year=(value)
        raise ArgumentError, 'Resource must have a four-digit publication year' unless value&.to_i&.between?(1000, 9999)

        @publication_year = value.to_i
      end

      def subjects=(value)
        @subjects = value&.select(&:value) || []
      end

      def contributors=(value)
        @contributors = value || []
      end

      def dates=(value)
        @dates = value || []
      end

      def funding_references=(value)
        @funding_references = value || []
      end

      def alternate_identifiers=(value)
        @alternate_identifiers = value || []
      end

      def related_identifiers=(value)
        @related_identifiers = value || []
      end

      def sizes=(value)
        @sizes = value || []
      end

      def formats=(value)
        @formats = value || []
      end

      def version=(value)
        new_value = value&.to_s
        @version = new_value&.strip
      end

      def rights_list=(value)
        @rights_list = value || []
      end

      def descriptions=(value)
        @descriptions = value&.select(&:value) || []
      end

      def geo_locations=(value)
        @geo_locations = value&.select(&:location?) || []
      end

      # @!attribute [rw] identifier
      #   @return [Identifier] a persistent identifier that identifies a resource.
      identifier_node :identifier, 'identifier', class: Identifier

      # @!attribute [rw] creators
      #   @return [Array<Creator>] the main researchers involved working on the data, or the authors of the publication in priority order.
      array_node :creators, 'creators', 'creator', class: Creator

      # @!attribute [rw] titles
      #   @return [Array<Title>] the names or titles by which a resource is known.
      array_node :titles, 'titles', 'title', class: Title

      # @!attribute [rw] publisher
      #   @return [Publisher] the name of the entity that holds, archives, publishes prints, distributes, releases, issues, or produces the resource.
      object_node :publisher, 'publisher', class: Publisher

      # @!attribute [rw] resource_type
      #   @return [ResourceType, nil] the type of the resource
      object_node :resource_type, 'resourceType', class: ResourceType, default_value: nil

      # @!attribute [rw] publication_year
      #   @return [Integer] year when the resource is made publicly available.
      numeric_node :publication_year, 'publicationYear'

      # @!attribute [rw] subjects
      #   @return [Array<Subject>] subjects, keywords, classification codes, or key phrases describing the resource.
      empty_filtering_array_node :subjects, 'subjects', 'subject', class: Subject, default_value: []

      # @!attribute [rw] fundingReferences
      #   @return [Array<FundingReference>] information about financial support (funding) for the resource being registered.
      array_node :funding_references, 'fundingReferences', 'fundingReference', class: FundingReference, default_value: []

      # @!attribute [rw] contributors
      #   @return [Array<Contributor>] institutions or persons responsible for collecting, creating, or otherwise contributing to the developement of the dataset.
      array_node :contributors, 'contributors', 'contributor', class: Contributor, default_value: []

      # @!attribute [rw] dates
      #   @return [Array<Date>] different dates relevant to the work.
      array_node :dates, 'dates', 'date', class: Date, default_value: []

      # @!attribute [rw] language
      #   @return [String] Primary language of the resource: an IETF BCP 47, ISO 639-1 language code.
      text_node :language, 'language', default_value: nil

      # @!attribute [rw] resource_type
      #   @return [ResourceType, nil] the type of the resource. Optional.
      object_node :resource_type, 'resourceType', class: ResourceType, default_value: nil

      # @!attribute [rw] alternate_identifiers
      #   @return [Array<AlternateIdentifier>] an identifier or identifiers other than the primary {Identifier} applied to the resource being registered.
      array_node :alternate_identifiers, 'alternateIdentifiers', 'alternateIdentifier', class: AlternateIdentifier, default_value: []

      # @!attribute [rw] related_identifiers
      #   @return [Array<RelatedIdentifier>] identifiers of related resources.
      array_node :related_identifiers, 'relatedIdentifiers', 'relatedIdentifier', class: RelatedIdentifier, default_value: []

      # @!attribute [rw] sizes
      #   @return [Array<String>] unstructured size information about the resource.
      array_node :sizes, 'sizes', 'size', class: String, default_value: []

      # @!attribute [rw] formats
      #   @return [Array<String>] technical format of the resource, e.g. file extension or MIME type.
      array_node :formats, 'formats', 'format', class: String, default_value: []

      # @!attribute [rw] version
      #   @return [String] version number of the resource. Optional.
      text_node :version, 'version', default_value: nil

      # @!attribute [rw] rights_list
      #   @return [Array<Rights>] rights information for this resource.
      array_node :rights_list, 'rightsList', 'rights', class: Rights, default_value: []

      # @!attribute [rw] descriptions
      #   @return [Array<Description>] all additional information that does not fit in any of the other categories.
      empty_filtering_array_node :descriptions, 'descriptions', 'description', class: Description, default_value: []

      # @!attribute [rw] geo_locations
      #   @return [Array<GeoLocations>] spatial region or named place where the data was gathered or about which the data is focused.
      array_node :geo_locations, 'geoLocations', 'geoLocation', class: GeoLocation, default_value: []

      # Convenience method to get the creators' names.
      # @return [[Array[String]] An array of the creators' names.
      def creator_names
        creators.map { |c| c&.creator_name&.value }
      end

      # Convenience method to get the creators' affiliations. (Bear in mind that each creator
      # can have multiple affiliations.)
      # @return [Array[Array[String]]] An array containing each creator's array of affiliations.
      def creator_affiliations
        creators.map(&:affiliation_names)
      end

      # Convenience method to get the funding contributor.
      # @return [Contributor, nil] the contributor of type FUNDER, if any.
      def funder_contrib
        contributors.find { |c| c.type == ContributorType::FUNDER }
      end

      # Convenience method to get the name of the funding contributor.
      # @deprecated contributor type 'funder' is deprecated. Use {FundingReference} instead.
      # @return [String, nil] the name of the funding contributor, if any.
      def funder_name
        funder_contrib&.name
      end

      # Convenience method to get the funding contributor identifier.
      # @return [NameIdentifier, nil] the identifier of the funding contributor, if any.
      def funder_id
        funder_contrib&.identifier
      end

      # Convenience method to get the funding contributor identifier as a string.
      # @return [String, nil] the string value of the funding contributor's identifier, if any.
      def funder_id_value
        funder_id&.value
      end

      extend Gem::Deprecate
      deprecate(:funder_contrib, FundingReference, 2016, 9)
      deprecate(:funder_name, FundingReference, 2016, 9)
      deprecate(:funder_id, FundingReference, 2016, 9)
      deprecate(:funder_id_value, FundingReference, 2016, 9)

      use_mapping :datacite_3

      read_only_array_node :funding_references, 'fundingReferences', 'fundingReference', class: FundingReference, default_value: [], warn_reason: '<fundingReferences/> not supported in Datacite 3'

      fallback_mapping :datacite_3, :_default
    end

  end
end