uc3-dmp-id 0.0.140 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,7 @@ require 'time'
6
6
  module Uc3DmpId
7
7
  class UpdaterError < StandardError; end
8
8
 
9
+ # Class that handles updating a DMP ID
9
10
  class Updater
10
11
  class << self
11
12
  # Update a DMP ID
@@ -13,7 +14,7 @@ module Uc3DmpId
13
14
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
14
15
  # -------------------------------------------------------------------------
15
16
  def update(provenance:, p_key:, json: {}, note: nil, logger: nil)
16
- raise UpdaterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
17
+ raise UpdaterError, Helper::MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
17
18
 
18
19
  mods = Helper.parse_json(json: json).fetch('dmp', {})
19
20
  p_key = Helper.append_pk_prefix(p_key: p_key)
@@ -26,18 +27,19 @@ module Uc3DmpId
26
27
  logger.debug(message: "Latest version for PK #{p_key}", details: latest_version) if logger.respond_to?(:debug)
27
28
 
28
29
  # Verify that the DMP ID is updateable with the info passed in
29
- errs = _updateable?(provenance: provenance, p_key: p_key, latest_version: latest_version['dmp'], mods: mods['dmp'])
30
- logger.error(message: errs.join(', ')) if errs.is_a?(Array) && errs.any?
30
+ errs = _updateable?(provenance: provenance, p_key: p_key, latest_version: latest_version['dmp'],
31
+ mods: mods['dmp'])
32
+ logger.error(message: errs.join(', ')) if logger.respond_to?(:error) && errs.is_a?(Array) && errs.any?
31
33
  raise UpdaterError, errs if errs.is_a?(Array) && errs.any?
32
34
  # Don't continue if nothing has changed!
33
- raise UpdaterError, MSG_NO_CHANGE if Helper.eql?(dmp_a: latest_version, dmp_b: mods)
35
+ raise UpdaterError, Helper::MSG_NO_CHANGE if Helper.eql?(dmp_a: latest_version, dmp_b: mods)
34
36
 
35
37
  # Version the DMP ID record (if applicable).
36
38
  owner = latest_version['dmphub_provenance_id']
37
39
  updater = provenance['PK']
38
- version = Versioner.generate_version(client: client, latest_version: latest_version, owner: owner, updater: updater,
39
- logger: logger)
40
- raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if version.nil?
40
+ version = Versioner.generate_version(client: client, latest_version: latest_version, owner: owner,
41
+ updater: updater, logger: logger)
42
+ raise UpdaterError, Helper::MSG_DMP_UNABLE_TO_VERSION if version.nil?
41
43
 
42
44
  # Remove the version info because we don't want to save it on the record
43
45
  version.delete('dmphub_versions')
@@ -45,105 +47,132 @@ module Uc3DmpId
45
47
  # Splice the assertions
46
48
  version = _process_modifications(owner: owner, updater: updater, version: version, mods: mods, note: note,
47
49
  logger: logger)
48
-
49
50
  # Set the :modified timestamps
50
- now = Time.now.utc.iso8601
51
- version['modified'] = now
51
+ now = Time.now.utc
52
+ version['modified'] = now.iso8601
53
+ version['dmphub_modification_day'] = now.strftime('%Y-%m-%d')
52
54
 
53
55
  # Save the changes
54
56
  resp = client.put_item(json: version, logger: logger)
55
- raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if resp.nil?
57
+ raise UpdaterError, Helper::MSG_DMP_UNABLE_TO_VERSION if resp.nil?
56
58
 
57
59
  # Send the updates to EZID
58
60
  _post_process(provenance: provenance, json: version, logger: logger)
59
61
 
60
62
  # Return the new version record
61
63
  logger.info(message: "Updated DMP ID: #{p_key}") if logger.respond_to?(:debug)
