cocina-models 0.77.0 → 0.80.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +31 -2
  3. data/description_types.yml +2 -0
  4. data/docs/description_types.md +1 -0
  5. data/lib/cocina/generator/schema.rb +2 -2
  6. data/lib/cocina/generator/schema_base.rb +10 -0
  7. data/lib/cocina/generator/schema_value.rb +0 -4
  8. data/lib/cocina/models/access_role.rb +1 -0
  9. data/lib/cocina/models/access_role_member.rb +1 -0
  10. data/lib/cocina/models/admin_policy_access_template.rb +1 -0
  11. data/lib/cocina/models/admin_policy_administrative.rb +1 -0
  12. data/lib/cocina/models/admin_policy_with_metadata.rb +1 -0
  13. data/lib/cocina/models/applies_to.rb +1 -0
  14. data/lib/cocina/models/collection.rb +1 -0
  15. data/lib/cocina/models/collection_access.rb +1 -0
  16. data/lib/cocina/models/collection_with_metadata.rb +1 -0
  17. data/lib/cocina/models/contributor.rb +1 -0
  18. data/lib/cocina/models/descriptive_access_metadata.rb +1 -0
  19. data/lib/cocina/models/descriptive_admin_metadata.rb +1 -0
  20. data/lib/cocina/models/descriptive_basic_value.rb +1 -0
  21. data/lib/cocina/models/descriptive_geographic_metadata.rb +1 -0
  22. data/lib/cocina/models/descriptive_grouped_value.rb +1 -0
  23. data/lib/cocina/models/descriptive_parallel_contributor.rb +2 -0
  24. data/lib/cocina/models/descriptive_parallel_event.rb +1 -0
  25. data/lib/cocina/models/descriptive_parallel_value.rb +1 -0
  26. data/lib/cocina/models/descriptive_structured_value.rb +1 -0
  27. data/lib/cocina/models/descriptive_value.rb +1 -0
  28. data/lib/cocina/models/descriptive_value_language.rb +1 -0
  29. data/lib/cocina/models/dro.rb +1 -0
  30. data/lib/cocina/models/dro_structural.rb +1 -0
  31. data/lib/cocina/models/dro_with_metadata.rb +1 -0
  32. data/lib/cocina/models/event.rb +1 -0
  33. data/lib/cocina/models/file.rb +1 -0
  34. data/lib/cocina/models/file_access.rb +1 -0
  35. data/lib/cocina/models/file_set.rb +1 -0
  36. data/lib/cocina/models/file_set_structural.rb +1 -0
  37. data/lib/cocina/models/geographic.rb +1 -0
  38. data/lib/cocina/models/language.rb +1 -0
  39. data/lib/cocina/models/mapping/from_mods/name_builder.rb +15 -10
  40. data/lib/cocina/models/mapping/normalizers/mods/name_normalizer.rb +27 -2
  41. data/lib/cocina/models/mapping/to_mods/name_writer.rb +0 -2
  42. data/lib/cocina/models/mapping/to_mods/subject.rb +2 -2
  43. data/lib/cocina/models/message_digest.rb +1 -0
  44. data/lib/cocina/models/object_metadata.rb +1 -0
  45. data/lib/cocina/models/presentation.rb +1 -0
  46. data/lib/cocina/models/related_resource.rb +1 -0
  47. data/lib/cocina/models/release_tag.rb +1 -0
  48. data/lib/cocina/models/request_admin_policy.rb +1 -0
  49. data/lib/cocina/models/request_collection.rb +1 -0
  50. data/lib/cocina/models/request_description.rb +1 -0
  51. data/lib/cocina/models/request_dro.rb +1 -0
  52. data/lib/cocina/models/request_dro_structural.rb +1 -0
  53. data/lib/cocina/models/request_file_set_structural.rb +1 -0
  54. data/lib/cocina/models/request_identification.rb +1 -0
  55. data/lib/cocina/models/sequence.rb +1 -0
  56. data/lib/cocina/models/source.rb +1 -0
  57. data/lib/cocina/models/standard.rb +1 -0
  58. data/lib/cocina/models/validators/associated_name_validator.rb +2 -4
  59. data/lib/cocina/models/validators/description_types_validator.rb +2 -3
  60. data/lib/cocina/models/validators/description_values_validator.rb +76 -0
  61. data/lib/cocina/models/validators/validator.rb +3 -3
  62. data/lib/cocina/models/version.rb +1 -1
  63. data/lib/cocina/models.rb +2 -2
  64. data/lib/cocina/rspec/factories.rb +13 -6
  65. data/openapi.yml +2 -1
  66. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3448513fd9c5a834a341c41dc2e532fe9196d5aafca3ed6812f4243ef952e960
