• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / bibdata / f04bc944-f9b4-4a42-8b26-dcacd0e3e688

11 Mar 2025 10:27PM UTC coverage: 34.017% (-58.1%) from 92.162%
f04bc944-f9b4-4a42-8b26-dcacd0e3e688

Pull #2653

circleci

christinach
Add new lc_subject_facet field.
Helps with the vocabulary work https://github.com/pulibrary/orangelight/pull/3386
In this new field we index only the lc subject heading and the subdivisions
So that when the user searches using the Details section, they can query solr for
all the subject headings and their divisions.

This is needed for the Subject browse Vocabulary work.
example: "lc_subject_facet": [
             "Booksellers and bookselling—Italy—Directories",
             "Booksellers and bookselling-Italy",
             "Booksellers and bookselling"
              ]
Pull Request #2653: Add new lc_subject_facet field.

1 of 3 new or added lines in 1 file covered. (33.33%)

2215 existing lines in 93 files now uncovered.

1294 of 3804 relevant lines covered (34.02%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

20.51
/app/controllers/bibliographic_controller.rb
1
class BibliographicController < ApplicationController
1✔
2
  include FormattingConcern
1✔
3

4
  def adapter
1✔
UNCOV
5
    @adapter ||= AlmaAdapter.new
×
6
  end
7

8
  def index
1✔
UNCOV
9
    if params[:bib_id]
×
UNCOV
10
      if params.fetch(:holdings_only, '0') == '1'
×
UNCOV
11
        redirect_to action: :bib_holdings, bib_id: sanitized_bibid, status: :moved_permanently
×
UNCOV
12
      elsif params.fetch(:items_only, '0') == '1'
×
UNCOV
13
        redirect_to action: :bib_items, bib_id: sanitized_bibid, status: :moved_permanently
×
14
      else
UNCOV
15
        redirect_to action: :bib, bib_id: sanitized_bibid, status: :moved_permanently
×
16
      end
17
    else
UNCOV
18
      render plain: 'Record please supply a bib id', status: :not_found
×
19
    end
20
  end
21

22
  # Returns availability for a single ID
23
  # Client: This endpoint is used by orangelight to render status on the catalog
24
  #   show page
25
  def availability
1✔
UNCOV
26
    id = params[:bib_id]
×
UNCOV
27
    availability = adapter.get_availability_one(id:, deep_check: (params[:deep] == 'true'))
×
UNCOV
28
    respond_to do |wants|
×
UNCOV
29
      wants.json { render json: availability }
×
30
    end
31
  rescue StandardError => e
UNCOV
32
    handle_alma_exception(exception: e, message: "Failed to retrieve availability for ID: #{id}")
×
33
  end
34

35
  # Returns availability for multiple IDs
36
  # Client: This endpoint is used by orangelight to render status on the catalog
37
  #   search results page
38
  def availability_many
1✔
UNCOV
39
    ids = (params[:bib_ids] || '').split(',')
×
UNCOV
40
    availability = adapter.get_availability_many(ids:, deep_check: ActiveModel::Type::Boolean.new.cast(params[:deep]))
×
UNCOV
41
    respond_to do |wants|
×
UNCOV
42
      wants.json { render json: availability }
×
43
    end
44
  rescue StandardError => e
UNCOV
45
    handle_alma_exception(exception: e, message: "Failed to retrieve availability for IDs: #{ids}")
×
46
  end
47

48
  # Returns availability for a single holding in a bib record
49
  # Client: This endpoint is used by Requests to populate a request form and
50
  #   submit requests to the ILS
51
  def availability_holding
1✔
UNCOV
52
    if params[:bib_id] && params[:holding_id]
×
UNCOV
53
      availability = adapter.get_availability_holding(id: params[:bib_id], holding_id: params[:holding_id])
×
UNCOV
54
      respond_to do |wants|
×
UNCOV
55
        wants.json { render json: availability, status: availability.nil? ? 404 : 200 }
×
56
      end
57
    else
58
      render plain: 'Please supply a bib id and a holding id', status: :not_found
×
59
    end
60
  rescue StandardError => e
UNCOV
61
    handle_alma_exception(exception: e, message: "Failed to retrieve holdings for: #{params[:bib_id]}/#{params[:holding_id]}")
×
62
  end
63

64
  # Client: This endpoint is used by orangelight to present the staff view
65
  #   and sometimes by individuals to pull records from the ILS
66
  def bib
1✔
67
    opts = {
UNCOV
68
      holdings: params.fetch('holdings', 'true') == 'true',
×
69
      holdings_in_bib: params.fetch('holdings_in_bib', 'true') == 'true'
70
    }
71

72
    begin
UNCOV
73
      records = adapter.get_bib_record(sanitized_bibid)
×
UNCOV
74
      records.strip_non_numeric! unless opts[:holdings]
×
75
    rescue StandardError => e
UNCOV
76
      return handle_alma_exception(exception: e, message: "Failed to retrieve the record using the bib. ID: #{sanitized_bibid}")
×
77
    end
78

UNCOV
79
    if records.nil?
×
80
      render plain: "Record #{params[:bib_id]} not found or suppressed", status: :not_found
×
81
      Rails.logger.error "Record #{params[:bib_id]} not found or suppressed"
×
82
    else
UNCOV
83
      respond_to do |wants|
×
UNCOV
84
        wants.json  do
×
85
          json = MultiJson.dump(pass_records_through_xml_parser(records))
×
86
          render json:
×
87
        end
UNCOV
88
        wants.xml do
×
UNCOV
89
          xml = records_to_xml_string(records)
×
UNCOV
90
          render xml:
×
91
        end
92
      end
93
    end
94
  end
95

96
  # Client: Used by firestone_locator to pull bibliographic data
97
  #   Also used to pull orangelight and pul_solr test fixtures
98
  def bib_solr
1✔
99
    opts = {
UNCOV
100
      holdings: params.fetch('holdings', 'true') == 'true',
×
101
      holdings_in_bib: params.fetch('holdings_in_bib', 'true') == 'true'
102
    }
UNCOV
103
    records = adapter.get_bib_record(sanitized_bibid)
×
104
    if records.nil?
×
105
      render plain: "Record #{params[:bib_id]} not found or suppressed", status: :not_found
×
106
    else
107
      solr_doc = indexer.map_record(records)
×
108
      render json: solr_doc
×
109
    end
110
  rescue StandardError => e
111
    handle_alma_exception(exception: e, message: "Failed to retrieve the holding records for the bib. ID: #{sanitized_bibid}")
×
112
  end
113

114
  # Client: No known use cases
115
  def bib_holdings
1✔
UNCOV
116
    records = adapter.get_holding_records(sanitized_bibid)
×
UNCOV
117
    if records.empty?
×
118
      render plain: "Record #{params[:bib_id]} not found or suppressed", status: :not_found
×
119
    else
UNCOV
120
      respond_to do |wants|
×
UNCOV
121
        wants.json  do
×
122
          json = MultiJson.dump(pass_records_through_xml_parser(records))
×
123
          render json:
×
124
        end
UNCOV
125
        wants.xml do
×
UNCOV
126
          xml = records_to_xml_string(records)
×
UNCOV
127
          render xml:
×
128
        end
129
      end
130
    end
131
  rescue StandardError => e
UNCOV
132
    handle_alma_exception(exception: e, message: "Failed to retrieve the holding records for the bib. ID: #{sanitized_bibid}")
×
133
  end
134

135
  # bibliographic/:bib_id/items
136
  # Client: Used by figgy to check CDL status. Used by firestone_locator for
137
  #   call number and location data
138
  def bib_items
1✔
UNCOV
139
    item_keys = %w[id pid perm_location temp_location cdl]
×
UNCOV
140
    holding_summary = adapter.get_items_for_bib(sanitized_bibid).holding_summary(item_key_filter: item_keys)
×
141

UNCOV
142
    respond_to do |wants|
×
UNCOV
143
      wants.json  { render json: MultiJson.dump(add_locator_call_no(holding_summary)) }
×
UNCOV
144
      wants.xml { render xml: '<todo but="You probably want JSON anyway" />' }
×
145
    end
146
  rescue Alma::BibItemSet::ResponseError
UNCOV
147
    render_not_found(params[:bib_id])
×
148
  rescue StandardError => e
149
    handle_alma_exception(exception: e, message: "Failed to retrieve items for bib ID: #{sanitized_bibid}")
×
150
  end
151

152
  private
1✔
153

154
    def render_not_found(id)
1✔
UNCOV
155
      render plain: "Record #{id} not found or suppressed", status: :not_found
×
156
    end
157

158
    # Ensure that the client is authenticated and the user is a catalog administrator
159
    def protect
1✔
160
      if user_signed_in?
×
161
        render plain: 'You are unauthorized', status: :forbidden if !current_user.catalog_admin?
×
162
      else
163
        redirect_to user_cas_omniauth_authorize_path
×
164
      end
165
    end
166

167
    # Generate the options for retrieving bib. records from Voyager
168
    # @return [Hash]
169
    def voyager_opts
1✔
170
      {
171
        holdings: params.fetch('holdings', 'true') == 'true',
×
172
        holdings_in_bib: params.fetch('holdings_in_bib', 'true') == 'true'
173
      }
174
    end
175

176
    # Access the URL helpers for the application
177
    # @return [Array<ActionDispatch::Routing::RouteSet::NamedRouteCollection::UrlHelper>]
178
    def url_helpers
1✔
179
      Rails.application.routes.url_helpers
×
180
    end
181

182
    # Access the global Traject Object
183
    # @return [Traject::Indexer::MarcIndexer] the Traject indexer
184
    def indexer
1✔
185
      TRAJECT_INDEXER
×
186
    end
187

188
    # Generate the URL for the application root
189
    # @return [String] the root URL
190
    def root_url
1✔
191
      url_helpers.root_url(host: request.host_with_port)
×
192
    end
193

194
    # Generates the URL for the bibliographic record
195
    # @return [String] the URL
196
    def bib_id_url
1✔
197
      url_helpers.show_bib_url(params[:bib_id], host: request.host_with_port)
×
198
    end
199

200
    # Sanitizes the bib_id HTTP parameter
201
    # @return [String]
202
    def sanitized_bibid
1✔
UNCOV
203
      CGI.escape(params[:bib_id])
×
204
    end
205

206
    def add_locator_call_no(records)
1✔
UNCOV
207
      records.each do |location, holdings|
×
UNCOV
208
        next unless location == 'firestone$stacks'
×
209

UNCOV
210
        holdings.each do |holding|
×
UNCOV
211
          holding['sortable_call_number'] = sortable_call_number(holding['call_number'])
×
212
        end
213
      end
214
    end
215

216
    def sortable_call_number(call_no)
1✔
UNCOV
217
      return call_no unless /^[A-Za-z]/.match?(call_no)
×
218

UNCOV
219
      call_no = make_sortable_call_number(call_no)
×
UNCOV
220
      lsort_result = Lcsort.normalize(call_no)
×
UNCOV
221
      return lsort_result.gsub('..', '.') unless lsort_result.nil?
×
222

223
      force_number_part_to_have_4_digits(call_no)
×
224
    rescue StandardError
225
      call_no
×
226
    end
227

228
    def make_sortable_call_number(call_no)
1✔
UNCOV
229
      tokens = call_no.split(' ')
×
UNCOV
230
      needs_adjustment = %w[oversize folio].include? tokens.first.downcase
×
UNCOV
231
      return call_no unless needs_adjustment
×
232

233
      # Move the first token (e.g. Oversize or Folio) to the end
UNCOV
234
      (tokens[1..] << tokens[0]).join(' ')
×
235
    end
236

237
    # This routine adjust something from "A53.blah" to "A0053.blah" for sorting purposes
238
    #
239
    def force_number_part_to_have_4_digits(call_no)
1✔
240
      dot_parts = call_no.tr(',', '.').split('.')
×
241
      return call_no if dot_parts.count <= 1
×
242

243
      parts = dot_parts[0].scan(/[A-Za-z]+|\d+/)
×
244
      parts[1] = parts[1].rjust(4, '0')
×
245
      dot_parts[0] = parts.join('.')
×
246
      dot_parts.join('.')
×
247
    end
248
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc