cocina-models 0.93.0 → 0.93.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9cf74f9126fce8106b3bfd5964c11731c460546a4f457da9ba7f35f895243c7a
4
- data.tar.gz: 58467364ec8a96eac4f6d335dce400c0086833830f5704e263bae7365741d473
3
+ metadata.gz: 8cd049152e51891b2ab7fc1bd63fff3f95f8914f9939122ffb6990667f17ad98
4
+ data.tar.gz: 59645a00ccb0d8b21ae4830563f037dba0655a85d4f1b268ba21da66e6406d43
5
5
  SHA512:
6
- metadata.gz: e2106344f663362d1609f5e40daf001c43f976abbbe7ae8a43c52295b2a2e280952c7d6d01b6f6b4cbdf124a07c26372bdfc17f9a903cce666edc39ba3b3db35
7
- data.tar.gz: 5675d10901a099d8869baed51c4bdb82608305053d0e830130c28577644daddf73a650410cb20425e4e27ff4dfc60188f0e2cb0b77954c49bd3f00980ee56cf1
6
+ metadata.gz: df8635b1b7318cd957f2071aac2e46cce144119d5c497d166d2e0152db0269fdccd611ea72872e80217a9e1e07ed354393add1e440cc580f86e8493953684767
7
+ data.tar.gz: f12b79669c54e657aa0956412227a18f7445ba4940d4a6a5d27f9d6d9a79fe72aa408df096114e636ff8bd99d76bdfeb99f017efef989f12a27ef891bb4a9033
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cocina-models (0.93.0)
4
+ cocina-models (0.93.1)
5
5
  activesupport
6
6
  deprecation
7
7
  dry-struct (~> 1.0)
@@ -21,7 +21,7 @@ PATH
21
21
  GEM
22
22
  remote: https://rubygems.org/
23
23
  specs:
24
- activesupport (7.1.1)
24
+ activesupport (7.1.2)
25
25
  base64
26
26
  bigdecimal
27
27
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -33,8 +33,8 @@ GEM
33
33
  tzinfo (~> 2.0)
34
34
  ast (2.4.2)
35
35
  attr_extras (7.1.0)
36
- base64 (0.1.1)
37
- bigdecimal (3.1.4)
36
+ base64 (0.2.0)
37
+ bigdecimal (3.1.5)
38
38
  byebug (11.1.3)
39
39
  committee (5.0.0)
40
40
  json_schema (~> 0.14, >= 0.14.3)
@@ -47,7 +47,7 @@ GEM
47
47
  activesupport
48
48
  diff-lcs (1.5.0)
49
49
  docile (1.4.0)
50
- drb (2.1.1)
50
+ drb (2.2.0)
51
51
  ruby2_keywords
52
52
  dry-core (1.0.1)
53
53
  concurrent-ruby (~> 1.0)
@@ -75,7 +75,7 @@ GEM
75
75
  i18n (1.14.1)
76
76
  concurrent-ruby (~> 1.0)
77
77
  ice_nine (0.11.2)
78
- json (2.6.3)
78
+ json (2.7.1)
79
79
  json_schema (0.21.0)
80
80
  jsonpath (1.1.5)
81
81
  multi_json
@@ -83,15 +83,15 @@ GEM
83
83
  mini_portile2 (2.8.5)
84
84
  minitest (5.20.0)
85
85
  multi_json (1.15.0)
86
- mutex_m (0.1.2)
87
- nokogiri (1.15.4)
86
+ mutex_m (0.2.0)
87
+ nokogiri (1.16.0)
88
88
  mini_portile2 (~> 2.8.2)
89
89
  racc (~> 1.4)
90
90
  openapi3_parser (0.9.2)
91
91
  commonmarker (~> 0.17)
92
92
  openapi_parser (1.0.0)
93
93
  optimist (3.1.0)
94
- parallel (1.23.0)
94
+ parallel (1.24.0)
95
95
  parser (3.2.2.4)
96
96
  ast (~> 2.4.1)
97
97
  racc
@@ -101,7 +101,7 @@ GEM
101
101
  rack (3.0.8)
102
102
  rainbow (3.1.1)
103
103
  rake (13.1.0)
104
- regexp_parser (2.8.2)
104
+ regexp_parser (2.8.3)
105
105
  rexml (3.2.6)
106
106
  rspec (3.12.0)
107
107
  rspec-core (~> 3.12.0)
@@ -120,7 +120,7 @@ GEM
120
120
  rspec-core (>= 2, < 4, != 2.12.0)
121
121
  rss (0.3.0)
122
122
  rexml
123
- rubocop (1.57.2)
123
+ rubocop (1.59.0)
124
124
  json (~> 2.3)
125
125
  language_server-protocol (>= 3.17.0)
126
126
  parallel (~> 1.10)
@@ -128,7 +128,7 @@ GEM
128
128
  rainbow (>= 2.2.2, < 4.0)
129
129
  regexp_parser (>= 1.8, < 3.0)
130
130
  rexml (>= 3.2.5, < 4.0)
131
- rubocop-ast (>= 1.28.1, < 2.0)
131
+ rubocop-ast (>= 1.30.0, < 2.0)
132
132
  ruby-progressbar (~> 1.7)
133
133
  unicode-display_width (>= 2.4.0, < 3.0)
134
134
  rubocop-ast (1.30.0)
data/README.md CHANGED
@@ -12,6 +12,8 @@ The data model is expressed in an OpenAPI specification that lives in this codeb
12
12
 
13
13
  Note that the data model encodes properties as camelCase, which the team believes to be consistent with other HTTP APIs and the original design of the Cocina data model. While using camelCase in Ruby code may look and feel wrong, we did explore automagic conversion between camelCase in the model and snake_case in the Ruby context. We ultimately concluded that we have enough representations of the data model in enough codebases to reasonably worry about data inconsistency problems, none of which we need in our work on SDR.
14
14
 
15
+ For more about the model for description see https://consul.stanford.edu/display/DIGMETADATA/Digital+Object+Metadata+Documentation#DigitalObjectMetadataDocumentation-Cocinamodel
16
+
15
17
  ## Configuration
16
18
 
17
19
  Set the PURL url base:
@@ -6,8 +6,7 @@ module Cocina
6
6
  module Models
7
7
  module Builders
8
8
  # TitleBuilder selects the prefered title from the cocina object for solr indexing
9
- # rubocop:disable Metrics/ClassLength
10
- class TitleBuilder
9
+ class TitleBuilder # rubocop:disable Metrics/ClassLength
11
10
  extend Deprecation
12
11
  # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
13
12
  # @param [Symbol] strategy ":first" is the strategy for selection when primary or display
@@ -24,24 +23,57 @@ module Cocina
24
23
  new(strategy: strategy, add_punctuation: add_punctuation).build(titles)
25
24
  end
26
25
 
26
+ # the "main title" is the title withOUT subtitle, part name, etc. We want to index it separately so
27
+ # we can boost matches on it in search results (boost matching this string higher than matching full title string)
28
+ # e.g. "The Hobbit" (main_title) vs "The Hobbit, or, There and Back Again (full_title)
29
+ # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
30
+ # @return [String] the main title value for Solr
31
+ def self.main_title(titles)
32
+ new(strategy: :first, add_punctuation: false).main_title(titles)
33
+ end
34
+
35
+ # the "full title" is the title WITH subtitle, part name, etc. We want to able able to index it separately so
36
+ # we can boost matches on it in search results (boost matching this string higher than other titles present)
37
+ # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
38
+ # @return [String] the title value for Solr
39
+ def self.full_title(titles)
40
+ new(strategy: :first, add_punctuation: false).build(titles)
41
+ end
42
+
43
+ # "additional titles" are all title data except for full_title. We want to able able to index it separately so
44
+ # we can boost matches on it in search results (boost matching these strings lower than other titles present)
45
+ # @param [[Array<Cocina::Models::Title,Cocina::Models::DescriptiveValue>] titles the titles to consider
46
+ # @return [Array<String>] the values for Solr
47
+ def self.additional_titles(titles)
48
+ new(strategy: :all, add_punctuation: false).build(titles) - [full_title(titles)]
49
+ end
50
+
27
51
  def initialize(strategy:, add_punctuation:)
