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

pulibrary / bibdata / 31a2a5a6-71e0-4c97-89ac-edd0d51725ca

23 Oct 2025 09:51PM UTC coverage: 89.759% (-0.03%) from 89.788%
31a2a5a6-71e0-4c97-89ac-edd0d51725ca

Pull #2969

circleci

sandbergja
Remove unnecessary call from bibliographic holding availability endpoint

This affects the endpoint /bibliographic/:bib_id/holdings/:holding_id/availability,
which is used by the Requests form.

Prior to this commit, this endpoint made 2 or more requests to Alma:
* 1 to retrieve the MarcXML record along with requests and general
  availability data
* 1 or more to retrieve the item availability data

The first call was only used to retrieve the bib id -- something that
we already have because the user provided it in the request. Therefore,
we can eliminate this call and save an extra 500-1000ms each time we
load the request form (and be gentler on our API limits).

Co-authored-by: Christina Chortaria <christinach@users.noreply.github.com>
Co-authored-by: Mark Zelesky <mzelesky@users.noreply.github.com>
Pull Request #2969: Remove unnecessary call from bibliographic holding availability endpoint

6 of 6 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

9019 of 10048 relevant lines covered (89.76%)

344.57 hits per line

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

88.46
/app/adapters/alma_adapter/availability_status.rb
1
class AlmaAdapter
1✔
2
  class AvailabilityStatus
1✔
3
    # @param bib [Alma::Bib]
4
    def self.from_bib(bib:)
1✔
UNCOV
5
      new(bib:)
×
6
    end
7

8
    attr_reader :bib
1✔
9

10
    def initialize(bib:, deep_check: false)
1✔
11
      @bib = bib
17✔
12
      @deep_check = deep_check
17✔
13
    end
14

15
    # Returns availability information for each of the holdings in the Bib record.
16
    def bib_availability
1✔
17
      sequence = 0
12✔
18
      holdings.each_with_object({}) do |holding, acc|
12✔
19
        sequence += 1
14✔
20
        status = holding_status(holding:)
14✔
21
        acc[status[:id]] = status unless status.nil?
14✔
22
      end
23
    end
24

25
    # Returns availability information for each of the holdings in the Bib record.
26
    # Notice that although we return the information by holding, we drill into item
27
    # information to get details.
28
    def bib_availability_from_items
1✔
29
      availability = {}
5✔
30
      item_data.each do |holding_id, items|
5✔
31
        next if items.count == 0
9✔
32

33
        # Process all the items for the holding and keep the "status" information from the last one.
34
        # Notice that we also gather enough information to determine whether the holding as a whole
35
        # is available, not available, or some items available.
36
        all_available = true
9✔
37
        none_available = true
9✔
38
        items.each do |item|
9✔
39
          alma_item = AlmaAdapter::AlmaItem.new(Alma::BibItem.new(item.item))
15✔
40
          status = holding_status_from_item(alma_item)
15✔
41
          availability[holding_id] = status
15✔
42
          all_available &&= status[:status_label] == 'Available'
15✔
43
          none_available &&= status[:status_label] == 'Unavailable'
15✔
44
        end
45

46
        # Update the availability's status_label of the holding as a whole.
47
        holding_availability = if all_available
9✔
48
                                 'Available'
5✔
49
                               elsif none_available
4✔
50
                                 'Unavailable'
1✔
51
                               else
52
                                 Flipflop.change_status? ? 'Some Available' : 'Some items not available'
3✔
53
                               end
54
        availability[holding_id][:status_label] = holding_availability
9✔
55
      end
56
      availability
5✔
57
    end
58

59
    def holding_status(holding:)
1✔
60
      # Ignore electronic and digital records
61
      return nil if holding['inventory_type'] != 'physical'
14✔
62

63
      location_info = location_record(holding)
9✔
64
      status_label = Status.new(bib:, holding:, aeon: aeon?(location_info)).to_s
9✔
65
      status = {
66
        on_reserve: AlmaItem.reserve_location?(holding['library_code'], holding['location_code']) ? 'Y' : 'N',
18✔
67
        location: holding_location_code(holding),
68
        label: holding_location_label(holding, location_info),
69
        status_label:,
70
        copy_number: nil,
71
        temp_location: false,
72
        id: holding['holding_id']
73
      }
74

75
      if holding['holding_id'].nil?
