cocina_display 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 13fa31ddb1ced6dce4cff9ba6c23273a717c517853724b0338834c8bc0584053
4
- data.tar.gz: 393ea9fd0d3b923526eb2ef9165d1341bd0a59d4053e5cd0d3994cdc4b2e0ad2
3
+ metadata.gz: ddd051f7ce562501ccdbfb06831d81619742b1eeda24df081b79502473000dbc
4
+ data.tar.gz: 958bfe184a73058d469d6961284b0480544d7d26bad6e2a0c278cd968d143f1f
5
5
  SHA512:
6
- metadata.gz: 269e719358ed43c1b76ff2f9a7dc63b2fca62f5fd3b1e9fd2a08b9fd978f3d052347a06424c5b06b54e64b39b32330cb77a6ee0ba840cae8b4b0f3ffeb578c10
7
- data.tar.gz: 8227461a14e0a326652b49d32582ab7847b1946a7851e3b2b6f8123b5a5a21efbce2d4d86ff754c9ab65ac6bae315cdeb0fe4a9bbd159dd64199c0d522a061fd
6
+ metadata.gz: d0df0a44ee42f7ecb5163e8ce71d9f1564ff4f6b5ffbca529c41838393d81122d365e3dcae1e86f018131e48ccb40cca8552163d6655e14482191d860d838282
7
+ data.tar.gz: 78ac4f16998a9adf1bebd70f7d05a0f0bf1d6f5489f0ff4f5b76eb573c4f2c74c85bcf682a409459721de6e42b88bdaa7eeaef69cb1f046658629fd82be6fae7
data/README.md CHANGED
@@ -88,6 +88,79 @@ cat spec/fixtures/bb112zx3193.json | janeway "$.description.contributor[?@.role[
88
88
  ]
