summon 1.0.0

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. data/.specification +58 -0
  2. data/History.txt +4 -0
  3. data/Manifest.txt +52 -0
  4. data/PostInstall.txt +4 -0
  5. data/README.rdoc +77 -0
  6. data/Rakefile +25 -0
  7. data/bin/summon +10 -0
  8. data/bin/summonh +19 -0
  9. data/ispec/integration_spec.rb +61 -0
  10. data/lib/summon.rb +41 -0
  11. data/lib/summon/cli.rb +136 -0
  12. data/lib/summon/log.rb +40 -0
  13. data/lib/summon/schema.rb +109 -0
  14. data/lib/summon/schema/availability.rb +14 -0
  15. data/lib/summon/schema/citation.rb +11 -0
  16. data/lib/summon/schema/date.rb +23 -0
  17. data/lib/summon/schema/document.rb +84 -0
  18. data/lib/summon/schema/error.rb +4 -0
  19. data/lib/summon/schema/facet.rb +51 -0
  20. data/lib/summon/schema/query.rb +96 -0
  21. data/lib/summon/schema/range.rb +52 -0
  22. data/lib/summon/schema/search.rb +37 -0
  23. data/lib/summon/schema/suggestion.rb +5 -0
  24. data/lib/summon/service.rb +44 -0
  25. data/lib/summon/transport.rb +13 -0
  26. data/lib/summon/transport/canned.json +2327 -0
  27. data/lib/summon/transport/canned.rb +9 -0
  28. data/lib/summon/transport/errors.rb +12 -0
  29. data/lib/summon/transport/headers.rb +55 -0
  30. data/lib/summon/transport/http.rb +114 -0
  31. data/lib/summon/transport/qstring.rb +49 -0
  32. data/script/console +10 -0
  33. data/script/destroy +14 -0
  34. data/script/generate +14 -0
  35. data/spec/spec.opts +1 -0
  36. data/spec/spec_helper.rb +19 -0
  37. data/spec/summon/log_spec.rb +28 -0
  38. data/spec/summon/schema/availability_spec.rb +31 -0
  39. data/spec/summon/schema/citation_spec.rb +34 -0
  40. data/spec/summon/schema/date_spec.rb +12 -0
  41. data/spec/summon/schema/document_spec.rb +235 -0
  42. data/spec/summon/schema/facet_spec.rb +115 -0
  43. data/spec/summon/schema/query_spec.rb +163 -0
  44. data/spec/summon/schema/range_spec.rb +45 -0
  45. data/spec/summon/schema/search_spec.rb +62 -0
  46. data/spec/summon/schema_spec.rb +143 -0
  47. data/spec/summon/service_spec.rb +18 -0
  48. data/spec/summon/transport/headers_spec.rb +47 -0
  49. data/spec/summon/transport/http_spec.rb +29 -0
  50. data/spec/summon/transport/qstring_spec.rb +24 -0
  51. data/spec/summon_spec.rb +48 -0
  52. data/summon.gemspec +41 -0
  53. metadata +145 -0
