evergreen_holdings 0.2.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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