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/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