9✔
76
        holding['holding_id'] = "#{holding['library_code']}$#{holding['location_code']}"
2✔
77
        # The ALma call from the Alma::AvailabilityResponse returns holding in temp_location with holding_id nil
78
        # see https://github.com/tulibraries/alma_rb/blob/affabad4094bc2abf0e8546b336d2a667d5ffab5/lib/alma/bib_item.rb#L53
79
        # In this case we create a holding_id using the name of the 'temporary_library$temporary_location'
80
        status[:id] = holding['holding_id']
2✔
81
        status[:temp_location] = true
2✔
82
      end
83

84
      status
9✔
85
    end
86

87
    # @param alma_item [AlmaAdapter::AlmaItem]
88
    def holding_status_from_item(alma_item)
1✔
89
      {
90
        on_reserve: alma_item.on_reserve? ? 'Y' : 'N',
30✔
91
        location: alma_item.composite_location_display,
92
        label: alma_item.composite_location_label_display,
93
        status_label: alma_item.calculate_status[:code],
94
        copy_number: alma_item.copy_number,
95
        temp_location: alma_item.in_temp_location?,
96
        id: alma_item.holding_id
97
      }
98
    end
99

100
    def to_h
1✔
101
      holdings.each_with_object({}) do |holding, acc|
×
102
        acc[holding['holding_id']] = holding_summary(holding)
×
103
      end
104
    end
105

106
    def holding_summary(holding)
1✔
107
      holding_item_data = item_data[holding['holding_id']]
×
108
      location_info = location_record(holding)
×
109
      status = Status.new(bib:, holding:, aeon: aeon?(location_info))
×
110
      {
111
        item_id: holding_item_data&.first&.item_data&.fetch('pid', nil),
×
112
        location: "#{holding['library_code']}-#{holding['location_code']}",
113
        copy_number: holding_item_data&.first&.holding_data&.fetch('copy_id', ''),
114
        label: holding_location_label(holding, location_info),
115
        status: status.to_s
116
      }
117
    end
118

119
    def item_data
1✔
120
      return @item_data if @item_data
5✔
121

122
      options = { timeout: 10 }
5✔
123
      message = "All items for #{bib.id}"
5✔
124
      items = AlmaAdapter::Execute.call(options:, message:) do
5✔
125
        # This method DOES issue a separate call to the Alma API to get item information.
126
        # Internally this call passes "ALL" to ExLibris to get data for all the holdings
127
        # in the current bib record.
128
        opts = { order_by: 'enum_a' }
5✔
129
        Alma::BibItem.find(bib.id, opts).items
5✔
130
      end
131

132
      @item_data = items.group_by do |item|
5✔
133
        item['holding_data']['holding_id']
15✔
134
      end
135
    end
136

137
    def marc_record
1✔
138
      @marc_record ||= MARC::XMLReader.new(StringIO.new(bib.response['anies'].join(''))).to_a.first
×
139
    end
140

141
    def holdings
1✔
142
      # This method does NOT issue a separate call to the Alma API to get the information, instead it
143
      # extracts the availability information (i.e. the AVA and AVE fields) from the bib record.
144
      # If temp_location is true cannot get holding_id from this call because of https://github.com/tulibraries/alma_rb/blob/affabad4094bc2abf0e8546b336d2a667d5ffab5/lib/alma/bib_item.rb#L53
145
      @availability_response ||= Alma::AvailabilityResponse.new(Array.wrap(bib)).availability[bib.id][:holdings]
12✔
146
    end
147

148
    def holding(holding_id:)
1✔
149
      holdings.find { |h| h['holding_id'] == holding_id }
×
150
    end
151

152
    private
1✔
153

154
      # Returns the extra location information that we store in the local database
155
      def location_record(holding)
1✔
156
        HoldingLocation.find_by(code: holding_location_code(holding))
9✔
157
      end
158

159
      # The status label retrieves the value from holding_location.label
160
      # which is equivalent to the alma external_name value
161
      def holding_location_label(holding, location_record)
1✔
162
        label = location_record&.label
9✔
163
        [holding['library'], label].select(&:present?).join(' - ')
9✔
164
      end
165

166
      def holding_location_code(holding)
1✔
167
        [holding['library_code'], holding['location_code']].join('$')
18✔
168
      end
169

170
      def aeon?(location_record)
1✔
171
        location_record&.aeon_location
9✔
172
      end
173
  end
174
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