Class: Resource

Constant Summary

Constants included from RightsRestrictionNotes

RightsRestrictionNotes::RESTRICTION_NOTE_TYPES

Constants included from Trees

Trees::NODE_PAGE_SIZE

Constants included from Identifiers

Identifiers::MAX_LENGTH

Constants included from JSONModel

JSONModel::REFERENCE_KEY_REGEX

Constants inherited from Record

Record::ABSTRACT

Instance Attribute Summary collapse

Attributes inherited from Record

#agents, #classifications, #container_display, #container_summary_for_badge, #container_titles_and_uris, #criteria, #dates, #display_string, #extents, #external_documents, #full, #identifier, #json, #lang_materials, #level, #linked_digital_objects, #notes, #other_level, #primary_type, #raw, #repository_information, #resolved_repository, #resolved_resource, #resolved_top_container, #subjects, #uri

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ResourceRequestItems

#build_request_item

Methods included from Arks

#apply_nested_records, included

Methods included from Assessments::LinkedRecord

#delete, included

Methods included from RepresentativeFileVersion

included

Methods included from RightsRestrictionNotes

included

Methods included from ReindexTopContainers

#accession_instance_root_record_update, #ao_instance_root_record_update, #delete, #reindex_top_containers, #reindex_top_containers_by_any_means_necessary, #resource_instance_update, #set_parent_and_position, #set_root

Methods included from RevisionStatements

included

Methods included from Publishable

db_value_for, included

Methods included from Events

included

Methods included from Transferable

#transfer_to_repository

Methods included from AutoGenerator

included

Methods included from Classifications

included

Methods included from ComponentsAddChildren

#add_children, included

Methods included from UserDefineds

included

Methods included from MetadataRights

included

Methods included from CollectionManagements

included

Methods included from ExternalIDs

included

Methods included from Notes

included, #persistent_id_context

Methods included from ResourceTrees

#build_node_query, #load_node_properties, #load_root_properties, #set_node_instances, #set_node_level

Methods included from Trees

#adopt_children, #apply_exclusions_to_descendants, #assimilate, #build_node_query, #children, #children?, included, #load_node_properties, #load_root_properties, #ordered_records, #partial_tree, #transfer_to_repository, #tree, #trigger_index_of_entire_tree

Methods included from Agents

included

Methods included from Deaccessions

included

Methods included from Instances

#eagerly_load!, included

Methods included from RightsStatements

included

Methods included from ExternalDocuments

included

Methods included from LangMaterials

included

Methods included from Dates

included

Methods included from Extents

included

Methods included from Subjects

included

Methods included from Identifiers

#after_initialize, #before_validation, format, #format_multipart_identifier, #id_0=, #id_1=, #id_2=, #id_3=, included, parse, #validate

Methods included from ASModel

all_models, included, update_publish_flag, update_suppressed_flag

Methods included from JSONModel

JSONModel, #JSONModel, add_error_handler, all, allow_unmapped_enum_value, backend_url, check_valid_refs, client_mode?, custom_validations, destroy_model, enum_default_value, enum_values, handle_error, init, load_schema, #models, models, parse_jsonmodel_ref, parse_reference, repository, repository_for, schema_src, set_publish_flags!, set_repository, strict_mode, strict_mode?, validate_schema, with_repository

Methods inherited from Record

#[], #dig, #note, #parse_full_title, #request_item

Methods included from PrefixHelper

app_prefix, #app_prefix, app_prefix_js, #app_prefix_js

Methods included from RecordHelper

#badge_for_type, #icon_for_type, #record_class_for_type, #record_for_type, #record_from_resolved_json

Methods included from JsonHelper

#merge_notes, #process_json_notes

Methods included from ManipulateNode

#inheritance, #process_mixed_content, #strip_mixed_content

Constructor Details

#initialize(*args) ⇒ Resource

Returns a new instance of Resource.



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'frontend/app/models/resource.rb', line 3

def initialize(values)
  super

  if !self.extents || self.extents.empty?
    self.extents = [JSONModel(:extent).new._always_valid!]
  end

  if !self.dates || self.dates.empty?
    self.dates = [JSONModel(:date).new._always_valid!]
  end

  if !self.lang_materials || self.lang_materials.empty?
    self.lang_materials = [JSONModel(:lang_material).new._always_valid!]
  end

  self