62
- Helper.cleanse_dmp_json(json: JSON.parse({ dmp: version }.to_json))
64
+
65
+ # Append the :dmphub_versions Array
66
+ json = JSON.parse({ dmp: version }.to_json)
67
+ json = Versioner.append_versions(p_key: p_key, dmp: json, client: client, logger: logger)
68
+ Helper.cleanse_dmp_json(json: json)
63
69
  end
64
70
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
65
71
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
66
72
 
67
73
  # Save a DMP ID's corresponding narrative PDF document to S3 and add the download URL for that
68
74
  # document to the DMP ID's :dmpraodmap_related_identifiers array as an `is_metadata_for` relation
75
+ # rubocop:disable Metrics/AbcSize
69
76
  def attach_narrative(provenance:, p_key:, url:, logger: nil)
70
- raise UpdaterError, MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
77
+ raise UpdaterError, Helper::MSG_DMP_INVALID_DMP_ID unless p_key.is_a?(String) && !p_key.strip.empty?
71
78
 
72
79
  # fetch the existing latest version of the DMP ID
73
80
  client = Uc3DmpDynamo::Client.new(logger: logger)
74
81
  dmp = Finder.by_pk(p_key: p_key, client: client, logger: logger, cleanse: false)
75
- logger.info(message: "Existing latest record", details: dmp) if logger.respond_to?(:debug)
76
- raise UpdaterError, MSG_DMP_FORBIDDEN unless provenance.is_a?(Hash) && !provenance['PK'].nil? &&
77
- provenance['Pk'] == dmp['dmphub_provenance_id']
82
+ logger.info(message: 'Existing latest record', details: dmp) if logger.respond_to?(:debug)
83
+ raise UpdaterError, Helper::MSG_DMP_FORBIDDEN unless provenance.is_a?(Hash) && !provenance['PK'].nil? &&
84
+ provenance['PK'] == dmp['dmp']['dmphub_provenance_id']
78
85
 
79
86
  # Add the download URl for the PDF as a related identifier on the DMP ID record
80
87
  annotated = Helper.annotate_dmp_json(provenance: provenance, p_key: p_key, json: dmp['dmp'])
81
88
  annotated['dmproadmap_related_identifiers'] = [] if annotated['dmproadmap_related_identifiers'].nil?
82
- annotated['dmproadmap_related_identifiers'] << {
89
+ annotated['dmproadmap_related_identifiers'] << JSON.parse({
83
90
  descriptor: 'is_metadata_for', work_type: 'output_management_plan', type: 'url', identifier: url
84
- }
91
+ }.to_json)
85
92
 
86
93
  # Save the changes without creating a new version!
87
94
  resp = client.put_item(json: annotated, logger: logger)
88
- raise UpdaterError, MSG_DMP_UNABLE_TO_VERSION if resp.nil?
95
+ raise UpdaterError, Helper::MSG_DMP_UNABLE_TO_VERSION if resp.nil?
89
96
 
90
97
  logger.info(message: "Added DMP ID narrative for PK: #{p_key}, Narrative: #{url}") if logger.respond_to?(:debug)
91
98
  true
92
99
  end
100
+ # rubocop:enable Metrics/AbcSize
93
101
 
94
102
  private
95
103
 
96
104
  # Check to make sure the incoming JSON is valid, the DMP ID requested matches the DMP ID in the JSON
105
+ # rubocop:disable Metrics/AbcSize
97
106
  def _updateable?(provenance:, p_key:, latest_version: {}, mods: {})
98
107
  # Validate the incoming JSON first
99
108
  errs = Validator.validate(mode: 'author', json: JSON.parse({ dmp: mods }.to_json))
100
109
  return errs.join(', ') if errs.is_a?(Array) && errs.any? && errs.first != Validator::MSG_VALID_JSON
101
110
  # Fail if the provenance is not defined
