evergreen_holdings 0.2.1 → 0.5.0

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
- SHA1:
3
- metadata.gz: '099d21142437946323589d61103ee3199d041045'
4
- data.tar.gz: cfb387b243746a34c9f8233fa75699be53ff74e0
2
+ SHA256:
3
+ metadata.gz: b34258835d3686af2107f5ea96b6a3a95f2eca5dc4e19630fe1cc8fecce583d1
4
+ data.tar.gz: f285caa0b0a3cc693ad27a9b169c540dd3bc30f1cde438967d1b44971a6d97f2
5
5
  SHA512:
6
- metadata.gz: 0f300bb0e70ff7dfa6942c8ee8da1a138809fd4c2340329ed2fb78e308d42d883bf26fe0bdc3d75367bee1d1b1d6b56f962710981b30ddbd478202a96f96f538
7
- data.tar.gz: 77c78a3b402989e81c1a7c942c229fbb0b62e2a52ad819c262b17ef53ca6c51126dc17c2da12eb0a3e87039a294e6f690409aa88015f6ca6e34ce0581001f7b4
6
+ metadata.gz: 79564fcba394887f6efaeb9046a269a47796d14efc6def71771c22bce4852c6eec148c3e9ef92b4fa408461cde87fa407d2e1fd27e9d9ad4a1c46ea3c575dd90
7
+ data.tar.gz: defa58f9e2349386a241c9d70bef66764d74d37bc993fd3a09202a6803300e4bf6e9b8501781862e7f8424266a243668e3086d2632054b92f3accab4cd0db7cc
@@ -1,206 +1,232 @@
1
- require 'net/http'
2
- require 'json'
3
- require 'evergreen_holdings/errors'
4
-
5
- OSRF_PATH = '/osrf-gateway-v1'
6
-
7
- module EvergreenHoldings
8
- class Connection
9
- attr_reader :org_units
10
- # Create a new object with the evergreen_domain
11
- # specified, e.g. http://libcat.linnbenton.edu
12
- #
13
- # Usage: `conn = EvergreenHoldings::Connection.new 'http://gapines.org'`
14
- def initialize evergreen_domain
15
- @gateway = URI evergreen_domain+OSRF_PATH
16
- unless fetch_statuses
17
- raise CouldNotConnectToEvergreenError
18
- end
19
- fetch_ou_tree
20
- end
21
-
22
- # Fetch holdings data from the Evergreen server
23
- # Returns a Status object
24
- #
25
- # Usage: `stat = conn.get_holdings 23405`
26
- # If you just want holdings at a specific org_unit: `my_connection.get_holdings 23405, org_unit: 5`
27
- def get_holdings tcn, options = {}
28
- if options.key?(:org_unit)
29
- if options[:descendants]
30
- params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}"
31
- if @org_units[options[:org_unit]][:descendants]
32
- @org_units[options[:org_unit]][:descendants].each do |ou|
33
- params << + "&param=#{ou}"
34
- end
35
- end
36
- else
37
- params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}&param=#{options[:org_unit]}"
38
- end
39
- else
40
- params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.global.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}"
41
- end
42
- @gateway.query = params
43
-
44
- res = send_query
45
- return Status.new res.body, self if res
46
- end
47
-
48
- # Given an ID, returns a human-readable name
49
- def location_name id
50
- params = "format=json&input_format=json&service=open-ils.circ&method=open-ils.circ.copy_location.retrieve&param=#{id}"
51
- @gateway.query = params
52
- res = send_query
53
- if res
54
- data = JSON.parse(res.body)['payload'][0]
55
- unless data.key? 'stacktrace'
56
- return data['__p'][4]
57
- end
58
- end
59
- return id
60
- end
61
-
62
- def status_name id
63
- return @possible_item_statuses[id]
64
- end
65
-
66
- def ou_name id
67
- return @org_units[id][:name]
68
- end
69
-
70
- private
71
-
72
- def add_ou_descendants id, parent
73
- (@org_units[parent][:descendants] ||= []) << id
74
- if @org_units[parent][:parent]
75
- add_ou_descendants id, @org_units[parent][:parent]
76
- end
77
- end
78
-
79
- def take_info_from_ou_tree o
80
- @org_units[o[3]] = {}
81
- @org_units[o[3]][:name] = o[6]
82
- if o[8]
83
- @org_units[o[3]][:parent] = o[8]
84
- add_ou_descendants o[3], o[8]
85
- end
86
- o[0].each do |p|
87
- take_info_from_ou_tree p['__p']
88
- end
89
- end
90
-
91
-
92
- def send_query
93
- begin
94
- res = Net::HTTP.get_response(@gateway)
95
- rescue Errno::ECONNREFUSED, Net::ReadTimeout
96
- return nil
97
- end
98
- return res if res.is_a?(Net::HTTPSuccess)
99
- return nil
100
- end
101
-
102
- def fetch_statuses
103
- @possible_item_statuses = []
104
- params = 'format=json&input_format=json&service=open-ils.search&method=open-ils.search.config.copy_status.retrieve.all'
105
- @gateway.query = params
106
- res = send_query
107
- if res
108
- stats = JSON.parse(res.body)['payload'][0]
109
- stats.each do |stat|
110
- @possible_item_statuses[stat['__p'][1]] = stat['__p'][2]
111
- end
112
- return true if stats.size > 0
113
- end
114
- return false
115
- end
116
-
117
- def fetch_ou_tree
118
- @org_units = {}
119
- params = 'format=json&input_format=json&service=open-ils.actor&method=open-ils.actor.org_tree.retrieve'
120
- @gateway.query = params
121
- res = send_query
122
- if res
123
- raw_orgs = JSON.parse(res.body)['payload'][0]['__p']
124
- take_info_from_ou_tree raw_orgs
125
- return true if @org_units.size > 0
126
- end
127
- return false
128
- end
129
-
130
- end
131
-
132
- # Status objects represent all the holdings attached to a specific tcn
133
- class Status
134
- attr_reader :copies, :libraries
135
- def initialize json_data, connection = nil
136
- @connection = connection
137
- @raw_data = JSON.parse(json_data)['payload'][0]
138
- extract_copies
139
- substitute_values_for_ids unless @connection.nil?
140
- @available_copies = []
141
- @next_copy_available = 'a date'
142
- end
143
-
144
- # Determines if any copies are available for your patrons
145
- def any_copies_available?
146
- @copies.each do |copy|
147
- return true if 0 == copy.status
148
- return true if 'Available' == copy.status
149
- end
150
- return false
151
- end
152
-
153
- private
154
-
155
- # Look through @raw_data and find the copies
156
- def extract_copies
157
- @copies = Array.new
158
- @raw_data.each do |vol|
159
- if vol['__p'][0].size > 0
160
- vol['__p'][0].each do |item|
161
- i = 0
162
- unless item['__p'][35].is_a? Array
163
- @copies.push Item.new barcode: item['__p'][2], call_number: vol['__p'][7], location: item['__p'][24], status: item['__p'][28], owning_lib: item['__p'][5]
164
- else
165
- begin
166
- @copies.push Item.new barcode: item['__p'][2], call_number: vol['__p'][7], due_date: item['__p'][35][0]['__p'][6], location: item['__p'][24], status: item['__p'][28], owning_lib: item['__p'][5]
167
- rescue
168
- @copies.push Item.new barcode: item['__p'][2], call_number: vol['__p'][7], location: item['__p'][24], status: item['__p'][28], owning_lib: item['__p'][5]
169
- end
170
- end
171
- end
172
- end
173
- end
174
- end
175
-
176
- def substitute_values_for_ids
177
- @libraries = @connection.org_units.clone
178
- @libraries.each { |key, lib| lib[:copies] = Array.new }
179
- @copies.each do |copy|
180
- if copy.location.is_a? Numeric
181
- copy.location = @connection.location_name copy.location
182
- end
183
- if copy.status.is_a? Numeric
184
- copy.status = @connection.status_name copy.status
185
- end
186
- if copy.owning_lib.is_a? Numeric
187
- ou_id = copy.owning_lib
188
- copy.owning_lib = @connection.ou_name copy.owning_lib
189
- @libraries[ou_id][:copies].push copy
190
- end
191
- end
192
- end
193
-
194
- end
195
-
196
- # A physical copy of an item
197
- class Item
198
- attr_accessor :location, :status, :owning_lib
199
- attr_reader :barcode, :call_number, :due_date
200
- def initialize data = {}
201
- data.each do |k,v|
202
- instance_variable_set("@#{k}", v) unless v.nil?
203
- end
204
- end
205
- end
206
- end
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'evergreen_holdings/errors'
6
+ require 'evergreen_holdings/idl_parser'
7
+ require 'nokogiri'
8
+ require 'open-uri'
9
+
10
+ OSRF_PATH = '/osrf-gateway-v1'
11
+
12
+ module EvergreenHoldings
13
+ class Connection
14
+ attr_reader :org_units
15
+
16
+ # Create a new object with the evergreen_domain
17
+ # specified, e.g. http://libcat.linnbenton.edu
18
+ #
19
+ # Usage: `conn = EvergreenHoldings::Connection.new 'http://gapines.org'`
20
+ def initialize(evergreen_domain)
21
+ @evergreen_domain = evergreen_domain
22
+ @gateway = URI evergreen_domain + OSRF_PATH
23
+ @acpl_cache = {}
24
+ fetch_idl_order
25
+ raise CouldNotConnectToEvergreenError unless fetch_statuses
26
+
27
+ fetch_ou_tree
28
+ end
29
+
30
+ # Fetch holdings data from the Evergreen server
31
+ # Returns a Status object
32
+ #
33
+ # Usage: `stat = conn.get_holdings 23405`
34
+ # If you just want holdings at a specific org_unit: `my_connection.get_holdings 23405, org_unit: 5`
35
+ def get_holdings(tcn, options = {})
36
+ if options.key?(:org_unit)
37
+ if options[:descendants]
38
+ params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}"
39
+ @org_units[options[:org_unit]][:descendants]&.each do |ou|
40
+ params << + "&param=#{ou}"
41
+ end
42
+ else
43
+ params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}&param=#{options[:org_unit]}"
44
+ end
45
+ else
46
+ params = "format=json&input_format=json&service=open-ils.cat&method=open-ils.cat.asset.copy_tree.global.retrieve&param=auth_token_not_needed_for_this_call&param=#{tcn}"
47
+ end
48
+ @gateway.query = params
49
+
50
+ res = send_query
51
+ return Status.new res.body, @idl_order, self if res
52
+ end
53
+
54
+ # Given an ID, returns a human-readable name
55
+ def location_name(id)
56
+ @acpl_cache.fetch(id) { |id| fetch_new_acpl(id) || id }
57
+ end
58
+
59
+ def status_name(id)
60
+ @possible_item_statuses[id]
61
+ end
62
+
63
+ def ou_name(id)
64
+ @org_units[id][:name]
65
+ end
66
+
67
+ private
68
+
69
+ def add_ou_descendants(id, parent)
70
+ (@org_units[parent][:descendants] ||= []) << id
71
+ add_ou_descendants id, @org_units[parent][:parent] if @org_units[parent][:parent]
72
+ end
73
+
74
+ def take_info_from_ou_tree(o)
75
+ id = o[@idl_order[:aou]['id']]
76
+ @org_units[id] = {}
77
+ @org_units[id][:name] = o[@idl_order[:aou]['name']]
78
+ if o[@idl_order[:aou]['parent_ou']]
79
+ @org_units[id][:parent] = o[@idl_order[:aou]['parent_ou']]
80
+ add_ou_descendants id, o[@idl_order[:aou]['parent_ou']]
81
+ end
82
+ o[@idl_order[:aou]['children']].each do |p|
83
+ take_info_from_ou_tree p['__p']
84
+ end
85
+ end
86
+
87
+ # Given the ID of a shelving location, this method
88
+ # finds the name of the location, caches it, and
89
+ # returns it
90
+ def fetch_new_acpl(id)
91
+ params = "format=json&input_format=json&service=open-ils.circ&method=open-ils.circ.copy_location.retrieve&param=#{id}"
92
+ @gateway.query = params
93
+ res = send_query
94
+ if res
95
+ data = JSON.parse(res.body)['payload'][0]
96
+ name = data['__p'][@idl_order[:acpl]['name']] unless data.key? 'stacktrace'
97
+ @acpl_cache[id] = name
98
+ return name if name
99
+ end
100
+ false
101
+ end
102
+
103
+ def send_query
104
+ begin
105
+ res = Net::HTTP.get_response(@gateway)
106
+ rescue Errno::ECONNREFUSED, Net::ReadTimeout
107
+ return nil
108
+ end
109
+ return res if res.is_a?(Net::HTTPSuccess)
110
+
111
+ nil
112
+ end
113
+
114
+ def fetch_idl_order
115
+ begin
116
+ idl = Nokogiri::XML(URI.parse("#{@evergreen_domain}/reports/fm_IDL.xml").open)
117
+ rescue Errno::ECONNREFUSED, Net::ReadTimeout, OpenURI::HTTPError
118
+ raise CouldNotConnectToEvergreenError
119
+ end
120
+
121
+ @idl_order = IDLParser.new(idl).field_order_by_class %i[acn acp acpl aou ccs circ]
122
+ end
123
+
124
+ def fetch_statuses
125
+ @possible_item_statuses = []
126
+ params = 'format=json&input_format=json&service=open-ils.search&method=open-ils.search.config.copy_status.retrieve.all'
127
+ @gateway.query = params
128
+ res = send_query
129
+ if res
130
+ stats = JSON.parse(res.body)['payload'][0]
131
+ stats.each do |stat|
132
+ @possible_item_statuses[stat['__p'][@idl_order[:ccs]['id']]] = stat['__p'][@idl_order[:ccs]['name']]
133
+ end
134
+ return true unless stats.empty?
135
+ end
136
+ false
137
+ end
138
+
139
+ def fetch_ou_tree
140
+ @org_units = {}
141
+ params = 'format=json&input_format=json&service=open-ils.actor&method=open-ils.actor.org_tree.retrieve'
142
+ @gateway.query = params
143
+ res = send_query
144
+ if res
145
+ raw_orgs = JSON.parse(res.body)['payload'][0]['__p']
146
+ take_info_from_ou_tree raw_orgs
147
+ return true unless @org_units.empty?
148
+ end
149
+ false
150
+ end
151
+ end
152
+
153
+ # Status objects represent all the holdings attached to a specific tcn
154
+ class Status
155
+ attr_reader :copies, :libraries
156
+
157
+ def initialize(json_data, idl_order, connection = nil)
158
+ @idl_order = idl_order
159
+ @connection = connection
160
+ @raw_data = JSON.parse(json_data)['payload'][0]
161
+ extract_copies
162
+ substitute_values_for_ids unless @connection.nil?
163
+ @available_copies = []
164
+ @next_copy_available = 'a date'
165
+ end
166
+
167
+ # Determines if any copies are available for your patrons
168
+ def any_copies_available?
169
+ @copies.each do |copy|
170
+ return true if copy.status.zero?
171
+ return true if copy.status == 'Available'
172
+ end
173
+ false
174
+ end
175
+
176
+ private
177
+
178
+ # Look through @raw_data and find the copies
179
+ def extract_copies
180
+ @copies = []
181
+ @raw_data.each do |vol|
182
+ next if vol['__p'][0].empty?
183
+
184
+ vol['__p'][0].each do |item|
185
+ item_info = {
186
+ barcode: item['__p'][@idl_order[:acp]['barcode']],
187
+ call_number: vol['__p'][@idl_order[:acn]['label']],
188
+ location: item['__p'][@idl_order[:acp]['location']],
189
+ status: item['__p'][@idl_order[:acp]['status']],
190
+ owning_lib: item['__p'][@idl_order[:acp]['circ_lib']]
191
+ }
192
+ if item['__p'][@idl_order[:acp]['circulations']].is_a? Array
193
+ begin
194
+ item_info[:due_date] =
195
+ item['__p'][@idl_order[:acp]['circulations']][0]['__p'][@idl_order[:circ]['due_date']]
196
+ rescue StandardError
197
+ end
198
+ @copies.push Item.new item_info
199
+ else
200
+ @copies.push Item.new item_info
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ def substitute_values_for_ids
207
+ @libraries = @connection.org_units.clone
208
+ @libraries.each { |_key, lib| lib[:copies] = [] }
209
+ @copies.each do |copy|
210
+ copy.location = @connection.location_name copy.location if copy.location.is_a? Numeric
211
+ copy.status = @connection.status_name copy.status if copy.status.is_a? Numeric
212
+ next unless copy.owning_lib.is_a? Numeric
213
+
214
+ ou_id = copy.owning_lib
215
+ copy.owning_lib = @connection.ou_name copy.owning_lib
216
+ @libraries[ou_id][:copies].push copy
217
+ end
218
+ end
219
+ end
220
+
221
+ # A physical copy of an item
222
+ class Item
223
+ attr_accessor :location, :status, :owning_lib
224
+ attr_reader :barcode, :call_number, :due_date
225
+
226
+ def initialize(data = {})
227
+ data.each do |k, v|
228
+ instance_variable_set("@#{k}", v) unless v.nil?
229
+ end
230
+ end
231
+ end
232
+ end
@@ -1,2 +1,4 @@
1
+ # frozen_string_literals: true
2
+
1
3
  class CouldNotConnectToEvergreenError < StandardError