end

Instance Attribute Details

#citeObject (readonly)

Returns the value of attribute cite



4
5
6
# File 'public/app/models/resource.rb', line 4

def cite
  @cite
end

#cite_itemObject (readonly)

Returns the value of attribute cite_item



4
5
6
# File 'public/app/models/resource.rb', line 4

def cite_item
  @cite_item
end

#cite_item_descriptionObject (readonly)

Returns the value of attribute cite_item_description



4
5
6
# File 'public/app/models/resource.rb', line 4

def cite_item_description
  @cite_item_description
end

#digital_instancesObject (readonly)

Returns the value of attribute digital_instances



4
5
6
# File 'public/app/models/resource.rb', line 4

def digital_instances
  @digital_instances
end

#finding_aidObject (readonly)

Returns the value of attribute finding_aid



4
5
6
# File 'public/app/models/resource.rb', line 4

def finding_aid
  @finding_aid
end

Returns the value of attribute related_accessions



4
5
6
# File 'public/app/models/resource.rb', line 4

def related_accessions
  @related_accessions
end

Returns the value of attribute related_deaccessions



4
5
6
# File 'public/app/models/resource.rb', line 4

def related_deaccessions
  @related_deaccessions
end

Class Method Details

.create_from_json(json, opts = {}) ⇒ Object

Maintain a finding_aid_sponsor_sha1 column to allow us to do quick lookups for OAI.



71
72
73
74
75
76
77
78
79
# File 'backend/app/model/resource.rb', line 71

def self.create_from_json(json, opts = {})
  sponsor = {}

  if json.finding_aid_sponsor
    sponsor[:finding_aid_sponsor_sha1] = Digest::SHA1.hexdigest(json.finding_aid_sponsor)
  end

  super(json, opts.merge(sponsor))
end

.id_to_identifier(id) ⇒ Object



95
96
97
98
# File 'backend/app/model/resource.rb', line 95

def self.id_to_identifier(id)
  res = Resource[id]
  [res[:id_0], res[:id_1], res[:id_2], res[:id_3]].compact.join(".")
end

.ordered_record_properties(record_ids) ⇒ Object

For resources, we want the level returned in our ordered_record response



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'backend/app/model/resource.rb', line 101

def self.ordered_record_properties(record_ids)
  result = super.clone

  self.filter(:id => record_ids).select(:id, :level_id, :other_level).each do |row|
    id = row[:id]
    level = if row[:other_level]
              row[:other_level]
            else
              BackendEnumSource.value_for_id('archival_record_level', row[:level_id])
            end

    result[id] ||= {}
    result[id][:level] = level
  end

  result
end

Instance Method Details



19
20
21
22
23
24
25
26
27
# File 'public/app/models/resource.rb', line 19

def breadcrumb
  [
    {
      :uri => '',
      :type => 'resource',
      :crumb => display_string
    }
  ]
end

#deaccessionsObject



29
30
31
32
# File 'public/app/models/resource.rb', line 29

def deaccessions
  return '' unless AppConfig[:pui_display_deaccessions]
  ASUtils.wrap(json['deaccessions'])
end

#ead_idObject



34
35
36
# File 'public/app/models/resource.rb', line 34

def ead_id
  @json['ead_id']
end

#four_part_identifierObject

Return the four parts as an array

The result might contain nils if not all parts were present.



41
42
43
# File 'public/app/models/resource.rb', line 41

def four_part_identifier
  (0..3).map {|part| @json["id_#{part}"]}
end

#instancesObject



181
182
183
# File 'public/app/models/resource.rb', line 181

def instances
  json['instances']
end

#level_for_md_mappingObject



45
46
47
48
49
50
51
# File 'public/app/models/resource.rb', line 45

def level_for_md_mapping
  if ['recordgrp', 'fonds', 'collection'].include?(json['level'].downcase)
    ['Collection', 'ArchiveComponent']
  else
    'ArchiveComponent'
  end