4
- data.tar.gz: 2c1e122cd5d903ce1d92a443f1d8ab2879aaf8cdc9550f3b27c67342ce32bdc8
3
+ metadata.gz: 5c78843bdc4b3ba3ec0ea9e2e8fa569fe7979de0e5d1a52fc4923103ed20de36
4
+ data.tar.gz: b3e317a0154e15973e9a0e57aab07d0d91702a97a31889963d4df2c26339a6d4
5
5
  SHA512:
6
- metadata.gz: ca06ecb3bf3952a5190dbff8ce3510c4612a49e50f863d70511dd93f6678a184bd2f664197bc7a3ad5004b55d2bc7d66865b85796b39c5701e9eebdb71b580b7
7
- data.tar.gz: 804c0deb6ec304f9e2ab44808ec7c9f9e47a4a8df15432ec66d4dd068939116f1f5a22405658a7e20651b961fba4949708b5bd28ede4f63ac0060c126416f7cc
6
+ metadata.gz: 2fa6986b659b6474ed9ac476237c6da8ff75487bb5c7ab61b33a0226af95c44b22f14210524f258bd75bf826ea141dcc3bb88ce31e2a85b6f4590ec1ea353eec
7
+ data.tar.gz: e41c8c60d4eafe62b0f4025213f73aa7629284a0e5f73e93d7b356204d075635a3ea7da3af1aaf9557de487e1eee5236c3eec23a92a04d17fc71ecec14e50c48
data/README.md CHANGED
@@ -55,8 +55,37 @@ If there is a possibility that a model or validation change will conflict with s
55
55
 
56
56
  1. Create a cocina-models branch containing the proposed change and push to Github.
57
57
  2. On sdr-deploy, check out `main`, update the `Gemfile` so that cocina-models references the branch, and `bundle install`.
58
- 3. Run `bin/validate-cocina`.
59
- 4. Check `validate-cocina.csv` for validation errors.
58
+ 3. Select the appropriate database.
59
+ For QA:
60
+ ```
61
+ export DATABASE_NAME="dor_services"
62
+ export DATABASE_USERNAME=$DOR_SERVICES_DB_USER
63
+ export DATABASE_HOSTNAME=$DOR_SERVICES_DB_QA_HOST
64
+ export DATABASE_PASSWORD=$DOR_SERVICES_DB_QA_PWD
65
+ ```
66
+
67
+ For stage:
68
+ ```
69
+ export DATABASE_NAME="dor_services"
70
+ export DATABASE_USERNAME=$DOR_SERVICES_DB_USER
71
+ export DATABASE_HOSTNAME=$DOR_SERVICES_DB_STAGE_HOST
72
+ export DATABASE_PASSWORD=$DOR_SERVICES_DB_STAGE_PWD
73
+ ```
74
+
75
+ For production:
76
+ ```
77
+ export DATABASE_NAME="dor_services"
78
+ export DATABASE_USERNAME=$DOR_SERVICES_DB_USER
79
+ export DATABASE_HOSTNAME=$DOR_SERVICES_DB_PROD_HOST
80
+ export DATABASE_PASSWORD=$DOR_SERVICES_DB_PROD_PWD
81
+ ```
82
+
83
+ 4. Run `bin/validate-cocina`:
84
+ ```
85
+ export RUBYOPT='-W:no-deprecated -W:no-experimental'
86
+ RAILS_ENV=production bin/validate-cocina -p 8
87
+ ```
88
+ 5. Check `validate-cocina.csv` for validation errors.
60
89
 
61
90
  ## Releasing
62
91
 
@@ -391,6 +391,8 @@ identifier:
391
391
  - value: record id
392
392
  - value: Senate Number
393
393
  - value: Series
394
+ - value: SICI
395
+ code: sici
394
396
  - value: SIRSI
395
397
  - value: Source ID
396
398
  - value: sourceID
@@ -385,6 +385,7 @@ _Path: identifier.type_
385
385
  * record id
386
386
  * Senate Number
387
387
  * Series
388
+ * SICI
388
389
  * SIRSI
389
390
  * Source ID
390
391
  * sourceID
@@ -13,8 +13,8 @@ module Cocina
13
13
  # frozen_string_literal: true
14
14
 
15
15
  module Cocina
16
- module Models
17
- class #{name} < Struct
16
+ module Models#{' '}
17
+ #{preamble}class #{name} < Struct
18
18
 
