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

pulibrary / pdc_describe / 9091a1ae-29be-458c-984a-339d213919c4

12 Dec 2024 07:41PM UTC coverage: 26.434% (-69.7%) from 96.113%
9091a1ae-29be-458c-984a-339d213919c4

Pull #2000

circleci

jrgriffiniii
Removing integration with ActiveStorage
Pull Request #2000: Bump actionpack from 7.2.1.1 to 7.2.2.1

945 of 3575 relevant lines covered (26.43%)

0.35 hits per line

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

28.57
/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
×
29
      @errors = []
×
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
×
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 = []
×
44
      datacite_xml = Nokogiri::XML(to_xml)
×
45
      schema_location = Rails.root.join("config", "schema")
×
46
      Dir.chdir(schema_location) do
×
47
        xsd = Nokogiri::XML::Schema(File.read("datacite_4_4.xsd"))
×
48
        xsd.validate(datacite_xml).each do |error|
×
49
          @errors << error
×
50
        end
51
      end
52
      return true if @errors.empty?
×
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:, resource_type_general:)
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:,
73
        resource_type: datacite_resource_type(resource_type_general, 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)
×
82
    end
83

84
    ##
85
    # Creates a PDCSerialization::Datacite object from a PDCMetadata::Resource
86
    # rubocop:disable Metrics/MethodLength
87
    def self.new_from_work_resource(resource)
1✔
88
      mapping = ::Datacite::Mapping::Resource.new(
×
89
        identifier: ::Datacite::Mapping::Identifier.new(value: resource.doi),
90
        creators: creators_from_work_resource(resource.creators),
91
        contributors: individual_contributors_from_work_resource(resource.individual_contributors) +
92
          organizational_contributors_from_work_resource(resource.organizational_contributors),
93
        descriptions: descriptions_from_work_resource(resource.description),
94
        titles: titles_from_work_resource(resource.titles),
95
        publisher: ::Datacite::Mapping::Publisher.new(value: resource.publisher),
96
        publication_year: resource.publication_year,
97
        resource_type: datacite_resource_type(resource.resource_type_general, resource.resource_type),
98
        related_identifiers: related_identifiers_from_work_resource(resource),
99
        rights_list: rights_from_work_resource(resource),
100
        version: resource.version_number,
101
        funding_references: funding_references_from_work_resource(resource)
102
      )
103
      Datacite.new(mapping)
×
104
    end
105
    # rubocop:enable Metrics/MethodLength
106

107
    class << self
1✔
108
      def datacite_resource_type(resource_type_general, value)
1✔
109
        resource_type = ::Datacite::Mapping::ResourceTypeGeneral.find_by_value(resource_type_general)
×
110
        ::Datacite::Mapping::ResourceType.new(resource_type_general: resource_type, value:)
×
111
      end
112

113
      def datacite_contributor_type(key)
1✔
114
        ::Datacite::Mapping::ContributorType.find_by_key(key.upcase.to_sym)
×
115
      end
116

117
      private
1✔
118

119
        def creators_from_work_resource(creators)
1✔
120
          creators.sort_by(&:sequence).map do |creator|
×
121
            ::Datacite::Mapping::Creator.new(
×
122
              name: creator.value,
123
              given_name: creator.given_name,
124
              family_name: creator.family_name,
125
              identifier: name_identifier_from_identifier(creator.identifier),
126
              affiliations: creator.affiliations&.map { |affiliation| ::Datacite::Mapping::Affiliation.new(**affiliation.datacite_attributes) }
×
127
            )
128
          end
129
        end
130

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

142
        def organizational_contributors_from_work_resource(contributors)
1✔
143
          contributors.map do |contributor|
×
144
            ::Datacite::Mapping::Contributor.new(
×
145
              name: contributor.value,
146
              identifier: name_identifier_from_identifier(contributor.identifier),
147
              affiliations: nil,
148
              type: datacite_contributor_type(contributor.type)
149
            )
150
          end
151
        end
152

153
        ##