2
4
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literals: true
2
+
3
+ module EvergreenHoldings
4
+ class IDLParser
5
+ def initialize(idl)
6
+ @idl = idl
7
+ end
8
+
9
+ def field_order_by_class(classes)
10
+ classes.map do |idl_class|
11
+ fields = @idl.xpath("//idl:class[@id='#{idl_class}']/idl:fields/idl:field", 'idl' => 'http://opensrf.org/spec/IDL/base/v1')
12
+ .map.with_index { |field, index| [field['name'], index] }
13
+ [idl_class, fields.to_h]
14
+ end.to_h
15
+ end
16
+ end
17
+ end
@@ -1,21 +1,21 @@
1
- require 'coveralls'
2
- Coveralls.wear!
3
- require 'minitest/autorun'
4
- require 'evergreen_holdings'
5
-
6
- class EvergreenHoldingsTest < Minitest::Test
7
- def test_connecting_to_eg_returns_a_connection_object
8
- conn = EvergreenHoldings::Connection.new 'http://gapines.org'
9
- assert_instance_of EvergreenHoldings::Connection, conn
10
- end
11
- def test_connecting_to_a_404_throws_an_error
12
- assert_raises('CouldNotConnectToEvergreenError') {
13
- EvergreenHoldings::Connection.new('http://httpstat.us/404')
14
- }
15
- end
16
- def test_connecting_to_a_non_evergreen_server_throws_an_error
17
- assert_raises('CouldNotConnectToEvergreenError') {
18
- EvergreenHoldings::Connection.new('http://libfind.linnbenton.edu')
19
- }
20
- end
21
- end
1
+ require 'minitest/autorun'
2
+ require 'evergreen_holdings'
3
+
4
+ class EvergreenHoldingsTest < Minitest::Test
5
+ def test_connecting_to_eg_returns_a_connection_object
6
+ conn = EvergreenHoldings::Connection.new 'https://gapines.org'
7
+ assert_instance_of EvergreenHoldings::Connection, conn
8
+ end
9
+
10
+ def test_connecting_to_a_404_throws_an_error
11
+ assert_raises(CouldNotConnectToEvergreenError) do
12
+ EvergreenHoldings::Connection.new('http://httpstat.us/404')
13
+ end
14
+ end
15
+
16
+ def test_connecting_to_a_non_evergreen_server_throws_an_error
17
+ assert_raises(CouldNotConnectToEvergreenError) do
18
+ EvergreenHoldings::Connection.new('http://libfind.linnbenton.edu')
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,17 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: evergreen_holdings
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jane Sandberg
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-07-13 00:00:00.000000000 Z
11
+ date: 2021-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.0
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
45
  - - ">="
