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

pulibrary / bibdata / 1dcebae2-3318-4e77-bc53-82276e293354

02 May 2025 04:45PM UTC coverage: 28.256% (-63.9%) from 92.189%
1dcebae2-3318-4e77-bc53-82276e293354

push

circleci

sandbergja
Add basic infrastructure for compiling rust code

* Add a rake compile task to compile
* Run the rake task in CI
* Run the rake task before rspec tests with the rust tag, to provide quick feedback on rust changes in TDD cycles

2 of 7 new or added lines in 2 files covered. (28.57%)

2467 existing lines in 97 files now uncovered.

1089 of 3854 relevant lines covered (28.26%)

0.29 hits per line

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

24.0
/app/adapters/alma_adapter.rb
1
require 'open-uri'
1✔
2

3
class AlmaAdapter
1✔
4
  class ::Alma::PerSecondThresholdError < Alma::StandardError; end
1✔
5
  class ::Alma::NotFoundError < Alma::StandardError; end
1✔
6

7
  attr_reader :connection
1✔
8

9
  def initialize(connection: AlmaAdapter::Connector.connection)
1✔
UNCOV
10
    @connection = connection
×
11
  end
12

13
  # Get /almaws/v1/bibs Retrieve bibs
14
  # @param id [String] e.g. id = "991227830000541"
15
  # @see https://developers.exlibrisgroup.com/console/?url=/wp-content/uploads/alma/openapi/bibs.json#/Catalog/get%2Falmaws%2Fv1%2Fbibs Values that could be passed to the alma API
16
  # get one bib record is supported in the bibdata UI and in the bibliographic_controller
17
  # @return [AlmaAdapter::MarcRecord]
18
  def get_bib_record(id, show_suppressed: false)
1✔
UNCOV
19
    return nil unless /\A\d+\z/.match? id
×
20

UNCOV
21
    get_bib_records([id], show_suppressed:)&.first
×
22
  end
23

24
  # Get /almaws/v1/bibs Retrieve bibs
25
  # @param ids [Array] e.g. ids = ["991227850000541","991227840000541","99222441306421"]
26
  # @see https://developers.exlibrisgroup.com/console/?url=/wp-content/uploads/alma/openapi/bibs.json#/Catalog/get%2Falmaws%2Fv1%2Fbibs Values that could be passed to the alma API
27
  # @return [Array<AlmaAdapter::MarcRecord>]
28
  def get_bib_records(ids, show_suppressed: false)
1✔
UNCOV
29
    cql_query = show_suppressed ? '' : 'alma.mms_tagSuppressed=false%20and%20'
×
UNCOV
30
    cql_query << ids.map { |id| "alma.mms_id=#{id}" }.join('%20or%20')
×
UNCOV
31
    sru_url = "#{Rails.configuration.alma['sru_url']}?"\
×
32
              'version=1.2&operation=searchRetrieve&'\
33
              "recordSchema=marcxml&query=#{cql_query}"\
34
              "&maximumRecords=#{ids.count}"
UNCOV
35
    MARC::XMLReader.new(URI(sru_url).open, parser: :nokogiri).map do |record|
×
UNCOV
36
      MarcRecord.new(nil, record)
×
37
    end
38
  end
39

40
  def get_availability(ids:)
1✔
41
    bibs = Alma::Bib.find(Array.wrap(ids), expand: %w[p_avail e_avail d_avail requests].join(',')).each
×
42
    AvailabilityStatus.from_bib(bib: bibs&.first).to_h
×
43
  end
44

45
  # Returns availability for one bib id
46
  def get_availability_one(id:, deep_check: false)
1✔
UNCOV
47
    get_availability_status = lambda do |bib|
×
UNCOV
48
      av_info = AvailabilityStatus.new(bib:, deep_check:).bib_availability
×
UNCOV
49
      temp_locations = av_info.any? { |_key, value| value[:temp_location] }
×
UNCOV
50
      if temp_locations && deep_check
×
51
        # We don't get enough information at the holding level for items located
52
        # in temporary locations. But if the client requests it we can drill into
53
        # the item information to get all the information (keep in mind that this
54
        # involves an extra API call that is slow-ish.)
UNCOV
55
        AvailabilityStatus.new(bib:).bib_availability_from_items
×
56
      else
UNCOV
57
        av_info
×
58
      end
59
    end
60

UNCOV
61
    Alma::Bib.find(Array.wrap(id), expand: %w[p_avail e_avail d_avail requests].join(','))
×
62
             .each
63
             .lazy
UNCOV
64
             .map { |bib| { bib.id => get_availability_status.call(bib) } }
×
65
             .first
66
  rescue Alma::StandardError => e
UNCOV
67
    handle_alma_error(client_error: e)
×
68
  end
69

70
  # Returns availability for a list of bib ids
71
  def get_availability_many(ids:, deep_check: false)
1✔
UNCOV
72
    options = { timeout: 20 }
×
UNCOV
73
    AlmaAdapter::Execute.call(options:, message: "Find bibs #{ids.join(',')}") do
×
UNCOV
74
      bibs = Alma::Bib.find(Array.wrap(ids), expand: %w[p_avail e_avail d_avail requests].join(',')).each
×
UNCOV
75
      return nil if bibs.count == 0
×
76

UNCOV
77
      availability = bibs.each_with_object({}) do |bib, acc|
×
UNCOV
78
        acc[bib.id] = AvailabilityStatus.new(bib:, deep_check:).bib_availability
×
79
      end
UNCOV
80
      availability
×
81
    end
82
  rescue Alma::StandardError => e
UNCOV
83
    handle_alma_error(client_error: e)
×
84
  end
85

86
  def get_availability_holding(id:, holding_id:)
1✔
87
    # Fetch the bib record and get the information for the individual holding
