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.
@@ -1,34 +1,76 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
- class LoanSet
3
- extend Forwardable
4
- include Enumerable
5
- #include Alma::Error
4
+ class LoanSet < ResultSet
5
+ class ResponseError < Alma::StandardError
6
+ end
7
+
8
+ alias :total_records :total_record_count
9
+
6
10
 
7
- attr_reader :response
8
- def_delegators :response, :[], :fetch
11
+ attr_reader :results, :raw_response
12
+ def_delegators :results, :empty?
9
13
 
10
- def initialize(response_body_hash)
11
- @response = response_body_hash
14
+ def initialize(raw_response, search_args={})
15
+ @raw_response = raw_response
16
+ @response = raw_response.parsed_response
17
+ @search_args = search_args
18
+ validate(raw_response)
19
+ @results = @response.fetch(key, [])
20
+ .map { |item| single_record_class.new(item) }
21
+ # args passed to the search that returned this set
22
+ # such as limit, expand, order_by, etc
12
23
  end
13
24
 
14
- def each
15
- @response.fetch(key, []).map{|item| Alma::Loan.new(item)}
25
+ def loggable
26
+ { search_args: @search_args,
27
+ uri: @raw_response&.request&.uri.to_s
28
+ }.select { |k, v| !(v.nil? || v.empty?) }
16
29
  end
17
- alias list each
18
30
 
19
- def size
20
- each.count
31
+ def validate(response)
32
+ if response.code != 200
33
+ error = "Could not find loans info."
34
+ log = loggable.merge(response.parsed_response)
35
+ raise ResponseError.new(error, log)
36
+ end
21
37
  end
22
38
 
39
+ def all
40
+ Enumerator.new do |yielder|
41
+ offset = 0
42
+ loop do
43
+ extra_args = @search_args.merge({limit: 100, offset: offset})
44
+ r = (offset == 0) ? self : single_record_class.where_user(user_id, extra_args)
45
+ unless r.empty?
46
+ r.map { |item| yielder << item }
47
+ offset += 100
48
+ else
49
+ raise StopIteration
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def each(&block)
56
+ @results.each(&block)
57
+ end
58
+
59
+ def success?
60
+ raw_response.response.code.to_s == "200"
61
+ end
23
62
 
24
63
  def key
25
- 'item_loan'
64
+ "item_loan"
26
65
  end
27
66
 
28
- def total_record_count
29
- fetch('total_record_count', 0)
67
+ def single_record_class
68
+ Alma::Loan
30
69
  end
31
- alias :total_records :total_record_count
32
70
 
71
+ private
72
+ def user_id
73
+ @user_id ||= results.first.user_id
74
+ end
33
75
  end
34
76
  end
@@ -1,13 +1,17 @@
1
1
  module Alma
2
2
  class RenewalResponse
3
3
 
4
-
5
-
6
4
  def initialize(response)
7
- @response = response
5
+ @raw_response = response
6
+ @response = response.parsed_response
8
7
  @success = response.has_key?('loan_id')
9
8
  end
10
9
 
10
+ def loggable
11
+ { uri: @raw_response&.request&.uri.to_s
12
+ }.select { |k, v| !(v.nil? || v.empty?) }
13
+ end
14
+
11
15
  def renewed?
12
16
  @success
13
17
  end
