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,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,91 +1,88 @@
1
- require 'xmlsimple'
1
+ # frozen_string_literal: true
2
+
3
+ require "xmlsimple"
2
4
 
3
5
  module Alma
4
6
  class AvailabilityResponse
5
-
6
-
7
7
  attr_accessor :availability
8
8
 
9
9
  def initialize(response)
10
- @availability = parse_bibs_data(response.list)
10
+ @availability = parse_bibs_data(response.each)
11
11
  end
12
12
 
13
- # Data structure for holdings information of bib records.
13
+ # Data structure for holdings information of bib records.
14
14
  # A hash with mms ids as keys, with values of an array of
15
15
  # one or more hashes of holdings info
16
16
  def parse_bibs_data(bibs)
17
17
  bibs.reduce(Hash.new) { |acc, bib|
18
- acc.merge({"#{bib.id}" => {holdings: build_holdings_for(bib)}})
18
+ acc.merge({ "#{bib.id}" => { holdings: build_holdings_for(bib) } })
19
19
  }
20
20
  end
21
21
 
22
22
  def build_holdings_for(bib)
23
23
  get_inventory_fields_for(bib).map do |inventory_field|
24
24
  # Use the mapping for this inventory type
25
- subfield_codes = Alma::INVENTORY_SUBFIELD_MAPPING[inventory_field['tag']]
26
-
25
+ subfield_codes = Alma::INVENTORY_SUBFIELD_MAPPING[inventory_field["tag"]]
26
+
27
27
  inventory_field.
28
28
  # Get all the subfields for this inventory field
29
- fetch('subfield', []).
29
+ fetch("subfield", []).
30
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']})
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
35
  }.
36
36
  # Include the inventory type
37
- merge({'inventory_type' => subfield_codes['INVENTORY_TYPE']})
37
+ merge({ "inventory_type" => subfield_codes["INVENTORY_TYPE"] })
38
38
  end
39
39
  end
40
40
 
41
41
  def get_inventory_fields_for(bib)
42
- # Return only the datafields with tags AVA, AVD, or AVE
42
+ # Return only the datafields with tags AVA, AVD, or AVE
43
43
  bib.record
44
- .fetch('datafield', [])
45
- .select { |df| Alma::INVENTORY_SUBFIELD_MAPPING.key?(df['tag']) }
44
+ .fetch("datafield", [])
45
+ .select { |df| Alma::INVENTORY_SUBFIELD_MAPPING.key?(df["tag"]) }
46
46
  end
47
47
  end
48
48
 
49
49
  INVENTORY_SUBFIELD_MAPPING =
50
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',
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
66
  },
67
- 'AVD' => {
68
- 'INVENTORY_TYPE' => 'digital',
69
- 'a' => 'institution',
70
- 'b' => 'representations_id',
71
- 'c' => 'representation',
72
- 'd' => 'repository_name',
73
- 'e' => 'label',
67
+ "AVD" => {
68
+ "INVENTORY_TYPE" => "digital",
69
+ "a" => "institution",
70
+ "b" => "representations_id",
71
+ "c" => "representation",
72
+ "d" => "repository_name",
73
+ "e" => "label",
74
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',
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
86
  }
87
87
  }
88
-
89
-
90
-
91
88
  end
data/lib/alma/bib.rb CHANGED
@@ -1,28 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class Bib
5
+ extend Alma::ApiDefaults
3
6
  extend Forwardable
4
-
7
+
5
8
  def self.find(ids, args)
6
9
  get_bibs(ids, args)
7
10
  end
8
11
 
9
- def self.get_bibs(ids, args={})
12
+ def self.get_bibs(ids, args = {})
10
13
  response = HTTParty.get(
11
- self.bibs_base_path,
12
- query: {mms_id: ids_from_array(ids)},
13
- headers: headers
14
+ self.bibs_base_path,
15
+ query: { mms_id: ids_from_array(ids) }.merge(args),
16
+ headers: headers,
17
+ timeout: timeout
14
18
  )
15
19
 
16
20
  if response.code == 200
17
21
  Alma::BibSet.new(get_body_from(response))
18
22
  else
19
23
  raise StandardError, get_body_from(response)
20
- end
24
+ end
21
25
  end
22
26
 
23
27
 
24
- def self.get_availability(ids, args={})
25
- args.merge!({expand: 'p_avail,e_avail,d_avail'})
28
+ def self.get_availability(ids, args = {})
29
+ args.merge!({ expand: "p_avail,e_avail,d_avail" })
26
30
  bibs = get_bibs(ids, args)
