berkeley_library-alma 0.0.3 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 581717a54a7d13196acbf99d46db7c2e24931038a144994b9a262097efa3aae1
4
- data.tar.gz: 3d9aad321ac4dcb7ee392c2ffb509a53c143936ae8256c758de0689a40c5be60
3
+ metadata.gz: ab227f94abea9d1275bceb4ed361cf2d75021ae7d9cf49283e6139404e9e327f
4
+ data.tar.gz: 2370efa8b2bc2b72e6e506cf38271a74ee8a785cbebf7f42b19e49bf84b4a6da
5
5
  SHA512:
6
- metadata.gz: fd8d67af487bac3d335a672d538fe72289a08e9abe470f560c3af5157a5724acd2619937b3e35dc802811a33e7181719516eece6a9c894ecfb1e38899ff6fe0f
7
- data.tar.gz: 7f3d90e3777dd0b129931a7a919366afbb0db0fbb6ca4d5aa331c4be42c925f8ffc0d9b31f3b05bfcf4957c61afd343f3d496697ff10a979fb3b75c2c8f6db50
6
+ metadata.gz: 438181aea5e19163ca57dd7c5c9086fd6d324ac5bb6e05fd33917cc70327ca5682e1f07a9ffc5280104207562f6fdda4443070bdc8ba3163b519ba70f96f2558
7
+ data.tar.gz: dc9cafbf0b36ee0ac1875d8cc89b0b4a10bdccd37456f67004e0c3abb0097fde7b8d5da31c946ab1eb4f300dd1f14ad5dcaf550ca697aef0de4f8b6d81521bdb
data/.idea/alma.iml CHANGED
@@ -12,23 +12,23 @@
12
12
  </content>
13
13
  <orderEntry type="jdk" jdkName="RVM: ruby-2.7.5" jdkType="RUBY_SDK" />
14
14
  <orderEntry type="sourceFolder" forTests="false" />
15
- <orderEntry type="library" scope="PROVIDED" name="actionpack (v6.1.4.4, RVM: ruby-2.7.5) [gem]" level="application" />
16
- <orderEntry type="library" scope="PROVIDED" name="actionview (v6.1.4.4, RVM: ruby-2.7.5) [gem]" level="application" />
17
- <orderEntry type="library" scope="PROVIDED" name="activesupport (v6.1.4.4, RVM: ruby-2.7.5) [gem]" level="application" />
15
+ <orderEntry type="library" scope="PROVIDED" name="actionpack (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
16
+ <orderEntry type="library" scope="PROVIDED" name="actionview (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
17
+ <orderEntry type="library" scope="PROVIDED" name="activesupport (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
18
18
  <orderEntry type="library" scope="PROVIDED" name="addressable (v2.8.0, RVM: ruby-2.7.5) [gem]" level="application" />
19
19
  <orderEntry type="library" scope="PROVIDED" name="amazing_print (v1.4.0, RVM: ruby-2.7.5) [gem]" level="application" />
20
20
  <orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, RVM: ruby-2.7.5) [gem]" level="application" />
21
- <orderEntry type="library" scope="PROVIDED" name="berkeley_library-logging (v0.2.5, RVM: ruby-2.7.5) [gem]" level="application" />
21
+ <orderEntry type="library" scope="PROVIDED" name="berkeley_library-logging (v0.2.6, RVM: ruby-2.7.5) [gem]" level="application" />
22
22
  <orderEntry type="library" scope="PROVIDED" name="berkeley_library-marc (v0.3.1, RVM: ruby-2.7.5) [gem]" level="application" />
23
- <orderEntry type="library" scope="PROVIDED" name="berkeley_library-util (v0.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
23
+ <orderEntry type="library" scope="PROVIDED" name="berkeley_library-util (v0.1.2, RVM: ruby-2.7.5) [gem]" level="application" />
24
24
  <orderEntry type="library" scope="PROVIDED" name="builder (v3.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
25
25
  <orderEntry type="library" scope="PROVIDED" name="bundle-audit (v0.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
26
- <orderEntry type="library" scope="PROVIDED" name="bundler (v2.2.31, RVM: ruby-2.7.5) [gem]" level="application" />
26
+ <orderEntry type="library" scope="PROVIDED" name="bundler (v2.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
27
27
  <orderEntry type="library" scope="PROVIDED" name="bundler-audit (v0.9.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
28
28
  <orderEntry type="library" scope="PROVIDED" name="ci_reporter (v2.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
