alma 0.2.6 → 0.3.3

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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +54 -0
  3. data/.circleci/setup-rubygems.sh +3 -0
  4. data/.github/dependabot.yml +7 -0
  5. data/.gitignore +3 -0
  6. data/.rubocop.yml +134 -0
  7. data/.ruby-version +1 -1
  8. data/CODE_OF_CONDUCT.md +1 -1
  9. data/Gemfile +4 -3
  10. data/Guardfile +75 -0
  11. data/README.md +136 -26
  12. data/Rakefile +3 -1
  13. data/alma.gemspec +21 -16
  14. data/lib/alma/alma_record.rb +3 -3
  15. data/lib/alma/api_defaults.rb +39 -0
  16. data/lib/alma/availability_response.rb +50 -53
  17. data/lib/alma/bib.rb +26 -42
  18. data/lib/alma/bib_holding.rb +25 -0
  19. data/lib/alma/bib_item.rb +28 -38
  20. data/lib/alma/bib_item_set.rb +72 -12
  21. data/lib/alma/bib_set.rb +7 -21
  22. data/lib/alma/config.rb +10 -4
  23. data/lib/alma/course.rb +47 -0
  24. data/lib/alma/course_set.rb +17 -0
  25. data/lib/alma/electronic/README.md +20 -0
  26. data/lib/alma/electronic/batch_utils.rb +224 -0
  27. data/lib/alma/electronic/business.rb +29 -0
  28. data/lib/alma/electronic.rb +167 -0
  29. data/lib/alma/error.rb +16 -4
  30. data/lib/alma/fine.rb +16 -0
  31. data/lib/alma/fine_set.rb +36 -21
  32. data/lib/alma/item_request_options.rb +23 -0
  33. data/lib/alma/library.rb +29 -0
  34. data/lib/alma/library_set.rb +21 -0
  35. data/lib/alma/loan.rb +31 -2
  36. data/lib/alma/loan_set.rb +62 -15
  37. data/lib/alma/location.rb +29 -0
  38. data/lib/alma/location_set.rb +21 -0
  39. data/lib/alma/renewal_response.rb +19 -11
  40. data/lib/alma/request.rb +167 -0
  41. data/lib/alma/request_options.rb +36 -17
  42. data/lib/alma/request_set.rb +64 -15
  43. data/lib/alma/response.rb +45 -0
  44. data/lib/alma/result_set.rb +27 -35
  45. data/lib/alma/user.rb +111 -92
  46. data/lib/alma/user_request.rb +19 -0
  47. data/lib/alma/user_set.rb +5 -6
  48. data/lib/alma/version.rb +3 -1
  49. data/lib/alma.rb +34 -22
  50. data/log/.gitignore +4 -0
  51. metadata +118 -10
  52. data/.travis.yml +0 -5
  53. 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
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "httparty"
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ require "alma/config"
7
+
8
+ module Alma
9
+ # Alma::Electronic APIs wrapper.
10
+ class Electronic
11
+ class ElectronicError < ArgumentError
12
+ end
13
+
14
+ def self.get(params = {})
15
+ retries_count = 0
16
+ response = nil
17
+ while retries_count < http_retries do
18
+ begin
19
+ response = get_api(params)
20
+ break;
21
+
22
+ rescue Net::ReadTimeout
23
+ retries_count += 1
24
+ log.error("Retrying http after timeout with : #{params}")
25
+ no_more_retries_left = retries_count == http_retries
26
+
27
+ raise Net::ReadTimeout.new("Failed due to net timeout after #{http_retries}: #{params}") if no_more_retries_left
28
+ end
29
+ end
30
+
31
+ return response
32
+ end
33
+
34
+ def self.get_totals
35
+ @totals ||= get(limit: "0").data["total_record_count"]
36
+ end
37
+
38
+ def self.log
39
+ Alma.configuration.logger
40
+ end
41
+
42
+ def self.get_ids
43
+ total = get_totals()
44
+ limit = 100
45
+ offset = 0
46
+ log.info("Retrieving #{total} collection ids.")
47
+ groups = Array.new(total / limit + 1, limit)
48
+ @ids ||= groups.map { |limit|
49
+ prev_offset = offset
50
+ offset += limit
51
+ { offset: prev_offset, limit: limit }
52
+ }
53
+ .map { |params| Thread.new { self.get(params) } }
54
+ .map(&:value).map(&:data)
55
+ .map { |data| data["electronic_collection"].map { |coll| coll["id"] } }
56
+ .flatten.uniq
57
+ end
58
+
59
+ def self.http_retries
60
+ Alma.configuration.http_retries
61
+ end
62
+
63
+ private
64
+ class ElectronicAPI
65
+ include ::HTTParty
66
+ include ::Enumerable
67
+ extend ::Forwardable
68
+
69
+ REQUIRED_PARAMS = []
70
+ RESOURCE = "/almaws/v1/electronic"
71
+
72
+ attr_reader :params, :data
73
+ def_delegators :@data, :each, :each_pair, :fetch, :values, :keys, :dig,
74
+ :slice, :except, :to_h, :to_hash, :[], :with_indifferent_access
75
+
76
+ def initialize(params = {})
77
+ @params = params
78
+ headers = self.class::headers
79
+ log.info(url: url, query: params)
80
+ response = self.class::get(url, headers: headers, query: params, timeout: timeout)
81
+ @data = JSON.parse(response.body) rescue {}
82
+ end
83
+
84
+ def url
85
+ "#{Alma.configuration.region}#{resource}"
86
+ end
87
+
88
+ def timeout
89
+ Alma.configuration.timeout
90
+ end
91
+
92
+ def log
93
+ Alma::Electronic.log
94
+ end
95
+
96
+ def resource
97
+ @params.inject(self.class::RESOURCE) { |path, param|
98
+ key = param.first
99
+ value = param.last
100
+
101
+ if key && value
102
+ path.gsub(/:#{key}/, value.to_s)
103
+ else
104
+ path
105
+ end
106
+ }
107
+ end
108
+
109
+ def self.can_process?(params = {})
110
+ type = self.to_s.split("::").last.parameterize
111
+ self::REQUIRED_PARAMS.all? { |param| params.include? param } &&
112
+ params[:type].blank? || params[:type] == type
113
+ end
114
+
115
+ private
116
+ def self.headers
117
+ { "Authorization": "apikey #{apikey}",
118
+ "Accept": "application/json",
119
+ "Content-Type": "application/json" }
120
+ end
121
+
122
+ def self.apikey
123
+ Alma.configuration.apikey
124
+ end
125
+ end
126
+
127
+ class Portfolio < ElectronicAPI
128
+ REQUIRED_PARAMS = [ :collection_id, :service_id, :portfolio_id ]
129
+ RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services/:service_id/portfolios/:portfolio_id"
130
+ end
131
+
132
+ class Service < ElectronicAPI
133
+ REQUIRED_PARAMS = [ :collection_id, :service_id ]
134
+ RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services/:service_id"
135
+ end
136
+
137
+ class Services < ElectronicAPI
138
+ REQUIRED_PARAMS = [ :collection_id, :type ]
139
+ RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id/e-services"
140
+ end
141
+
142
+ class Collection < ElectronicAPI
143
+ REQUIRED_PARAMS = [ :collection_id ]
144
+ RESOURCE = "/almaws/v1/electronic/e-collections/:collection_id"
145
+ end
146
+
147
+ # Catch all Electronic API.
148
+ # By default returns all collections
149
+ class Collections < ElectronicAPI
150
+ REQUIRED_PARAMS = []
151
+ RESOURCE = "/almaws/v1/electronic/e-collections"
152
+
153
+ def self.can_process?(params = {})
154
+ true
155
+ end
156
+ end
157
+
158
+ # Order matters because parameters can repeat.
159
+ REGISTERED_APIs = [Portfolio, Service, Services, Collection, Collections]
160
+
161
+ def self.get_api(params)
162
+ REGISTERED_APIs
163
+ .find { |m| m.can_process? params }
164
+ .new(params)
165
+ end
166
+ end
167
+ end
data/lib/alma/error.rb CHANGED
@@ -1,15 +1,27 @@
1
- module Alma::Error
1
+ # frozen_string_literal: true
2
2
 
3
+ module Alma::Error
3
4
  def has_error?
4
5
  !error.empty?
5
6
  end
6
7
 
7
8
  def error_message
8
- (has_error?) ? error['errorList']['error']['errorMessage'] : ''
9
+ (has_error?) ? error["errorList"]["error"]["errorMessage"] : ""
9
10
  end
10
11
 
11
12
  def error
12
- @response.fetch('web_service_result', {})
13
+ @response.fetch("web_service_result", {})
13
14
  end
15
+ end
14
16
 
15
- end
17
+ module Alma
18
+ class StandardError < ::StandardError
19
+ def initialize(message, loggable = {})
20
+ if Alma.configuration.enable_loggable
21
+ message = { error: message }.merge(loggable).to_json
22
+ end
23
+
24
+ super message
25
+ end
26
+ end
27
+ end
data/lib/alma/fine.rb ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class Fine < AlmaRecord
5
+ extend Alma::ApiDefaults
6
+
7
+ def self.where_user(user_id, args = {})
8
+ response = HTTParty.get("#{users_base_path}/#{user_id}/fees", query: args, headers: headers, timeout: timeout)
9
+ if response.code == 200
10
+ Alma::FineSet.new(response)
11
+ else
12
+ raise StandardError, get_body_from(response)
13
+ end
14
+ end
15
+ end
16
+ end
data/lib/alma/fine_set.rb CHANGED
@@ -1,39 +1,54 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
- class FineSet
3
- extend Forwardable
4
- include Enumerable
5
- #include Alma::Error
4
+ class FineSet < ResultSet
5
+ class ResponseError < Alma::StandardError
6
+ end
6
7
 
7
- attr_reader :response
8
- def_delegators :list, :each, :size
9
- def_delegators :response, :[], :fetch
8
+ attr_reader :results, :raw_response
9
+ def_delegators :results, :empty?
10
10
 
11
- def initialize(response_body_hash)
12
- @response = response_body_hash
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) }
13
17
  end