27
31
 
28
32
  Alma::AvailabilityResponse.new(bibs)
@@ -37,51 +41,31 @@ module Alma
37
41
 
38
42
  def initialize(response_body)
39
43
  @response = response_body
40
- @id = @response['mms_id'].to_s
44
+ @id = @response["mms_id"].to_s
41
45
  end
42
46
 
43
47
  # The raw MARCXML record, converted to a Hash
44
48
  def record
45
- @record ||= XmlSimple.xml_in(response['anies'].first)
49
+ @record ||= XmlSimple.xml_in(response["anies"].first)
46
50
  end
47
51
 
48
52
 
49
53
  private
50
54
 
51
- def self.bibs_base_path
52
- "#{self.region}/almaws/v1/bibs"
53
- end
54
-
55
- def bibs_base_path
56
- self.class.bibs_base_path
57
- end
55
+ def bibs_base_path
56
+ self.class.bibs_base_path
57
+ end
58
58
 
59
- def self.headers
60
- { "Authorization": "apikey #{self.apikey}",
61
- "Accept": "application/json",
62
- "Content-Type": "application/json" }
63
- end
64
-
65
-
66
- def headers
67
- self.class.headers
68
- end
69
-
70
-
71
- def self.apikey
72
- Alma.configuration.apikey
73
- end
74
-
75
- def self.region
76
- Alma.configuration.region
77
- end
59
+ def headers
60
+ self.class.headers
61
+ end
78
62
 
79
- def self.get_body_from(response)
80
- JSON.parse(response.body)
81
- end
63
+ def self.get_body_from(response)
64
+ JSON.parse(response.body)
65
+ end
82
66
 
83
- def self.ids_from_array(ids)
84
- ids.map(&:to_s).map(&:strip).join(',')
85
- end
67
+ def self.ids_from_array(ids)
68
+ ids.map(&:to_s).map(&:strip).join(",")
69
+ end
86
70
  end
87
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
data/lib/alma/bib_item.rb CHANGED
@@ -1,22 +1,36 @@
1
- require 'alma/bib_item_set'
1
+ # frozen_string_literal: true
2
+
3
+ require "alma/bib_item_set"
2
4
  module Alma
3
5
  class BibItem
6
+ extend Alma::ApiDefaults
4
7
  extend Forwardable
5
8
 
6
9
  attr_reader :item
7
10
  def_delegators :item, :[], :has_key?, :keys, :to_json
8
11
 
9
- PERMITTED_ARGS = [
12
+ PERMITTED_ARGS = [
10
13
  :limit, :offset, :expand, :user_id, :current_library,
11
14
  :current_location, :q, :order_by, :direction
12
15
  ]
13
16
 
14
- def self.find(mms_id, options={})
17
+ def self.find(mms_id, options = {})
15
18
  holding_id = options.delete(:holding_id) || "ALL"
16
- options.select! {|k,_| PERMITTED_ARGS.include? k }
19
+ options.select! { |k, _| PERMITTED_ARGS.include? k }
17
20
  url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}/items"
18
- response = HTTParty.get(url, headers: headers, query: options)
19
- BibItemSet.new(response)
21
+ response = HTTParty.get(url, headers: headers, query: options, timeout: timeout)
22
+ BibItemSet.new(response, options.merge({ mms_id: mms_id, holding_id: holding_id }))
23
+ end
24
+
25
+ def self.find_by_barcode(barcode)
26
+ response = HTTParty.get(items_base_path, headers: headers, query: { item_barcode: barcode }, timeout: timeout, follow_redirects: true)
27
+ new(response)
28
+ end
29
+
30
+ def self.scan(mms_id:, holding_id:, item_pid:, options: {})
31
+ url = "#{bibs_base_path}/#{mms_id}/holdings/#{holding_id}/items/#{item_pid}"
32
+ response = HTTParty.post(url, headers: headers, query: options)
33
+ new(response)
20
34
  end
21
35
 
22
36
  def initialize(item)
@@ -51,7 +65,6 @@ module Alma
51
65
  in_temp_location? ? temp_location_name : holding_location_name
52
66
  end
53
67
 
54
-
55
68
  def holding_library
56
69
  item_data.dig("library", "value")
57
70
  end
@@ -85,7 +98,7 @@ module Alma
85
98
  end