102
- return [MSG_DMP_FORBIDDEN] unless provenance.is_a?(Hash) && !provenance['PK'].nil?
111
+ return [Helper::MSG_DMP_FORBIDDEN] unless provenance.is_a?(Hash) && !provenance['PK'].nil?
103
112
  # Verify that the JSON is for the same DMP in the PK
104
- return [MSG_DMP_FORBIDDEN] unless Helper.dmp_id_to_pk(json: mods.fetch('dmp_id', {})) == p_key
113
+ return [Helper::MSG_DMP_FORBIDDEN] unless Helper.dmp_id_to_pk(json: mods.fetch('dmp_id', {})) == p_key
105
114
  # Bail out if the DMP ID could not be found or the PKs do not match for some reason
106
- return [MSG_DMP_UNKNOWN] if latest_version.nil? || latest_version.fetch['PK'] != p_key
115
+ return [Helper::MSG_DMP_UNKNOWN] unless latest_version.is_a?(Hash) && latest_version['PK'] == p_key
107
116
  end
117
+ # rubocop:enable Metrics/AbcSize
108
118
 
119
+ # rubocop:disable Metrics/ParameterLists
109
120
  def _process_modifications(owner:, updater:, version:, mods:, note: nil, logger: nil)
110
121
  return version unless mods.is_a?(Hash) && !updater.nil?
111
122
  return mods unless version.is_a?(Hash) && !owner.nil?
112
123
 
113
- # Splice together any assertions that may have been made while the user was editing the DMP ID
114
- updated = Asserter.splice(latest_version: version, modified_version: mods, logger: logger) if owner == updater
124
+ updated = if owner == updater
125
+ # Splice together any assertions that may have been made while the user was editing the DMP ID
126
+ Asserter.splice(latest_version: version, modified_version: mods, logger: logger)
127
+ else
128
+ # Attach the incoming changes as an assertion to the DMP ID since the updater is NOT the owner
129
+ Asserter.add(updater: updater, latest_version: version, modified_version: mods, note: note,
130
+ logger: logger)
131
+ end
115
132
 
116
- # Attach the incoming changes as an assertion to the DMP ID since the updater is NOT the owner
117
- updated = Asserter.add(updater: updater, dmp: version, mods: mods, note: note, logger: logger) if owner != updater
118
-
119
- merge_versions(latest_version: version, mods: updated, logger: logger)
133
+ _merge_versions(latest_version: version, mods: updated, logger: logger)
120
134
  end
135
+ # rubocop:enable Metrics/ParameterLists
121
136
 
122
137
  # We are replacing the latest version with the modifcations but want to retain the PK, SK and any dmphub_ prefixed
123
138
  # entries in the metadata so that we do not lose creation timestamps, provenance ids, etc.
124
- def merge_versions(latest_version:, mods:, logger: nil)
125
- logger.debug(message: 'Modifications before merge.', details: mods)
139
+ # rubocop:disable Metrics/AbcSize
140
+ def _merge_versions(latest_version:, mods:, logger: nil)
141
+ return mods unless latest_version.is_a?(Hash)
142
+
143
+ logger.debug(message: 'Modifications before merge.', details: mods) if logger.respond_to?(:debug)
126
144
  keys_to_retain = latest_version.keys.select do |key|
127
- (key.start_with?('dmphub_') && !%w[dmphub_assertions].include?(key)) ||
145
+ (key.start_with?('dmphub_') && !%w[dmphub_modifications dmphub_versions].include?(key)) ||
128
146
  key.start_with?('PK') || key.start_with?('SK')
129
147
  end
130
148
  keys_to_retain.each do |key|
131
149
  mods[key] = latest_version[key]
132
150
  end
133
- logger.debug(message: 'Modifications after merge.', details: mods)
151
+ logger.debug(message: 'Modifications after merge.', details: mods) if logger.respond_to?(:debug)
134
152
  mods
135
153
  end
154
+ # rubocop:enable Metrics/AbcSize
136
155
 