28
52
  @strategy = strategy
29
53
  @add_punctuation = add_punctuation
30
54
  end
31
55
 
32
- # @param [[Array<Cocina::Models::Title>] titles the titles to consider
56
+ # @param [[Array<Cocina::Models::Title>] cocina_titles the titles to consider
33
57
  # @return [String] the title value for Solr
34
- def build(titles)
35
- cocina_title = primary_title(titles) || untyped_title(titles)
36
- cocina_title = other_title(titles) if cocina_title.blank?
58
+ def build(cocina_titles)
59
+ cocina_title = primary_title(cocina_titles) || untyped_title(cocina_titles)
60
+ cocina_title = other_title(cocina_titles) if cocina_title.blank?
37
61
 
38
62
  if strategy == :first
39
63
  extract_title(cocina_title)
40
64
  else
41
- cocina_title.map { |one| extract_title(one) }
65
+ cocina_titles.map { |ctitle| extract_title(ctitle) }.flatten
42
66
  end
43
67
  end
44
68
 
69
+ def main_title(titles)
70
+ cocina_title = primary_title(titles) || untyped_title(titles)
71
+ cocina_title = other_title(titles) if cocina_title.blank?
72
+
73
+ cocina_title = cocina_title.first if cocina_title.is_a?(Array)
74
+ extract_main_title(cocina_title)
75
+ end
76
+
45
77
  private
46
78
 
47
79
  attr_reader :strategy
@@ -57,6 +89,16 @@ module Cocina
57
89
  remove_trailing_punctuation(result.strip) if result.present?
58
90
  end
59
91
 
92
+ def extract_main_title(cocina_title)
93
+ if cocina_title.value
94
+ cocina_title.value # covers both title and main title types
95
+ elsif cocina_title.structuredValue.present?
96
+ main_title_from_structured_values(cocina_title)
97
+ elsif cocina_title.parallelValue.present?
98
+ main_title(cocina_title.parallelValue)
99
+ end
100
+ end
101
+
60
102
  def add_punctuation?
61
103
  @add_punctuation
62
104
  end
@@ -93,7 +135,8 @@ module Cocina
93
135
  end
94
136
  end
95
137
 
96
- # This handles 'main title', 'uniform' or 'translated'
138
+ # This is called when there is no primary title and no untyped title
139
+ # @return [Cocina::Models::Title, Array<Cocina::Models::Title>] first title or all titles
97
140
  def other_title(titles)
98
141
  if strategy == :first
99
142
  titles.first
@@ -102,17 +145,19 @@ module Cocina
102
145
  end
103
146
  end
104
147
 
105
- # rubocop:disable Metrics/BlockLength
106
- # rubocop:disable Metrics/CyclomaticComplexity
107
- # rubocop:disable Metrics/PerceivedComplexity
108
- # rubocop:disable Metrics/MethodLength
109
148
  # @param [Cocina::Models::Title] title with structured values
110
149
  # @return [String] the title value from combining the pieces of the structured_values by type and order
111
150
  # with desired punctuation per specs
151
+ #
152
+ # rubocop:disable Metrics/CyclomaticComplexity
153
+ # rubocop:disable Metrics/MethodLength
154
+ # rubocop:disable Metrics/PerceivedComplexity
112
155
  def title_from_structured_values(title)
113
- structured_title = ''
156
+ # parse out the parts
157
+ main_title = ''
158
+ subtitle = ''
159
+ non_sort_value = ''
114
160
  part_name_number = ''
115
- # combine pieces of the cocina structuredValue into a single title
116
161
  title.structuredValue.each do |structured_value|
117
162
  # There can be a structuredValue inside a structuredValue. For example,
118
163
  # a uniform title where both the name and the title have internal StructuredValue
@@ -125,42 +170,88 @@ module Cocina
125
170
  case structured_value.type&.downcase
126
171
  when 'nonsorting characters'
127
172
  non_sort_value = "#{value}#{non_sorting_padding(title, value)}"
128
- structured_title = if structured_title.present?
129
- "#{structured_title}#{non_sort_value}"
130
- else
131
- non_sort_value
132
- end
133
173
  when 'part name', 'part number'