89
89
  ```
90
90
 
91
+ ### Formatting for display
92
+
93
+ Methods ending in `_display_data` usually return arrays of a class called `DisplayData`, which is designed for rendering into HTML by a consuming application. Each `DisplayData` object has a `label`, which serves as a heading under which its data is grouped. The `values` method returns an array of strings, which are the individual values to be displayed under that heading.
94
+
95
+ For example, when displaying contributors, the label is usually determined by the role of the contributor, and the values are the display names of the contributors with that role:
96
+
97
+ ```ruby
98
+ > rec.contributor_display_data.first.label
99
+ => "Former owner"
100
+ > rec.contributor_display_data.first.values
101
+ => ["Hearst Magazines, Inc."]
102
+ ```
103
+
104
+ The `#to_hash` helper method, which collapses all provided `DisplayData` into a single hash, can be used as a quick way to check the overall structure:
105
+
106
+ ```ruby
107
+ > CocinaDisplay::DisplayData.to_hash(record.subject_display_data)
108
+ => {"Marque"=>["Bugatti"], "Model"=>["Bugatti T51A"], "Subject"=>["Bugatti automobile"]}
109
+ ```
110
+
111
+ Note that usage of the `displayLabel` attribute in Cocina overrides the `label` that would ordinarily be used to group an item. If some items in a field have a `displayLabel` and others do not, each unique `displayLabel` will get its own `DisplayData` object, because those items will be grouped under a separate heading. If you call a method like `#subject_display_data`, it's always possible that you will get some items grouped under the default label "Subject" as well as others grouped under custom labels.
112
+
113
+ #### Creating display data
114
+
115
+ In some cases, you may wish to create `DisplayData` for some data that isn't immediately available via a `_display_data` method. Depending on what you have, there are several helper methods available to do this that will label and group the data for you.
116
+
117
+ If the data you have is an array of objects that respond to `#label` and `#to_s`, you can use `DisplayData.from_objects`, which will automatically group the objects by their `label` and set the `values` to the result of calling `#to_s` on each object. Most of the objects returned by `CocinaRecord` methods, like `Contributor` and `Subject`, respond to these methods, and also handle nested `structuredValue`s in the Cocina when rendering to string. Handling of `parallelValue`s is also included for some object types like `Name` and `Title`.
118
+
119
+ ```ruby
120
+ # This is actually equivalent to record.contributor_display_data!
121
+ > CocinaDisplay::DisplayData.from_objects(record.contributors)
122
+ ```
123
+
124
+ If the data you have is a simple array of strings, you can use `DisplayData.from_strings`. You need to provide a `label` to group the strings under:
125
+
126
+ ```ruby
127
+ > CocinaDisplay::DisplayData.from_strings(["Bugatti", "Bugatti T51A", "Bugatti automobile"], label: "Subject")
128
+ ```
129
+
130
+ If the data you have is a hash from parsed Cocina JSON, you can use `DisplayData.from_cocina`. This will respect any `displayLabel` attributes in the provided Cocina. You can optionally provide a `label` to use if the Cocina did not contain a `displayLabel` attribute:
131
+
132
+ ```ruby
133
+ > cocina = { 'value' => 'Bugatti', 'displayLabel' => 'Marque' }
134
+ # Will create a DisplayData with label "Marque" and value "Bugatti"
135
+ > CocinaDisplay::DisplayData.from_cocina(cocina)
136
+ # The same, but if the Cocina did not contain a displayLabel, it would use "Brand" instead
137
+ > CocinaDisplay::DisplayData.from_cocina(cocina, label: 'Brand')
138
+ ```
139
+
140
+ Note that `DisplayData.from_cocina` does not handle `structuredValue`s or `parallelValue`s in the provided Cocina. Because the correct handling is dependent on the type of data, you're usually better off selecting the appropriate objects from the `CocinaRecord` and using `DisplayData.from_objects` instead. To find out more about the various objects returned by `CocinaRecord` methods, see the [API Documentation](https://sul-dlss.github.io/cocina_display/).
141
+
142
+ #### Custom formatting
143
+
144
+ In some cases, you may need more control over the formatting. The `DisplayData#objects` method gives access to the underlying objects that were grouped under a particular `label`, allowing you to format them as needed.
145
+
146
+ For contributors, the underlying objects are `Contributor` instances, which provide access to the associated `Name` and `Role` objects, as well as some other useful methods like `#forename` and `#organization?`:
147
+
148
+ ```ruby
149
+ > record.contributor_display_data.first.label
150
+ => "Former owner"
151
+ # All of the Contributors grouped under "Former owner"
152
+ > former_owners = record.contributor_display_data.first.objects
153
+ =>
154
+ [#<CocinaDisplay::Contributors::Contributor:0x0000000123ad6550
155
+ ...
156
+ > former_owners.first.names.first.to_s
157
+ => "Hearst Magazines, Inc."
158
+ > former_owners.first.organization?
159
+ => true
160
+ ```
161
+
162
+ To review all the methods available on `Contributor`, see the [API Documentation](https://sul-dlss.github.io/cocina_display/CocinaDisplay/Contributors/Contributor.html).
163
+
91
164
  ### Searching for records
92
165
 
93
166
  Sometimes you need to determine if records exist "in the wild" that exhibit particular characteristics in the Cocina metadata, like the presence or absence of a field, or a specific value in a field. There is a template script in the `scripts/` directory that can be used to crawl all DRUIDs released to a particular target, like Searchworks, and examine each record.
@@ -104,13 +177,13 @@ find /stacks -name cocina.json | head -10000 |
104
177
  You may create a custom error handler by implementing the `Honeybadger` interface (or just using Honeybadger) and assigning it to the `CocinaRecord.notifier`.
105
178
 
106
179
  For example:
180
+
107
181
  ```ruby
108
182
  Rails.application.config.to_prepare do
109
183
  CocinaDisplay.notifier = Honeybadger
110
184
  end
111
185
  ```
112
186
 
113
-
114
187
  ## Development
115
188
 
116
189
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. This is a useful place to try out JSONPath expressions with `CocinaRecord#path`.
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CocinaDisplay
4
+ module Contributors
5
+ # An affiliation associated with a contributor.
6
+ class Affiliation
7
+ attr_reader :cocina
8
+
9
+ # Initialize an Affiliation from Cocina structured data.
10
+ # @param cocina [Hash]
11
+ def initialize(cocina)
12
+ @cocina = cocina
13
+ end
14
+
15
+ # String representation of the affiliation, using display name.
16
+ # @return [String, nil]
17
+ def to_s
18
+ display_name
19
+ end
20
+
21
+ # The name of the institution or organization.
22
+ # @return [String, nil]
23
+ # @example "Stanford University, Department of Special Collections"
24
+ def display_name
25
+ name_components.join(", ") if name_components.any?
26
+ end
27
+
28
+ # Does this Affiliation have a ROR ID?
29
+ # @return [Boolean]
30
+ def ror?
31
+ ror_identifier.present?
32
+ end
33
+
34
+ # ROR URI for the Affiliation, if present.
35
+ # @return [String, nil]
36
+ # @example https://ror.org/00f54p054
37
+ def ror
38
+ ror_identifier&.uri
39
+ end
40
+
41
+ # ROR ID for the Affiliation, if present.
42
+ # @return [String, nil]
43
+ # @example 00f54p054
44
+ def ror_id
45
+ ror_identifier&.identifier
46
+ end
47
+
48
+ # Identifiers associated with the Affiliation.
49
+ # @return [Array<CocinaDisplay::Identifier>]
50
+ def identifiers
51
+ @identifiers ||= Utils.flatten_nested_values(cocina)
52
+ .pluck("identifier").flatten.compact_blank.map { |id| CocinaDisplay::Identifier.new(id) }
53
+ end
54
+
55
+ # All components of the Affiliation name as an array of strings.
56
+ # @return [Array<String>]
57
+ # @example ["Stanford University", "Department of Special Collections"]
58
+ def name_components
59
+ @name_components ||= Utils.flatten_nested_values(cocina).pluck("value").compact_blank
60
+ end
61
+
62
+ # The first Identifier object that contains a ROR ID.
63
+ # @note This will usually be the most general ROR ID, if multiple.
64
+ # @return [CocinaDisplay::Identifier, nil]
65
+ def ror_identifier
66
+ identifiers.find { |id| id.type == "ROR" }
67
+ end
68
+ end
69
+ end
70
+ end
@@ -24,12 +24,6 @@ module CocinaDisplay
24
24
  other.is_a?(Contributor) && other.cocina == cocina
25
25
  end
26
26
 
27
- # Identifiers for the contributor.
28
- # @return [Array<Identifier>]
29
- def identifiers
30
- Array(cocina["identifier"]).map { |id| Identifier.new(id) }
31
- end
32
-
33
27
  # Is this contributor a human?
34
28
  # @return [Boolean]
35
29
  def person?
@@ -136,6 +130,46 @@ module CocinaDisplay
136
130
  def roles
137
131
  @roles ||= Array(cocina["role"]).map { |role| Role.new(role) }
138
132
  end
133
+
134
+ # Affiliation data for the contributor.
135
+ # @return [Array<CocinaDisplay::Contributors::Affiliation>]
136
+ def affiliations
137
+ @affiliations ||= Array(cocina["affiliation"]).map { |affiliation| Affiliation.new(affiliation) }
138
+ end
139
+
140
+ # Identifiers for the contributor.
141
+ # @return [Array<Identifier>]
142
+ def identifiers
143
+ @identifiers ||= Array(cocina["identifier"]).map { |id| Identifier.new(id) }
144
+ end
145
+
146
+ # Does this contributor have an ORCID?
147
+ # @return [Boolean]
148
+ def orcid?
149
+ orcid_identifier.present?
150
+ end
151
+
152
+ # ORCID URI for the contributor, if present.
153
+ # @return [String, nil]
154
+ # @example https://orcid.org/0000-0003-4168-7198
155
+ def orcid
156
+ orcid_identifier&.uri
157
+ end
158
+
159
+ # ORCID ID for the contributor, if present.
160
+ # @return [String, nil]
161
+ # @example 0000-0003-4168-7198
162
+ def orcid_id
163
+ orcid_identifier&.identifier
164
+ end
165
+
166
+ private
167
+
168
+ # The first Identifier object containing an ORCID.
169
+ # @return [CocinaDisplay::Identifier, nil]
170
+ def orcid_identifier
171
+ identifiers.find { |id| id.type == "ORCID" }
172
+ end
139
173
  end
140
174
  end
141
175
  end
@@ -51,29 +51,29 @@ module CocinaDisplay
51
51
  end
52
52
 
53
53
  # Recursively remove empty values from a hash, including nested hashes and arrays.
54
- # @param hash [Hash] The hash to process
55
- # @param output [Hash] Used for recursion, should be empty on first call
56
- # @return [Hash] The hash with empty values removed
54
+ # @param [Hash, String, NilClass] node The object to process
55
+ # @return [Hash, String] The hash with empty values removed, string if the node you pass in is a string
57
56
  # @example
58
57
  # hash = { "name" => "", "age" => nil, "address => { "city" => "Anytown", "state" => [] } }
59
58
  # # Utils.remove_empty_values(hash)
60
59
  # #=> { "address" => { "city" => "Anytown" } }
61
- def self.deep_compact_blank(node, output = {})
60
+ def self.deep_compact_blank(node)
62
61
  return node unless node.is_a?(Hash)
63
62
 
64
- node.each do |key, value|
65
- if value.is_a?(Hash)
63
+ node.each_with_object({}) do |(key, value), output|
64
+ case value
65
+ when Hash
66
66
  nested = deep_compact_blank(value)
67
67
  output[key] = nested unless nested.empty?
68
- elsif value.is_a?(Array)
69
- compacted_array = value.map { |v| deep_compact_blank(v) }.reject(&:blank?)
68
+ when Array
69
+ compacted_array = value.map { |v| deep_compact_blank(v) }.compact_blank
70
70
  output[key] = compacted_array unless compacted_array.empty?
71
- elsif value.present?
71
+ when TrueClass, FalseClass
72
72
  output[key] = value
73
+ else
74
+ output[key] = value if value.present?
73
75
  end
74
76
  end
75
-
76
- output
77
77
  end
78
78
  end
79
79
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  # :nodoc:
4
4
  module CocinaDisplay
5
- VERSION = "1.2.2" # :nodoc:
5
+ VERSION = "1.3.0" # :nodoc:
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cocina_display
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Budak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-09-26 00:00:00.000000000 Z
11
+ date: 2025-10-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: janeway-jsonpath
@@ -246,6 +246,7 @@ files:
246
246
  - lib/cocina_display/concerns/subjects.rb
247
247
  - lib/cocina_display/concerns/titles.rb
248
248
  - lib/cocina_display/concerns/url_helpers.rb
249
+ - lib/cocina_display/contributors/affiliation.rb
249
250
  - lib/cocina_display/contributors/contributor.rb
250
251
  - lib/cocina_display/contributors/name.rb
251
252
  - lib/cocina_display/contributors/role.rb