137
156
  # Once the DMP has been updated, we need to update it's DOI metadata
138
157
  # -------------------------------------------------------------------------
158
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
159
+ # rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
139
160
  def _post_process(provenance:, json:, logger: nil)
140
- return false unless json.is_a?(Hash)
161
+ return false unless json.is_a?(Hash) && provenance.is_a?(Hash) && !provenance['PK'].nil? &&
162
+ !json['dmphub_provenance_id'].nil?
163
+
164
+ publishable = provenance['PK'] == json['dmphub_provenance_id']
165
+ return true unless publishable
166
+
167
+ # TODO: we will want to send and related_identifiers in :dmphub_modifications as well!!!
141
168
 
142
- # Indicate whether or not the updater is the provenance system
143
- json['dmphub_updater_is_provenance'] = provenance['PK'] == json['dmphub_provenance_id']
144
- # Publish the change to the EventBridge
145
169
  publisher = Uc3DmpEventBridge::Publisher.new
146
- publisher.publish(source: 'DmpUpdater', event_type: 'EZID update', dmp: json, logger: logger)
170
+ # Publish the change to the EventBridge if the updater is the owner of the DMP ID
171
+ if publishable && logger.respond_to?(:debug)
172
+ logger.debug(message: 'Sending event for EZID publication',
173
+ details: json)
174
+ end
175
+ publisher.publish(source: 'DmpUpdater', event_type: 'EZID update', dmp: json, logger: logger) if publishable
147
176
 
148
177
  # Determine if there are any related identifiers that we should try to fetch a citation for
149
178
  citable_identifiers = Helper.citable_related_identifiers(dmp: json)
@@ -155,10 +184,16 @@ module Uc3DmpId
155
184
  SK: json['SK'],
156
185
  dmproadmap_related_identifiers: citable_identifiers
157
186
  }
158
- logger.debug(message: "Fetching citations", details: citable_identifiers)
159
- publisher.publish(source: 'DmpUpdater', dmp: json, event_type: 'Citation Fetch', detail: citer_detail, logger: logger)
187
+ if logger.respond_to?(:debug)
188
+ logger.debug(message: 'Sending event to fetch citations',
189
+ details: citable_identifiers)
190
+ end
191
+ publisher.publish(source: 'DmpUpdater', dmp: json, event_type: 'Citation Fetch', detail: citer_detail,
192
+ logger: logger)
160
193
  true
161
194
  end
195
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
196
+ # rubocop:enable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
162
197
  end
163
198
  end
164
199
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'json-schema'
4
+
3
5
  module Uc3DmpId
4
6
  class Uc3DmpIdValidatorError < StandardError; end
5
7
 
8
+ # Method that compares incoming JSON against our JSON Schema and provides detailed errors
6
9
  class Validator
7
10
  # Valid Validation modes are:
8
11
  # - :author --> system of provenance is attempting to create or update
@@ -18,8 +21,7 @@ module Uc3DmpId
18
21
 
19
22
  class << self
20
23
  # Validate the specified DMP's :json against the schema for the specified :mode
21
- #
22
- # ------------------------------------------------------------------------------------
24
+ # rubocop:disable Metrics/AbcSize
23
25
  def validate(mode:, json:)
24
26
  json = Helper.parse_json(json: json)
25
27
  return [MSG_EMPTY_JSON] if json.nil? || !VALIDATION_MODES.include?(mode)
@@ -36,6 +38,7 @@ module Uc3DmpId
36
38
  rescue JSON::Schema::ValidationError => e
37
39
  ["#{MSG_INVALID_JSON} - #{e.message}"]
38
40
  end
41
+ # rubocop:enable Metrics/AbcSize
39
42
 
40
43
  # ------------------------------------------------------------------------------------
41
44
  # METHODS BELOW ARE ONLY MEANT TO BE INVOKED FROM WITHIN THIS MODULE
