• 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

32.03
/app/adapters/alma_adapter/alma_item.rb
1
class AlmaAdapter
1✔
2
  class AlmaItem < SimpleDelegator
1✔
3
    attr_reader :item
1✔
4

5
    # @param item [Alma::BibItem]
6
    def initialize(item)
1✔
UNCOV
7
      @item = item
×
UNCOV
8
      super(item)
×
9
    end
10

11
    def self.reserve_location?(library_code, location_code)
1✔
UNCOV
12
      return false if library_code.nil? || location_code.nil?
×
13

UNCOV
14
      Rails.cache.fetch("library_#{library_code}_#{location_code}", expires_in: 30.minutes) do
×
15
        # We could get this information from our location table if we want to avoid the Alma API call.
UNCOV
16
        record = Alma::Location.find(library_code:, location_code:)
×
UNCOV
17
        record.response.dig('fulfillment_unit', 'value') == 'Reserves'
×
18
      end
19
    end
20

21
    def composite_location
1✔
UNCOV
22
      "#{library}$#{location}"
×
23
    end
24

25
    def composite_temp_location
1✔
UNCOV
26
      return unless in_temp_location?
×
27

UNCOV
28
      "#{temp_library}$#{temp_location}"
×
29
    end
30

31
    def composite_perm_location
1✔
UNCOV
32
      "#{holding_library}$#{holding_location}"
×
33
    end
34

35
    def composite_location_display
1✔
UNCOV
36
      if in_temp_location?
×
UNCOV
37
        composite_temp_location
×
38
      else
UNCOV
39
        composite_location
×
40
      end
41
    end
42

43
    def composite_location_label_display
1✔
UNCOV
44
      if in_temp_location?
×
UNCOV
45
        holding_location_label(composite_temp_location)
×
46
      else
UNCOV
47
        holding_location_label(composite_location)
×
48
      end
49
    end
50

51
    def composite_library_label_display
1✔
UNCOV
52
      if in_temp_location?
×
UNCOV
53
        holding_data.dig('temp_library', 'desc')
×
54
      else
UNCOV
55
        item_data.dig('library', 'desc')
×
56
      end
57
    end
58

59
    def on_reserve?
1✔
UNCOV
60
      AlmaItem.reserve_location?(library, location)
×
61
    end
62

63
    # @note This is called type because item_type is the value used in the
64
    #   /items endpoint. In migrating to Alma this is largely the policy value
65
    #   with a fallback.
66
    def type
1✔
UNCOV
67
      return 'Gen' if item_data['policy']['value'].blank?
×
68

UNCOV
69
      item_data['policy']['value']
×
70
    end
71

72
    # 876 field used for enrichment in
73
    # AlmaAdapter::MarcRecord#enrich_with_item
74
    def enrichment_876
1✔
UNCOV
75
      MARC::DataField.new(
×
76
        '876', ' ', ' ',
77
        *subfields_for_876
78
      )
79
    end
80

81
    def subfields_for_876
1✔
82
      [
UNCOV
83
        MARC::Subfield.new('0', holding_id),
×
84
        MARC::Subfield.new('3', enum_cron),
85
        MARC::Subfield.new('a', item_id),
86
        MARC::Subfield.new('p', barcode),
87
        MARC::Subfield.new('t', copy_number)
88
      ] + recap_876_fields
89
    end
90

91
    def recap_876_fields
1✔
UNCOV
92
      return [] unless recap_item?
×
93

94
      [
UNCOV
95
        MARC::Subfield.new('h', recap_use_restriction),
×
96
        MARC::Subfield.new('x', group_designation),
97
        MARC::Subfield.new('z', recap_customer_code),
98
        MARC::Subfield.new('j', recap_status),
99
        MARC::Subfield.new('l', 'RECAP'),
100
        MARC::Subfield.new('k', item.holding_library)
101
      ]
102
    end
103

104
    # Status isn't used for recap records, but 876j is a required field.
105
    def recap_status
1✔
UNCOV
106
      'Not Used'
×
107
    end
108

109
    def enum_cron
1✔
UNCOV
110
      return if enumeration.blank? && chronology.blank?
×
UNCOV
111
      return enumeration if chronology.blank?
×
UNCOV
112
      return chronology if enumeration.blank?
×
113

UNCOV
114
      "#{enumeration} (#{chronology})"
×
115
    end
116

117
    def enumeration
1✔
UNCOV
118
      enums = []
×
UNCOV
119
      enums << item.item_data['enumeration_a']