14
18
 
15
- def key
16
- 'fee'
19
+ def loggable
20
+ { uri: @raw_response&.request&.uri.to_s
21
+ }.select { |k, v| !(v.nil? || v.empty?) }
17
22
  end
18
23
 
19
- def list
20
- fetch(key, [])
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
21
30
  end
22
31
 
23
- def sum
24
- fetch('total_sum', 0)
32
+ def each(&block)
33
+ @results.each(&block)
25
34
  end
26
- alias :total_sum :sum
27
35
 
28
- def currency
29
- fetch('currency', nil)
36
+ def success?
37
+ raw_response.response.code.to_s == "200"
30
38
  end
31
39
 
32
- def total_record_count
33
- fetch('total_record_count', 0)
40
+ def key
41
+ "fee"
34
42
  end
35
- alias :total_records :total_record_count
36
43
 
44
+ def sum
45
+ fetch("total_sum", 0)
46
+ end
47
+
48
+ alias :total_sum :sum
37
49
 
50
+ def currency
51
+ fetch("currency", nil)
52
+ end
38
53
  end
39
54
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class ItemRequestOptions < RequestOptions
5
+ class ResponseError < Alma::StandardError
6
+ end
7
+
8
+ def self.get(mms_id, holding_id = nil, item_pid = nil, options = {})
9
+ url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}/items/#{item_pid}/request-options"
10
+ options.select! { |k, _| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
11
+ response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
12
+ new(response)
13
+ end
14
+
15
+ def validate(response)
16
+ if response.code != 200
17
+ message = "Could not get item request options."
18
+ log = loggable.merge(response.parsed_response)
19
+ raise ResponseError.new(message, log)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class Library < AlmaRecord
5
+ extend Alma::ApiDefaults
6
+
7
+ def self.all(args: {})
8
+ response = HTTParty.get("#{configuration_base_path}/libraries", query: args, headers: headers, timeout: timeout)
9
+ if response.code == 200
10
+ LibrarySet.new(response)
11
+ else
12
+ raise StandardError, get_body_from(response)
13
+ end
14
+ end
15
+
16
+ def self.find(library_code:, args: {})
17
+ response = HTTParty.get("#{configuration_base_path}/libraries/#{library_code}", query: args, headers: headers, timeout: timeout)
18
+ if response.code == 200
19
+ AlmaRecord.new(response)
20
+ else
21
+ raise StandardError, get_body_from(response)
22
+ end
23
+ end
24
+
25
+ def self.get_body_from(response)
26
+ JSON.parse(response.body)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class LibrarySet < ResultSet
5
+ def_delegators :results, :[], :empty?
6
+
7
+ def each(&block)
8
+ results.each(&block)
9
+ end
10
+
11
+ def results
12
+ @results ||= @response.fetch(key, [])
13
+ .map { |item| single_record_class.new(item) }
14
+ end
15
+
16
+ protected
17
+ def key
18
+ "library"
19
+ end
20
+ end
21
+ end
data/lib/alma/loan.rb CHANGED
@@ -1,9 +1,38 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class Loan < AlmaRecord
5
+ extend Alma::ApiDefaults
6
+
7
+
8
+ def renewable?
9
+ !!renewable
10
+ end
11
+
12
+ def renewable
13
+ response.fetch("renewable", false)
14
+ end
15
+
16
+ def overdue?
17
+ loan_status == "Overdue"
18
+ end
3
19
 
4
20
  def renew
5
- Alma::User.renew_loan({user_id: user_id, loan_id: loan_id})
21
+ Alma::User.renew_loan({ user_id: user_id, loan_id: loan_id })
6
22
  end
7
23
 
24
+ def self.where_user(user_id, args = {})
25
+ # Always expand renewable unless you really don't want to
26
+ args[:expand] ||= "renewable"
27
+ # Default to upper limit
28
+ args[:limit] ||= 100
29
+ response = HTTParty.get(
30
+ "#{users_base_path}/#{user_id}/loans",
31
+ query: args,
32
+ headers: headers,
33
+ timeout: timeout
34
+ )
35
+ Alma::LoanSet.new(response, args)
36
+ end
8
37
  end
9
- end
38
+ end