29
29
  <orderEntry type="library" scope="PROVIDED" name="ci_reporter_rspec (v1.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
30
30
  <orderEntry type="library" scope="PROVIDED" name="colorize (v0.8.1, RVM: ruby-2.7.5) [gem]" level="application" />
31
- <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.9, RVM: ruby-2.7.5) [gem]" level="application" />
31
+ <orderEntry type="library" scope="PROVIDED" name="concurrent-ruby (v1.1.10, RVM: ruby-2.7.5) [gem]" level="application" />
32
32
  <orderEntry type="library" scope="PROVIDED" name="crack (v0.4.5, RVM: ruby-2.7.5) [gem]" level="application" />
33
33
  <orderEntry type="library" scope="PROVIDED" name="crass (v1.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
34
34
  <orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, RVM: ruby-2.7.5) [gem]" level="application" />
@@ -38,16 +38,17 @@
38
38
  <orderEntry type="library" scope="PROVIDED" name="hashdiff (v1.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
39
39
  <orderEntry type="library" scope="PROVIDED" name="http-accept (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
40
40
  <orderEntry type="library" scope="PROVIDED" name="http-cookie (v1.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
41
- <orderEntry type="library" scope="PROVIDED" name="i18n (v1.9.0, RVM: ruby-2.7.5) [gem]" level="application" />
42
- <orderEntry type="library" scope="PROVIDED" name="lograge (v0.11.2, RVM: ruby-2.7.5) [gem]" level="application" />
43
- <orderEntry type="library" scope="PROVIDED" name="loofah (v2.13.0, RVM: ruby-2.7.5) [gem]" level="application" />
41
+ <orderEntry type="library" scope="PROVIDED" name="i18n (v1.10.0, RVM: ruby-2.7.5) [gem]" level="application" />
42
+ <orderEntry type="library" scope="PROVIDED" name="lograge (v0.12.0, RVM: ruby-2.7.5) [gem]" level="application" />
43
+ <orderEntry type="library" scope="PROVIDED" name="loofah (v2.17.0, RVM: ruby-2.7.5) [gem]" level="application" />
44
44
  <orderEntry type="library" scope="PROVIDED" name="marc (v1.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
45
45
  <orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, RVM: ruby-2.7.5) [gem]" level="application" />
46
46
  <orderEntry type="library" scope="PROVIDED" name="mime-types (v3.4.1, RVM: ruby-2.7.5) [gem]" level="application" />
47
47
  <orderEntry type="library" scope="PROVIDED" name="mime-types-data (v3.2022.0105, RVM: ruby-2.7.5) [gem]" level="application" />
48
+ <orderEntry type="library" scope="PROVIDED" name="mini_portile2 (v2.8.0, RVM: ruby-2.7.5) [gem]" level="application" />
48
49
  <orderEntry type="library" scope="PROVIDED" name="minitest (v5.15.0, RVM: ruby-2.7.5) [gem]" level="application" />
49
50
  <orderEntry type="library" scope="PROVIDED" name="netrc (v0.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
50
- <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.1, RVM: ruby-2.7.5) [gem]" level="application" />
51
+ <orderEntry type="library" scope="PROVIDED" name="nokogiri (v1.13.5, RVM: ruby-2.7.5) [gem]" level="application" />
51
52
  <orderEntry type="library" scope="PROVIDED" name="oj (v3.13.11, RVM: ruby-2.7.5) [gem]" level="application" />
52
53
  <orderEntry type="library" scope="PROVIDED" name="ougai (v1.9.1, RVM: ruby-2.7.5) [gem]" level="application" />
53
54
  <orderEntry type="library" scope="PROVIDED" name="parallel (v1.21.0, RVM: ruby-2.7.5) [gem]" level="application" />
@@ -59,23 +60,23 @@
59
60
  <orderEntry type="library" scope="PROVIDED" name="rack-test (v1.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
60
61
  <orderEntry type="library" scope="PROVIDED" name="rails-dom-testing (v2.0.3, RVM: ruby-2.7.5) [gem]" level="application" />
61
62
  <orderEntry type="library" scope="PROVIDED" name="rails-html-sanitizer (v1.4.2, RVM: ruby-2.7.5) [gem]" level="application" />
62
- <orderEntry type="library" scope="PROVIDED" name="railties (v6.1.4.4, RVM: ruby-2.7.5) [gem]" level="application" />
63
+ <orderEntry type="library" scope="PROVIDED" name="railties (v7.0.2.4, RVM: ruby-2.7.5) [gem]" level="application" />
63
64
  <orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