×
UNCOV
120
      enums << item.item_data['enumeration_b']
×
UNCOV
121
      enums << item.item_data['enumeration_c']
×
UNCOV
122
      enums << item.item_data['enumeration_d']
×
UNCOV
123
      enums << item.item_data['enumeration_e']
×
UNCOV
124
      enums << item.item_data['enumeration_f']
×
UNCOV
125
      enums << item.item_data['enumeration_g']
×
UNCOV
126
      enums << item.item_data['enumeration_h']
×
UNCOV
127
      enums.compact_blank.join(', ')
×
128
    end
129

130
    def chronology
1✔
UNCOV
131
      chrons = []
×
UNCOV
132
      chrons << item.item_data['chronology_i']
×
UNCOV
133
      chrons << item.item_data['chronology_j']
×
UNCOV
134
      chrons << item.item_data['chronology_k']
×
UNCOV
135
      chrons << item.item_data['chronology_l']
×
UNCOV
136
      chrons << item.item_data['chronology_m']
×
UNCOV
137
      chrons.compact_blank.join(', ')
×
138
    end
139

140
    def holding_id
1✔
UNCOV
141
      item.holding_data['holding_id']
×
142
    end
143

144
    def item_id
1✔
UNCOV
145
      item.item_data['pid']
×
146
    end
147

148
    def barcode
1✔
UNCOV
149
      item.item_data['barcode']
×
150
    end
151

152
    def copy_number
1✔
UNCOV
153
      item.holding_data['copy_id']
×
154
    end
155

156
    def call_number
1✔
UNCOV
157
      item.holding_data['call_number']
×
158
    end
159

160
    def recap_customer_code
1✔
UNCOV
161
      return unless recap_item?
×
UNCOV
162
      return 'PG' if item.location[0].casecmp('x').zero?
×
163

UNCOV
164
      item.location.upcase
×
165
    end
166

167
    def recap_use_restriction
1✔
UNCOV
168
      return unless recap_item?
×
169

UNCOV
170
      case item.location
×
171
      when *in_library_recap_groups
UNCOV
172
        'In Library Use'
×
173
      when *supervised_recap_groups
UNCOV
174
        'Supervised Use'
×
175
      end
176
    end
177

178
    def group_designation
1✔
UNCOV
179
      return unless recap_item?
×
UNCOV
180
      return 'Committed' if item_cgd_committed?
×
181

UNCOV
182
      case item.location
×
183
      when 'pv', 'pa', 'gp', 'qk', 'pf'
UNCOV
184
        'Shared'
×
UNCOV
185
      when *(in_library_recap_groups_and_private + supervised_recap_groups + no_access_recap_groups)
×
UNCOV
186
        'Private'
×
187
      end
188
    end
189

190
    def recap_item?
1✔
UNCOV
191
      all_recap_groups.include?(holding_location)
×
192
    end
193

194
    def all_recap_groups
1✔
UNCOV
195
      default_recap_groups +
×
196
        in_library_recap_groups +
197
        supervised_recap_groups +
198
        no_access_recap_groups
199
    end
200

201
    def default_recap_groups
1✔
UNCOV
202
      %w[pa gp qk pf]
×
203
    end
204

205
    def in_library_recap_groups
1✔
UNCOV
206
      in_library_recap_groups_and_shared + in_library_recap_groups_and_private
×
207
    end
208

209
    def in_library_recap_groups_and_shared
1✔
UNCOV
210
      ['pv']
×
211
    end
212

213
    def in_library_recap_groups_and_private
1✔
UNCOV
214
      %w[pj pk pl pm pn pt]
×
215
    end
216

217
    def supervised_recap_groups
1✔
UNCOV
218
      %w[pb ph ps pw pz xc xg xm xn xp xr xw xx xgr xcr phr xrr xmr]
×
219
    end
220

221
    def no_access_recap_groups
1✔
UNCOV
222
      %w[jq pe pg ph pq qb ql qv qx]
×
223
    end
224

225
    # Returns a JSON representation used for the /items endpoint.
226
    def as_json
1✔
UNCOV
227
      item['item_data'].merge(
×
228
        'id' => item_id,
229
        'copy_number' => copy_number.to_i,
230
        'temp_location' => composite_temp_location,
231
        'perm_location' => composite_perm_location,
232
        'item_type' => type
233
      )
234
    end
235

236
    def availability_summary
1✔
UNCOV
237
      status = calculate_status