@@ -45,10 +48,13 @@ module Uc3DmpId
45
48
  # ------------------------------------------------------------------------------------
46
49
  def _load_schema(mode:)
47
50
  # Instatiate the matching schema
48
- schema = "Uc3DmpId::Schemas::#{mode.to_s.downcase.capitalize}".split('::').inject(Object) { |o,c| o.const_get c }
51
+ schema = "Uc3DmpId::Schemas::#{mode.to_s.downcase.capitalize}".split('::').inject(Object) do |o, c|
52
+ o.const_get c
53
+ end
49
54
  schema.respond_to?(:load) ? schema.load : nil
55
+ rescue NameError
56
+ nil
50
57
  end
51
-
52
58
  end
53
59
  end
54
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Uc3DmpId
4
- VERSION = '0.0.140'
4
+ VERSION = '0.1.0'
5
5
  end
@@ -6,11 +6,11 @@ require 'time'
6
6
  module Uc3DmpId
7
7
  class VersionerError < StandardError; end
8
8
 
9
+ # Logic to handle the versioning of DMP IDs and to retrieve the versions for a PK
9
10
  class Versioner
10
11
  SOURCE = 'Uc3DmpId::Versioner'
11
12
 
12
13
  class << self
13
-
14
14
  # Find the DMP ID's versions
15
15
  # -------------------------------------------------------------------------
16
16
  def get_versions(p_key:, client: nil, logger: nil)
@@ -29,11 +29,12 @@ module Uc3DmpId
29
29
 
30
30
  # Generate a snapshot of the current latest version of the DMP ID using the existing :modified as
31
31
  # the new SK.
32
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
32
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength,
33
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
33
34
  def generate_version(client:, latest_version:, owner:, updater:, logger: nil)
34
35
  # Only create a version if the Updater is not the Owner OR the changes have happened on a different day
35
36
  mod_time = Time.parse(latest_version.fetch('modified', Time.now.utc.iso8601))
36
- now = Time.now
37
+ now = Time.now.utc
37
38
  if mod_time.nil? || !(now - mod_time).is_a?(Float)
38
39
  logger.error(message: "#{SOURCE} unable to determine mod time: #{mod_time}") if logger.respond_to?(:debug)
39
40
  return latest_version
@@ -42,9 +43,11 @@ module Uc3DmpId
42
43
  # Only allow a new version if the owner and updater are the same and it has been at least one hour since
43
44
  # the last version was created
44
45
  same_hour = (now - mod_time).round <= 3600
45
- if owner != updater || (owner == updater && same_hour)
46
+ if owner == updater && same_hour
46
47
  logger.debug(message: "#{SOURCE} same owner and updater? #{owner == updater}") if logger.respond_to?(:debug)
47
- logger.debug(message: "#{SOURCE} already updated within the past hour? #{same_hour}") if logger.respond_to?(:debug)
48
+ if logger.respond_to?(:debug)
49
+ logger.debug(message: "#{SOURCE} already updated within the past hour? #{same_hour}")
50
+ end
48
51
  return latest_version
49
52
  end
50
53
 
@@ -52,6 +55,7 @@ module Uc3DmpId
52
55
  # We essentially make a snapshot of the record before making changes
53
56
  prior = Helper.deep_copy_dmp(obj: latest_version)
54
57
  prior['SK'] = "#{Helper::SK_DMP_PREFIX}#{latest_version['modified'] || Time.now.utc.iso8601}"
58
+
55
59
  # Create the prior version record ()
56
60
  client = client.nil? ? Uc3DmpDynamo::Client.new : client
57
61
  resp = client.put_item(json: prior, logger: logger)
@@ -61,7 +65,8 @@ module Uc3DmpId
61
65
  logger.info(message: msg, details: prior) if logger.respond_to?(:debug)
62
66
  latest_version
63
67
  end
64
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
68
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength,
69
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
65
70
 