end

#metadataObject



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'public/app/models/resource.rb', line 53

def 
  md = {
    '@context' => "http://schema.org/",
    '@id' => AppConfig[:public_proxy_url] + uri,
    '@type' => level_for_md_mapping,
    'name' => display_string,
    'identifier' => raw['four_part_id'],
  }

  md['description'] = json['notes'].select {|n| n['type'] == 'abstract'}.map {|abstract|
                        strip_mixed_content(abstract['content'].join(' '))
                      }

  if md['description'].empty?
    md['description'] = json['notes'].select {|n| n['type'] == 'scopecontent'}.map {|scope|
                          strip_mixed_content(scope['subnotes'].map {|s| s['content']}.join(' '))
                        }
  end
  md['description'] = md['description'][0] if md['description'].length == 1

  md['creator'] = json['linked_agents'].select {|la| la['role'] == 'creator'}.map {|a| a['_resolved']}.map do |ag|
    {
      '@id' => AppConfig[:public_proxy_url] + ag['uri'],
      '@type' => ag['jsonmodel_type'] == 'agent_person' ? 'Person' : 'Organization',
      'name' => ag['title'],
      'sameAs' => ag['display_name']['authority_id']
    }.compact
  end

  md['dateCreated'] = @dates.select {|d| d['label'] == 'creation' && ['inclusive', 'single'].include?(d['date_type'])}
  .map do |date| date['final_expression']
  end

  #just mapping the whole extents for now (no need to worry about inherited extents)
  md['materialExtent'] = json['extents'].select {|e| e['portion'] == 'whole'}.map do |extent|
    {
      "@type": "QuantitativeValue",
      "unitText": I18n.t("enumerations.extent_extent_type.#{extent['extent_type']}", :default => extent['extent_type']),
      "value": extent['number']
    }
  end

  md['isRelatedTo'] = json['notes'].select {|n| n['type'] == 'relatedmaterial'}.map {|related|
                        strip_mixed_content(related['subnotes'].map {|text| text['content']}.join(' '))
                      }

  #keeping this as is for now.  Archives-Linked-Data group recommends mapping geographic headings
  #to contentLocation rather than about.
  #e.g. https://schema.org/contentLocation (with, I guess, @type as AdminstrativeArea)
  term_type_to_about_type = {
    'geographic' => 'Place',
    'temporal' => 'TemporalCoverage',
    'uniform_title' => 'CreativeWork',
    'topical' => 'Intangible',
    'occupation' => 'Intangible'
  }

  md['about'] = json['subjects'].select {|s|
    term_type_to_about_type.keys.include?(s['_resolved']['terms'][0]['term_type'])
  }.map {|s| s['_resolved']}.map {|subj|
    hash = {'@type' => term_type_to_about_type[subj['terms'][0]['term_type']]}
    hash['sameAs'] = subj['authority_id'] if subj['authority_id']
    hash['name'] = subj['title']
    hash
  }

  md['about'].concat(json['linked_agents'].select {|la| la['role'] == 'subject'}.map {|a| a['_resolved']}.map {|ag|
                       {
                         '@type' => ag['jsonmodel_type'] == 'agent_person' ? 'Person' : 'Organization',
                         'name' => strip_mixed_content(ag['title']),
                       }
                     })

  md['genre'] = json['subjects'].select {|s|
    s['_resolved']['terms'][0]['term_type'] == 'genre_form'
  }.map {|s| s['_resolved']}.map {|subj|
    subj['authority_id'] ? subj['authority_id'] : subj['title']
  }

  # schema.org spec for inLanguage states: "Please use one of the language codes from the IETF BCP 47 standard" which seems to imply that only one language can be provided here.  Unsure how to handle post-ANW-697 instances where multiple languages are present.  Currently iterating for each language, and completely ignoring script.
  if !json['lang_materials'].blank?
    md['inLanguage'] = json['lang_materials'].select {|lang_material|
      !lang_material['language_and_script'].blank?
    }.map {|lang_material|
                         {
                           '@type' => 'Language',
                           'name' => I18n.t("enumerations.language_iso639_2.#{lang_material['language_and_script']['language']}", :default => lang_material['language_and_script']['language'])
                         }
                       }
  end

  #will need to update here (and elsewhere) once ASpace allows more than one authority ID.
  #at that point, move those over to "sameAs" relationships and move the URL value to @id.
  #also, are there any changes needed now that the PUI has the ability to override the database ids in the URIs?
  md['holdingArchive'] = {
    '@id' => AppConfig[:public_proxy_url] + raw['repository'],
    '@type' => 'ArchiveOrganization',
    'name' => json['repository']['_resolved']['name'],
    'sameAs' => json['repository']['_resolved']['agent_representation']['_resolved']['display_name']['authority_id']
  }.compact

  # add repository address to holdingArchive
  if repository_information["address"]
    md['holdingArchive']["address"] = {
      '@type' => 'PostalAddress',
      'streetAddress' => repository_information["address"],
      'addressLocality' => repository_information["city"],
      'addressRegion' => repository_information["region"],
      'postalCode' => repository_information["post_code"],
      'addressCountry' => repository_information["country"],
    }.compact
  end

  # add repository telephone to holdingArchive
  if repository_information['telephones']
    md['holdingArchive']['faxNumber'] = repository_information['telephones']
      .select {|t| t['number_type'] == 'fax'}
      .map {|f| f['number']}

    md['holdingArchive']['telephone'] = repository_information['telephones']
      .select {|t| t['number_type'] == 'business'}
      .map {|b| b['number']}
  end
  md['holdingArchive'].delete_if { |key, value| value.empty? }

  md.delete_if { |key, value| value.empty? }