19
19
  #{validate}
20
20
  #{types}
@@ -41,6 +41,12 @@ module Cocina
41
41
  "# #{schema_doc.description}\n"
42
42
  end
43
43
 
44
+ def deprecation
45
+ return '' unless schema_doc.deprecated?
46
+
47
+ "# DEPRECATED\n"
48
+ end
49
+
44
50
  def example
45
51
  return '' unless schema_doc.example
46
52
 
@@ -82,6 +88,10 @@ module Cocina
82
88
  'Strict::String'
83
89
  end
84
90
  end
91
+
92
+ def preamble
93
+ "#{deprecation}#{description}#{example}#{relaxed_comment}"
94
+ end
85
95
  end
86
96
  end
87
97
  end
@@ -19,10 +19,6 @@ module Cocina
19
19
  "Types::#{dry_datatype(schema_doc)}#{optional}#{default}#{enum}"
20
20
  end
21
21
 
22
- def preamble
23
- "#{description}#{example}#{relaxed_comment}"
24
- end
25
-
26
22
  def enum
27
23
  return '' if !schema_doc.enum || relaxed
28
24
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Access role conferred by an AdminPolicy to objects within it. (used by Argo)
5
6
  class AccessRole < Struct
6
7
  # Name of role
7
8
  attribute :name, Types::Strict::String.enum('dor-apo-depositor', 'dor-apo-manager', 'dor-apo-viewer', 'sdr-administrator', 'sdr-viewer', 'hydrus-collection-creator', 'hydrus-collection-manager', 'hydrus-collection-depositor', 'hydrus-collection-item-depositor', 'hydrus-collection-reviewer', 'hydrus-collection-viewer')
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Represents a user or group that is a member of an AccessRole
5
6
  class AccessRoleMember < Struct
6
7
  include Checkable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Provides the template of access settings that is copied to the items goverend by an AdminPolicy. This is almost the same as DROAccess, but it provides no defaults and has no embargo.
5
6
  class AdminPolicyAccessTemplate < Struct
6
7
  attribute? :view, Types::Strict::String.enum('world', 'stanford', 'location-based', 'citation-only', 'dark')
7
8
  # Available for controlled digital lending.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Administrative properties for an AdminPolicy
5
6
  class AdminPolicyAdministrative < Struct
6
7
  attribute(:accessTemplate, AdminPolicyAccessTemplate.default { AdminPolicyAccessTemplate.new })
7
8
  attribute :registrationWorkflow, Types::Strict::Array.of(Types::Strict::String).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Admin Policy with addition object metadata.
5
6
  class AdminPolicyWithMetadata < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Property model for indicating the parts, aspects, or versions of the resource to which a descriptive element is applicable.
5
6
  class AppliesTo < Struct
6
7
  attribute :appliesTo, Types::Strict::Array.of(DescriptiveBasicValue).default([].freeze)
7
8
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # A group of Digital Repository Objects that indicate some type of conceptual grouping within the domain that is worth reusing across the system.
5
6
  class Collection < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Access metadata for collections
5
6
  class CollectionAccess < Struct
6
7
  # Access level
7
8
  attribute? :view, Types::Strict::String.default('dark').enum('world', 'dark')
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Collection with addition object metadata.
5
6
  class CollectionWithMetadata < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Property model for describing agents contributing in some way to the creation and history of the resource.
5
6
  class Contributor < Struct
6
7
  attribute :name, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  # Entity type of the contributor (person, organization, etc.). See https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md for valid types.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Information about how to access digital and physical versions of the object.
5
6
  class DescriptiveAccessMetadata < Struct
6
7
  attribute :url, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  attribute :physicalLocation, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Information about this resource description.
5
6
  class DescriptiveAdminMetadata < Struct
6
7
  attribute :contributor, Types::Strict::Array.of(Contributor).default([].freeze)
7
8
  attribute :event, Types::Strict::Array.of(Event).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Basic value model for descriptive elements. Can only have one of value, parallelValue, groupedValue, or structuredValue.
5
6
  class DescriptiveBasicValue < Struct
6
7
  attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  attribute :parallelValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Value model for mods geographic extension metadata
5
6
  class DescriptiveGeographicMetadata < Struct
6
7
  attribute :form, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  attribute :subject, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Value model for a set of descriptive elements grouped together in an unstructured way.
5
6
  class DescriptiveGroupedValue < Struct
6
7
  attribute :groupedValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # DEPRECATED