134
- if part_name_number.blank?
135
- part_name_number = part_name_number(title.structuredValue)
136
- structured_title = if !add_punctuation?
137
- [structured_title, part_name_number].join(' ')
138
- elsif structured_title.present?
139
- "#{structured_title.sub(/[ .,]*$/, '')}. #{part_name_number}. "
140
- else
141
- "#{part_name_number}. "
142
- end
143
- end
174
+ part_name_number = part_name_number(title.structuredValue) if part_name_number.blank?
144
175
  when 'main title', 'title'
145
- structured_title = "#{structured_title}#{value}"
176
+ main_title = value
146
177
  when 'subtitle'
147
- # subtitle is preceded by space colon space, unless it is at the beginning of the title string
148
- structured_title = if !add_punctuation?
149
- [structured_title, value].join(' ')
150
- elsif structured_title.present?
151
- "#{structured_title.sub(/[. :]+$/, '')} : #{value.sub(/^:/, '').strip}"
152
- else
153
- structured_title = value.sub(/^:/, '').strip
154
- end
178
+ # combine multiple subtitles into a single string
179
+ subtitle = if !add_punctuation?
180
+ if subtitle.present?
181
+ [subtitle, value].join(' ')
182
+ else
183
+ value
184
+ end
185
+ elsif subtitle.present?
186
+ # subtitle is preceded by space colon space, unless it is at the beginning of the title string
187
+ "#{subtitle.sub(/[. :]+$/, '')} : #{value.sub(/^:/, '').strip}"
188
+ else
189
+ value.sub(/^:/, '').strip
190
+ end
155
191
  end
156
192
  end
157
- structured_title
193
+
194
+ # combine the parts into a single title string
195
+ if add_punctuation?
196
+ combine_with_punctuation(non_sort_value: non_sort_value, main_title: main_title, subtitle: subtitle,
197
+ part_name_number: part_name_number)
198
+ else
199
+ ["#{non_sort_value}#{main_title}", subtitle, part_name_number].select(&:presence).join(' ')
200
+ end
158
201
  end
159
- # rubocop:enable Metrics/MethodLength
160
- # rubocop:enable Metrics/BlockLength
161
202
  # rubocop:enable Metrics/CyclomaticComplexity
203
+ # rubocop:enable Metrics/MethodLength
162
204
  # rubocop:enable Metrics/PerceivedComplexity
163
205
 
206
+ # main_title is title.structuredValue.value with type 'main title' (or just title.value)
207
+ # @param [Cocina::Models::Title] title with structured values
208
+ # @return [String] the main title value
209
+ def main_title_from_structured_values(cocina_title) # rubocop:disable Metrics/MethodLength
210
+ result = ''
211
+ # combine pieces of the cocina structuredValue into a single title
212
+ cocina_title.structuredValue.each do |structured_value|
213
+ # There can be a structuredValue inside a structuredValue. For example,
214
+ # a uniform title where both the name and the title have internal StructuredValue
215
+ return main_title_from_structured_values(structured_value) if structured_value.structuredValue.present?
216
+
217
+ value = structured_value.value&.strip
218
+ next unless value
219
+
220
+ case structured_value.type&.downcase
221
+ when 'nonsorting characters'
222
+ non_sort_value = "#{value}#{non_sorting_padding(cocina_title, value)}"
223
+ result = "#{non_sort_value}#{result}" # non-sorting characters are at the beginning of the title
224
+ when 'main title'
225
+ result = "#{result}#{value}"
226
+ when 'title'
227
+ result = value
228
+ end
229
+ end
230
+ result
231
+ end
232
+
233
+ # Thank MARC and catalog cards for this mess. We need to add punctuation.
234
+ # rubocop:disable Metrics/MethodLength
235
+ def combine_with_punctuation(non_sort_value:, main_title:, subtitle:, part_name_number:)
236
+ result = "#{non_sort_value}#{main_title}"
237
+ if subtitle.present?
238
+ result = if result.present?
239
+ "#{result.sub(/[. :]+$/, '')} : #{subtitle.sub(/^:/, '').strip}"
240
+ else
241
+ result = subtitle
242
+ end
243
+ end
244
+ if part_name_number.present?
245
+ result = if result.present?
246
+ "#{result.sub(/[ .,]*$/, '')}. #{part_name_number}."
247
+ else
248
+ "#{part_name_number}."
249
+ end
250
+ end
251
+ result
252
+ end
253
+ # rubocop:enable Metrics/MethodLength
254
+
164
255
  def remove_trailing_punctuation(title)