end

#populate_from_accession(accession) ⇒ Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'frontend/app/models/resource.rb', line 22

def populate_from_accession(accession)
  values = accession.to_hash(:raw)

  # Recursively remove bits that don't make sense to copy (like "lock_version"
  # properties)
  values = JSONSchemaUtils.map_hash_with_schema(values, JSONModel(:accession).schema,
                                                      [proc { |hash, schema|
                                                        hash = hash.clone
                                                        hash.delete_if {|k, v| k.to_s =~ /^(id_[0-9]|lock_version|instances|deaccessions|collection_management|user_defined|external_documents)$/}
                                                        hash
                                                      }])

  # We'll replace this with our own relationship, linking us back to the
  # accession we were spawned from.
  values.delete('related_accessions')
  self.related_accessions = [{'ref' => accession.uri, '_resolved' => accession}]

  notes ||= []

  if accession.content_description
    notes << JSONModel(:note_multipart).from_hash(:type => "scopecontent",
                                                  :label => I18n.t('accession.content_description'),
                                                  :subnotes => [{
                                                                  'content' => accession.content_description,
                                                                  'jsonmodel_type' => 'note_text'
                                                                }])
  end

  if accession.condition_description
    notes << JSONModel(:note_singlepart).from_hash(:type => "physdesc",
                                                   :label => I18n.t('accession.condition_description'),
                                                   :content => [accession.condition_description])
  end

  self.notes.concat(notes)

  self.rights_statements = Array(accession.rights_statements).map {|rights_statement|
    rights_statement.clone.tap {|r| r.delete('identifier')}
  }

  if !self.extents || self.extents.empty?
    self.extents = [JSONModel(:extent).new._always_valid!]
  end

  if !self.dates || self.dates.empty?
    self.dates = [JSONModel(:date).new._always_valid!]
  end

  if !self.lang_materials || self.lang_materials.empty?
    self.lang_materials = [JSONModel(:lang_material).new._always_valid!]
  end

  prepare_for_clone(values)
  self.update(values)
end

#update_from_json(json, opts = {}, apply_nested_records = true) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
# File 'backend/app/model/resource.rb', line 82

def update_from_json(json, opts = {}, apply_nested_records = true)
  sponsor = {}

  if json.finding_aid_sponsor
    sponsor[:finding_aid_sponsor_sha1] = Digest::SHA1.hexdigest(json.finding_aid_sponsor)
  else
    sponsor[:finding_aid_sponsor_sha1] = nil
  end

  super(json, opts.merge(sponsor), apply_nested_records)
end