6
+ # Value model for multiple representations of information about the same contributor (e.g. in different languages).
5
7
  class DescriptiveParallelContributor < Struct
6
8
  attribute :name, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
9
  # Entity type of the contributor (person, organization, etc.). See https://github.com/sul-dlss/cocina-models/blob/main/docs/description_types.md for valid types.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Value model for multiple representations of information about the same event (e.g. in different languages).
5
6
  class DescriptiveParallelEvent < Struct
6
7
  attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  # Description of the event (creation, publication, etc.).
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Value model for multiple representations of the same information (e.g. in different languages).
5
6
  class DescriptiveParallelValue < Struct
6
7
  attribute :parallelValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Value model for descriptive elements structured as typed, ordered values.
5
6
  class DescriptiveStructuredValue < Struct
6
7
  attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Default value model for descriptive elements.
5
6
  class DescriptiveValue < Struct
6
7
  attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  attribute :parallelValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Language of the descriptive element value
5
6
  class DescriptiveValueLanguage < Struct
6
7
  # Code representing the standard or encoding.
7
8
  attribute? :code, Types::Strict::String
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Domain-defined abstraction of a 'work'. Digital Repository Objects' abstraction is describable for our domain’s purposes, i.e. for management needs within our system.
5
6
  class DRO < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Structural metadata
5
6
  class DROStructural < Struct
6
7
  attribute :contains, Types::Strict::Array.of(FileSet).default([].freeze)
7
8
  attribute :hasMemberOrders, Types::Strict::Array.of(Sequence).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # DRO with addition object metadata.
5
6
  class DROWithMetadata < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Property model for describing events in the history of the resource.
5
6
  class Event < Struct
6
7
  attribute :structuredValue, Types::Strict::Array.of(DescriptiveValue).default([].freeze)
7
8
  # Description of the event (creation, publication, etc.).
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Binaries that are the basis of what our domain manages. Binaries here do not include metadata files generated for the domain's own management purposes.
5
6
  class File < Struct
6
7
  include Checkable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Access metadata for files
5
6
  class FileAccess < Struct
6
7
  # Access level.
7
8
  # Validation of this property is relaxed. See the openapi for full validation.
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Relevant groupings of Files. Also called a File Grouping.
5
6
  class FileSet < Struct
6
7
  include Checkable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Structural metadata
5
6
  class FileSetStructural < Struct
6
7
  attribute :contains, Types::Strict::Array.of(File).default([].freeze)
7
8
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Geographic metadata
5
6
  class Geographic < Struct
6
7
  # Geographic ISO 19139 XML metadata
7
8
  attribute :iso19139, Types::Strict::String
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Languages, scripts, symbolic systems, and notations used in all or part of a resource or its descriptive metadata.
5
6
  class Language < Struct
6
7
  attribute :appliesTo, Types::Strict::Array.of(DescriptiveBasicValue).default([].freeze)
7
8
  # Code value of the descriptive element.
@@ -41,8 +41,8 @@ module Cocina
41
41
  status: name_elements.filter_map { |name_element| name_element['usage'] }.first
42
42
  }.compact
43
43
  { name: [names] }.tap do |attrs|
44
- roles = name_elements.flat_map { |name_node| build_roles(name_node) }.compact.uniq
45
- attrs[:role] = roles.presence
44
+ attrs[:role] = name_elements.flat_map { |name_node| build_roles(name_node) }.compact.uniq.presence
45
+ attrs[:note] = name_elements.flat_map { |name_node| build_affiliation_notes(name_node) }.compact.uniq.presence
46
46
  end.compact
47
47
  end
48
48
 
@@ -51,7 +51,7 @@ module Cocina
51
51
  status: name_node['usage']
52
52
  }.compact.merge(common_lang_script(name_node))
53
53
 
54
- name_attrs = name_attrs.merge(common_name(name_node, name_attrs[:name]))
54
+ name_attrs = name_attrs.merge(common_name(name_node, name_attrs[:name], is_parallel: true))
55
55
  name_parts = build_name_parts(name_node)
56
56
  notifier.warn('Missing name/namePart element') if name_parts.all?(&:empty?)
57
57
  name_parts.each { |name_part| name_attrs = name_part.merge(name_attrs) }
@@ -85,9 +85,9 @@ module Cocina
85
85
  }.compact.merge(common_name(name_node, name_parts))
86
86
  end
87
87
 
88
- def common_name(name_node, name)
88
+ def common_name(name_node, name, is_parallel: false)
89
89
  {
90
- note: build_notes(name_node),
90
+ note: build_notes(name_node, is_parallel: is_parallel),
91
91
  identifier: build_identifier(name_node)
92
92
  }.tap do |attrs|
93
93
  roles = build_roles(name_node)
@@ -224,12 +224,8 @@ module Cocina
224
224
  end.presence
225
225
  end
226
226
 
227
- def build_notes(name_node)
227
+ def build_notes(name_node, is_parallel:)
228
228
  [].tap do |parts|
229
- name_node.xpath('mods:affiliation', mods: Description::DESC_METADATA_NS).each do |affiliation_node|
230
- parts << { value: affiliation_node.text, type: 'affiliation' }
231
- end
232
-
233
229
  description = name_node.xpath('mods:description', mods: Description::DESC_METADATA_NS).first
234
230
  if description
235
231
  parts << if description.text == UNCITED_DESCRIPTION
@@ -238,9 +234,18 @@ module Cocina
238
234
  { value: description.text, type: 'description' }
239
235
  end
240
236
  end
237
+ parts.concat(build_affiliation_notes(name_node)) unless is_parallel
241
238
  end.presence
242
239
  end
243
240
 
241
+ def build_affiliation_notes(name_node)
242
+ [].tap do |parts|
243
+ name_node.xpath('mods:affiliation', mods: Description::DESC_METADATA_NS).each do |affiliation_node|
244
+ parts << { value: affiliation_node.text, type: 'affiliation' }
245
+ end
246
+ end
247
+ end
248
+
244
249
  def build_roles(name_node)
245
250
  role_nodes = name_node.xpath('mods:role', mods: Description::DESC_METADATA_NS)
246
251
  role_nodes.filter_map { |role_node| role_for(role_node) }.presence
@@ -20,6 +20,7 @@ module Cocina
20
20
 
21
21
  def normalize
22
22
  normalize_parallel_name_role
23
+ normalize_parallel_affiliation_note
23
24
  normalize_text_role_term
24
25
  normalize_role_term
25
26
  normalize_role # must be after normalize_role_term
@@ -35,10 +36,13 @@ module Cocina
35
36
 
36
37
  attr_reader :ng_xml
37
38
 
39
+ def grouped_name_nodes
40
+ name_nodes = ng_xml.root.xpath('//mods:name[@altRepGroup]', mods: ModsNormalizer::MODS_NS)
41
+ name_nodes.group_by { |name_node| name_node['altRepGroup'] }.values.reject { |name_node_group| name_node_group.size == 1 }
42
+ end
43
+
38
44
  def normalize_parallel_name_role
39
45
  # For parallel names, all should have the same roles.
40
- name_nodes = ng_xml.root.xpath('//mods:name[@altRepGroup]', mods: ModsNormalizer::MODS_NS)
41
- grouped_name_nodes = name_nodes.group_by { |name_node| name_node['altRepGroup'] }.values.reject { |name_node_group| name_node_group.size == 1 }
42
46
  grouped_name_nodes.each do |name_node_group|
43
47
  name_node_with_role = name_node_group.find { |name_node| role_node_for(name_node) }
44
48
  next unless name_node_with_role
@@ -58,6 +62,27 @@ module Cocina
58
62
  name_node.xpath('mods:role', mods: ModsNormalizer::MODS_NS).first
59
63
  end
60
64
 
65
+ def normalize_parallel_affiliation_note
66
+ # For parallel names, all should have the same affiliation.
67
+ grouped_name_nodes.each do |name_node_group|
68
+ name_node_with_affiliation = name_node_group.find { |name_node| affiliation_node_for(name_node) }
69
+ next unless name_node_with_affiliation
70
+
71
+ name_node_group.each do |name_node|
72
+ next if name_node == name_node_with_affiliation
73
+
74
+ existing_affiliation_node = affiliation_node_for(name_node)
75
+ existing_affiliation_node&.remove
76
+
77
+ name_node << affiliation_node_for(name_node_with_affiliation).dup
78
+ end
79
+ end
80
+ end
81
+
82
+ def affiliation_node_for(name_node)
83
+ name_node.xpath('mods:affiliation', mods: ModsNormalizer::MODS_NS).first
84
+ end
85
+
61
86
  def normalize_text_role_term
62
87
  # Add the type="text" attribute to roleTerms that don't have a type (seen in MODS 3.3 druid:yy910cj7795)
63
88
  ng_xml.root.xpath('//mods:roleTerm[not(@type)]', mods: ModsNormalizer::MODS_NS).each do |role_term_node|