64
65
  <orderEntry type="library" scope="PROVIDED" name="rake (v13.0.6, RVM: ruby-2.7.5) [gem]" level="application" />
65
66
  <orderEntry type="library" scope="PROVIDED" name="regexp_parser (v2.2.0, RVM: ruby-2.7.5) [gem]" level="application" />
66
67
  <orderEntry type="library" scope="PROVIDED" name="request_store (v1.5.1, RVM: ruby-2.7.5) [gem]" level="application" />
67
68
  <orderEntry type="library" scope="PROVIDED" name="rest-client (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
68
69
  <orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.5, RVM: ruby-2.7.5) [gem]" level="application" />
69
- <orderEntry type="library" scope="PROVIDED" name="rspec (v3.10.0, RVM: ruby-2.7.5) [gem]" level="application" />
70
- <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.10.1, RVM: ruby-2.7.5) [gem]" level="application" />
71
- <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.10.2, RVM: ruby-2.7.5) [gem]" level="application" />
72
- <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.10.2, RVM: ruby-2.7.5) [gem]" level="application" />
73
- <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.10.3, RVM: ruby-2.7.5) [gem]" level="application" />
70
+ <orderEntry type="library" scope="PROVIDED" name="rspec (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
71
+ <orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
72
+ <orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
73
+ <orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
74
+ <orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
74
75
  <orderEntry type="library" scope="PROVIDED" name="rubocop (v1.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
75
76
  <orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.15.1, RVM: ruby-2.7.5) [gem]" level="application" />
76
77
  <orderEntry type="library" scope="PROVIDED" name="rubocop-rake (v0.6.0, RVM: ruby-2.7.5) [gem]" level="application" />
77
78
  <orderEntry type="library" scope="PROVIDED" name="rubocop-rspec (v2.4.0, RVM: ruby-2.7.5) [gem]" level="application" />
78
- <orderEntry type="library" scope="PROVIDED" name="ruby-marc-spec (v0.1.1, RVM: ruby-2.7.5) [gem]" level="application" />
79
+ <orderEntry type="library" scope="PROVIDED" name="ruby-marc-spec (v0.1.3, RVM: ruby-2.7.5) [gem]" level="application" />
79
80
  <orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.11.0, RVM: ruby-2.7.5) [gem]" level="application" />
80
81
  <orderEntry type="library" scope="PROVIDED" name="scrub_rb (v1.0.1, RVM: ruby-2.7.5) [gem]" level="application" />
81
82
  <orderEntry type="library" scope="PROVIDED" name="simplecov (v0.21.2, RVM: ruby-2.7.5) [gem]" level="application" />
@@ -86,12 +87,12 @@
86
87
  <orderEntry type="library" scope="PROVIDED" name="typesafe_enum (v0.3.0, RVM: ruby-2.7.5) [gem]" level="application" />
87
88
  <orderEntry type="library" scope="PROVIDED" name="tzinfo (v2.0.4, RVM: ruby-2.7.5) [gem]" level="application" />
88
89
  <orderEntry type="library" scope="PROVIDED" name="unf (v0.1.4, RVM: ruby-2.7.5) [gem]" level="application" />
89
- <orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8, RVM: ruby-2.7.5) [gem]" level="application" />
90
+ <orderEntry type="library" scope="PROVIDED" name="unf_ext (v0.0.8.1, RVM: ruby-2.7.5) [gem]" level="application" />
90
91
  <orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.1.0, RVM: ruby-2.7.5) [gem]" level="application" />
91
92
  <orderEntry type="library" scope="PROVIDED" name="webmock (v3.14.0, RVM: ruby-2.7.5) [gem]" level="application" />
92
93
  <orderEntry type="library" scope="PROVIDED" name="webrick (v1.7.0, RVM: ruby-2.7.5) [gem]" level="application" />
93
94
  <orderEntry type="library" scope="PROVIDED" name="yard (v0.9.27, RVM: ruby-2.7.5) [gem]" level="application" />
94
- <orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.5.3, RVM: ruby-2.7.5) [gem]" level="application" />
95
+ <orderEntry type="library" scope="PROVIDED" name="zeitwerk (v2.5.4, RVM: ruby-2.7.5) [gem]" level="application" />
95
96
  </component>
96
97
  <component name="RModuleSettingsStorage">
97
98
  <LOAD_PATH number="2" string0="$MODULE_DIR$/lib" string1="$MODULE_DIR$/spec" />