86
99
 
87
100
  def temp_call_number
88
- holding_data.fetch("temp_call_number","")
101
+ holding_data.fetch("temp_call_number", "")
89
102
  end
90
103
 
91
104
  def has_temp_call_number?
@@ -93,13 +106,11 @@ module Alma
93
106
  end
94
107
 
95
108
  def call_number
96
- if has_temp_call_number?
97
- holding_data.fetch("temp_call_number")
98
- elsif has_alt_call_number?
99
- alt_call_number
100
- else
101
- holding_data.fetch("call_number","")
102
- end
109
+ if has_temp_call_number?
110
+ holding_data.fetch("temp_call_number")
111
+ else
112
+ holding_data.fetch("call_number", "")
113
+ end
103
114
  end
104
115
 
105
116
  def has_alt_call_number?
@@ -107,7 +118,7 @@ module Alma
107
118
  end
108
119
 
109
120
  def alt_call_number
110
- item_data.fetch("alternative_call_number","")
121
+ item_data.fetch("alternative_call_number", "")
111
122
  end
112
123
 
113
124
  def has_process_type?
@@ -123,7 +134,7 @@ module Alma
123
134
  end
124
135
 
125
136
  def base_status
126
- item_data.dig("base_status","value")|| ""
137
+ item_data.dig("base_status", "value") || ""
127
138
  end
128
139
 
129
140
  def in_place?
@@ -149,26 +160,5 @@ module Alma
149
160
  def public_note
150
161
  item_data.fetch("public_note", "")
151
162
  end
152
-
153
- private
154
-
155
- def self.region
156
- Alma.configuration.region
157
- end
158
-
159
- def self.bibs_base_path
160
- "#{region}/almaws/v1/bibs"
161
- end
162
-
163
- def self.headers
164
- { "Authorization": "apikey #{apikey}",
165
- "Accept": "application/json",
166
- "Content-Type": "application/json" }
167
- end
168
-
169
- def self.apikey
170
- Alma.configuration.apikey
171
- end
172
163
  end
173
-
174
164
  end
@@ -1,23 +1,41 @@
1
- module Alma
2
- class BibItemSet
3
- extend Forwardable
1
+ # frozen_string_literal: true
4
2
 
5
- # Let BibItemSet respond to the Enumerable duck type
6
- # by delegating responsibility for #each to items
7
- include Enumerable
8
- attr_accessor :items
9
- def_delegators :items, :each
3
+ module Alma
4
+ class BibItemSet < ResultSet
5
+ ITEMS_PER_PAGE = 100
10
6
 
11
- def_delegators :items,:[], :[]=, :empty?, :size
7
+ class ResponseError < ::Alma::StandardError
8
+ end
12
9
 
10
+ attr_accessor :items
13
11
  attr_reader :raw_response, :total_record_count
12
+
13
+ def_delegators :items, :[], :[]=, :empty?, :size, :each
14
14
  def_delegators :raw_response, :response, :request
15
15
 
16
- def initialize(response)
16
+ def initialize(response, options = {})
17
17
  @raw_response = response
18
- parsed = JSON.parse(response.body)
18
+ parsed = response.parsed_response
19
19
  @total_record_count = parsed["total_record_count"]
20
- @items = parsed.fetch("item",[]).map {|item| BibItem.new(item)}
20
+ @options = options
21
+ @mms_id = @options.delete(:mms_id)
22
+
23
+ validate(response)
24
+ @items = parsed.fetch(key, []).map { |item| single_record_class.new(item) }
25
+ end
26
+
27
+ def loggable
28
+ { total_record_count: @total_record_count.to_s,
29
+ mms_id: @mms_id,
30
+ uri: @raw_response&.request&.uri.to_s
31
+ }.select { |k, v| !(v.nil? || v.empty?) }
32
+ end
33
+
34
+ def validate(response)
35
+ if response.code != 200
36
+ log = loggable.merge(response.parsed_response)
37
+ raise ResponseError.new("Could not get bib items.", log)
38
+ end
21
39
  end
22
40
 
23
41
  def grouped_by_library
@@ -29,5 +47,47 @@ module Alma
29
47
  clone.items = reject(&:missing_or_lost?)
30
48
  clone
31
49
  end