@@ -199,8 +199,6 @@ module Cocina
199
199
  xml.affiliation note.value
200
200
  when 'description'
201
201
  xml.description note.value
202
- when 'citation status'
203
- xml.description UNCITED_DESCRIPTION if note.value == 'false'
204
202
  end
205
203
  end
206
204
  end
@@ -394,7 +394,7 @@ module Cocina
394
394
  xml.name name_attrs do
395
395
  write_name_part(subject_value)
396
396
  write_display_form(display_values)
397
- write_roles(subject.note)
397
+ write_roles(subject_value.note)
398
398
  write_other_notes(subject.note, 'description')
399
399
  write_other_notes(subject.note, 'affiliation')
400
400
  end
@@ -410,7 +410,7 @@ module Cocina
410
410
  write_display_form(display_values)
411
411
  write_roles(subject.note)
412
412
  write_other_notes(subject.note, 'description')
413
- write_other_notes(subject.note, 'affiliation')
413
+ write_other_notes(subject_value.note, 'affiliation')
414
414
  end
415
415
  write_genres(subject_value)
416
416
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # The output of the message digest algorithm.
5
6
  class MessageDigest < Struct
6
7
  include Checkable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Metadata for a cocina object.
5
6
  class ObjectMetadata < Struct
6
7
  # When the object was created.
7
8
  attribute? :created, Types::Params::DateTime
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Presentation data for the File.
5
6
  class Presentation < Struct
6
7
  # Height in pixels
7
8
  attribute? :height, Types::Strict::Integer
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Other resource associated with the described resource.
5
6
  class RelatedResource < Struct
6
7
  # The relationship of the related resource to the described resource.
7
8
  attribute? :type, Types::Strict::String
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # A tag that indicates the item or collection should be released.
5
6
  class ReleaseTag < Struct
6
7
  # Who did this release
7
8
  # example: petucket
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Same as an AdminPolicy, but doesn't have an externalIdentifier as one will be created
5
6
  class RequestAdminPolicy < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Same as a Collection, but doesn't have an externalIdentifier as one will be created
5
6
  class RequestCollection < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Description that is included in a request to create a DRO. This is the same as a Description, except excludes PURL.
5
6
  class RequestDescription < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # A request to create a DRO. This has the same general structure as a DRO but doesn't have externalIdentifier and doesn't require the access subschema. If no access subschema is provided, these values will be inherited from the AdminPolicy.
5
6
  class RequestDRO < Struct
6
7
  include Validatable
7
8
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Structural metadata
5
6
  class RequestDROStructural < Struct
6
7
  attribute :contains, Types::Strict::Array.of(RequestFileSet).default([].freeze)
7
8
  attribute :hasMemberOrders, Types::Strict::Array.of(Sequence).default([].freeze)
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Structural metadata
5
6
  class RequestFileSetStructural < Struct
6
7
  attribute :contains, Types::Strict::Array.of(RequestFile).default([].freeze)
7
8
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Same as a Identification, but requires a sourceId and doesn't permit a DOI.
5
6
  class RequestIdentification < Struct
6
7
  # A barcode
7
8
  attribute? :barcode, Types::Nominal::Any
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # A sequence or ordering of resources within a Collection or Object.
5
6
  class Sequence < Struct
6
7
  attribute :members, Types::Strict::Array.of(Types::Strict::String).default([].freeze)
7
8
  # The direction that a sequence of canvases should be displayed to the user
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Property model for indicating the vocabulary, authority, or other origin for a term, code, or identifier.
5
6
  class Source < Struct
6
7
  # Code representing the value source.
7
8
  attribute? :code, Types::Strict::String
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
+ # Property model for indicating the encoding, standard, or syntax to which a value conforms (e.g. RDA).
5
6
  class Standard < Struct
6
7
  # Code representing the standard or encoding.
7
8
  attribute? :code, Types::Strict::String
@@ -11,7 +11,7 @@ module Cocina
11
11
 
12
12
  def initialize(clazz, attributes)
13
13
  @clazz = clazz
14
- @attributes = attributes.deep_symbolize_keys
14
+ @attributes = attributes
15
15
  @error_paths = []
16
16
  end
17
17
 
@@ -29,9 +29,7 @@ module Cocina
29
29
  attr_reader :clazz, :attributes, :error_paths
30
30
 
31
31
  def meets_preconditions?
32
- resources.any? do |resource|
33
- titles_with_associated_name_note_for(resource).present?
34
- end
32
+ [Cocina::Models::Description, Cocina::Models::RequestDescription].include?(clazz)
35
33
  end
