Skip to content

Commit

Permalink
Switch to OCLC Discovery API Citation service
Browse files Browse the repository at this point in the history
  • Loading branch information
corylown committed Sep 12, 2024
1 parent 6a3a7ce commit f8566b9
Show file tree
Hide file tree
Showing 30 changed files with 498 additions and 302 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ gem "devise"
gem "devise-guests"
gem 'devise-remote-user'
gem "faraday"
gem 'oauth2'
gem "config"
gem "mods_display", "~> 1.1"
gem "font-awesome-rails"
Expand Down
1 change: 1 addition & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ DEPENDENCIES
mysql2
newrelic_rpm
nokogiri (>= 1.7.1)
oauth2
okcomputer
parslet (~> 2.0)
puma (~> 6.0)
Expand Down
12 changes: 12 additions & 0 deletions app/components/citations/citation_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<% citations.each do |style, citation| %>
<div class="mb-3">
<% unless style == 'NULL' %>
<h4><%= t("searchworks.citations.styles.#{style}") %></h4>
<% end %>
<% Array(citation).each do |cite| %>
<div class="mb-2">
<%= cite %>
</div>
<% end %>
</div>
<% end %>
16 changes: 16 additions & 0 deletions app/components/citations/citation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# frozen_string_literal: true

module Citations
class CitationComponent < ViewComponent::Base
attr_reader :citations

def initialize(citations:)
@citations = citations
super()
end

def render?
citations.present?
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render Citations::CitationComponent.new(citations: grouped_citations) %>
35 changes: 35 additions & 0 deletions app/components/citations/grouped_citation_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Citations
class GroupedCitationComponent < ViewComponent::Base
attr_reader :citations

PREFERRED_CITATION_KEY = 'preferred'

def initialize(citations:)
@citations = citations
super()
end

def grouped_citations
citation_styles.index_with { |style| citations.pluck(style).compact }
end

def render?
citations.present?
end

private

def citation_styles
keys = citations.map(&:keys).flatten.uniq

return keys unless keys.include?(PREFERRED_CITATION_KEY)

# If the preferred citation is present, move it to the front of the list
# so that it always displays first
keys.delete(PREFERRED_CITATION_KEY)
keys.unshift(PREFERRED_CITATION_KEY)
end
end
end
4 changes: 0 additions & 4 deletions app/helpers/catalog_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,6 @@ def link_to_database_search(subject)
link_to(subject, search_catalog_path(f: { db_az_subject: [subject], SolrDocument::FORMAT_KEY => ['Database'] }))
end

def grouped_citations(documents)
Citation.grouped_citations(documents.map(&:citations))
end

