alma 0.2.4 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  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/Gemfile +5 -1
  9. data/Guardfile +75 -0
  10. data/README.md +146 -57
  11. data/Rakefile +3 -1
  12. data/alma.gemspec +21 -12
  13. data/lib/alma.rb +34 -54
  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 +69 -31
  17. data/lib/alma/bib.rb +54 -29
  18. data/lib/alma/bib_holding.rb +25 -0
  19. data/lib/alma/bib_item.rb +164 -0
  20. data/lib/alma/bib_item_set.rb +93 -0
  21. data/lib/alma/bib_set.rb +5 -10
  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.rb +167 -0
  26. data/lib/alma/electronic/README.md +20 -0
  27. data/lib/alma/electronic/batch_utils.rb +224 -0
  28. data/lib/alma/electronic/business.rb +29 -0
  29. data/lib/alma/error.rb +16 -4
  30. data/lib/alma/fine.rb +16 -0
  31. data/lib/alma/fine_set.rb +41 -8
  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 -4
  37. data/lib/alma/location.rb +29 -0
  38. data/lib/alma/location_set.rb +21 -0
  39. data/lib/alma/renewal_response.rb +25 -14
  40. data/lib/alma/request.rb +167 -0
  41. data/lib/alma/request_options.rb +66 -0
  42. data/lib/alma/request_set.rb +69 -5
  43. data/lib/alma/response.rb +45 -0
  44. data/lib/alma/result_set.rb +27 -35
  45. data/lib/alma/user.rb +142 -86
  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/log/.gitignore +4 -0
  50. metadata +149 -10
  51. data/.travis.yml +0 -5
  52. data/lib/alma/api.rb +0 -33
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
data/lib/alma/loan_set.rb CHANGED
@@ -1,18 +1,76 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class LoanSet < ResultSet
5
+ class ResponseError < Alma::StandardError
6
+ end
7
+
8
+ alias :total_records :total_record_count
9
+
10
+
11
+ attr_reader :results, :raw_response
12
+ def_delegators :results, :empty?
13
+
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
23
+ end
3
24
 
25
+ def loggable
26
+ { search_args: @search_args,
27
+ uri: @raw_response&.request&.uri.to_s
28
+ }.select { |k, v| !(v.nil? || v.empty?) }
29
+ end
30
+
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
37
+ end
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
4
58
 
5
- def top_level_key
6
- 'item_loans'
59
+ def success?
60
+ raw_response.response.code.to_s == "200"
7
61
  end
8
62
 
9
- def response_records_key
10
- 'item_loan'
63
+ def key
64
+ "item_loan"
11
65
  end
12
66
 
13
67
  def single_record_class
14
68
  Alma::Loan
15
69
  end
16
70
 
71
+ private
72
+ def user_id
73
+ @user_id ||= results.first.user_id
74
+ end
17
75
  end
18
76
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class Location < AlmaRecord
5
+ extend Alma::ApiDefaults
6
+
7
+ def self.all(library_code:, args: {})
8
+ response = HTTParty.get("#{configuration_base_path}/libraries/#{library_code}/locations", query: args, headers: headers, timeout: timeout)
9
+ if response.code == 200
10
+ LocationSet.new(response)
11
+ else
12
+ raise StandardError, get_body_from(response)
13
+ end
14
+ end
15
+
16
+ def self.find(library_code:, location_code:, args: {})
17
+ response = HTTParty.get("#{configuration_base_path}/libraries/#{library_code}/locations/#{location_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 LocationSet < 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
+ "location"
19
+ end
20
+ end
21
+ end
@@ -1,42 +1,53 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class RenewalResponse
3
-
4
- include Alma::Error
5
-
6
5
  def initialize(response)
7
- @response = response
8
- @success = response.fetch('item_loan', {})
9
- @renewed = error.empty?
6
+ @raw_response = response
7
+ @response = response.parsed_response
8
+ @success = response.has_key?("loan_id")
9
+ end
10
+
11
+ def loggable
12
+ { uri: @raw_response&.request&.uri.to_s
13
+ }.select { |k, v| !(v.nil? || v.empty?) }
10
14
  end
11
15
 
12
16
  def renewed?
13
- @renewed
17
+ @success
18
+ end
19
+
20
+ def has_error?
21
+ !renewed?
14
22
  end
15
23
 
16
24
  def due_date
17
- @success.fetch('due_date', '')
25
+ @response.fetch("due_date", "")
18
26
  end
19
27
 
20
28
 
21
29
  def due_date_pretty
22
- Time.parse(due_date).strftime('%m-%e-%y %H:%M')
30
+ Time.parse(due_date).strftime("%m-%e-%y %H:%M")
23
31
  end
24
32
 
25
33
  def item_title
26
- if @success
27
- @success['title']
34
+ if renewed?
35
+ @response["title"]
28
36
  else
29
- 'This Item'
37
+ "This Item"
30
38
  end
31
39
  end
32
40
 
33
41
  def message
34
- if @success
42
+ if renewed?
35
43
  "#{item_title} is now due #{due_date}"
36
44
  else
37
45
  "#{item_title} could not be renewed."
38
46
  end
39
47
  end
40
48
 
49
+ def error_message
50
+ @response unless renewed?
51
+ end
41
52
  end
42
- end
53
+ end
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class BibRequest
5
+ class ItemAlreadyExists < Alma::StandardError
6
+ end
7
+
8
+ extend Alma::ApiDefaults
9
+
10
+ REQUEST_TYPES = %w[HOLD DIGITIZATION BOOKING]
11
+
12
+ def self.submit(args)
13
+ request = new(args)
14
+ response = HTTParty.post(
15
+ "#{bibs_base_path}/#{request.mms_id}/requests",
16
+ query: { user_id: request.user_id },
17
+ headers: headers,
18
+ body: request.body.to_json
19
+ )
20
+ Alma::Response.new(response)
21
+ end
22
+
23
+ attr_reader :mms_id, :user_id, :body, :request_type
24
+ def initialize(args)
25
+ @mms_id = args.delete(:mms_id) { raise ArgumentError.new(":mms_id option must be specified to create request") }
26
+ @user_id = args.delete(:user_id) { raise ArgumentError.new(":user_id option must be specified to create request") }
27
+ @request_type = args.fetch(:request_type, "NOT_SPECIFIED")
28
+ validate!(args)
29
+ normalize!(args)
30
+ @body = args
31
+ end
32
+
33
+
34
+ def normalize!(args)
35
+ request_type_normalization!(args)
36
+ additional_normalization!(args)
37
+ end
38
+
39
+ def request_type_normalization!(args)
40
+ method = "#{@request_type.downcase}_normalization".to_sym
41
+ send(method, args) if respond_to? method
42
+ end
43
+
44
+ # Intended to be overridden by subclasses, allowing extra normalization logic to be provided
45
+ def additional_normalization!(args)
46
+ end
47
+
48
+ def validate!(args)
49
+ unless REQUEST_TYPES.include?(request_type)
50
+ raise ArgumentError.new(":request_type option must be specified and one of #{REQUEST_TYPES.join(", ")} to submit a request")
51
+ end
52
+ request_type_validation!(args)
53
+ additional_validation!(args)
54
+ end
55
+
56
+ def request_type_validation!(args)
57
+ method = "#{@request_type.downcase}_validation".to_sym
58
+ send(method, args) if respond_to? method
59
+ end
60
+
61
+ # Intended to be overridden by subclasses, allowing extra validation logic to be provided
62
+ def additional_validation!(args)
63
+ end
64
+
65
+ def digitization_normalization(args)
66
+ if args[:target_destination].is_a? String
67
+ args[:target_destination] = { value: args[:target_destination] }
68
+ end
69
+ end
70
+
71
+ def digitization_validation(args)
72
+ args.fetch(:target_destination) do
73
+ raise ArgumentError.new(
74
+ ":target_destination option must be specified when request_type is DIGITIZATION"
75
+ )
76
+ end
77
+ pd = args.fetch(:partial_digitization) do
78
+ raise ArgumentError.new(
79
+ ":partial_digitization option must be specified when request_type is DIGITIZATION"
80
+ )
81
+ end
82
+ if pd == true
83
+ args.fetch(:comment) do
84
+ raise ArgumentError.new(
85
+ ":comment option must be specified when :request_type is DIGITIZATION and :partial_digitization is true"
86
+ )
87
+ end
88
+ end
89
+ end
90
+
91
+ def booking_normalization(args)
92
+ if args[:material_type].is_a? String
93
+ args[:material_type] = { value: args[:material_type] }
94
+ end
95
+ end
96
+
97
+ def booking_validation(args)
98
+ args.fetch(:booking_start_date) do
99
+ raise ArgumentError.new(
100
+ ":booking_start_date option must be specified when request_type is BOOKING"
101
+ )
102
+ end
103
+ args.fetch(:booking_end_date) do
104
+ raise ArgumentError.new(
105
+ ":booking_end_date option must be specified when request_type is BOOKING"
106
+ )
107
+ end
108
+ args.fetch(:pickup_location_type) do
109
+ raise ArgumentError.new(
110
+ ":pickup_location_type option must be specified when request_type is BOOKING"
111
+ )
112
+ end
113
+ args.fetch(:pickup_location_library) do
114
+ raise ArgumentError.new(
115
+ ":pickup_location_library option must be specified when request_type is BOOKING"
116
+ )
117
+ end
118
+ end
119
+
120
+ def hold_normalization(args)
121
+ # if args[:material_type].is_a? String
122
+ # args[:material_type] = { value: args[:material_type] }
123
+ # end
124
+ end
125
+
126
+ def hold_validation(args)
127
+ args.fetch(:pickup_location_type) do
128
+ raise ArgumentError.new(
129
+ ":pickup_location_type option must be specified when request_type is HOLD"
130
+ )
131
+ end
132
+ args.fetch(:pickup_location_library) do
133
+ raise ArgumentError.new(
134
+ ":pickup_location_library option must be specified when request_type is HOLD"
135
+ )
136
+ end
137
+ end
138
+ end
139
+
140
+ class ItemRequest < BibRequest
141
+ def self.submit(args)
142
+ request = new(args)
143
+ response = HTTParty.post(
144
+ "#{bibs_base_path}/#{request.mms_id}/holdings/#{request.holding_id}/items/#{request.item_pid}/requests",
145
+ query: { user_id: request.user_id },
146
+ headers: headers,
147
+ body: request.body.to_json
148
+ )
149
+ Alma::Response.new(response)
150
+ end
151
+
152
+ attr_reader :holding_id, :item_pid
153
+ def initialize(args)
154
+ super(args)
155
+ @holding_id = args.delete(:holding_id) { raise ArgumentError.new(":holding_id option must be specified to create request") }
156
+ @item_pid = args.delete(:item_pid) { raise ArgumentError.new(":item_pid option must be specified to create request") }
157
+ end
158
+
159
+ def additional_validation!(args)
160
+ args.fetch(:description) do
161
+ raise ArgumentError.new(
162
+ ":description option must be specified when request_type is DIGITIZATION"
163
+ )
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class RequestOptions
5
+ class ResponseError < Alma::StandardError
6
+ end
7
+
8
+ extend Forwardable
9
+ extend Alma::ApiDefaults
10
+
11
+ attr_accessor :request_options, :raw_response
12
+ def_delegators :raw_response, :response, :request
13
+
14
+ REQUEST_OPTIONS_PERMITTED_ARGS = [:user_id]
15
+
16
+ def initialize(response)
17
+ @raw_response = response
18
+ validate(response)
19
+ @request_options = response.parsed_response["request_option"]
20
+ end
21
+
22
+
23
+ def self.get(mms_id, options = {})
24
+ url = "#{bibs_base_path}/#{mms_id}/request-options"
25
+ options.select! { |k, _| REQUEST_OPTIONS_PERMITTED_ARGS.include? k }
26
+ response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
27
+ new(response)
28
+ end
29
+
30
+ def loggable
31
+ { uri: @raw_response&.request&.uri.to_s
32
+ }.select { |k, v| !(v.nil? || v.empty?) }
33
+ end
34
+
35
+ def validate(response)
36
+ if response.code != 200
37
+ raise ResponseError.new("Could not get request options.", loggable.merge(response.parsed_response))
38
+ end
39
+ end
40
+
41
+ def hold_allowed?
42
+ !request_options.nil? &&
43
+ !request_options.select { |option| option["type"]["value"] == "HOLD" }.empty?
44
+ end
45
+
46
+ def digitization_allowed?
47
+ !request_options.nil? &&
48
+ !request_options.select { |option| option["type"]["value"] == "DIGITIZATION" }.empty?
49
+ end
50
+
51
+ def booking_allowed?
52
+ !request_options.nil? &&
53
+ !request_options.select { |option| option["type"]["value"] == "BOOKING" }.empty?
54
+ end
55
+
56
+ def resource_sharing_broker_allowed?
57
+ !request_options.nil? &&
58
+ !request_options.select { |option| option["type"]["value"] == "RS_BROKER" }.empty?
59
+ end
60
+
61
+ def ez_borrow_link
62
+ broker = request_options.select { |option| option["type"]["value"] == "RS_BROKER" }
63
+ broker.collect { |opt| opt["request_url"] }.first
64
+ end
65
+ end
66
+ end