@@ -108,7 +109,7 @@
108
109
  </RakeTaskImpl>
109
110
  <RakeTaskImpl description="Run all specs in spec directory, with coverage" fullCommand="coverage" id="coverage" />
110
111
  <RakeTaskImpl description="Run tests, check test coverage, check code style, check for vulnerabilities, build gem" fullCommand="default" id="default" />
111
- <RakeTaskImpl description="Build berkeley_library-alma.gemspec as berkeley_library-alma-0.0.2.gem" fullCommand="gem" id="gem" />
112
+ <RakeTaskImpl description="Build berkeley_library-alma.gemspec as berkeley_library-alma-0.0.5.gem" fullCommand="gem" id="gem" />
112
113
  <RakeTaskImpl description="Run RuboCop with auto-correct, and output results to console" fullCommand="ra" id="ra" />
113
114
  <RakeTaskImpl description="Run rubocop with HTML output" fullCommand="rubocop" id="rubocop" />
114
115
  <RakeTaskImpl id="rubocop">
@@ -7,6 +7,7 @@
7
7
  <inspection_tool class="GoUnusedGlobalVariable" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
8
8
  <inspection_tool class="GoUnusedVariable" enabled="true" level="WARNING" enabled_by_default="true" />
9
9
  <inspection_tool class="GrazieInspection" enabled="false" level="TYPO" enabled_by_default="false" />
10
+ <inspection_tool class="HttpUrlsUsage" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
10
11
  <inspection_tool class="LanguageDetectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
11
12
  <inspection_tool class="Rubocop" enabled="false" level="WARNING" enabled_by_default="false" />
12
13
  <inspection_tool class="RubyCaseWithoutElseBlockInspection" enabled="false" level="WARNING" enabled_by_default="false" />
@@ -15,6 +16,7 @@
15
16
  </inspection_tool>
16
17
  <inspection_tool class="RubyNilAnalysis" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
17
18
  <inspection_tool class="RubyStringKeysInHashInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
19
+ <inspection_tool class="RubyTooManyInstanceVariablesInspection" enabled="false" level="WARNING" enabled_by_default="false" />
18
20
  <inspection_tool class="RubyUnnecessaryReturnStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
19
21
  <inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
20
22
  <option name="processCode" value="true" />
data/CHANGES.md CHANGED
@@ -1,3 +1,19 @@
1
+ # 0.0.6 (5 May 2022)
2
+
3
+ - `SRU#get_marc_records` now supports paginated results.
4
+
5
+ # 0.0.5 (4 May 2022)
6
+
7
+ - extracts `SRU` module for performing SRU queries.
8
+ - MARCXML parsing now uses `MARC::NokogiriReader` rather than the `REXML` default.
9
+
10
+ # 0.0.4 (15 February 2022)
11
+
12
+ - adds `BarCode` subclass of `RecordId`, to look up bibliographic records by
13
+ item barcode. Note that since barcodes have no consistent format, `BarCode`
14
+ objects need to be constructed deliberately with `BarCode#new`, not with
15
+ `RecordId#parse`.
16
+
1
17
  # 0.0.3 (3 February 2022)
2
18
 
3
19
  - use `local_field_996` instead of `other_system_number` when making SRU queries
data/README.md CHANGED
@@ -41,10 +41,34 @@ options to either:
41
41
 
42
42
  ## Retrieving Alma records
43
43
 
44
- The [`RecordId`](lib/berkeley_library/alma/record_id.rb) class encapsulates
45
- an ID that can be used to look up records in Alma via
46
- [SRU](https://developers.exlibrisgroup.com/alma/integrations/sru/).
47
- This can be either an Alma MMS ID:
44
+ ### Via `SRU`
45
+
46
+ The [`SRU`](lib/berkeley_library/alma/sru.rb) module encapsulates Alma
47
+ [SRU](https://developers.exlibrisgroup.com/alma/integrations/sru/) queries.
48
+
49
+ Retrieving MARC records:
50
+
51
+ ```ruby
52
+ reader = BerkeleyLibrary::Alma::SRU.get_marc_records('991005668209706532', '991005668359706532')
53
+ # => #<MARC::XMLReader:0x0000000135b940e8 @freeze=false, @handle=#<StringIO:0x0000000135b94160>...
54
+ ```
55
+
56
+ Making arbitrary SRU queries:
57
+
58
+ ```ruby
59
+ BerkeleyLibrary::Alma::SRU.make_sru_query('alma.other_system_number=UCB-b123230470-01ucs_ber')
60
+ # => "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?><searchRetrieveResponse xmlns=...
61
+ ```
62
+
63
+ ### Via `RecordId`
64
+
65
+ In addition, individual records can be retrieved from an instance of the
66
+ [`RecordId`](lib/berkeley_library/alma/record_id.rb) class, which encapsulates
67
+ an Alma MMS ID, Millennium bib number, or item barcode.
68
+
69
+ #### Initializing `RecordID` objects
70
+
71
+ Alma MMS ID:
48
72
 
49
73
  ```ruby
50
74
  mms_id_str = '991054360089706532'
@@ -52,7 +76,7 @@ record_id = BerkeleyLibrary::Alma::RecordId.parse(mms_id_str)
52
76
  # => #<BerkeleyLibrary::Alma::MMSID:0x0000000138949830 @institution="6532", @mms_id="991054360089706532", @type_prefix="99", @unique_part="105436008970">
53
77
  ```
54
78
 
55
- Or a Millennium bib number:
79
+ Millennium bib number:
56
80
 
57
81
  ```ruby
58
82
  bib_number_str = 'b11082434'
@@ -60,6 +84,20 @@ record_id = BerkeleyLibrary::Alma::RecordId.parse(bib_number_str)
60
84
  # => #<BerkeleyLibrary::Alma::BibNumber:0x0000000118815038 @check_str="9", @digit_str="11082434">
61
85
  ```
62
86
 
87
+ Item barcode:
88
+
89
+ ```ruby
90
+ barcode_str = 'C084093187'
91
+ barcode = BerkeleyLibrary::Alma::BarCode.new(barcode_str)
92
+ # => #<BerkeleyLibrary::Alma::BarCode:0x000000013fac4c08 @barcode="C084093187">
93
+ ```
94
+
95
+ ⚠️ Note that because of the free-form nature of barcodes, they cannot be auto-detected,
96
+ and hence are **not** supported by the `RecordId#parse` method; they must be instantiated
97
+ directly.
98
+
99
+ #### Using `RecordId` objects to make SRU queries
100
+
63
101
  Given a `RecordId` object, you can get the SRU query URI for the corresponding MARC record:
64
102
 
65
103
  ```ruby
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.add_dependency 'berkeley_library-logging', '~> 0.2'
26
26
  spec.add_dependency 'berkeley_library-marc', '~> 0.3.1'
27
- spec.add_dependency 'berkeley_library-util', '~> 0.1', '>= 0.1.1'
27
+ spec.add_dependency 'berkeley_library-util', '~> 0.1', '>= 0.1.2'
28
28
  spec.add_dependency 'nokogiri', '~> 1.12'
29
29
 
30
30
  spec.add_development_dependency 'bundle-audit', '~> 0.1'
data/bin/alma-mms-lookup CHANGED
@@ -25,9 +25,7 @@ require 'berkeley_library/alma'
25
25
  # Configuration
26
26
 
27
27
  # Configure Alma URLs etc.
28
- BerkeleyLibrary::Alma.configure do
29
- Config.default!
30
- end
28
+ BerkeleyLibrary::Alma::Config.default!
31
29
 
32
30
  # Set log level
33
31
  BerkeleyLibrary::Logging.logger.level = Logger::Severity::WARN
@@ -0,0 +1,33 @@
1
+ require 'berkeley_library/alma/record_id'
2
+
3
+ module BerkeleyLibrary
4
+ module Alma
5
+ # {RecordId} subclass representing an item barcode.
6
+ class BarCode
7
+ include RecordId
8
+
9
+ attr_reader :barcode
10
+
11
+ # Initialize a barcode. Since we purchase barcodes of varied formats and accept vendor
12
+ # barcodes as well we are only validating whether it's a string or not.
13
+ def initialize(barcode)
14
+ string?(barcode)
15
+ @barcode = barcode
16
+ end
17
+
18
+ # Returns the SRU query value for this Barcode.
19
+ #
20
+ # @return [String] the Barcode query value
21
+ def sru_query_value
22
+ "alma.barcode=#{@barcode}"
23
+ end
24
+
25
+ private
26
+
27
+ def string?(barcode)
28
+ raise ArgumentError, "Barcode must be a string: #{barcode.inspect}" unless barcode.is_a?(String)
29
+ end
30
+
31
+ end
32
+ end
33
+ end
@@ -7,7 +7,7 @@ module BerkeleyLibrary
7
7
  SUMMARY = 'Alma/Primo utilities for the UC Berkeley Library'.freeze
8
8
  DESCRIPTION = 'A gem providing Alma/Primo-related utility code for the UC Berkeley Library'.freeze
9
9
  LICENSE = 'MIT'.freeze
10
- VERSION = '0.0.3'.freeze
10
+ VERSION = '0.0.6'.freeze
11
11
  HOMEPAGE = 'https://github.com/BerkeleyLibrary/alma'.freeze
12
12
  end
13
13
  end
@@ -2,6 +2,7 @@ require 'berkeley_library/logging'
2
2
  require 'berkeley_library/marc'
3
3
  require 'berkeley_library/util/uris'
4
4
  require 'berkeley_library/alma/constants'
5
+ require 'berkeley_library/alma/sru'
5
6
 
6
7
  module BerkeleyLibrary
7
8
  module Alma
@@ -22,6 +23,9 @@ module BerkeleyLibrary
22
23
  # also accepts a {RecordId} and simply returns it, so it can be used in
23
24
  # situations where it may not be clear whether the ID has already been parsed.
24
25
  #
26
+ # **Note:** Use the {BarCode} class for barcodes, which don't have a consistent
27
+ # format and hence can't be auto-detected.
28
+ #
25
29
  # @param id [String, RecordId] the ID to parse
26
30
  # @return [RecordId, nil] an {MMSID} or {BibNumber}, depending on the type of ID,
27
31
  # or `nil` if the specified `id` is neither an MMS ID nor a bib number
@@ -44,13 +48,7 @@ module BerkeleyLibrary
44
48
  #
45
49
  # @return [URI] the MARC URI
46
50
  def marc_uri
47
- query_string = URI.encode_www_form(
48
- 'version' => '1.2',
49
- 'operation' => 'searchRetrieve',
50
- 'query' => sru_query_value
51
- )
52
-
53
- URIs.append(Config.alma_sru_base_uri, '?', query_string)
51
+ SRU.sru_query_uri(sru_query_value)
54
52
  end
55
53
 
56
54
  # Makes an SRU query for this record and returns a MARC record, or nil if the
@@ -62,8 +60,8 @@ module BerkeleyLibrary
62
60
  # @return [MARC::Record, nil] the MARC record
63
61
  # rubocop:disable Naming/AccessorMethodName
64
62
  def get_marc_record
65
- marc_xml = get_marc_xml
66
- logger.warn("GET #{marc_uri} did not return a MARC record") unless (marc_record = parse_marc_xml(marc_xml))
63
+ records = SRU.marc_records_for(sru_query_value)
64
+ logger.warn("GET #{marc_uri} did not return a MARC record") unless (marc_record = records.first)
67
65
  marc_record
68
66
  end
69
67
  # rubocop:enable Naming/AccessorMethodName
@@ -74,10 +72,7 @@ module BerkeleyLibrary
74
72
  # @return [String, nil] the SRU query response body, or nil in the event of an error.
75
73
  # rubocop:disable Naming/AccessorMethodName
76
74
  def get_marc_xml
77
- URIs.get(marc_uri, headers: { user_agent: DEFAULT_USER_AGENT })
78
- rescue RestClient::Exception => e
79
- logger.warn("GET #{marc_uri} failed", e)
80
- nil
75
+ SRU.make_sru_query(sru_query_value)
81
76
  end
82
77
  # rubocop:enable Naming/AccessorMethodName
83
78
 
@@ -95,19 +90,6 @@ module BerkeleyLibrary
95
90
 
96
91
  to_s <=> other.to_s
97
92
  end
98
-
99
- # ------------------------------------------------------------
100
- # Private methods
101
-
102
- private
103
-
104
- def parse_marc_xml(xml)
105
- return unless xml
106
-
107
- input = StringIO.new(xml.scrub)
108
- reader = MARC::XMLReader.new(input)
109
- reader.first
110
- end
111
93
  end
112
94
  end
113
95
  end
@@ -0,0 +1,102 @@
1
+ require 'uri'
2
+ require 'berkeley_library/util/uris'
3
+ require 'berkeley_library/alma/config'
4
+ require 'berkeley_library/alma/constants'
5
+ require 'berkeley_library/alma/record_id'
6
+ require 'berkeley_library/alma/sru/xml_reader'
7
+
8
+ module BerkeleyLibrary
9
+ module Alma
10
+ module SRU
11
+ include BerkeleyLibrary::Logging
12
+ include Constants
13
+
14
+ class << self
15
+ include SRU
16
+ end
17
+
18
+ # Given a list of record IDs, returns the MARC records for each ID (if found).
19
+ # Note that the order of the records is not guaranteed.
20
+ #
21
+ # @param record_ids [Array<String, RecordId>] the record IDs to look up
22
+ # @param freeze [Boolean] whether to freeze the records
23
+ # @return [Enumerator::Lazy<MARC::Record>] the records
24
+ def get_marc_records(*record_ids, freeze: false)
25
+ # noinspection RubyMismatchedReturnType
26
+ parsed_ids = record_ids.filter_map { |id| RecordId.parse(id) }
27
+ raise ArgumentError, "Argument #{record_ids.inspect} contain no valid record IDs" if parsed_ids.empty?
28
+
29
+ sru_query_value = parsed_ids.map(&:sru_query_value).join(' or ')
30
+ SRU.marc_records_for(sru_query_value, freeze: freeze)
31
+ end
32
+
33
+ # Returns a URI for retrieving records for the specified query
34
+ # via SRU. Requires {Config#alma_sru_base_uri} to be set.
35
+ #
36
+ # @return [URI] the MARC URI
37
+ def sru_query_uri(sru_query_value)
38
+ query_string = URI.encode_www_form(
39
+ 'version' => '1.2',
40
+ 'operation' => 'searchRetrieve',
41
+ 'query' => sru_query_value
42
+ )
43
+
44
+ Util::URIs.append(Config.alma_sru_base_uri, '?', query_string)
45
+ end
46
+
47
+ # Makes an SRU query for the specified query value and returns the query response
48
+ # as a string.
49
+ #
50
+ # @param query_value [String] the value of the SRU query parameter
51
+ # @return [String, nil] the SRU query response body, or nil in the event of an error.
52
+ def make_sru_query(query_value)
53
+ uri = sru_query_uri(query_value)
54
+ do_get(uri)
55
+ end
56
+
57
+ # Makes an SRU query for the specified query value and returns the query response
58
+ # as MARC records.
59
+ #
60
+ # @param query_value [String] the value of the SRU query parameter
61
+ # @param freeze [Boolean] whether to freeze the records
62
+ # @return [Enumerator::Lazy<MARC::Record>] the records
63
+ def marc_records_for(query_value, freeze: false)
64
+ Enumerator.new do |y|
65
+ uri = sru_query_uri(query_value)
66
+ perform_query(uri, freeze: freeze) { |rec| y << rec }
67
+ end.lazy
68
+ end
69
+
70
+ private
71
+
72
+ def perform_query(query_uri, start_record: nil, freeze: false, &block)
73
+ full_query_uri = full_query_uri_for(query_uri, start_record)
74
+ next_start_record = perform_single_query(full_query_uri, freeze, &block)
75
+ return unless next_start_record
76
+
77
+ perform_query(query_uri, start_record: next_start_record, freeze: freeze, &block)
78
+ end
79
+
80
+ def full_query_uri_for(query_uri, start_record)
81
+ return query_uri unless start_record
82
+
83
+ BerkeleyLibrary::Util::URIs.append(query_uri, "&startRecord=#{start_record}")
84
+ end
85
+
86
+ def perform_single_query(query_uri, freeze, &block)
87
+ return unless (xml = do_get(query_uri))
88
+
89
+ xml_reader = XMLReader.read(xml, freeze: freeze)
90
+ xml_reader.each(&block)
91
+ xml_reader.next_record_position
92
+ end
93
+
94
+ def do_get(uri)
95
+ Util::URIs.get(uri, headers: { user_agent: DEFAULT_USER_AGENT })
96
+ rescue RestClient::Exception => e
97
+ logger.warn("GET #{uri} failed", e)
98
+ nil
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,148 @@
1
+ require 'nokogiri'
2
+ require 'marc/xml_parsers'
3
+ require 'berkeley_library/util/files'
4
+
5
+ module BerkeleyLibrary
6
+ module Alma
7
+ module SRU
8
+ # A customized XML reader for reading MARC records from SRU search results.
9
+ class XMLReader
10
+ include Enumerable
11
+ include ::MARC::NokogiriReader
12
+ include BerkeleyLibrary::Util::Files
13
+
14
+ # ############################################################
15
+ # Constants
16
+
17
+ NS_SRW = 'http://www.loc.gov/zing/srw/'.freeze
18
+ NS_MARC = 'http://www.loc.gov/MARC21/slim'.freeze
19
+
20
+ # ############################################################
21
+ # Attributes
22
+
23
+ # @return [Integer, nil] the record identifier of the most recently parsed record, if any
24
+ attr_reader :last_record_id
25
+
26
+ # @return [Integer, nil] the record position of the most recently parsed record, if any
27
+ attr_reader :last_record_position
28
+
29
+ # @return [Integer, nil] the next record position, if present
30
+ attr_reader :next_record_position
31
+
32
+ # Returns the total number of records, based on the `<numberOfRecords/>` tag
33
+ # returned in the SRU response.
34
+ #
35
+ # Note that the total is not guaranteed to be present, and if present,
36
+ # may not be present unless at least some records have been parsed.
37
+ #
38
+ # @return [Integer, nil] the total number of records, or `nil` if the total has not been read yet
39
+ def num_records
40
+ @num_records&.to_i
41
+ end
42
+
43
+ # Returns the number of records yielded.
44
+ #
45
+ # @return [Integer] the number of records yielded.
46
+ def records_yielded
47
+ @records_yielded ||= 0
48
+ end
49
+
50
+ # ############################################################
51
+ # Initializer
52
+
53
+ def initialize(source, freeze: false)
54
+ @handle = ensure_io(source)
55
+ @freeze = freeze
56
+ init
57
+ end
58
+
59
+ class << self
60
+ # Reads MARC records from an XML datasource given either as an XML string, a file path,
61
+ # or as an IO object.
62
+ #
63
+ # @param source [String, Pathname, IO] an XML string, the path to a file, or an IO to read from directly
64
+ # @param freeze [Boolean] whether to freeze each record after reading
65
+ def read(source, freeze: false)
66
+ new(source, freeze: freeze)
67
+ end
68
+ end
69
+
70
+ # ############################################################
71
+ # MARC::GenericPullParser overrides
72
+
73
+ def yield_record
74
+ @record[:record].tap do |record|
75
+ record.freeze if @freeze
76
+ end
77
+
78
+ super
79
+ ensure
80
+ increment_records_yielded!
81
+ end
82
+
83
+ # ############################################################
84
+ # Nokogiri::XML::SAX::Document overrides
85
+
86
+ # @see Nokogiri::XML::Sax::Document#start_element_namespace
87
+ # rubocop:disable Metrics/ParameterLists
88
+ def start_element_namespace(name, attrs = [], prefix = nil, uri = nil, ns = [])
89
+ super
90
+
91
+ @current_element_ns = uri
92
+ @current_element_name = name
93
+ end
94
+ # rubocop:enable Metrics/ParameterLists
95
+
96
+ # @see Nokogiri::XML::Sax::Document#end_element_namespace
97
+ def end_element_namespace(name, prefix = nil, uri = nil)
98
+ # Delay yielding record till we reach the end of the outer SRU <record/>
99
+ # element (not the inner MARC <record/> element), so we can record the
100
+ # values of <recordIdentifier> and <recordPosition/>
101
+ if name.downcase == 'record'
102
+ yield_record if uri == NS_SRW
103
+ elsif uri == NS_MARC
104
+ super
105
+ end
106
+
107
+ @current_element_name = nil
108
+ end
109
+
110
+ # @see Nokogiri::XML::Sax::Document#characters
111
+ # rubocop:disable Metrics/MethodLength
112
+ def characters(string)
113
+ return super unless NS_SRW == @current_element_ns
114
+ return unless (name = @current_element_name)
115
+
116
+ case name
117
+ when 'numberOfRecords'
118
+ @num_records = string
119
+ when 'recordIdentifier'
120
+ @last_record_id = string
121
+ when 'recordPosition'
122
+ @last_record_position = string.to_i
123
+ when 'nextRecordPosition'
124
+ @next_record_position = string.to_i
125
+ end
126
+ end
127
+ # rubocop:enable Metrics/MethodLength
128
+
129
+ # ############################################################
130
+ # Private
131
+
132
+ private
133
+
134
+ def ensure_io(file)
135
+ return file if reader_like?(file)
136
+ return File.new(file) if file_exists?(file)
137
+ return StringIO.new(file) if file =~ /^\s*</x
138
+
139
+ raise ArgumentError, "Don't know how to read XML from #{file.inspect}: not an IO, file path, or XML text"
140
+ end
141
+
142
+ def increment_records_yielded!
143
+ @records_yielded = records_yielded + 1
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1 @@
1
+ Dir.glob(File.expand_path('sru/*.rb', __dir__)).sort.each(&method(:require))