×
238
      {
UNCOV
239
        barcode: item_data['barcode'],
×
240
        id: item_data['pid'],
241
        holding_id:,
242
        copy_number: holding_data['copy_id'],
243
        status: status[:code],        # Available
244
        status_label: status[:label], # Item in place
245
        status_source: status[:source], # e.g. work_order, process_type, base_status
246
        process_type: status[:process_type],
UNCOV
247
        on_reserve: on_reserve? ? 'Y' : 'N',
×
248
        item_type:, # e.g., Gen
249
        pickup_location_id: library, # firestone
250
        pickup_location_code: library, # firestone
251
        location: composite_location, # firestone$stacks
252
        label: holding_location_label(composite_location), # Firestore Library
253
        description: item_data['description'], # "v. 537, no. 7618 (2016 Sept. 1)" - new in Alma
254
        enum_display: enumeration, # in Alma there are many enumerations
255
        chron_display: chronology # in Alma there are many chronologies
256
      }.merge(temp_library_availability_summary)
257
    end
258

259
    def temp_library_availability_summary
1✔
UNCOV
260
      if in_temp_location?
×
UNCOV
261
        {
×
262
          in_temp_library: true,
263
          temp_library_code: temp_library,
264
          temp_library_label: holding_location_label(composite_temp_location),
265
          temp_location_code: composite_temp_location,
266
          temp_location_label: holding_location_label(composite_temp_location)
267
        }
268
      else
UNCOV
269
        { in_temp_library: false }
×
270
      end
271
    end
272

273
    def item_cgd_committed?
1✔
UNCOV
274
      item_committed_to_retain == 'true' && committed_retention_reasons.include?(item_retention_reason)
×
275
    end
276

277
    def committed_retention_reasons
1✔
UNCOV
278
      %w[ReCAPItalianImprints IPLCBrill ReCAPSACAP]
×
279
    end
280

281
    def item_retention_reason
1✔
UNCOV
282
      item_data.dig('retention_reason', 'value')
×
283
    end
284

285
    def item_committed_to_retain
1✔
UNCOV
286
      item_data.dig('committed_to_retain', 'value')
×
287
    end
288

289
    def item_type
1✔
UNCOV
290
      item_data.dig('policy', 'value')
×
291
    end
292

293
    def calculate_status
1✔
UNCOV
294
      return status_from_work_order_type if item_data.dig('work_order_type', 'value').present?
×
UNCOV
295
      return status_from_process_type if item_data.dig('process_type', 'value').present?
×
296

UNCOV
297
      status_from_base_status
×
298
    end
299

300
    def status_from_work_order_type
1✔
UNCOV
301
      value = item_data['work_order_type']['value']
×
UNCOV
302
      desc = item_data['work_order_type']['desc']
×
303

304
      # [Source for values](https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/)
305
      # [Work Order documentation](https://pul-confluence.atlassian.net/wiki/spaces/ALMA/pages/1770142/Work+Orders)
UNCOV
306
      code = if value.in?(%w[Bind Pres AcqWorkOrder CollDev HMT])
×
UNCOV
307
               'Unavailable'
×
308
             else
309
               # "COURSE" or "PHYSICAL_TO_DIGITIZATION"
310
               'Available'
×
311
             end
UNCOV
312
      { code:, label: desc, source: 'work_order' }
×
313
    end
314

315
    def status_from_process_type
1✔
316
      # For now we return "Unavailable" for any item that has a process_type.
317
      # You can see a list of all the possible values here:
318
      #   https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
UNCOV
319
      value = item_data.dig('process_type', 'value')
×
UNCOV
320
      desc = item_data.dig('process_type', 'desc')
×
321

UNCOV
322
      { code: 'Unavailable', label: desc, source: 'process_type', process_type: value }
×
323
    end
324

325
    def status_from_base_status
1✔
UNCOV
326
      value = item_data.dig('base_status', 'value')
×
UNCOV
327
      desc = item_data.dig('base_status', 'desc')
×
328

329
      # Source for values: https://developers.exlibrisgroup.com/alma/apis/docs/xsd/rest_item.xsd/
UNCOV
330
      code = value == '1' ? 'Available' : 'Unavailable'
×
UNCOV
331
      { code:, label: desc, source: 'base_status' }
×
332
    end
333

334
    # Create the label by retrieving the value from the holding library label (external_name in Alma)
335
    # Add the library label in front if it exists
336
    def holding_location_label(code)
1✔
UNCOV
337
      label = HoldingLocation.find_by(code:)&.label
×
UNCOV
338
      [composite_library_label_display, label].select(&:present?).join(' - ')
×
339
    end
340
  end
341
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