@@ -0,0 +1,9 @@
1
+ module Summon
2
+ module Transport
3
+ class Canned
4
+ def get(*args)
5
+ JSON.parse(File.read(File.dirname(__FILE__) + '/canned.json'))
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module Summon::Transport
2
+
3
+ class TransportError < StandardError; end
4
+
5
+ class AuthorizationError < TransportError; end
6
+
7
+ class ServiceError < TransportError; end
8
+
9
+ class RequestError < TransportError; end
10
+
11
+ class UnknownResponseError < TransportError; end
12
+ end
@@ -0,0 +1,55 @@
1
+ require 'time'
2
+ require 'uri'
3
+ require 'openssl'
4
+ require 'sha1'
5
+ require 'base64'
6
+
7
+ module Summon::Transport
8
+ class Headers < Hash
9
+ include Qstring
10
+ def initialize(options = {})
11
+ @params = options[:params] || {}
12
+ @url = options[:url]
13
+ @uri = URI.parse(@url)
14
+ @access_id = options[:access_id]
15
+ @client_key = options[:client_key]
16
+ @secret_key = options[:secret_key]
17
+ @session_id = options[:session_id]
18
+ @log = Summon::Log.new(options[:log])
19
+ @accept = "application/#{options[:accept] || 'json'}"
20
+ @time = Time.now.httpdate
21
+ validate!
22
+ merge!({
23
+ "Content-Type" => "application/x-www-form-urlencoded; charset=utf8",
24
+ "Accept" => @accept,
25
+ "x-summon-date" => @time,
26
+ "x-summon-session-id" => @session_id,
27
+ "Authorization" => "Summon #{[@access_id, @client_key, digest].reject {|o| o.nil?}.join(';')}",
28
+ "Host" => "#{@uri.host}:#{@uri.port}"
29
+ }).reject! {|k,v| v.nil?}
30
+ @log.debug {
31
+ "#{self.class.name}\n#{self.map {|k,v| "#{k}: #{v}"}.join("\n")}"
32
+ }
33
+ end
34
+
35
+ def digest
36
+ id = [@accept, @time, "#{@uri.host}:#{@uri.port}", @uri.path, qstr].join("\n") + "\n"
37
+ Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, @secret_key, id)).chomp.tap {|digest|
38
+ # @log.debug {"ID: #{id.inspect}"}
39
+ # @log.debug {"DIGEST: #{digest}\n"}
40
+ }
41
+ end
42
+
43
+ def qstr
44
+ to_query_string(@params, false)
45
+ end
46
+
47
+ private
48
+
49
+ def validate!
50
+ raise AuthorizationError, "No Access ID specified" if @access_id.nil?
51
+ raise AuthorizationError, "Secret Key not provided for Access ID: #{@access_id}" if @secret_key.nil?
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,114 @@
1
+ require 'socket'
2
+ require 'net/http'
3
+
4
+ module Summon::Transport
5
+ class Http
6
+ include Qstring
7
+
8
+ DEFAULTS = {:url => "http://api.summon.serialssolutions.com"}
9
+
10
+ def initialize(options = {:url => nil, :access_id => nil, :secret_key => nil, :client_key => nil, :session_id => nil, :log => nil})
11
+ @options = DEFAULTS.merge options
12
+ @access_id = @options[:access_id]
13
+ @secret_key = @options[:secret_key]
14
+ @client_key = @options[:client_key]
15
+ @session_id = @options[:session_id] || "SUMMON-SESSION-#{Socket.gethostname}-#{$$}-#{sidalloc}"
16
+ @url = @options[:url]
17
+ @log = Summon::Log.new(options[:log])
18
+ end
19
+
20
+ def get(path, params = {})
21
+ session_id = params["s.session.id"]
22
+ params.delete "s.session.id"
23
+ urlget "#{@url}#{path}?#{to_query_string(params, true)}", params, session_id
24
+ end
25
+
26
+ def urlget(url, params = nil, session_id = nil)
27
+ uri = URI.parse url
28
+ params ||= from_query_string(uri.query)
29
+ session_id ||= @session_id
30
+ headers = Headers.new(
31
+ :url => url,
32
+ :params => params,
33
+ :access_id => @access_id,
34
+ :secret_key => @secret_key,
35
+ :client_key => @client_key,
36
+ :session_id => session_id,
37
+ :log => @log
38
+ )
39
+
40
+ @log.info("ruby-summon:transport") {
41
+ "GET: #{url}"
42
+ }
43
+ http = Net::HTTP.new(uri.host, uri.port)
44
+ http.start do
45
+ get = Net::HTTP::Get.new("#{uri.path}#{'?' + uri.query if uri.query && uri.query != ''}")
46
+ get.merge! headers
47
+ http.request(get) do |response|
48
+ case response
49
+ when Net::HTTPSuccess
50
+ return parse(response)
51
+ when Net::HTTPUnauthorized
52
+ raise AuthorizationError, status(response)
53
+ when Net::HTTPClientError
54
+ raise RequestError, error(response)
55
+ when Net::HTTPServerError
56
+ raise ServiceError, error(response)
57
+ else
58
+ raise UnknownResponseError, "Unknown response: #{response}"
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def parse(response)
65
+ case response.content_type
66
+ when "application/json"
67
+ JSON.parse(response.body).tap do |json|
68
+ @log.debug("ruby-summon::transport") { "JSON RESPONSE: #{json.inspect}" }
69
+ end
70
+
71
+ when "text/plain"
72
+ response.body.tap do |text|
73
+ @log.debug("ruby-summon::transport") { "TEXT RESPONSE: #{text.inspect}" }
74
+ end
75
+
76
+ else
77
+ raise ServiceError, "service returned unexpected #{response.content_type} : #{response.body}"
78
+ end
79
+ end
80
+
81
+ def error(response)
82
+ case response.content_type
83
+ when "application/json"
84
+ JSON.parse(response.body)["errors"].first["message"]
85
+ else
86
+ status(response)
87
+ end
88
+ end
89
+
90
+ def status(response)
91
+ "#{response.code}: #{response.message}"
92
+ end
93
+
94
+ private
95
+
96
+ @sessions = 0
97
+
98
+ def sidalloc
99
+ self.class.instance_eval do
100
+ @sessions += 1
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+
107
+
108
+ module Net::HTTPHeader
109
+ def merge!(hash)
110
+ for k, v in hash
111
+ self[k] = v
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,49 @@
1
+
2
+ require 'cgi'
3
+
4
+ module Summon::Transport
5
+ module Qstring
6
+
7
+ def to_query_string(hash, urlencode = true)
8
+ hash.reject {|k,v| v.nil? || v == ''}.inject([]) do |qs,pair|
9
+ qs.tap do
10
+ k,v = pair
11
+ if v.is_a?(Array)
12
+ for el in v
13
+ qs << encode_param(k, el, urlencode)
14
+ end
15
+ else
16
+ qs << encode_param(k, v, urlencode)
17
+ end
18
+ end
19
+ end.reject{|o| o.nil? || o.empty?}.sort.join('&')
20
+ end
21
+
22
+ def urlencode(str)
23
+ str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
24
+ end
25
+
26
+ def urldecode(str)
27
+ CGI.unescape(str)
28
+ end
29
+
30
+ def encode_param(k, v, do_urlencode)
31
+ "#{k.to_s}=#{do_urlencode ? urlencode(v.to_s) : v.to_s}"
32
+ end
33
+
34
+ def from_query_string(qstr)
35
+ qstr ||= ""
36
+ {}.tap do |result|
37
+ for param in qstr.split('&')
38
+ m,k,v = *param.match(/^(.*?)=(.*?)$/)
39
+ if current = result[k]
40
+ result[k] = current.to_a
41
+ result[k] << urldecode(v)
42
+ else
43
+ result[k] = urldecode(v)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion -rubygems"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/summon.rb'}"
9
+ puts "Loading summon gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/summon')
2
+ gem 'rspec'
3
+ require 'spec'
4
+
5
+ def pre(text)
6
+ puts "<pre>" +
7
+ text.to_s.
8
+ gsub("&", "&amp;").
9
+ gsub("<", "&lt;").
10
+ gsub(">", "&gt;") +
11
+ "</pre>"
12
+ end
13
+
14
+ class Object
15
+ def remove_src
16
+ @src = nil
17
+ end
18
+ end
19
+
@@ -0,0 +1,28 @@
1
+
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ describe Summon::Log do
5
+
6
+ before(:each) do
7
+ @logger = stub(:Logger)
8
+ end
9
+
10
+ it "passes through all methods to the underlying logger" do
11
+ log = Summon::Log.new(@logger)
12
+ @logger.should_receive(:info).with("foo")
13
+ log.info("foo")
14
+ end
15
+
16
+ it "creates a stderr logger when no spec is provided" do
17
+ Logger.should_receive(:new).with($stderr).and_return(@logger)
18
+ @logger.should_receive(:level=).with(Logger::WARN)
19
+ log = Summon::Log.new
20
+ end
21
+
22
+ it "creates a logger from an option hash when the spec is a set of options" do
23
+ out = stub(:OutputStream)
24
+ Logger.should_receive(:new).with(out).and_return(@logger)
25
+ @logger.should_receive(:level=).with(Logger::ERROR)
26
+ log = Summon::Log.new(:level => :error, :initialize => [out])
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Availability do
4
+ it "should map" do
5
+ availabilities = Summon::Availability.parse_results("Result"=>{"RecordSummary"=>[
6
+ {
7
+ "ID"=>"1038053",
8
+ "RecordList"=>"http://api.staging.summon.serialssolutions.com:8093/status?key=HD3UV5FG9E&output=application%2Fjson&action=list&token=1038053",
9
+ "Record"=>{"Status"=>"unknown", "StatusMessage"=>"Available", "Location"=>"Architecture Library", "CallNumber"=>" 720.979 A534"},
10
+ "RecordCount"=>1},
11
+ {
12
+ "ID"=>"1038082",
13
+ "RecordList"=>"http://api.staging.summon.serialssolutions.com:8093/status?key=HD3UV5FG9E&output=application%2Fjson&action=list&token=1038082",
14
+ "Record"=>{"Status"=>"unknown", "StatusMessage"=>"Available", "Location"=>"Main Library", "CallNumber"=>" 628.5 B6157"},
15
+ "RecordCount"=>1}]})
16
+
17
+ one, two = availabilities
18
+
19
+ one.token.should == "1038053"
20
+ one.status.should == "unknown"
21
+ one.status_message.should == "Available"
22
+ one.location.should == "Architecture Library"
23
+ one.call_number.should == " 720.979 A534"
24
+
25
+ two.token.should == "1038082"
26
+ two.status.should == "unknown"
27
+ two.status_message.should == "Available"
28
+ two.location.should == "Main Library"
29
+ two.call_number.should == " 628.5 B6157"
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Citation do
4
+ it "should map" do
5
+ results = {"Results"=>{"Citations"=>{"Citation"=>[
6
+ {
7
+ "Name"=>"American Medical Association, 10th Edition",
8
+ "Text"=>"Morrow BH, Price VB. <span class=\"italic\">Anasazi architecture and American design</span>. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed. www.magnolia.com.",
9
+ "ShortName"=>"AMA"},
10
+ {
11
+ "Label"=>"Chicago 15th Edition (Notes & Bibliography)",
12
+ "Text"=>"Morrow, Baker H. and V. B. Price. Anasazi Architecture and American Design. 1st ed. ed. Albuquerque: University of New Mexico Press, Wed, (accessed August 5, 2009).",
13
+ "ShortName"=>"Chicago NB"},
14
+ {
15
+ "Label"=>"MLA",
16
+ "Text"=>"Morrow, Baker H., and V. B. Price. Anasazi Architecture and American Design. 1st ed. ed. Albuquerque: University of New Mexico Press, Wed. . 5 Aug. 2009",
17
+ "Caption"=>"Modern Language Association, 6th Edition",
18
+ "ShortName"=>"MLA"},
19
+ {
20
+ "Label"=>"Vancouver",
21
+ "Text"=>"Morrow BH, Price VB. Anasazi architecture and American design. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed.",
22
+ "ShortName"=>"Vancouver"}
23
+ ]}, "Timing"=>{"ElapsedQueryTime"=>16, "TotalQueryTime"=>14, "TotalExecutionTime"=>1938}}}
24
+
25
+ citations = Summon::Citation.parse_results(results)
26
+ citations[0].name.should == "American Medical Association, 10th Edition"
27
+ citations[0].text.should == "Morrow BH, Price VB. <span class=\"italic\">Anasazi architecture and American design</span>. 1st ed. ed. Albuquerque: University of New Mexico Press; Wed. www.magnolia.com."
28
+ citations[0].short_name.should == "AMA"
29
+
30
+ citations[2].name.should == nil
31
+ citations[2].label.should == "MLA"
32
+
33
+ end
34
+ end
@@ -0,0 +1,12 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Date do
4
+ it "should do stuff w/ date" do
5
+ date = Summon::Date.new({"month"=>"01", "text"=>"c2000.", "day"=>"02", "year"=>"2000"})
6
+
7
+ date.day.should == 2
8
+ date.month.should == 1
9
+ date.year.should == 2000
10
+ date.text.should == "c2000."
11
+ end
12
+ end
@@ -0,0 +1,235 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe Summon::Document do
4
+ it "should map" do
5
+ doc = Summon::Document.new(JSON.parse(EXAMPLE_DOCUMENT_JSON))
6
+ doc.remove_src
7
+ doc.publication_date.remove_src
8
+ doc.authors.each {|a| a.remove_src }
9
+ doc.to_yaml.should == EXPECTED_DOCUMENT_YAML
10
+ end
11
+
12
+ EXAMPLE_DOCUMENT_JSON = <<-JSON
13
+ {
14
+ "Publisher_xml": [
15
+ {
16
+ "name": "Spirulina Records"
17
+ },
18
+ {
19
+ "name": "Swingsistersound"
20
+ }
21
+ ],
22
+ "Abstract": ["This is the most awesome document ever"],
23
+ "DOI": [ "10.1109\/CBMS.2008.1"],
24
+ "ISSN": ["1063-7125", "0000-1111"],
25
+ "Issue": ["7"],
26
+ "PublicationTitle": ["Batman Books"],
27
+ "StartPage": ["pp23"],
28
+ "SubjectTerms": [
29
+ "Women's music",
30
+ "Popular music",
31
+ "Rock music"
32
+ ],
33
+ "EndPage": ["i"],
34
+ "TableOfContents": [
35
+ "The very thing -- Dark night -- 2 truths & uh lie -- Reckless -- Spin -- Free -- Real of you -- Fire -- Purple hair -- Ghost -- Nuthin' -- Faith."
36
+ ],
37
+ "XQueryRevision": [
38
+ "Rev: 6229"
39
+ ],
40
+ "inHoldings": false,
41
+ "DBID": [
42
+ "GXQ"
43
+ ],
44
+ "Notes": [
45
+ "Compact disc."
46
+ ],
47
+ "PublicationDateYear": [
48
+ "2000"
49
+ ],
50
+ "hasFullText": false,
51
+ "timestamp": [
52
+ "Thu Jul 09 19:44:12 EDT 2009"
53
+ ],
54
+ "Author": [
55
+ "Hunter, Lisa"
56
+ ],
57
+ "PublicationDate": [
58
+ "c2000."
59
+ ],
60
+ "Subtitle": [
61
+ "the life and death of Joey Stefano"
62
+ ],
63
+ "Title": [
64
+ "Lisa Hunter -- alive"
65
+ ],
66
+ "ID": [
67
+ "gvsu_catalog_b16644323"
68
+ ],
69
+ "LCCallNum": [
70
+ "M1630.18 .H95 2000"
71
+ ],
72
+ "Language": [
73
+ "English"
74
+ ],
75
+ "PublicationDateCentury": [
76
+ "2000"
77
+ ],
78
+ "PublicationDateDecade": [
79
+ "2000"
80
+ ],
81
+ "openUrl": "ctx_ver=Z39.88-2004&rfr_id=info:sid\/summon.serialssolutions.com&rft_val_fmt=info:ofi\/fmt:kev:mtx:dc&rft.title=Lisa+Hunter+--+alive&rft.creator=Hunter%2C+Lisa&rft.date=c200-0.&rft.pub=Spirulina+Records&rft.externalDBID=n%2Fa&rft.externalDocID=b16644323",
82
+ "Author_xml": [
83
+ {
84
+ "fullname": "Hunter, Lisa",
85
+ "surname": "Hunter",
86
+ "givenname": "Lisa"
87
+ }
88
+ ],
89
+ "Library": [
90
+ "Women's Center Library"
91
+ ],
92
+ "PublicationDate_xml": [
93
+ {
94
+ "month": "01",
95
+ "text": "c2000.",
96
+ "day": "02",
97
+ "year": "2000"
98
+ }
99
+ ],
100
+ "PublicationPlace": [
101
+ "S.l.",
102
+ "Ann Arbor, Mich"
103
+ ],
104
+ "TemporalSubjectTerms": [
105
+ "1991-2000"
106
+ ],
107
+ "PublicationPlace_xml": [
108
+ {
109
+ "name": "S.l."
110
+ },
111
+ {
112
+ "name": "Ann Arbor, Mich"
113
+ }
114
+ ],
115
+ "DocumentTitleAlternate": [
116
+ "Alive"
117
+ ],
118
+ "score": [
119
+ "1.0"
120
+ ],
121
+ "Snippet": [
122
+ "This is the snippet"
123
+ ],
124
+ "availabilityToken": "b16644323",
125
+ "ContentType": [
126
+ "Audio Recording"
127
+ ],
128
+ "Publisher": [
129
+ "Spirulina Records",
130
+ "Swingsistersound"
131
+ ],
132
+ "PageCount": [
133
+ "xxviii, 140 p."
134
+ ],
135
+ "Genre": [
136
+ "Biography",
137
+ "Congress"
138
+ ],
139
+ "ISBN": [
140
+ "0849343763 (v. 1)",
141
+ "0849343771 (v. 2)"
142
+ ],
143
+ "PublicationSeriesTitle": [
144
+ "A Bantam book"
145
+ ],
146
+ "DissertationAdvisor": [
147
+ "Claudio Friedmann"
148
+ ],
149
+ "DissertationCategory": [
150
+ "Education"
151
+ ],
152
+ "DissertationDegree": [
153
+ "M.S.J."
154
+ ],
155
+ "DissertationDegreeDate": [
156
+ "2001"
157
+ ],
158
+ "DissertationDegreeDateCentury": [
159
+ "2000"
160
+ ],
161
+ "DissertationDegreeDateDecade":[
162
+ "2000"
163
+ ],
164
+ "DissertationDegreeDateYear": [
165
+ "2001"
166
+ ],
167
+ "DissertationSchool": [
168
+ "West Virginia University"
169
+ ]
170
+ }
171
+ JSON
172
+
173
+ EXPECTED_DOCUMENT_YAML = <<-YAML
174
+ --- !ruby/object:Summon::Document
175
+ abstract: This is the most awesome document ever
176
+ authors:
177
+ - Hunter, Lisa
178
+ availability_token: b16644323
179
+ call_number: M1630.18 .H95 2000
180
+ content_type: Audio Recording
181
+ dissertation_advisor: Claudio Friedmann
182
+ dissertation_category: Education
183
+ dissertation_degree: M.S.J.
184
+ dissertation_degree_date: "2001"
185
+ dissertation_degree_date_century: "2000"
186
+ dissertation_degree_date_decade: "2000"
187
+ dissertation_degree_date_year: "2001"
188
+ dissertation_school: West Virginia University
189
+ doi: 10.1109\/CBMS.2008.1
190
+ end_page: i
191
+ fulltext: false
192
+ genres:
193
+ - Biography
194
+ - Congress
195
+ id: gvsu_catalog_b16644323
196
+ isbns:
197
+ - 0849343763 (v. 1)
198
+ - 0849343771 (v. 2)
199
+ issns:
200
+ - 1063-7125
201
+ - 0000-1111
202
+ issue: "7"
203
+ languages:
204
+ - English
205
+ library: Women's Center Library
206
+ open_url: ctx_ver=Z39.88-2004&rfr_id=info:sid/summon.serialssolutions.com&rft_val_fmt=info:ofi/fmt:kev:mtx:dc&rft.title=Lisa+Hunter+--+alive&rft.creator=Hunter%2C+Lisa&rft.date=c200-0.&rft.pub=Spirulina+Records&rft.externalDBID=n%2Fa&rft.externalDocID=b16644323
207
+ page_count: xxviii, 140 p.
208
+ patent_number:
209
+ publication_date: !ruby/object:Summon::Date
210
+ day: "02"
211
+ month: "01"
212
+ src:
213
+ text: c2000.
214
+ year: "2000"
215
+ publication_series_title: A Bantam book
216
+ publication_title: Batman Books
217
+ publishers:
218
+ - Spirulina Records
219
+ - Swingsistersound
220
+ snippet: This is the snippet
221
+ src:
222
+ start_page: pp23
223
+ subject_terms:
224
+ - Women's music
225
+ - Popular music
226
+ - Rock music
227
+ subtitle: the life and death of Joey Stefano
228
+ thumbnail_large:
229
+ thumbnail_medium:
230
+ thumbnail_small:
231
+ title: Lisa Hunter -- alive
232
+ url:
233
+ volume:
234
+ YAML
235
+ end