50
+
51
+ def all
52
+ @last_page_index ||= false
53
+ Enumerator.new do |yielder|
54
+ offset = 0
55
+ while (!@last_page_index || @last_page_index >= offset / items_per_page) do
56
+ r = (offset == 0) ? self : single_record_class.find(@mms_id, options = @options.merge({ limit: items_per_page, offset: offset }))
57
+ unless r.empty?
58
+ r.map { |item| yielder << item }
59
+ @last_page_index = (offset / items_per_page)
60
+ else
61
+ @last_page_index = @last_page_index ? @last_page_index - 1 : -1
62
+ end
63
+
64
+ if r.size == items_per_page
65
+ @last_page_index += 1
66
+ end
67
+
68
+ offset += items_per_page
69
+ end
70
+ end
71
+ end
72
+
73
+ def each(&block)
74
+ @items.each(&block)
75
+ end
76
+
77
+ def success?
78
+ raw_response.response.code.to_s == "200"
79
+ end
80
+
81
+ def key
82
+ "item"
83
+ end
84
+
85
+ def single_record_class
86
+ Alma::BibItem
87
+ end
88
+
89
+ def items_per_page
90
+ ITEMS_PER_PAGE
91
+ end
32
92
  end
33
93
  end
data/lib/alma/bib_set.rb CHANGED
@@ -1,31 +1,17 @@
1
- module Alma
2
- class BibSet
3
-
4
- extend Forwardable
5
- include Enumerable
6
- #include Alma::Error
7
-
8
- attr_reader :response
9
- def_delegators :list, :each, :size
10
- def_delegators :response, :[], :fetch
11
-
12
- def initialize(response_body_hash)
13
- @response = response_body_hash
14
- end
1
+ # frozen_string_literal: true
15
2
 
16
- def list
17
- @list ||= response.fetch(key, []).map do |record|
18
- Alma::Bib.new(record)
19
- end
3
+ module Alma
4
+ class BibSet < ResultSet
5
+ def key
6
+ "bib"
20
7
  end
21
8
 
22
- def key
23
- 'bib'
9
+ def single_record_class
10
+ Alma::Bib
24
11
  end
25
12
 
26
13
  def total_record_count
27
14
  size
28
15
  end
29
-
30
16
  end
31
17
  end
data/lib/alma/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Alma
2
4
  class << self
3
5
  attr_accessor :configuration
@@ -9,12 +11,16 @@ module Alma
9
11
  end
10
12
 
11
13
  class Configuration
12
- attr_accessor :apikey, :region
14
+ attr_accessor :apikey, :region, :enable_loggable
15
+ attr_accessor :timeout, :http_retries, :logger
13
16
 
14
17
  def initialize
15
18
  @apikey = "TEST_API_KEY"
16
- @region = 'https://api-na.hosted.exlibrisgroup.com'
19
+ @region = "https://api-na.hosted.exlibrisgroup.com"
20
+ @enable_loggable = false
21
+ @timeout = 5
22
+ @http_retries = 3
23
+ @logger = Logger.new(STDOUT)
17
24
  end
18
-
19
25
  end
20
- end
26
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class Course
5
+ extend Alma::ApiDefaults
6
+ extend Forwardable
7
+
8
+ def self.all_courses(args: {})
9
+ response = HTTParty.get("#{courses_base_path}/courses",
10
+ query: args,
11
+ headers: headers,
12
+ timeout: timeout)
13
+ if response.code == 200
14
+ Alma::CourseSet.new(get_body_from(response))
15
+ else
16
+ raise StandardError, get_body_from(response)
17
+ end
18
+ end
19
+
20
+ attr_accessor :response
21
+
22
+ # The Course object can respond directly to Hash like access of attributes
23
+ def_delegators :response, :[], :[]=, :has_key?, :keys, :to_json
24
+
25
+ def initialize(response_body)
26
+ @response = response_body
27
+ end
28
+
29
+ private
30
+
31
+ def self.get_body_from(response)
32
+ JSON.parse(response.body)
33
+ end
34
+
35
+ def self.courses_base_path
36
+ "https://api-na.hosted.exlibrisgroup.com/almaws/v1"
37
+ end
38
+
39
+ def courses_base_path
40
+ self.class.courses_base_path
41
+ end
42
+
43
+ def headers
44
+ self.class.headers
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Alma
4
+ class CourseSet < ResultSet
5
+ def key
6
+ "course"
7
+ end
8
+
9
+ def single_record_class
10
+ Alma::Course
11
+ end
12
+
13
+ def total_record_count
14
+ size
15
+ end
16
+ end
17
+ end