• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / pdc_describe / 7031ff5b-e2d7-4285-af8b-e7b55adfc48a

pending completion
7031ff5b-e2d7-4285-af8b-e7b55adfc48a

Pull #899

circleci

mccalluc
Finish removal of explicit if-then in js
Pull Request #899: Move messages up to sidebar on the right which can be toggled

1623 of 1788 relevant lines covered (90.77%)

105.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.08
/app/models/pdc_serialization/datacite.rb
1
# frozen_string_literal: true
2
module PDCSerialization
1✔
3
  # The common use for this class is:
4
  #
5
  #   work = Work.find(123)
6
  #   datacite_xml = PDCSerialization::Datacite.new_from_work(work).to_xml
7
  #
8
  # For testing purposes you can also quickly get an XML serialization with the helper:
9
  #
10
  #   datacite_xml = PDCSerialization::Datacite.skeleton_datacite_xml(...)
11
  #
12
  # You can also pass a PDCMetadata::Resource which is useful to test with a more
13
  # complex dataset without saving the work to the database:
14
  #
15
  #   jsonb_hash = JSON.parse({...}.to_json)
16
  #   resource = PDCMetadata::Resource.new_from_jsonb(jsonb_hash)
17
  #   datacite_xml = PDCSerialization::Datacite.new_from_work_resource(resource).to_xml
18
  #
19
  # For information
20
  #   Datacite schema: https://support.datacite.org/docs/datacite-metadata-schema-v44-properties-overview
21
  #   Datacite mapping gem: https://github.com/CDLUC3/datacite-mapping
22
  #
23
  # rubocop:disable Metrics/ClassLength
24
  class Datacite
1✔
25
    attr_reader :mapping, :errors
1✔
26

27
    def initialize(mapping)
1✔
28
      @mapping = mapping
22✔
29
      @errors = []
22✔
30
    end
31

32
    # Returns the XML serialization for the Datacite record
33
    # Note that the actual XML serialization is done by the datacite-mapping gem.
34
    def to_xml
1✔
35
      @mapping.write_xml
22✔
36
    end
37

38
    # Validate this DataCite XML serialization against the official DataCite schema
39
    # By default we validate against DataCite 4.4. This will shift over time as new
40
    # versions of the datacite schema are released.
41
    # @return [Boolean]
42
    def valid?
1✔
43
      @errors = []
5✔
44
      datacite_xml = Nokogiri::XML(to_xml)
5✔
45
      schema_location = Rails.root.join("config", "schema")
5✔
46
      Dir.chdir(schema_location) do
5✔
47
        xsd = Nokogiri::XML::Schema(File.read("datacite_4_4.xsd"))
5✔
48
        xsd.validate(datacite_xml).each do |error|
5✔
49
          @errors << error
×
50
        end
51
      end
52
      return true if @errors.empty?
5✔
53
      false
×
54
    end
55

56
    ##
57
    # Returns the XML serialization for a valid Datacite skeleton record based on a few required values.
58
    # Useful for early in the workflow when we don't have much data yet and for testing.
59
    #
60
    # @param [String] identifier, e.g "10.1234/567"
61
    # @param [String] title
62
    # @param [String] creator
63
    # @param [String] publisher
64
    # @param [String] publication_year
65
    # @param [String] resource_type
66
    def self.skeleton_datacite_xml(identifier:, title:, creator:, publisher:, publication_year:, resource_type:)
1✔
67
      mapping = ::Datacite::Mapping::Resource.new(
×
68
        identifier: ::Datacite::Mapping::Identifier.new(value: identifier),
69
        creators: [] << ::Datacite::Mapping::Creator.new(name: creator),
70
        titles: [] << ::Datacite::Mapping::Title.new(value: title),
71
        publisher: ::Datacite::Mapping::Publisher.new(value: publisher),
72
        publication_year: publication_year,
73
        resource_type: datacite_resource_type(resource_type)
74
      )
75
      mapping.write_xml
×
76
    end
77

78
    ##
79
    # Creates a PDCSerialization::Datacite object from a Work
80
    def self.new_from_work(work)
1✔
81
      new_from_work_resource(work.resource)
5✔
82
    end
83

84
    ##
85
    # Creates a PDCSerialization::Datacite object from a PDCMetadata::Resource
86
    def self.new_from_work_resource(resource)
1✔
87
      mapping = ::Datacite::Mapping::Resource.new(
22✔
88
        identifier: ::Datacite::Mapping::Identifier.new(value: resource.doi),
89
        creators: creators_from_work_resource(resource.creators),
90
        contributors: contributors_from_work_resource(resource.contributors),
91
        descriptions: descriptions_from_work_resource(resource.description),
92
        titles: titles_from_work_resource(resource.titles),
93
        publisher: ::Datacite::Mapping::Publisher.new(value: resource.publisher),
94
        publication_year: resource.publication_year,
95
        resource_type: datacite_resource_type(resource.resource_type),
96
        related_identifiers: related_identifiers_from_work_resource(resource),
97
        rights_list: rights_from_work_resource(resource),
98
        version: resource.version_number,
99
        funding_references: funding_references_from_work_resource(resource)
100
      )
101
      Datacite.new(mapping)
22✔
102
    end
103

104
    class << self
1✔
105
      def datacite_resource_type(value)
1✔
106
        resource_type = ::Datacite::Mapping::ResourceTypeGeneral.find_by_value(value)