36
34
 
37
35
  def valid?(resource)
@@ -11,7 +11,7 @@ module Cocina
11
11
 
12
12
  def initialize(clazz, attributes)
13
13
  @clazz = clazz
14
- @attributes = attributes.deep_symbolize_keys
14
+ @attributes = attributes
15
15
  @error_paths = []
16
16
  end
17
17
 
@@ -30,8 +30,7 @@ module Cocina
30
30
  attr_reader :clazz, :attributes, :error_paths
31
31
 
32
32
  def meets_preconditions?
33
- attributes.key?(:description) || [Cocina::Models::Description,
34
- Cocina::Models::RequestDescription].include?(clazz)
33
+ [Cocina::Models::Description, Cocina::Models::RequestDescription].include?(clazz)
35
34
  end
36
35
 
37
36
  def validate_hash(hash, path)
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cocina
4
+ module Models
5
+ module Validators
6
+ # Validates that there is only one of value, groupedValue, structuredValue, or parallelValue.
7
+ class DescriptionValuesValidator
8
+ def self.validate(clazz, attributes)
9
+ new(clazz, attributes).validate
10
+ end
11
+
12
+ def initialize(clazz, attributes)
13
+ @clazz = clazz
14
+ @attributes = attributes
15
+ @error_paths = []
16
+ end
17
+
18
+ def validate
19
+ return unless meets_preconditions?
20
+
21
+ validate_obj(attributes, [])
22
+
23
+ return if error_paths.empty?
24
+
25
+ raise ValidationError, "Multiple value, groupedValue, structuredValue, and parallelValue in description: #{error_paths.join(', ')}"
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :clazz, :attributes, :error_paths
31
+
32
+ def meets_preconditions?
33
+ [Cocina::Models::Description, Cocina::Models::RequestDescription].include?(clazz)
34
+ end
35
+
36
+ def validate_hash(hash, path)
37
+ validate_values(hash, path)
38
+ hash.each do |key, obj|
39
+ validate_obj(obj, path + [key])
40
+ end
41
+ end
42
+
43
+ def validate_array(array, path)
44
+ array.each_with_index do |obj, index|
45
+ validate_obj(obj, path + [index])
46
+ end
47
+ end
48
+
49
+ def validate_obj(obj, path)
50
+ validate_hash(obj, path) if obj.is_a?(Hash)
51
+ validate_array(obj, path) if obj.is_a?(Array)
52
+ end
53
+
54
+ def validate_values(hash, path)
55
+ return unless hash.count { |key, value| %i[value groupedValue structuredValue parallelValue].include?(key) && value.present? } > 1
56
+
57
+ error_paths << path_to_s(path)
58
+ end
59
+
60
+ def path_to_s(path)
61
+ # This matches the format used by descriptive spreadsheets
62
+ path_str = ''
63
+ path.each_with_index do |part, index|
64
+ if part.is_a?(Integer)
65
+ path_str += (part + 1).to_s
66
+ else
67
+ path_str += '.' if index.positive?
68
+ path_str += part.to_s
69
+ end
70
+ end
71
+ path_str
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -11,7 +11,8 @@ module Cocina
11
11
  PurlValidator,
12
12
  CatalogLinksValidator,
13
13
  AssociatedNameValidator,
14
- DescriptionTypesValidator
14
+ DescriptionTypesValidator,
15
+ DescriptionValuesValidator
15
16
  ].freeze
16
17
 
17
18
  def self.validate(clazz, attributes)
@@ -25,8 +26,7 @@ module Cocina
25
26
  # In the meantime, copying code.
26
27
  attributes_hash = deep_transform_values(attributes.to_h) do |value|
27
28
  value.class.name.starts_with?('Cocina::Models') ? value.to_h : value
28
- end.with_indifferent_access
29
-
29
+ end.deep_symbolize_keys.with_indifferent_access
30
30
  VALIDATORS.each { |validator| validator.validate(clazz, attributes_hash) }
31
31
  end
32
32
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.77.0'
5
+ VERSION = '0.80.0'
6
6
  end
7
7
  end
data/lib/cocina/models.rb CHANGED
@@ -119,7 +119,7 @@ module Cocina
119
119
  # @param [DROWithMetadata,CollectionWithMetadata,AdminPolicyWithMetadata] cocina_object
120
120
  # @return [DRO,Collection,AdminPolicy]
121
121
  def self.without_metadata(cocina_object)
122
- build(cocina_object.to_h.except(:created, :modified, :lock))
122
+ build(cocina_object.to_h.except(:created, :modified, :lock), validate: false)
123
123
  end