@@ -25,19 +53,25 @@ dependencies:
25
53
  - !ruby/object:Gem::Version
26
54
  version: '0'
27
55
  - !ruby/object:Gem::Dependency
28
- name: coveralls
56
+ name: rubocop
29
57
  requirement: !ruby/object:Gem::Requirement
30
58
  requirements:
31
- - - '='
59
+ - - ">"
32
60
  - !ruby/object:Gem::Version
33
- version: 0.7.0
61
+ version: 1.0.0
62
+ - - "<"
63
+ - !ruby/object:Gem::Version
64
+ version: '2'
34
65
  type: :development
35
66
  prerelease: false
36
67
  version_requirements: !ruby/object:Gem::Requirement
37
68
  requirements:
38
- - - '='
69
+ - - ">"
70
+ - !ruby/object:Gem::Version
71
+ version: 1.0.0
72
+ - - "<"
39
73
  - !ruby/object:Gem::Version
40
- version: 0.7.0
74
+ version: '2'
41
75
  description: Access holdings information from Evergreen ILS
42
76
  email: sandbej@linnbenton.edu
43
77
  executables: []
@@ -46,8 +80,9 @@ extra_rdoc_files: []
46
80
  files:
47
81
  - lib/evergreen_holdings.rb
48
82
  - lib/evergreen_holdings/errors.rb
83
+ - lib/evergreen_holdings/idl_parser.rb
49
84
  - test/evergreen_holdings_test.rb
50
- homepage:
85
+ homepage: https://github.com/sandbergja/evergreen_holdings_gem
51
86
  licenses:
52
87
  - MIT
53
88
  metadata: {}
@@ -66,8 +101,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
66
101
  - !ruby/object:Gem::Version
67
102
  version: '0'
68
103
  requirements: []
69
- rubyforge_project:
70
- rubygems_version: 2.5.2
104
+ rubygems_version: 3.0.3
71
105
  signing_key:
72
106
  specification_version: 4
73
107
  summary: A ruby gem for getting information about copy availability from Evergreen