22✔
107
        ::Datacite::Mapping::ResourceType.new(resource_type_general: resource_type)
22✔
108
      end
109

110
      def datacite_contributor_type(value)
1✔
111
        ::Datacite::Mapping::ContributorType.find_by_value(value)
×
112
      end
113

114
      private
1✔
115

116
        def creators_from_work_resource(creators)
1✔
117
          creators.sort_by(&:sequence).map do |creator|
22✔
118
            ::Datacite::Mapping::Creator.new(
43✔
119
              name: creator.value,
120
              given_name: creator.given_name,
121
              family_name: creator.family_name,
122
              identifier: name_identifier_from_identifier(creator.identifier),
123
              affiliations: nil
124
            )
125
          end
126
        end
127

128
        def contributors_from_work_resource(contributors)
1✔
129
          contributors.sort_by(&:sequence).map do |contributor|
22✔
130
            ::Datacite::Mapping::Contributor.new(
×
131
              name: contributor.value,
132
              identifier: name_identifier_from_identifier(contributor.identifier),
133
              affiliations: nil,
134
              type: datacite_contributor_type(contributor.type)
135
            )
136
          end
137
        end
138

139
        ##
140
        # We are deliberately not differentiating between "abstract", "methods" and other kinds of descriptions.
141
        # We are instead putting all description into the same field and classifying it as "OTHER".
142
        def descriptions_from_work_resource(description)
1✔
143
          return [] if description.blank?
22✔
144
          [] << ::Datacite::Mapping::Description.new(type: ::Datacite::Mapping::DescriptionType::OTHER, value: description)
22✔
145
        end
146

147
        def name_identifier_from_identifier(identifier)
1✔
148
          return nil if identifier.nil?
43✔
149
          ::Datacite::Mapping::NameIdentifier.new(
×
150
            scheme: identifier.scheme,
151
            scheme_uri: identifier.scheme_uri,
152
            value: identifier.value
153
          )
154
        end
155

156
        def titles_from_work_resource(titles)
1✔
157
          titles.map do |title|
22✔
158
            if title.main?
23✔
159
              ::Datacite::Mapping::Title.new(value: title.title)
22✔
160
            else
161
              title_type = ::Datacite::Mapping::TitleType.find_by_value(title.title_type)
1✔
162
              ::Datacite::Mapping::Title.new(value: title.title, type: title_type) if title_type
1✔
163
            end
164
          end.compact
165
        end
166

167
        ##
168
        # Add related identifiers from various locations in the metadata.
169
        # @param [PDCMetadata::Resource] resource
170
        # @return [<::Datacite::Mapping::RelatedIdentifier>]
171
        def related_identifiers_from_work_resource(resource)
1✔
172
          related_identifiers = []
22✔
173
          related_identifiers = related_identifiers.union(extract_ark_as_related_identfier(resource))
22✔
174
          related_identifiers = related_identifiers.union(extract_related_objects(resource))
22✔
175
          related_identifiers
22✔
176
        end
177

178
        def extract_ark_as_related_identfier(resource)
1✔
179
          related_ids = []
22✔
180
          if resource.ark.present?
22✔
181
            related_ids << ::Datacite::Mapping::RelatedIdentifier.new(
6✔
182
              relation_type: ::Datacite::Mapping::RelationType::IS_IDENTICAL_TO,
183
              value: resource.ark,
184
              identifier_type: ::Datacite::Mapping::RelatedIdentifierType::ARK
185
            )
186
          end
187
          related_ids
22✔
188
        end
189

190
        def extract_related_objects(resource)
1✔
191
          related_objects = []
22✔
192
          resource.related_objects.each do |ro|
22✔
193
            related_objects << ::Datacite::Mapping::RelatedIdentifier.new(
×
194
              relation_type: ::Datacite::Mapping::RelationType.find_by_value(ro.relation_type),
195
              value: ro.related_identifier,
196
              identifier_type: ::Datacite::Mapping::RelatedIdentifierType.find_by_value(ro.related_identifier_type)
197
            )
198
          end
199
          related_objects
22✔
200
        end
201

202
        def rights_from_work_resource(resource)
1✔
203
          rights = []
22✔
204
          if resource.rights.present?
22✔
205
            rights << ::Datacite::Mapping::Rights.new(
22✔
206
              value: resource.rights.name,
207
              uri: resource.rights.uri,
208
              identifier: resource.rights.identifier
209
            )
210
          end
211
          rights
22✔
212
        end
213

214
        def funding_references_from_work_resource(resource)
1✔
215
          resource.funders.map do |funder|
22✔
216
            award = ::Datacite::Mapping::AwardNumber.new(uri: funder.award_uri, value: funder.award_number)
1✔
217
            if funder.ror.present?
1✔
218
              type = ::Datacite::Mapping::FunderIdentifierType::ROR
×
219
              funder_identifier = ::Datacite::Mapping::FunderIdentifier.new(type: type, value: funder.ror)
×
220
              ::Datacite::Mapping::FundingReference.new(name: funder.funder_name, award_number: award, identifier: funder_identifier)
×
221
            else
222
              ::Datacite::Mapping::FundingReference.new(name: funder.funder_name, award_number: award)
1✔
223
            end
224
          end
225
        end
226
      end
227
  end
228
  # rubocop:enable Metrics/ClassLength
229
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc