evergreen_holdings 0.1.9 → 0.4.0

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