165
256
  title.sub(%r{[ .,;:/\\]+$}, '')
166
257
  end
@@ -205,7 +296,6 @@ module Cocina
205
296
  end
206
297
  end
207
298
  end
208
- # rubocop:enable Metrics/ClassLength
209
299
  end
210
300
  end
211
301
  end
@@ -37,7 +37,7 @@ module Cocina
37
37
  def build
38
38
  grouped_altrepgroup_name_nodes, other_name_nodes = AltRepGroup.split(nodes: deduped_name_nodes)
39
39
  check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
40
- contributors = grouped_altrepgroup_name_nodes.map { |name_nodes| build_name_nodes(name_nodes) } + \
40
+ contributors = grouped_altrepgroup_name_nodes.map { |name_nodes| build_name_nodes(name_nodes) } +
41
41
  other_name_nodes.map { |name_node| build_name_nodes([name_node]) }
42
42
  contrib_level_type_and_status(contributors)
43
43
  adjust_primary(contributors.compact).presence
@@ -110,7 +110,7 @@ module Cocina
110
110
  end
111
111
  end
112
112
 
113
- result.each { |_k, role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
113
+ result.each_value { |role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
114
114
  end
115
115
 
116
116
  def check_altrepgroup_type_inconsistency(grouped_altrepgroup_name_nodes)
@@ -95,7 +95,7 @@ module Cocina
95
95
  node.text.size + add
96
96
  end
97
97
  [{
98
- value: count.to_s, # cast to String until cocina-models 0.40.0 is used. See https://github.com/sul-dlss/cocina-models/pull/146
98
+ value: count.to_s, # cast to String until cocina-models 0.40.0 is used. See https://github.com/sul-dlss/cocina-models/pull/146
99
99
  type: 'nonsorting character count'
100
100
  }]
101
101
  end
@@ -174,7 +174,7 @@ module Cocina
174
174
  [role_node]
175
175
  end
176
176
  end
177
- result.each { |_k, role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
177
+ result.each_value { |role_nodes| role_nodes.uniq! { |role_node| name_node_comparitor(role_node) } }
178
178
  end
179
179
 
180
180
  def normalize_type
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cocina
4
4
  module Models
5
- VERSION = '0.93.0'
5
+ VERSION = '0.93.1'
6
6
  end
7
7
  end
@@ -38,7 +38,7 @@ module Cocina
38
38
 
39
39
  build_type = type.to_s.delete_suffix(WITH_METADATA_SUFFIX)
40
40
 
41
- fixture = public_send("build_#{build_type}".to_sym, attributes)
41
+ fixture = public_send("build_#{build_type}".to_sym, attributes) # rubocop:disable Lint/SymbolConversion
42
42
  return fixture unless type.end_with?(WITH_METADATA_SUFFIX)
43
43
 
44
44
  Cocina::Models.with_metadata(fixture, 'abc123')
data/openapi.yml CHANGED
@@ -330,6 +330,7 @@ components:
330
330
  pattern: '^[0-9]+-[0-9]+$'
331
331
  example: '6772719-1001'
332
332
  CitationOnlyAccess:
333
+ description: A type of access for an object wherein users can see the metadata and a list of files, but the files will not have view or download access
333
334
  type: object
334
335
  properties:
335
336
  view:
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.93.0
4
+ version: 0.93.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Justin Coyne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-09 00:00:00.000000000 Z
11
+ date: 2024-01-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -562,7 +562,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
562
562
  - !ruby/object:Gem::Version
563
563
  version: '0'
564
564
  requirements: []
565
- rubygems_version: 3.4.19
565
+ rubygems_version: 3.4.13
566
566
  signing_key:
567
567
  specification_version: 4
568
568
  summary: Data models for the SDR