124
124
 
125
125
  # Adds metadata to a DRO, Collection, AdminPolicy
@@ -144,7 +144,7 @@ module Cocina
144
144
  else
145
145
  AdminPolicyWithMetadata
146
146
  end
147
- clazz.new(props)
147
+ clazz.new(props, false, false)
148
148
  end
149
149
 
150
150
  def self.type_for(dyn)
@@ -45,24 +45,31 @@ module Cocina
45
45
  type: Cocina::Models::ObjectType.object,
46
46
  id: 'druid:bc234fg5678',
47
47
  version: 1,
48
- label: 'test object',
49
- title: 'test object',
48
+ label: 'factory DRO label',
49
+ title: 'factory DRO title',
50
50
  source_id: 'sul:1234',
51
51
  admin_policy_id: 'druid:hv992ry2431'
52
52
  }.freeze
53
53
 
54
54
  REQUEST_DRO_DEFAULTS = DRO_DEFAULTS.except(:id)
55
55
 
56
- COLLECTION_DEFAULTS = DRO_DEFAULTS.except(:source_id).merge(type: Cocina::Models::ObjectType.collection)
56
+ COLLECTION_DEFAULTS = {
57
+ type: Cocina::Models::ObjectType.collection,
58
+ id: 'druid:bb222ff5555',
59
+ version: 1,
60
+ label: 'factory collection label',
61
+ title: 'factory collection title',
62
+ admin_policy_id: 'druid:hv992ry2431'
63
+ }.freeze
57
64
 
58
65
  REQUEST_COLLECTION_DEFAULTS = COLLECTION_DEFAULTS.except(:id)
59
66
 
60
67
  ADMIN_POLICY_DEFAULTS = {
61
68
  type: Cocina::Models::ObjectType.admin_policy,
62
- id: 'druid:bc234fg5678',
69
+ id: 'druid:cb432gf8765',
63
70
  version: 1,
64
- label: 'test admin policy',
65
- title: 'test admin policy',
71
+ label: 'factory APO label',
72
+ title: 'factory APO title',
66
73
  admin_policy_id: 'druid:hv992ry2431',
67
74
  agreement_id: 'druid:hp308wm0436'
68
75
  }.freeze
data/openapi.yml CHANGED
@@ -691,7 +691,7 @@ components:
691
691
  items:
692
692
  $ref: "#/components/schemas/DescriptiveValue"
693
693
  DescriptiveBasicValue:
694
- description: Basic value model for descriptive elements.
694
+ description: Basic value model for descriptive elements. Can only have one of value, parallelValue, groupedValue, or structuredValue.
695
695
  type: object
696
696
  # additionalProperties breaks the validator for allOf, unclear as to why.
697
697
  # additionalProperties: false
@@ -779,6 +779,7 @@ components:
779
779
  $ref: "#/components/schemas/DescriptiveValue"
780
780
  DescriptiveParallelContributor:
781
781
  description: Value model for multiple representations of information about the same contributor (e.g. in different languages).
782
+ deprecated: true
782
783
  type: object
783
784
  additionalProperties: false
784
785
  properties:
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocina-models
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.77.0
4
+ version: 0.80.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-05-02 00:00:00.000000000 Z
11
+ date: 2022-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -462,6 +462,7 @@ files:
462
462
  - lib/cocina/models/validators/catalog_links_validator.rb
463
463
  - lib/cocina/models/validators/dark_validator.rb
464
464
  - lib/cocina/models/validators/description_types_validator.rb
465
+ - lib/cocina/models/validators/description_values_validator.rb
465
466
  - lib/cocina/models/validators/open_api_validator.rb
466
467
  - lib/cocina/models/validators/purl_validator.rb
467
468
  - lib/cocina/models/validators/validator.rb
@@ -476,7 +477,7 @@ homepage: https://github.com/sul-dlss/cocina-models
476
477
  licenses: []
477
478
  metadata:
478
479
  rubygems_mfa_required: 'true'
479
- post_install_message:
480
+ post_install_message:
480
481
  rdoc_options: []
481
482
  require_paths:
482
483
  - lib
@@ -491,8 +492,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
491
492
  - !ruby/object:Gem::Version
492
493
  version: '0'
493
494
  requirements: []
494
- rubygems_version: 3.2.32
495
- signing_key:
495
+ rubygems_version: 3.3.9
496
+ signing_key:
496
497
  specification_version: 4
497
498
  summary: Data models for the SDR
498
499
  test_files: []