def tech_details(document)
details = []
details.push link_to(
Expand Down
116 changes: 16 additions & 100 deletions app/models/citation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,144 +2,60 @@

###
# Citation is a simple class that takes a Hash like object (SolrDocument)
# and returns a hash of citations for the configured formats
# and returns a hash of citations
class Citation
def initialize(document, formats = [])
def initialize(document)
@document = document
@formats = formats
end

def citable?
field.present? || citations_from_mods.present? || citations_from_eds.present?
show_oclc_citation? || citations_from_mods.present? || citations_from_eds.present?
end

def citations
return null_citation if return_null_citation?
return all_citations if all_formats_requested?

all_citations.select do |format, _|
desired_formats.include?(format)
end
end

def api_url
"#{base_url}/#{field}?cformat=all&wskey=#{api_key}"
end

class << self
def grouped_citations(all_citations)
citations = all_citations.each_with_object({}) do |cites, hash|
cites.each do |format, citation|
hash[format] ||= []
hash[format] << citation
end
end
# Append preferred citations to front of hash
citations = {
preferred_citation_key => citations[preferred_citation_key]
}.merge(citations.except(preferred_citation_key)) if citations[preferred_citation_key]
citations
end

def preferred_citation_key
'PREFERRED CITATION'
end

# This being a valid test URL is predicated on the fact
# that passing no OCLC number to the citations API responds successfully
def test_api_url
new(SolrDocument.new).api_url
end
all_citations.presence || null_citation
end

private

attr_reader :document, :formats

def return_null_citation?
all_citations.blank? || (field.blank? && all_citations.blank?)
end

def element_is_citation?(element)
element.attributes &&
element.attributes['class'] &&
element.attributes['class'].value =~ /^citation_style_/i
end

def all_formats_requested?
desired_formats == ['ALL']
end
attr_reader :document

def all_citations
@all_citations ||= begin
citation_hash = {}
if citations_from_mods.present?
citation_hash[self.class.preferred_citation_key] = "<p>#{citations_from_mods}</p>".html_safe
end

citation_hash.merge!(citations_from_mods) if citations_from_mods.present?
citation_hash.merge!(citations_from_eds) if citations_from_eds.present?
citation_hash.merge!(citations_from_oclc) if citations_from_oclc.present?

citation_hash.merge!(citations_from_oclc_response) if field.present?
citation_hash
end
end

def citations_from_oclc_response
Nokogiri::HTML(response).css('p').each_with_object({}) do |element, hash|
next unless element_is_citation?(element)
def citations_from_oclc
return unless show_oclc_citation?

element.attributes['class'].value[/^citation_style_(.*)$/i]
hash[Regexp.last_match[1].upcase] = element.to_html.html_safe
end
@citations_from_oclc ||= Citations::OclcCitation.new(oclc_number:).all_citations
end

def citations_from_mods
return unless document.mods && document.mods.note.present?

document.mods.note.find do |note|
note.label.downcase =~ /preferred citation:?/
end.try(:values).try(:join)
@citations_from_mods ||= Citations::ModsCitation.new(notes: document.mods.note).all_citations
end

def citations_from_eds
return unless document.eds? && document['eds_citation_styles'].present?

document['eds_citation_styles'].each_with_object({}) do |citation, hash|
next unless citation['id'] && citation['data']

hash[citation['id'].upcase] = citation['data'].html_safe
end
end

def response
@response ||= begin
Faraday.get(api_url).body
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
Rails.logger.warn("HTTP GET for #{api_url} failed with #{e}")
''
end
end

def field
Array(document[config.DOCUMENT_FIELD]).try(:first)
end

def desired_formats
return config.CITATION_FORMATS.map(&:upcase) unless formats.present?

formats.map(&:upcase)
end

def base_url
config.BASE_URL
@citations_from_eds ||= Citations::EdsCitation.new(eds_citation_styles: document['eds_citation_styles']).all_citations
end

def api_key
config.API_KEY
def show_oclc_citation?
Settings.oclc_discovery.citations.enabled && oclc_number.present?
end

def config
Settings.OCLC
def oclc_number
Array(document['oclc']).try(:first)
end

def null_citation
Expand Down
27 changes: 27 additions & 0 deletions app/models/citations/eds_citation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

###
# Returns an EDS citation formatted for use by SearchWorks
module Citations
class EdsCitation
CITATION_STYLES = %w[apa chicago harvard mla turabian].freeze

attr_reader :eds_citation_styles

def initialize(eds_citation_styles:)
@eds_citation_styles = eds_citation_styles
end

def all_citations
matching_styles.index_with do |id|
eds_citation_styles.select { |style| style.fetch('id', nil) == id }.pick('data')&.html_safe # rubocop:disable Rails/OutputSafety
end.compact
end

private

def matching_styles
eds_citation_styles.pluck('id').select { |id| CITATION_STYLES.include?(id) }
end
end
end
27 changes: 27 additions & 0 deletions app/models/citations/mods_citation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

###
# Returns an MODS citation formatted for use by SearchWorks
module Citations
class ModsCitation
CITATION_STYLE = 'preferred'

attr_reader :notes

def initialize(notes:)
@notes = notes
end

def all_citations
return { CITATION_STYLE => "<p>#{mods_citation}</p>".html_safe } if mods_citation.present? # rubocop:disable Rails/OutputSafety

{}
end

private

def mods_citation
notes.find { |note| note.label.downcase.match?(/preferred citation:?/) }&.values&.join
end
end
end
48 changes: 48 additions & 0 deletions app/models/citations/oclc_citation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# frozen_string_literal: true

###
# Returns an OCLC citation formatted for use by SearchWorks
module Citations
class OclcCitation
CITATION_STYLES = %w[apa chicago harvard mla turabian].freeze

attr_reader :oclc_number

def initialize(oclc_number:)
@oclc_number = oclc_number
end

def all_citations
@all_citations ||= CITATION_STYLES.index_with { |citation_style| citation(citation_style) }.compact
end

private

def citation(citation_style)
citation_from_oclc(citation_style:).fetch('entries', []).first.fetch('citationText', nil)&.html_safe # rubocop:disable Rails/OutputSafety
end

def citation_from_oclc(citation_style:)
oclc_client.citation(oclc_number:, citation_style: oclc_style_code(citation_style))
end

def oclc_client
@oclc_client ||= OclcDiscoveryClient.new
end

def oclc_style_code(searchworks_style_code)
case searchworks_style_code
when 'chicago'
'chicago-author-date'
when 'harvard'
'harvard-cite-them-right'
when 'mla'
'modern-language-association'
when 'turabian'
'turabian-author-date'
else
searchworks_style_code
end
end
end
end
Loading

0 comments on commit f8566b9

Please sign in to comment.