alma 0.2.4 → 0.3.2

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 (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/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
2
4
  require "rspec/core/rake_task"
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/alma.gemspec CHANGED
@@ -1,16 +1,18 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'alma/version'
6
+ require "alma/version"
5
7
 
6
8
  Gem::Specification.new do |spec|
7
9
  spec.name = "alma"
8
10
  spec.version = Alma::VERSION
9
- spec.authors = ["Chad Nelson"]
10
- spec.email = ["chad.nelson@temple.edu"]
11
+ spec.authors = ["Jennifer Anton", "David Kinzer", "Chad Nelson"]
12
+ spec.email = ["jennifer.anton@temple.edu", "david.kinzer@temple.edu", "chad.nelson@temple.edu"]
11
13
 
12
- spec.summary = %q{Client for Ex Libris Alma Web Services}
13
- spec.description = %q{Client for Ex Libris Alma Web Services}
14
+ spec.summary = "Client for Ex Libris Alma Web Services"
15
+ spec.description = "Client for Ex Libris Alma Web Services"
14
16
  spec.homepage = "https://github.com/tulibraries/alma_rb"
15
17
  spec.license = "MIT"
16
18
 
@@ -21,12 +23,19 @@ Gem::Specification.new do |spec|
21
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
24
  spec.require_paths = ["lib"]
23
25
 
24
- spec.add_dependency 'ezwadl'
25
-
26
+ spec.add_dependency "ezwadl"
27
+ spec.add_dependency "httparty"
28
+ spec.add_dependency "xml-simple"
29
+ spec.add_dependency "activesupport"
26
30
 
27
- spec.add_development_dependency "bundler", "~> 1.13"
28
- spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "bundler", "~> 2.0"
32
+ spec.add_development_dependency "rake", "~> 13.0"
29
33
  spec.add_development_dependency "rspec", "~> 3.0"
30
- spec.add_development_dependency 'webmock'
31
- spec.add_development_dependency 'pry'
34
+ spec.add_development_dependency "webmock"
35
+ spec.add_development_dependency "pry"
36
+ spec.add_development_dependency "rubocop"
37
+ spec.add_development_dependency "rubocop-rails"
38
+ spec.add_development_dependency "byebug"
39
+ spec.add_development_dependency "guard"
40
+ spec.add_development_dependency "guard-rspec"
32
41
  end
data/lib/alma.rb CHANGED
@@ -1,59 +1,39 @@
1
- require 'alma/version'
2
- require 'alma/config'
3
- require 'alma/api'
4
- require 'alma/error'
5
- require 'alma/alma_record'
6
- require 'alma/user'
7
- require 'alma/bib'
8
- require 'alma/loan'
9
- require 'alma/result_set'
10
- require 'alma/loan_set'
11
- require 'alma/user_set'
12
- require 'alma/fine_set'
13
- require 'alma/user_set'
14
- require 'alma/bib_set'
15
- require 'alma/request_set'
16
- require 'alma/renewal_response'
17
- require 'alma/availability_response'
1
+ # frozen_string_literal: true
2
+
3
+ require "alma/version"
4
+ require "alma/config"
5
+ require "alma/api_defaults"
6
+ require "alma/error"
7
+ require "alma/alma_record"
8
+ require "alma/response"
9
+ require "alma/user"
10
+ require "alma/bib"
11
+ require "alma/loan"
12
+ require "alma/result_set"
13
+ require "alma/loan_set"
14
+ require "alma/user_set"
15
+ require "alma/fine_set"
16
+ require "alma/fine"
17
+ require "alma/bib_set"
18
+ require "alma/request_set"
19
+ require "alma/renewal_response"
20
+ require "alma/availability_response"
21
+ require "alma/bib_item"
22
+ require "alma/request_options"
23
+ require "alma/item_request_options"
24
+ require "alma/request"
25
+ require "alma/user_request"
26
+ require "alma/electronic"
27
+ require "alma/bib_holding"
28
+ require "alma/library"
29
+ require "alma/library_set"
30
+ require "alma/location"
31
+ require "alma/location_set"
32
+ require "alma/course_set"
33
+ require "alma/course"
18
34
 
19
35
  module Alma
36
+ require "httparty"
20
37
 
21
38
  ROOT = File.dirname __dir__
22
- WADL_DIR = File.join(Alma::ROOT, 'lib','alma','wadl')
23
-
24
- INVENTORY_TO_SUBFIELD_TO_FIELDNAME =
25
- {
26
- 'AVA' => {
27
- 'INVENTORY_TYPE' => 'physical',
28
- 'a' => 'institution',
29
- 'b' => 'library_code',
30
- 'c' => 'location',
31
- 'd' => 'call_number',
32
- 'e' => 'availability',
33
- 'f' => 'total_items',
34
- 'g' => 'non_available_items',
35
- 'j' => 'location_code',
36
- 'k' => 'call_number_type',
37
- 'p' => 'priority',
38
- 'q' => 'library',
39
- },
40
- 'AVD' => {
41
- 'INVENTORY_TYPE' => 'digital',
42
- 'a' => 'institution',
43
- 'b' => 'representations_id',
44
- 'c' => 'representation',
45
- 'd' => 'repository_name',
46
- 'e' => 'label',
47
- },
48
- 'AVE' => {
49
- 'INVENTORY_TYPE' => 'electronic',
50
- 'l' => 'library_code',
51
- 'm' => 'collection',
52
- 'n' => 'public_note',
53
- 's' => 'coverage_statement',
54
- 't' => 'interface_name',
55
- 'u' => 'link_to_service_page',
56
- }
57
- }
58
-
59
39
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class AlmaRecord
3
-
4
5
  def initialize(record)
5
6
  @raw_record = record
6
7
  post_initialize()
@@ -23,6 +24,5 @@ module Alma
23
24
  # Subclasses can define this method to perform extra initialization
24
25
  # after the super class init.
25
26
  end
26
-
27
27
  end
28
- end
28
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ module ApiDefaults
5
+ def apikey
6
+ Alma.configuration.apikey
7
+ end
8
+
9
+ def region
10
+ Alma.configuration.region
11
+ end
12
+
13
+ def headers
14
+ { "Authorization": "apikey #{self.apikey}",
15
+ "Accept": "application/json",
16
+ "Content-Type": "application/json" }
17
+ end
18
+
19
+ def bibs_base_path
20
+ "#{self.region}/almaws/v1/bibs"
21
+ end
22
+
23
+ def users_base_path
24
+ "#{self.region}/almaws/v1/users"
25
+ end
26
+
27
+ def items_base_path
28
+ "#{self.region}/almaws/v1/items"
29
+ end
30
+
31
+ def configuration_base_path
32
+ "#{self.region}/almaws/v1/conf"
33
+ end
34
+
35
+ def timeout
36
+ Alma.configuration.timeout
37
+ end
38
+ end
39
+ end
@@ -1,50 +1,88 @@
1
- module Alma
2
- class AvailabilityResponse
1
+ # frozen_string_literal: true
3
2
 
3
+ require "xmlsimple"
4
4
 
5
+ module Alma
6
+ class AvailabilityResponse
5
7
  attr_accessor :availability
6
8
 
7
9
  def initialize(response)
8
- @availability = parse_bibs_data(response.list)
9
-
10
+ @availability = parse_bibs_data(response.each)
10
11
  end
11
12
 
13
+ # Data structure for holdings information of bib records.
14
+ # A hash with mms ids as keys, with values of an array of
15
+ # one or more hashes of holdings info
12
16
  def parse_bibs_data(bibs)
13
-
14
- bibs.map do |bib|
15
- record = Hash.new
16
-
17
- record['mms_id'] = bib.id
18
- record['holdings'] = build_holdings_for(bib)
19
-
20
- record
21
- end.reduce(Hash.new) do |acc, avail|
22
- acc[avail['mms_id']] = avail.select { |k, v| k != 'mms_id' }
23
- acc
24
- end
17
+ bibs.reduce(Hash.new) { |acc, bib|
18
+ acc.merge({ "#{bib.id}" => { holdings: build_holdings_for(bib) } })
19
+ }
25
20
  end
26
21
 
27
-
28
22
  def build_holdings_for(bib)
29
-
30
23
  get_inventory_fields_for(bib).map do |inventory_field|
31
- subfield_codes_to_fieldnames = Alma::INVENTORY_TO_SUBFIELD_TO_FIELDNAME[inventory_field['tag']]
32
-
33
- # make sure subfields is always an Array (which isn't the case if there's only one subfield element)
34
- subfields = [inventory_field.fetch('subfield', [])].flatten(1)
35
-
36
- holding = subfields.reduce(Hash.new) do |acc, subfield|
37
- fieldname = subfield_codes_to_fieldnames[subfield['code']]
38
- acc[fieldname] = subfield['__content__']
39
- acc
40
- end
41
- holding['inventory_type'] = subfield_codes_to_fieldnames['INVENTORY_TYPE']
42
- holding
24
+ # Use the mapping for this inventory type
25
+ subfield_codes = Alma::INVENTORY_SUBFIELD_MAPPING[inventory_field["tag"]]
26
+
27
+ inventory_field.
28
+ # Get all the subfields for this inventory field
29
+ fetch("subfield", []).
30
+ # Limit to only subfields codes for which we have a mapping
31
+ select { |sf| subfield_codes.key? sf["code"] }.
32
+ # Transform the array of subfields into a hash with mapped code as key
33
+ reduce(Hash.new) { |acc, subfield|
34
+ acc.merge({ "#{subfield_codes[subfield['code']]}" => subfield["content"] })
35
+ }.
36
+ # Include the inventory type
37
+ merge({ "inventory_type" => subfield_codes["INVENTORY_TYPE"] })
43
38
  end
44
39
  end
45
40
 
46
41
  def get_inventory_fields_for(bib)
47
- bib.record.fetch('datafield', []).select { |df| Alma::INVENTORY_TO_SUBFIELD_TO_FIELDNAME.key?(df['tag']) } || []
42
+ # Return only the datafields with tags AVA, AVD, or AVE
43
+ bib.record
44
+ .fetch("datafield", [])
45
+ .select { |df| Alma::INVENTORY_SUBFIELD_MAPPING.key?(df["tag"]) }
48
46
  end
49
47
  end
48
+
49
+ INVENTORY_SUBFIELD_MAPPING =
50
+ {
51
+ "AVA" => {
52
+ "INVENTORY_TYPE" => "physical",
53
+ "a" => "institution",
54
+ "b" => "library_code",
55
+ "c" => "location",
56
+ "d" => "call_number",
57
+ "e" => "availability",
58
+ "f" => "total_items",
59
+ "g" => "non_available_items",
60
+ "j" => "location_code",
61
+ "k" => "call_number_type",
62
+ "p" => "priority",
63
+ "q" => "library",
64
+ "t" => "holding_info",
65
+ "8" => "holding_id",
66
+ },
67
+ "AVD" => {
68
+ "INVENTORY_TYPE" => "digital",
69
+ "a" => "institution",
70
+ "b" => "representations_id",
71
+ "c" => "representation",
72
+ "d" => "repository_name",
73
+ "e" => "label",
74
+ },
75
+ "AVE" => {
76
+ "INVENTORY_TYPE" => "electronic",
77
+ "c" => "collection_id",
78
+ "e" => "activation_status",
79
+ "l" => "library_code",
80
+ "m" => "collection",
81
+ "n" => "public_note",
82
+ "s" => "coverage_statement",
83
+ "t" => "interface_name",
84
+ "u" => "link_to_service_page",
85
+ "8" => "portfolio_pid",
86
+ }
87
+ }
50
88
  end
data/lib/alma/bib.rb CHANGED
@@ -1,46 +1,71 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
- class Bib < AlmaRecord
3
- extend Alma::Api
4
+ class Bib
5
+ extend Alma::ApiDefaults
6
+ extend Forwardable
7
+
8
+ def self.find(ids, args)
9
+ get_bibs(ids, args)
10
+ end
4
11
 
5
- attr_accessor :id, :record
12
+ def self.get_bibs(ids, args = {})
13
+ response = HTTParty.get(
14
+ self.bibs_base_path,
15
+ query: { mms_id: ids_from_array(ids) }.merge(args),
16
+ headers: headers,
17
+ timeout: timeout
18
+ )
6
19
 
7
- def post_initialize
8
- @id = response['mms_id'].to_s
9
- @record = response.fetch('record', {})
20
+ if response.code == 200
21
+ Alma::BibSet.new(get_body_from(response))
22
+ else
23
+ raise StandardError, get_body_from(response)
24
+ end
10
25
  end
11
26
 
12
- class << self
13
27
 
14
- def get_availability(ids, args={})
15
- args.merge!({expand: 'p_avail,e_avail,d_avail'})
16
- bibs = get_bibs(ids, args)
28
+ def self.get_availability(ids, args = {})
29
+ args.merge!({ expand: "p_avail,e_avail,d_avail" })
30
+ bibs = get_bibs(ids, args)
17
31
 
18
- return bibs if bibs.has_error?
19
- Alma::AvailabilityResponse.new(bibs)
20
- end
32
+ Alma::AvailabilityResponse.new(bibs)
33
+ end
21
34
 
22
- def find(ids, args)
23
- get_bibs(ids, args)
24
- end
25
35
 
26
- def get_bibs(ids, args={})
27
- args[:mms_id] = ids_from_array(ids)
28
- params = query_merge(args)
29
- response = resources.almaws_v1_bibs.get(params)
30
36
 
31
- Alma::BibSet.new(response)
32
- end
37
+ attr_accessor :id, :response
38
+
39
+ # The User object can respond directly to Hash like access of attributes
40
+ def_delegators :response, :[], :[]=, :has_key?, :keys, :to_json, :each
41
+
42
+ def initialize(response_body)
43
+ @response = response_body
44
+ @id = @response["mms_id"].to_s
45
+ end
46
+
47
+ # The raw MARCXML record, converted to a Hash
48
+ def record
49
+ @record ||= XmlSimple.xml_in(response["anies"].first)
50
+ end
51
+
33
52
 
34
53
  private
35
54
 
36
- def ids_from_array(ids)
37
- ids.map(&:to_s).map(&:strip).join(',')
38
- end
55
+ def bibs_base_path
56
+ self.class.bibs_base_path
57
+ end
39
58
 
40
- def set_wadl_filename
41
- 'bib.wadl'
42
- end
59
+ def headers
60
+ self.class.headers
61
+ end
43
62
 
44
- end
63
+ def self.get_body_from(response)
64
+ JSON.parse(response.body)
65
+ end
66
+
67
+ def self.ids_from_array(ids)
68
+ ids.map(&:to_s).map(&:strip).join(",")
69
+ end
45
70
  end
46
71
  end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class BibHolding
5
+ extend Alma::ApiDefaults
6
+ extend Forwardable
7
+
8
+ def self.find(mms_id:, holding_id:)
9
+ url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}"
10
+ response = HTTParty.get(url, headers: headers, timeout: timeout)
11
+ new(response)
12
+ end
13
+
14
+ attr_reader :holding
15
+ def_delegators :holding, :[], :[]=, :has_key?, :keys, :to_json, :each
16
+
17
+ def initialize(holding)
18
+ @holding = holding
19
+ end
20
+
21
+ def holding_id
22
+ holding["holding_id"]
23
+ end
24
+ end
25
+ end