154
        # We are deliberately not differentiating between "abstract", "methods" and other kinds of descriptions.
155
        # We are instead putting all description into the same field and classifying it as "OTHER".
156
        def descriptions_from_work_resource(description)
1✔
157
          return [] if description.blank?
×
158
          [] << ::Datacite::Mapping::Description.new(type: ::Datacite::Mapping::DescriptionType::OTHER, value: description)
×
159
        end
160

161
        def name_identifier_from_identifier(identifier)
1✔
162
          return nil if identifier.nil?
×
163
          ::Datacite::Mapping::NameIdentifier.new(
×
164
            scheme: identifier.scheme,
165
            scheme_uri: identifier.scheme_uri,
166
            value: identifier.value
167
          )
168
        end
169

170
        def titles_from_work_resource(titles)
1✔
171
          titles.map do |title|
×
172
            if title.main?
×
173
              ::Datacite::Mapping::Title.new(value: title.title)
×
174
            else
175
              title_type = ::Datacite::Mapping::TitleType.find_by_value(title.title_type)
×
176
              ::Datacite::Mapping::Title.new(value: title.title, type: title_type) if title_type
×
177
            end
178
          end.compact
179
        end
180

181
        ##
182
        # Add related identifiers from various locations in the metadata.
183
        # @param [PDCMetadata::Resource] resource
184
        # @return [<::Datacite::Mapping::RelatedIdentifier>]
185
        def related_identifiers_from_work_resource(resource)
1✔
186
          related_identifiers = []
×
187
          related_identifiers = related_identifiers.union(extract_ark_as_related_identfier(resource))
×
188
          related_identifiers = related_identifiers.union(extract_related_objects(resource))
×
189
          related_identifiers
×
190
        end
191

192
        def extract_ark_as_related_identfier(resource)
1✔
193
          related_ids = []
×
194
          if resource.ark.present?
×
195
            related_ids << ::Datacite::Mapping::RelatedIdentifier.new(
×
196
              relation_type: ::Datacite::Mapping::RelationType::IS_IDENTICAL_TO,
197
              value: resource.ark,
198
              identifier_type: ::Datacite::Mapping::RelatedIdentifierType::ARK
199
            )
200
          end
201
          related_ids
×
202
        end
203

204
        def extract_related_objects(resource)
1✔
205
          related_objects = []
×
206
          resource.related_objects.each do |ro|
×
207
            related_objects << ::Datacite::Mapping::RelatedIdentifier.new(
×
208
              relation_type: ::Datacite::Mapping::RelationType.find_by_value(ro.relation_type),
209
              value: ro.related_identifier,
210
              identifier_type: ::Datacite::Mapping::RelatedIdentifierType.find_by_value(ro.related_identifier_type)
211
            )
212
          end
213
          related_objects
×
214
        end
215

216
        def rights_from_work_resource(resource)
1✔
217
          rights = []
×
218
          if resource.rights_many.present?
×
219
            resource.rights_many.each do |r|
×
220
              rights << ::Datacite::Mapping::Rights.new(value: r.name, uri: r.uri, identifier: r.identifier)
×
221
            end
222
          end
223
          rights
×
224
        end
225

226
        def funding_references_from_work_resource(resource)
1✔
227
          type_ror = ::Datacite::Mapping::FunderIdentifierType::ROR
×
228
          resource.funders.map do |funder|
×
229
            award = if funder.award_number.present?
×
230
                      ::Datacite::Mapping::AwardNumber.new(uri: funder.award_uri, value: funder.award_number)
×
231
                    end
232
            funder_identifier = if funder.ror.present?
×
233
                                  ::Datacite::Mapping::FunderIdentifier.new(type: type_ror, value: funder.ror)
×
234
                                end
235
            ::Datacite::Mapping::FundingReference.new(name: funder.funder_name, award_number: award, identifier: funder_identifier)
×
236
          end
237
        end
238
      end
239
  end
240
  # rubocop:enable Metrics/ClassLength
241
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