UNCOV
88
    bibs = Alma::Bib.find(Array.wrap(id), expand: %w[p_avail e_avail d_avail requests].join(',')).each
×
UNCOV
89
    return nil if bibs.count == 0
×
90

91
    # Fetch the items for the holding and create
92
    # the availability response for each item
UNCOV
93
    bib_status = AvailabilityStatus.from_bib(bib: bibs.first)
×
UNCOV
94
    holding_items = bib_status.holding_item_data(holding_id:)
×
UNCOV
95
    holding_items[:items].map(&:availability_summary)
×
96
  rescue Alma::StandardError => e
UNCOV
97
    handle_alma_error(client_error: e)
×
98
  end
99

100
  # Returns list of holding records for a given MMS
101
  # @param id [string]. e.g id = "991227850000541"
102
  def get_holding_records(id)
1✔
UNCOV
103
    res = connection.get(
×
104
      "bibs/#{id}/holdings",
105
      apikey:
106
    )
107

UNCOV
108
    res.body.force_encoding('utf-8') if validate_response!(response: res)
×
109
  end
110

111
  # @param id [String]. e.g id = "991227850000541"
112
  # @return [AlmaAdapter::BibItemSet]
113
  def get_items_for_bib(id)
1✔
UNCOV
114
    alma_options = { timeout: 10 }
×
UNCOV
115
    AlmaAdapter::Execute.call(options: alma_options, message: "Find items for bib #{id}") do
×
UNCOV
116
      find_options = { limit: Alma::BibItemSet::ITEMS_PER_PAGE, expand: 'due_date_policy,due_date', order_by: 'library', direction: 'asc' }
×
UNCOV
117
      bib_items = Alma::BibItem.find(id, find_options).all.map { |item| AlmaAdapter::AlmaItem.new(item) }
×
UNCOV
118
      AlmaAdapter::BibItemSet.new(items: bib_items, adapter: self)
×
119
    end
120
  end
121

122
  def item_by_barcode(barcode)
1✔
UNCOV
123
    item = Alma::BibItem.find_by_barcode(barcode)
×
UNCOV
124
    if item['errorsExist']
×
125
      # In this case although `item` is an object of type Alma::BibItem, its
126
      # content is really an HTTPartyResponse with the error information. :shrug:
UNCOV
127
      message = item.item.parsed_response.to_s
×
UNCOV
128
      error = message.include?('No items found') ? Alma::NotFoundError.new(message) : Alma::StandardError.new(message)
×
UNCOV
129
      handle_alma_error(client_error: error)
×
130
    end
UNCOV
131
    item
×
132
  end
133

134
  def holding_by_id(mms_id:, holding_id:)
1✔
UNCOV
135
    holding = Alma::BibHolding.find(mms_id:, holding_id:)
×
UNCOV
136
    if holding['errorsExist']
×
137
      # In this case although `holding` is an object of type Alma::BibHolding, its
138
      # content is really an HTTPartyResponse with the error information. :shrug:
UNCOV
139
      error = Alma::StandardError.new(holding.holding.parsed_response.to_s)
×
UNCOV
140
      handle_alma_error(client_error: error)
×
141
    end
UNCOV
142
    holding
×
143
  end
144

145
  def find_user(patron_id)
1✔
UNCOV
146
    options = { enable_loggable: true }
×
UNCOV
147
    AlmaAdapter::Execute.call(options:, message: "Find user #{patron_id}") do
×
UNCOV
148
      return Alma::User.find(patron_id, expand: '')
×
149
    rescue Alma::StandardError => e
150
      # The Alma gem throws "not found" for all errors but only error code 401861
151
      # really represents a record not found.
UNCOV
152
      raise Alma::NotFoundError, "User #{patron_id} was not found" if e.message.include?('"errorCode":"401861"')
×
153

UNCOV
154
      handle_alma_error(client_error: e)
×
155
    end
156
  end
157

158
  private
1✔
159

160
    def apikey
1✔
UNCOV
161
      Rails.configuration.alma[:read_only_apikey]
×
162
    end
163

164
    def build_alma_error_from(json:)
1✔
UNCOV
165
      error = json.deep_symbolize_keys
×
UNCOV
166
      error_code = error[:errorCode]
×
167

UNCOV
168
      case error_code
×
169
      when 'PER_SECOND_THRESHOLD'
UNCOV
170
        Alma::PerSecondThresholdError.new(error[:errorMessage])
×
171
      else
UNCOV
172
        Alma::StandardError.new(error[:errorMessage])
×
173
      end
174
    end
175

176
    def build_alma_errors_from(json:)
1✔
UNCOV
177
      error_list = json['errorList']
×
UNCOV
178
      errors = error_list['error']
×
UNCOV
179
      errors.map { |error| build_alma_error_from(json: error) }
×
180
    end
181

182
    def build_alma_errors(from:)
1✔
UNCOV
183
      message = from.message.gsub('=>', ':').gsub('nil', '"null"')
×
UNCOV
184
      parsed_message = JSON.parse(message)
×
UNCOV
185
      build_alma_errors_from(json: parsed_message)
×
186
    end
187

188
    def validate_response!(response:)
1✔
UNCOV
189
      return true if response.status == 200
×
190

UNCOV
191
      response_body = JSON.parse(response.body)
×
UNCOV
192
      errors = build_alma_errors_from(json: response_body)
×
UNCOV
193
      return true if errors.empty?
×
194

UNCOV
195
      raise(errors.first)
×
196
    end
197

198
    def handle_alma_error(client_error:)
1✔
UNCOV
199
      errors = build_alma_errors(from: client_error)
×
UNCOV
200
      raise errors.first if errors.first.is_a?(Alma::PerSecondThresholdError)
×
201

UNCOV
202
      raise client_error
×
203
    end
204
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