alma 0.2.8 → 0.3.1
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 +5 -5
- data/.ruby-version +1 -1
- data/.travis.yml +10 -2
- data/Guardfile +75 -0
- data/README.md +136 -26
- data/alma.gemspec +9 -4
- data/lib/alma.rb +8 -4
- data/lib/alma/api_defaults.rb +30 -0
- data/lib/alma/availability_response.rb +1 -1
- data/lib/alma/bib.rb +4 -22
- data/lib/alma/bib_item.rb +8 -31
- data/lib/alma/bib_item_set.rb +60 -11
- data/lib/alma/bib_set.rb +7 -21
- data/lib/alma/config.rb +7 -3
- data/lib/alma/electronic.rb +167 -0
- data/lib/alma/electronic/README.md +20 -0
- data/lib/alma/electronic/batch_utils.rb +224 -0
- data/lib/alma/electronic/business.rb +29 -0
- data/lib/alma/error.rb +12 -1
- data/lib/alma/fine.rb +15 -0
- data/lib/alma/fine_set.rb +34 -23
- data/lib/alma/item_request_options.rb +22 -0
- data/lib/alma/loan.rb +18 -0
- data/lib/alma/loan_set.rb +59 -17
- data/lib/alma/renewal_response.rb +7 -3
- data/lib/alma/request.rb +165 -0
- data/lib/alma/request_options.rb +31 -18
- data/lib/alma/request_set.rb +62 -17
- data/lib/alma/response.rb +45 -0
- data/lib/alma/result_set.rb +27 -35
- data/lib/alma/user.rb +65 -57
- data/lib/alma/user_request.rb +17 -0
- data/lib/alma/user_set.rb +5 -6
- data/lib/alma/version.rb +1 -1
- data/log/.gitignore +4 -0
- metadata +75 -8
- data/lib/alma/api.rb +0 -33
@@ -0,0 +1,20 @@
|
|
1
|
+
## Alma::Electronic
|
2
|
+
|
3
|
+
A wrapper for the Alma::Electronic API.
|
4
|
+
|
5
|
+
The main entry point is the get methods.
|
6
|
+
|
7
|
+
### To get a list of all the collections.
|
8
|
+
|
9
|
+
```
|
10
|
+
Alma::Electronic.get()
|
11
|
+
```
|
12
|
+
|
13
|
+
Will also accept these params:
|
14
|
+
|
15
|
+
| Parameter | Type | Required | Description |
|
16
|
+
| --------- | ----------| --------- | ---------------------------------------------------------------------------------------------------------------|
|
17
|
+
| q | xs:string | Optional. | Search query. Optional. Searching for words in interface_name, keywords, name or po_line_id (see Brief Search) |
|
18
|
+
| limit | xs:int | Optional. | Default: 10Limits the number of results. Optional. Valid values are 0-100. Default value: 10. |
|
19
|
+
| offset | xs:int | Optional. | Default: 0Offset of the results returned. Optional. Default value: 0, which methodseans that the first results will be returned. |
|
20
|
+
|
@@ -0,0 +1,224 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "alma/electronic"
|
5
|
+
|
6
|
+
# Contains batch processing utils for Alma Electronic APIs.
|
7
|
+
#
|
8
|
+
# This class and its methods are used to iterate over Alma Electronic IDs to
|
9
|
+
# process and fetch Alma electronic objects via the Alma Electronic APIs. The
|
10
|
+
# https calls are logged and can be used to rerun the batch process without
|
11
|
+
# making any further http calls or to just rerun parts of the full batch.
|
12
|
+
module Alma
|
13
|
+
class Electronic::BatchUtils
|
14
|
+
attr_reader :notes, :type
|
15
|
+
|
16
|
+
# @param [Hash] options The options to create a batch instance.
|
17
|
+
# @option [true, false] :chain true indicates a new instance of self returned.
|
18
|
+
# @option [Array<String>] :ids List of collection ids.
|
19
|
+
# @option ["collection", "service", "portfolio"] :type The Alma Electronic object type.
|
20
|
+
# @option [String] :tag A string used to tag the batch session.
|
21
|
+
# @option [Logger] :logger A logger used t
|
22
|
+
def initialize(options = {})
|
23
|
+
options ||= options {}
|
24
|
+
@chain = options.fetch(:chain, false)
|
25
|
+
@ids = options.fetch(:ids, [])
|
26
|
+
@type = options.fetch(:type, "collection")
|
27
|
+
@tag = options.fetch(:tag, Time.now.to_s)
|
28
|
+
|
29
|
+
@@logger = options[:logger] || Logger.new("log/electronic_batch_process.log")
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_collection_notes(ids = nil, options = {})
|
33
|
+
ids ||= @ids
|
34
|
+
get_notes(options.merge(ids: make_collection_ids(ids), type: "collection"))
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_service_notes(ids = nil, options = {})
|
38
|
+
ids ||= @ids
|
39
|
+
get_notes(options.merge(ids: get_service_ids(ids, options), type: "service"))
|
40
|
+
end
|
41
|
+
|
42
|
+
def get_portfolio_notes(ids = nil, options = {})
|
43
|
+
ids ||= @ids
|
44
|
+
get_notes(options.merge(ids: get_portfolio_ids(ids, options, type: "portfolio")))
|
45
|
+
end
|
46
|
+
|
47
|
+
def get_notes(options = {})
|
48
|
+
options ||= {}
|
49
|
+
chain = options.fetch(:chain, @chain)
|
50
|
+
ids = options[:ids] || (chain ? build_ids(options) : @ids)
|
51
|
+
|
52
|
+
type = options.fetch(:type, @type)
|
53
|
+
tag = options.fetch(:tag, @tag)
|
54
|
+
@notes = ids.inject({}) do |notes, params|
|
55
|
+
id = get_id(type, params)
|
56
|
+
start = Time.now
|
57
|
+
|
58
|
+
begin
|
59
|
+
item = Alma::Electronic.get(params)
|
60
|
+
rescue StandardError => e
|
61
|
+
item = { "error" => e.message }
|
62
|
+
end
|
63
|
+
|
64
|
+
data = ["error", "authentication_note", "public_note"].reduce({}) do |acc, field|
|
65
|
+
acc[field] = item[field] if item[field].present?
|
66
|
+
acc
|
67
|
+
end
|
68
|
+
|
69
|
+
unavailable = item.dig("service_temporarily_unavailable", "value")
|
70
|
+
if unavailable == "1" || unavailable == "true"
|
71
|
+
data.merge!(item.slice("service_temporarily_unavailable", "service_unavailable_date", "service_unavailable_reason"))
|
72
|
+
end
|
73
|
+
|
74
|
+
if data.present?
|
75
|
+
log(params.merge(data).merge(type: type, start: start, tag: tag))
|
76
|
+
|
77
|
+
notes[id] = data unless data["error"].present?
|
78
|
+
end
|
79
|
+
|
80
|
+
notes
|
81
|
+
end
|
82
|
+
|
83
|
+
self.class.new(options.merge(
|
84
|
+
chain: chain,
|
85
|
+
ids: ids,
|
86
|
+
type: type,
|
87
|
+
tag: tag,
|
88
|
+
notes: notes,
|
89
|
+
logger: @@logger,
|
90
|
+
))
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_service_ids(ids = @ids, options = {})
|
94
|
+
tag = options.fetch(:tag, @tag)
|
95
|
+
start = Time.now
|
96
|
+
|
97
|
+
make_collection_ids(ids)
|
98
|
+
.map { |id| id.merge(type: "services") }
|
99
|
+
.inject([]) do |service_ids, params|
|
100
|
+
params.merge!(tag: tag)
|
101
|
+
|
102
|
+
begin
|
103
|
+
item = Alma::Electronic.get(params)
|
104
|
+
|
105
|
+
if item["errorList"]
|
106
|
+
log params.merge(item["errorList"])
|
107
|
+
.merge(start: start)
|
108
|
+
else
|
109
|
+
item["electronic_service"].each { |service|
|
110
|
+
service_id = { service_id: service["id"].to_s }
|
111
|
+
service_ids << params.slice(:collection_id)
|
112
|
+
.merge(service_id)
|
113
|
+
|
114
|
+
log params.merge(service_id)
|
115
|
+
.merge(start: start)
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
rescue StandardError => e
|
120
|
+
log params.merge("error" => e.message)
|
121
|
+
.merge(start: start)
|
122
|
+
end
|
123
|
+
|
124
|
+
service_ids
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Builds the notes object using the logs.
|
129
|
+
def build_notes(options = {})
|
130
|
+
options ||= {}
|
131
|
+
type ||= options.fetch(:type, "collection")
|
132
|
+
|
133
|
+
get_logged_items(options)
|
134
|
+
.select { |item| item.slice("authentication_note", "public_note").values.any?(&:present?) }
|
135
|
+
.inject({}) do |nodes, item|
|
136
|
+
|
137
|
+
id = item["#{type}_id"]
|
138
|
+
nodes.merge(id => item.slice("authentication_note", "public_note"))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Builds list of ids from logs based on failed attempts.
|
143
|
+
# Useful for rebuilding part of collection.
|
144
|
+
def build_failed_ids(options = {})
|
145
|
+
successful_ids = build_successful_ids(options)
|
146
|
+
get_logged_items(options)
|
147
|
+
.select { |item| item.slice("authentication_note", "public_note").values.all?(&:nil?) }
|
148
|
+
.map { |item| item["collection_id"] }
|
149
|
+
.select { |id| !successful_ids.include? id }
|
150
|
+
.uniq
|
151
|
+
end
|
152
|
+
|
153
|
+
# Builds list of ids from logs based on successful attempts.
|
154
|
+
# Useful for verifying that failed ids have always failed.
|
155
|
+
def build_successful_ids(options = {})
|
156
|
+
get_logged_items(options)
|
157
|
+
.select { |item| item.slice("authentication_note", "public_note").values.present? }
|
158
|
+
.map { |item| item["collection_id"] }
|
159
|
+
.uniq
|
160
|
+
end
|
161
|
+
|
162
|
+
# Builds a list of all ids for a specific session.
|
163
|
+
# Useful for analytics purpose or rebuilds.
|
164
|
+
def build_ids(options = {})
|
165
|
+
build_failed_ids(options) + build_successful_ids(options)
|
166
|
+
end
|
167
|
+
|
168
|
+
def print_notes(options = {})
|
169
|
+
options ||= {}
|
170
|
+
chain = options.fetch(:chain, @chain)
|
171
|
+
notes = options[:notes] || chain ? build_notes(options) : @notes
|
172
|
+
type = options.fetch(:type, @type)
|
173
|
+
tag = options.fetch(:tag, @tag)
|
174
|
+
|
175
|
+
filename = options.fetch(:filename, "spec/fixtures/#{type}_notes.json")
|
176
|
+
|
177
|
+
File.open(filename, "w") do |file|
|
178
|
+
file.write(JSON.pretty_generate(notes))
|
179
|
+
end
|
180
|
+
|
181
|
+
self.class.new(options.merge(
|
182
|
+
chain: chain,
|
183
|
+
notes: notes,
|
184
|
+
type: type,
|
185
|
+
tag: tag,
|
186
|
+
logger: @@logger,
|
187
|
+
))
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
def log(params)
|
192
|
+
if defined?(LogUtils)
|
193
|
+
LogUtils.json_request_logger(@@logger, params)
|
194
|
+
else
|
195
|
+
@@logger.info(params)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def get_id(type, params = {})
|
200
|
+
id = "#{type}_id".to_sym
|
201
|
+
params[id]
|
202
|
+
end
|
203
|
+
|
204
|
+
def make_collection_ids(ids = @ids)
|
205
|
+
ids.map { |id|
|
206
|
+
id.class == Hash ? id : { collection_id: id.to_s }
|
207
|
+
}
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns JSON parsed list of logged items
|
211
|
+
def get_logged_items(options = {})
|
212
|
+
options ||= {}
|
213
|
+
type ||= options.fetch(:type, "collection")
|
214
|
+
tag ||= options.fetch(:tag, @tag)
|
215
|
+
filename = (@@logger.instance_variable_get :@logdev).filename
|
216
|
+
File.readlines(filename)
|
217
|
+
.map { |log| log.match(/{.*}/).to_s }
|
218
|
+
.select(&:present?)
|
219
|
+
.map { |json| JSON.parse(json) }
|
220
|
+
.select { |item| item["tag"] == tag }
|
221
|
+
.select { |item| item["type"] == type }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "alma/electronic"
|
5
|
+
|
6
|
+
module Alma
|
7
|
+
# Holds some custom business logic for our Alma Electronic API.
|
8
|
+
# This class is not intended for public use.
|
9
|
+
class Electronic::Business
|
10
|
+
# The Service ID is usually the Collection ID grouped by
|
11
|
+
# 2 digits with the first number incremented by 1 and the
|
12
|
+
# fifth number decremented by 1.
|
13
|
+
#
|
14
|
+
# @note However, this pattern does not hold for all cases.
|
15
|
+
#
|
16
|
+
# @param collection_id [String] The electronic collection id.
|
17
|
+
def service_id(collection_id)
|
18
|
+
collection_id.scan(/.{1,2}/).each_with_index.map { |char, index|
|
19
|
+
if index == 0
|
20
|
+
"%02d" % (char.to_i + 1)
|
21
|
+
elsif index == 4
|
22
|
+
"%02d" % (char.to_i - 1)
|
23
|
+
else
|
24
|
+
char
|
25
|
+
end
|
26
|
+
}.join
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/alma/error.rb
CHANGED
@@ -11,5 +11,16 @@ module Alma::Error
|
|
11
11
|
def error
|
12
12
|
@response.fetch('web_service_result', {})
|
13
13
|
end
|
14
|
+
end
|
14
15
|
|
15
|
-
|
16
|
+
module Alma
|
17
|
+
class StandardError < ::StandardError
|
18
|
+
def initialize(message, loggable = {})
|
19
|
+
if Alma.configuration.enable_loggable
|
20
|
+
message = { error: message }.merge(loggable).to_json
|
21
|
+
end
|
22
|
+
|
23
|
+
super message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/alma/fine.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
module Alma
|
2
|
+
class Fine < AlmaRecord
|
3
|
+
extend Alma::ApiDefaults
|
4
|
+
|
5
|
+
def self.where_user(user_id, args={})
|
6
|
+
|
7
|
+
response = HTTParty.get("#{users_base_path}/#{user_id}/fees", query: args, headers: headers, timeout: timeout)
|
8
|
+
if response.code == 200
|
9
|
+
Alma::FineSet.new(response)
|
10
|
+
else
|
11
|
+
raise StandardError, get_body_from(response)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/alma/fine_set.rb
CHANGED
@@ -1,43 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Alma
|
2
|
-
class FineSet
|
3
|
-
|
4
|
-
|
5
|
-
#include Alma::Error
|
4
|
+
class FineSet < ResultSet
|
5
|
+
class ResponseError < Alma::StandardError
|
6
|
+
end
|
6
7
|
|
7
|
-
attr_reader :
|
8
|
-
def_delegators :
|
8
|
+
attr_reader :results, :raw_response
|
9
|
+
def_delegators :results, :empty?
|
9
10
|
|
10
|
-
def initialize(
|
11
|
-
@
|
11
|
+
def initialize(raw_response)
|
12
|
+
@raw_response = raw_response
|
13
|
+
@response = raw_response.parsed_response
|
14
|
+
validate(raw_response)
|
15
|
+
@results = @response.fetch(key, [])
|
16
|
+
.map { |item| single_record_class.new(item) }
|
12
17
|
end
|
13
18
|
|
14
|
-
def
|
15
|
-
|
19
|
+
def loggable
|
20
|
+
{ uri: @raw_response&.request&.uri.to_s
|
21
|
+
}.select { |k, v| !(v.nil? || v.empty?) }
|
16
22
|
end
|
17
23
|
|
18
|
-
def
|
19
|
-
|
24
|
+
def validate(response)
|
25
|
+
if response.code != 200
|
26
|
+
message = "Could not find fines."
|
27
|
+
log = loggable.merge(response.parsed_response)
|
28
|
+
raise ResponseError.new(message, log)
|
29
|
+
end
|
20
30
|
end
|
21
|
-
alias list each
|
22
31
|
|
23
|
-
def
|
24
|
-
|
32
|
+
def each(&block)
|
33
|
+
@results.each(&block)
|
25
34
|
end
|
26
35
|
|
27
|
-
def
|
28
|
-
|
36
|
+
def success?
|
37
|
+
raw_response.response.code.to_s == "200"
|
29
38
|
end
|
30
|
-
alias :total_sum :sum
|
31
39
|
|
32
|
-
def
|
33
|
-
|
40
|
+
def key
|
41
|
+
"fee"
|
34
42
|
end
|
35
43
|
|
36
|
-
def
|
37
|
-
fetch(
|
44
|
+
def sum
|
45
|
+
fetch("total_sum", 0)
|
38
46
|
end
|
39
|
-
alias :total_records :total_record_count
|
40
47
|
|
48
|
+
alias :total_sum :sum
|
41
49
|
|
50
|
+
def currency
|
51
|
+
fetch("currency", nil)
|
52
|
+
end
|
42
53
|
end
|
43
54
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Alma
|
2
|
+
class ItemRequestOptions < RequestOptions
|
3
|
+
|
4
|
+
class ResponseError < Alma::StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.get(mms_id, holding_id=nil, item_pid=nil, options={})
|
8
|
+
url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}/items/#{item_pid}/request-options"
|
9
|
+
options.select! {|k,_| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
|
10
|
+
response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
|
11
|
+
new(response)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate(response)
|
15
|
+
if response.code != 200
|
16
|
+
message = "Could not get item request options."
|
17
|
+
log = loggable.merge(response.parsed_response)
|
18
|
+
raise ResponseError.new(message, log)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/alma/loan.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module Alma
|
2
2
|
class Loan < AlmaRecord
|
3
|
+
extend Alma::ApiDefaults
|
3
4
|
|
4
5
|
|
5
6
|
def renewable?
|
6
7
|
!!renewable
|
7
8
|
end
|
8
9
|
|
10
|
+
def renewable
|
11
|
+
response.fetch("renewable", false)
|
12
|
+
end
|
13
|
+
|
9
14
|
def overdue?
|
10
15
|
loan_status == "Overdue"
|
11
16
|
end
|
@@ -14,5 +19,18 @@ module Alma
|
|
14
19
|
Alma::User.renew_loan({user_id: user_id, loan_id: loan_id})
|
15
20
|
end
|
16
21
|
|
22
|
+
def self.where_user(user_id, args={})
|
23
|
+
# Always expand renewable unless you really don't want to
|
24
|
+
args[:expand] ||= "renewable"
|
25
|
+
# Default to upper limit
|
26
|
+
args[:limit] ||= 100
|
27
|
+
response = HTTParty.get(
|
28
|
+
"#{users_base_path}/#{user_id}/loans",
|
29
|
+
query: args,
|
30
|
+
headers: headers,
|
31
|
+
timeout: timeout
|
32
|
+
)
|
33
|
+
Alma::LoanSet.new(response, args)
|
34
|
+
end
|
17
35
|
end
|
18
36
|
end
|