@@ -0,0 +1,165 @@
1
+ module Alma
2
+ class BibRequest
3
+ class ItemAlreadyExists < Alma::StandardError
4
+ end
5
+
6
+ extend Alma::ApiDefaults
7
+
8
+ REQUEST_TYPES = %w[HOLD DIGITIZATION BOOKING]
9
+
10
+ def self.submit(args)
11
+ request = new(args)
12
+ response = HTTParty.post(
13
+ "#{bibs_base_path}/#{request.mms_id}/requests",
14
+ query: {user_id: request.user_id},
15
+ headers: headers,
16
+ body: request.body.to_json
17
+ )
18
+ Alma::Response.new(response)
19
+ end
20
+
21
+ attr_reader :mms_id, :user_id, :body, :request_type
22
+ def initialize(args)
23
+ @mms_id = args.delete(:mms_id) { raise ArgumentError.new(":mms_id option must be specified to create request") }
24
+ @user_id = args.delete(:user_id) { raise ArgumentError.new(":user_id option must be specified to create request") }
25
+ @request_type = args.fetch(:request_type, "NOT_SPECIFIED")
26
+ validate!(args)
27
+ normalize!(args)
28
+ @body = args
29
+ end
30
+
31
+
32
+ def normalize!(args)
33
+ request_type_normalization!(args)
34
+ additional_normalization!(args)
35
+ end
36
+
37
+ def request_type_normalization!(args)
38
+ method = "#{@request_type.downcase}_normalization".to_sym
39
+ send(method, args) if respond_to? method
40
+ end
41
+
42
+ # Intended to be overridden by subclasses, allowing extra normalization logic to be provided
43
+ def additional_normalization!(args)
44
+ end
45
+
46
+ def validate!(args)
47
+ unless REQUEST_TYPES.include?(request_type)
48
+ raise ArgumentError.new(":request_type option must be specified and one of #{REQUEST_TYPES.join(", ")} to submit a request")
49
+ end
50
+ request_type_validation!(args)
51
+ additional_validation!(args)
52
+ end
53
+
54
+ def request_type_validation!(args)
55
+ method = "#{@request_type.downcase}_validation".to_sym
56
+ send(method, args) if respond_to? method
57
+ end
58
+
59
+ # Intended to be overridden by subclasses, allowing extra validation logic to be provided
60
+ def additional_validation!(args)
61
+ end
62
+
63
+ def digitization_normalization(args)
64
+ if args[:target_destination].is_a? String
65
+ args[:target_destination] = { value: args[:target_destination] }
66
+ end
67
+ end
68
+
69
+ def digitization_validation(args)
70
+ args.fetch(:target_destination) do
71
+ raise ArgumentError.new(
72
+ ":target_destination option must be specified when request_type is DIGITIZATION"
73
+ )
74
+ end
75
+ pd = args.fetch(:partial_digitization) do
76
+ raise ArgumentError.new(
77
+ ":partial_digitization option must be specified when request_type is DIGITIZATION"
78
+ )
79
+ end
80
+ if pd == true
81
+ args.fetch(:comment) do
82
+ raise ArgumentError.new(
83
+ ":comment option must be specified when :request_type is DIGITIZATION and :partial_digitization is true"
84
+ )
85
+ end
86
+ end
87
+ end
88
+
89
+ def booking_normalization(args)
90
+ if args[:material_type].is_a? String
91
+ args[:material_type] = { value: args[:material_type] }
92
+ end
93
+ end
94
+
95
+ def booking_validation(args)
96
+ args.fetch(:booking_start_date) do
97
+ raise ArgumentError.new(
98
+ ":booking_start_date option must be specified when request_type is BOOKING"
99
+ )
100
+ end
101
+ args.fetch(:booking_end_date) do
102
+ raise ArgumentError.new(
103
+ ":booking_end_date option must be specified when request_type is BOOKING"
104
+ )
105
+ end
106
+ args.fetch(:pickup_location_type) do
107
+ raise ArgumentError.new(
108
+ ":pickup_location_type option must be specified when request_type is BOOKING"
109
+ )
110
+ end
111
+ args.fetch(:pickup_location_library) do
112
+ raise ArgumentError.new(
113
+ ":pickup_location_library option must be specified when request_type is BOOKING"
114
+ )
115
+ end
116
+ end
117
+
118
+ def hold_normalization(args)
119
+ # if args[:material_type].is_a? String
120
+ # args[:material_type] = { value: args[:material_type] }
121
+ # end
122
+ end
123
+
124
+ def hold_validation(args)
125
+ args.fetch(:pickup_location_type) do
126
+ raise ArgumentError.new(
127
+ ":pickup_location_type option must be specified when request_type is HOLD"
128
+ )
129
+ end
130
+ args.fetch(:pickup_location_library) do
131
+ raise ArgumentError.new(
132
+ ":pickup_location_library option must be specified when request_type is HOLD"
133
+ )
134
+ end
135
+ end
136
+ end
137
+
138
+ class ItemRequest < BibRequest
139
+ def self.submit(args)
140
+ request = new(args)
141
+ response = HTTParty.post(
142
+ "#{bibs_base_path}/#{request.mms_id}/holdings/#{request.holding_id}/items/#{request.item_pid}/requests",
143
+ query: {user_id: request.user_id},
144
+ headers: headers,
145
+ body: request.body.to_json
146
+ )
147
+ Alma::Response.new(response)
148
+ end
149
+
150
+ attr_reader :holding_id, :item_pid
151
+ def initialize(args)
152
+ super(args)
153
+ @holding_id = args.delete(:holding_id) { raise ArgumentError.new(":holding_id option must be specified to create request") }
154
+ @item_pid = args.delete(:item_pid) { raise ArgumentError.new(":item_pid option must be specified to create request") }
155
+ end
156
+
157
+ def additional_validation!(args)
158
+ args.fetch(:description) do
159
+ raise ArgumentError.new(
160
+ ":description option must be specified when request_type is DIGITIZATION"
161
+ )
162
+ end
163
+ end
164
+ end
165
+ end
@@ -1,6 +1,10 @@
1
1
  module Alma
2
2
  class RequestOptions
3
+ class ResponseError < Alma::StandardError
4
+ end
5
+
3
6
  extend Forwardable
7
+ extend Alma::ApiDefaults
4
8
 
5
9
  attr_accessor :request_options, :raw_response
6
10
  def_delegators :raw_response, :response, :request
@@ -9,43 +13,52 @@ module Alma
9
13
 
10
14
  def initialize(response)
11
15
  @raw_response = response
12
- @request_options = JSON.parse(response.body)["request_option"]
16
+ validate(response)
17
+ @request_options = response.parsed_response["request_option"]
13
18
  end
14
19
 
20
+
15
21
  def self.get(mms_id, options={})
16
22
  url = "#{bibs_base_path}/#{mms_id}/request-options"