66
71
  # Build the :dmphub_versions array and attach it to the DMP JSON
67
72
  # rubocop:disable Metrics/AbcSize
@@ -72,11 +77,15 @@ module Uc3DmpId
72
77
  results = get_versions(p_key: p_key, client: client, logger: logger)
73
78
  return json unless results.length > 1
74
79
 
80
+ # TODO: we may want to include milliseconds in the future if we get increased volume so that
81
+ # we don't end up with duplicate URLs if versions are made within the same second
75
82
  versions = results.map do |ver|
76
83
  next if ver['modified'].nil?
84
+
85
+ base_url = "#{Helper.landing_page_url}#{Helper.remove_pk_prefix(p_key: p_key)}"
77
86
  {
78
87
  timestamp: ver['modified'],
79
- url: "#{Helper.landing_page_url}#{Helper.remove_pk_prefix(p_key: p_key)}?version=#{ver['modified']}"
88
+ url: dmp['dmp']['modified'] == ver['modified'] ? base_url : "#{base_url}?version=#{ver['modified']}"
80
89
  }
81
90
  end
82
91
  json['dmp']['dmphub_versions'] = JSON.parse(versions.to_json)
data/lib/uc3-dmp-id.rb CHANGED
@@ -18,17 +18,7 @@ require 'uc3-dmp-id/versioner'
18
18
  require 'uc3-dmp-id/schemas/amend'
19
19
  require 'uc3-dmp-id/schemas/author'
20
20
 
21
+ # Support for working with DMP IDs
21
22
  module Uc3DmpId
22
- MSG_DMP_EXISTS = 'DMP already exists. Try :update instead.'
23
- MSG_DMP_FORBIDDEN = 'You do not have permission.'
24
- MSG_DMP_INVALID_DMP_ID = 'Invalid DMP ID format.'
25
- MSG_DMP_NO_DMP_ID = 'A DMP ID could not be registered at this time.'
26
- MSG_DMP_NO_HISTORICALS = 'You cannot modify a historical version of the DMP.'
27
- MSG_NO_OWNER_ORG = 'Could not determine ownership of the DMP ID.'
28
- MSG_DMP_NO_TOMBSTONE = 'Unable to tombstone the DMP ID at this time.'
29
- MSG_DMP_NO_UPDATE = 'Unable to update the DMP ID at this time.'
30
- MSG_DMP_NOT_FOUND = 'DMP does not exist.'
31
- MSG_DMP_UNABLE_TO_VERSION = 'Unable to version this DMP.'
32
- MSG_DMP_UNKNOWN = 'DMP does not exist. Try :create instead.'
33
23
  end
34
24
  # rubocop:enable Naming/FileName
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uc3-dmp-id
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.140
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Riley
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-08-17 00:00:00.000000000 Z
11
+ date: 2023-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -66,62 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.0'
69
- - !ruby/object:Gem::Dependency
70
- name: byebug
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - '='
74
- - !ruby/object:Gem::Version
75
- version: 11.1.3
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - '='
81
- - !ruby/object:Gem::Version
82
- version: 11.1.3
83
- - !ruby/object:Gem::Dependency
84
- name: rspec
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - '='
88
- - !ruby/object:Gem::Version
89
- version: 3.9.0
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - '='
95
- - !ruby/object:Gem::Version
96
- version: 3.9.0
97
- - !ruby/object:Gem::Dependency
98
- name: rubocop
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - '='
102
- - !ruby/object:Gem::Version
103
- version: 1.50.2
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - '='
109
- - !ruby/object:Gem::Version
110
- version: 1.50.2
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop-rspec
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - '='
116
- - !ruby/object:Gem::Version
117
- version: 2.20.0
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - '='
123
- - !ruby/object:Gem::Version
124
- version: 2.20.0
125
69
  description: Helpers for working with JSON that represents a DMP ID
126
70
  email:
127
71
  - brian.riley@ucop.edu