17
23
  options.select! {|k,_| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
18
- response = HTTParty.get(url, headers: headers, query: options)
24
+ response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
19
25
  new(response)
20
26
  end
21
27
 
22
- def hold_allowed?
23
- !request_options.select {|option| option["type"]["value"] == "HOLD" }.empty?
28
+ def loggable
29
+ { uri: @raw_response&.request&.uri.to_s
30
+ }.select { |k, v| !(v.nil? || v.empty?) }
24
31
  end
25
32
 
26
- def digitization_allowed?
27
- !request_options.select {|option| option["type"]["value"] == "DIGITIZATION" }.empty?
33
+ def validate(response)
34
+ if response.code != 200
35
+ raise ResponseError.new("Could not get request options.", loggable.merge(response.parsed_response))
36
+ end
28
37
  end
29
38
 
30
- private
31
-
32
- def self.region
33
- Alma.configuration.region
39
+ def hold_allowed?
40
+ !request_options.nil? &&
41
+ !request_options.select {|option| option["type"]["value"] == "HOLD" }.empty?
34
42
  end
35
43
 
36
- def self.bibs_base_path
37
- "#{region}/almaws/v1/bibs"
44
+ def digitization_allowed?
45
+ !request_options.nil? &&
46
+ !request_options.select {|option| option["type"]["value"] == "DIGITIZATION" }.empty?
38
47
  end
39
48
 
40
- def self.headers
41
- { "Authorization": "apikey #{apikey}",
42
- "Accept": "application/json",
43
- "Content-Type": "application/json" }
49
+ def booking_allowed?
50
+ !request_options.nil? &&
51
+ !request_options.select {|option| option["type"]["value"] == "BOOKING" }.empty?
44
52
  end
45
53
 
46
- def self.apikey
47
- Alma.configuration.apikey
54
+ def resource_sharing_broker_allowed?
55
+ !request_options.nil? &&
56
+ !request_options.select {|option| option["type"]["value"] == "RS_BROKER" }.empty?
48
57
  end
49
58
 
59
+ def ez_borrow_link
60
+ broker = request_options.select {|option| option["type"]["value"] == "RS_BROKER" }
61
+ broker.collect { |opt| opt["request_url"] }.first
62
+ end
50
63
  end
51
64
  end
@@ -1,33 +1,78 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
- class RequestSet
3
- extend Forwardable
4
- include Enumerable
5
- #include Alma::Error
4
+ class RequestSet < ResultSet
5
+ class ResponseError < Alma::StandardError
6
+ end
7
+
8
+ alias :total_records :total_record_count
6
9
 
7
- attr_reader :response
8
- def_delegators :response, :[], :fetch
10
+ attr_reader :results, :raw_response
11
+ def_delegators :results, :empty?
9
12
 
10
- def initialize(response_body_hash)
11
- @response = response_body_hash
13
+ def initialize(raw_response)
14
+ @raw_response = raw_response
15
+ @response = raw_response.parsed_response
16
+ validate(raw_response)
17
+ @results = @response.fetch(key, [])
18
+ .map { |item| single_record_class.new(item) }
12
19
  end
13
20
 
14
- def each
15
- @response.fetch(key, []).map{|item| Alma::AlmaRecord.new(item)}
21
+ def loggable
22
+ { uri: @raw_response&.request&.uri.to_s
23
+ }.select { |k, v| !(v.nil? || v.empty?) }
16
24
  end
17
- alias list each
18
25
 
19
- def size
20
- each.count
26
+ def validate(response)
27
+ if response.code != 200
28
+ error = "Could not find requests."
29
+ log = loggable.merge(response.parsed_response)
30
+ raise ResponseError.new(error, log)
31
+ end
21
32
  end
22
33
 
23
- def total_record_count
24
- fetch('total_record_count', 0)
34
+ def all
35
+ Enumerator.new do |yielder|
36
+ offset = 0
37
+ loop do
38
+ r = (offset == 0) ? self : single_record_class.where_user(user_id, {limit: 100, offset: offset})
39
+ unless r.empty?
40
+ r.map { |item| yielder << item }
41
+ offset += 100
42
+ else
43
+ raise StopIteration
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def each(&block)
50
+ @results.each(&block)
51
+ end
52
+
53
+ def success?
54
+ raw_response.response.code.to_s == "200"
25
55
  end
26
- alias :total_records :total_record_count
27
56
 
28
57
  def key
29
- 'user_request'
58
+ "user_request"
30
59
  end
31
60
 
61
+ def single_record_class
62
+ Alma::UserRequest
63
+ end
64
+
65
+ private
66
+ def user_id
67
+ @user_id ||= get_user_id_from_path(raw_response.request.uri.path)
68
+ end
69
+
70
+ def get_user_id_from_path(path)
71
+ # Path in user api calls starts with "/almaws/v1/users/123/maybe_something/else"
72
+ split_path = path.split("/")
73
+ # the part immediately following the "users" is going to be the user_id
74
+ user_id_index = split_path.index("users") + 1
75
+ split_path[user_id_index]
76
+ end
32
77
  end
33
78
  end