borrow_direct 0.9.1 → 0.10.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.
- checksums.yaml +8 -8
- data/README.md +13 -8
- data/bd_metrics/finditem_measure.rb +88 -21
- data/bd_metrics/isbn-bd-test-200.txt +200 -0
- data/lib/borrow_direct/defaults.rb +1 -1
- data/lib/borrow_direct/find_item.rb +11 -3
- data/lib/borrow_direct/generate_query.rb +75 -20
- data/lib/borrow_direct/request.rb +2 -2
- data/lib/borrow_direct/version.rb +1 -1
- data/test/find_item_test.rb +4 -0
- data/test/generate_query_test.rb +42 -39
- data/test/request_test.rb +29 -0
- data/test/support/assertions.rb +1 -1
- data/test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml +49 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
N2U0ZTMyZDhmZDA4ZTE3YWNjNDk0MTYwMDEzNTM4MTQ5MDY2NzQwOQ==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
YzQ0MDdjMGIyMjYzNmU1NmZkY2Q5MzRkNTcxNWRhNjkyZTE3ZDQ3ZA==
|
7
7
|
SHA512:
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
MThiMzE1MWJmNmQ3YzZiNjczYTY5MjU0OGIxYmRlOWQ5MTc5ZmQ2NzM5OTY4
|
10
|
+
MjRmMWQ5M2RjYzEzNGFjZGVlYjkwOWU2N2M3ZWFjMTM5OTFkOTk1YjEzZGM2
|
11
|
+
ZjQ5M2MwMTM4YzFlZGNjOTdmMzBlZGZjM2IwOGNjNDg5NzJhMjA=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
MmZhNzUyNTk5ODhiYWI5ZjdiNjVhZThhZTdlMTgyZDM3MzdhODhlOThkZjE4
|
14
|
+
ZWNmNzg4NzkwYzFmNmQ1OTA4MDk5MTY5N2IyODAwNWJhYjg4ZDY5NjY1YzVl
|
15
|
+
NGQ1YjllODU3M2Q0M2E0MjczN2VlMzUxZTVhYmU2ZjJhODcxYTg=
|
data/README.md
CHANGED
@@ -110,17 +110,22 @@ BorrowDirect::GenerateQuery.new.query_url_with(
|
|
110
110
|
:isbn => "1234435445")
|
111
111
|
~~~
|
112
112
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
method is available to automatically use ISBN if available, otherwise author/title.
|
113
|
+
Or specify your own query passed as a string, possibly a complex one
|
114
|
+
using BD's undocumented syntax that you figure out. Use `
|
115
|
+
BorrowDirect::GenerateQuery.escape` to escape values, but don't CGI escape the input.
|
117
116
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
117
|
+
~~~ruby
|
118
|
+
query = %Q{isbn="#{BorrowDirect::GenerateQuery.escape('1212')}" and (ti="#{BorrowDirect::GenerateQuery.escape('foo')}" or ti="#{BorrowDirect::GenerateQuery.escape('bar')}")}
|
119
|
+
BorrowDirect::GenerateQuery.new.query_url_with query
|
120
|
+
~~~
|
122
121
|
|
122
|
+
For the common case of doing an author-title keyword search, this gem has some suggested
|
123
|
+
normalization it applies to author and title, to maximize chances of succesful hits.
|
124
|
+
(limiting to 5 words in title, searching on main title only not subtitle, etc.)
|
123
125
|
|
126
|
+
~~~ruby
|
127
|
+
BorrowDirect::GenerateQuery.new.normalized_author_title_query(:title => some_title, :author => some_author)
|
128
|
+
~~~
|
124
129
|
|
125
130
|
### Errors
|
126
131
|
|
@@ -1,33 +1,106 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
+
# Quick and dirty script to do some testing.
|
4
|
+
|
3
5
|
# ruby -Ilib:test ./bd_metrics/finditem_measure.rb
|
4
6
|
|
5
7
|
require 'borrow_direct'
|
6
8
|
require 'borrow_direct/find_item'
|
9
|
+
require 'date'
|
10
|
+
|
11
|
+
key = "isbn"
|
12
|
+
sourcefile = ARGV[0] || File.expand_path("../isbn-bd-test-200.txt", __FILE__)
|
7
13
|
|
8
|
-
|
9
|
-
|
14
|
+
# How long to wait n BD before giving up.
|
15
|
+
timeout = 20
|
10
16
|
|
11
|
-
|
17
|
+
# Range of how long to wait between requests, in seconds. Actual
|
18
|
+
# delay each time randomly chosen from this range.
|
19
|
+
# wait one to 7 minutes.
|
20
|
+
delay = 60..420
|
12
21
|
|
13
|
-
|
22
|
+
puts "#{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}"
|
14
23
|
|
24
|
+
identifiers = File.readlines(sourcefile) #.shuffle
|
25
|
+
|
15
26
|
puts " #{identifiers.count} total input identifiers"
|
16
27
|
|
17
|
-
times
|
18
|
-
errors
|
19
|
-
|
28
|
+
times = []
|
29
|
+
errors = []
|
30
|
+
timeouts = []
|
31
|
+
|
32
|
+
finder = BorrowDirect::FindItem.new(ENV["BD_PATRON"], ENV["BD_LIBRARY_SYMBOL"])
|
33
|
+
finder.timeout = timeout
|
34
|
+
|
35
|
+
i = 0
|
36
|
+
|
37
|
+
stats = lambda do |times|
|
38
|
+
h = OpenStruct.new
|
39
|
+
|
40
|
+
h.min = times[0]
|
41
|
+
h.tenth = times[(times.count / 10) - 1]
|
42
|
+
h.median = times[(times.count / 2) - 1]
|
43
|
+
h.seventyfifth = times[(times.count - (times.count / 4)) - 1]
|
44
|
+
h.ninetieth = times[(times.count - (times.count / 10)) - 1]
|
45
|
+
h.ninetyninth = times[(times.count - (times.count / 100)) - 1]
|
46
|
+
h.max = times[times.count - 1]
|
47
|
+
|
48
|
+
# now without the ones that were timeouts
|
49
|
+
times1 = times.find_all {|t| t < timeout || (timeout - t) <= 0.01}
|
50
|
+
h1 = OpenStruct.new
|
51
|
+
h1.min = times[0]
|
52
|
+
h1.tenth = times[(times.count / 10) - 1]
|
53
|
+
h1.median = times[(times.count / 2) - 1]
|
54
|
+
h1.seventyfifth = times[(times.count - (times.count / 4)) - 1]
|
55
|
+
h1.ninetieth = times[(times.count - (times.count / 10)) - 1]
|
56
|
+
h1.ninetyninth = times[(times.count - (times.count / 100)) - 1]
|
57
|
+
h1.max = times[times.count - 1]
|
58
|
+
|
59
|
+
return [h, h1]
|
60
|
+
end
|
61
|
+
|
62
|
+
printresults = lambda do
|
63
|
+
times.sort!
|
64
|
+
|
65
|
+
(t, t1) = stats.call(times)
|
66
|
+
|
67
|
+
puts "\n"
|
68
|
+
puts "tested #{i} identifiers, with timeout #{timeout}s, delaying #{delay} seconds between FindItem api requests\n\n"
|
69
|
+
puts "timing distribution (inc. timeouts) min: #{t.min.round(1)}s; 10th %ile: #{t.tenth.round(1)}s; median: #{t.median.round(1)}s; 75th %ile: #{t.seventyfifth.round(1)}s; 90th %ile: #{t.ninetieth.round(1)}s; 99th %ile: #{t.ninetyninth.round(1)}s; max: #{t.max.round(1)}s\n\n"
|
70
|
+
puts "timing distribution (W/O timeouts) min: #{t1.min.round(1)}s; 10th %ile: #{t1.tenth.round(1)}s; median: #{t1.median.round(1)}s; 75th %ile: #{t1.seventyfifth.round(1)}s; 90th %ile: #{t1.ninetieth.round(1)}s; 99th %ile: #{t1.ninetyninth.round(1)}s; max: #{t1.max.round(1)}s\n\n"
|
71
|
+
puts " error count: #{errors.count}"
|
72
|
+
puts " timeout count: #{timeouts.count}"
|
73
|
+
puts "\n"
|
74
|
+
end
|
20
75
|
|
21
76
|
|
22
77
|
at_exit do
|
23
|
-
puts "\n\
|
78
|
+
puts "\n\n\n"
|
79
|
+
|
80
|
+
puts "Finished: #{ENV['BD_LIBRARY_SYMBOL']}: #{key}: #{sourcefile}: #{Time.now.localtime}"
|
81
|
+
|
82
|
+
|
83
|
+
puts "Finished at: #{Time.now.localtime}\n\n"
|
84
|
+
|
85
|
+
printresults.call
|
86
|
+
|
87
|
+
|
88
|
+
puts "\nAll errors: "
|
24
89
|
errors.each do |arr|
|
25
90
|
puts arr.inspect
|
26
91
|
end
|
92
|
+
|
93
|
+
puts "\nAll timeouts: "
|
94
|
+
timeouts.each do |arr|
|
95
|
+
puts arr.inspect
|
96
|
+
end
|
97
|
+
|
98
|
+
puts "\n\n\n"
|
99
|
+
|
27
100
|
end
|
28
101
|
|
29
|
-
i = 0
|
30
102
|
identifiers.each do |id|
|
103
|
+
print "."
|
31
104
|
id = id.chomp
|
32
105
|
i = i + 1
|
33
106
|
|
@@ -35,27 +108,21 @@ identifiers.each do |id|
|
|
35
108
|
|
36
109
|
begin
|
37
110
|
finder.find_item_request(key => id)
|
111
|
+
rescue BorrowDirect::HttpTimeoutError => e
|
112
|
+
timeouts << [key, id, e]
|
38
113
|
rescue BorrowDirect::Error => e
|
39
114
|
errors << [key, id, e]
|
40
115
|
end
|
41
116
|
elapsed = Time.now - start
|
42
117
|
|
43
118
|
times << elapsed
|
44
|
-
times.sort!
|
45
119
|
|
46
120
|
if i % 10 == 0
|
47
|
-
|
48
|
-
tenth = times[(times.count / 10) - 1]
|
49
|
-
median = times[(times.count / 2) - 1]
|
50
|
-
seventyfifth = times[(times.count - (times.count / 4)) - 1]
|
51
|
-
ninetieth = times[(times.count - (times.count / 10)) - 1]
|
52
|
-
ninetyninth = times[(times.count - (times.count / 100)) - 1]
|
53
|
-
|
54
|
-
max = times[times.count - 1]
|
55
|
-
|
56
|
-
puts "i==#{i}; min: #{min}; 10th %ile: #{tenth}; median: #{median}; 75th %ile: #{seventyfifth}; 90th %ile: #{ninetieth}; 99th %ile: #{ninetyninth}; max: #{max}"
|
57
|
-
puts " errors: #{errors.count}"
|
121
|
+
printresults.call
|
58
122
|
end
|
59
123
|
|
124
|
+
print "w"
|
125
|
+
sleep rand(delay)
|
126
|
+
|
60
127
|
end
|
61
128
|
|
@@ -0,0 +1,200 @@
|
|
1
|
+
9780585488363
|
2
|
+
0140206221
|
3
|
+
9780300070163
|
4
|
+
9781594202919
|
5
|
+
1904859208
|
6
|
+
9780309255998
|
7
|
+
9781555702212
|
8
|
+
9781118559215
|
9
|
+
9780898382600
|
10
|
+
9788764300086
|
11
|
+
0300070160
|
12
|
+
9781435666504
|
13
|
+
9780814725245
|
14
|
+
9781442225459
|
15
|
+
9780585147574
|
16
|
+
9781118836583
|
17
|
+
0520938038978
|
18
|
+
0938692942
|
19
|
+
3642034551
|
20
|
+
9780060243913
|
21
|
+
9781571686565
|
22
|
+
9781604653212
|
23
|
+
9780585048529
|
24
|
+
9780783219097
|
25
|
+
9780231520980
|
26
|
+
9781886449350
|
27
|
+
9780634090608
|
28
|
+
9780062503404
|
29
|
+
9781742980676
|
30
|
+
160486270X
|
31
|
+
9780309212861
|
32
|
+
9783540737087
|
33
|
+
0486122387
|
34
|
+
9780671869229
|
35
|
+
9783540644101
|
36
|
+
9781563084812
|
37
|
+
9780435270728
|
38
|
+
9780721601892
|
39
|
+
9780805015959
|
40
|
+
074533086X
|
41
|
+
9780415882293
|
42
|
+
9780314190826
|
43
|
+
9780781782029
|
44
|
+
9780292759367
|
45
|
+
9781593764944
|
46
|
+
9780470873908
|
47
|
+
187317683X
|
48
|
+
9780375856525
|
49
|
+
9781555841973
|
50
|
+
9781933115603
|
51
|
+
9780813506272
|
52
|
+
9781420081886
|
53
|
+
9780312331597
|
54
|
+
9780790739250
|
55
|
+
0853451753
|
56
|
+
9780470740958
|
57
|
+
9780840032980
|
58
|
+
9781491900987
|
59
|
+
9781605537665
|
60
|
+
9780066211312
|
61
|
+
9780585025117
|
62
|
+
9780375830730
|
63
|
+
9780316608459
|
64
|
+
0333914554
|
65
|
+
9780804717625
|
66
|
+
1904859062
|
67
|
+
9780817644222
|
68
|
+
9780060234836
|
69
|
+
9780333914557
|
70
|
+
9780470578827
|
71
|
+
9781401202019
|
72
|
+
9781619426344
|
73
|
+
0631165746
|
74
|
+
9780977696611
|
75
|
+
9786310335841
|
76
|
+
9780470584330
|
77
|
+
9781401213510
|
78
|
+
0142002429
|
79
|
+
9781118173091
|
80
|
+
9781905222759
|
81
|
+
9780517701010
|
82
|
+
9782207261163
|
83
|
+
9780226293516
|
84
|
+
0271028890
|
85
|
+
9781604860641
|
86
|
+
0520938038
|
87
|
+
9780670891573
|
88
|
+
0521891574
|
89
|
+
9780807084939
|
90
|
+
3531050850
|
91
|
+
1885923902
|
92
|
+
0470019352
|
93
|
+
2711823903
|
94
|
+
9780804776714
|
95
|
+
0195167643
|
96
|
+
2902524412
|
97
|
+
0300043910
|
98
|
+
0231051964
|
99
|
+
9789652351487
|
100
|
+
0299158101
|
101
|
+
0313221588
|
102
|
+
9780881326727
|
103
|
+
9781892127303
|
104
|
+
0444820914
|
105
|
+
0109578805
|
106
|
+
9042918713
|
107
|
+
9780415604017
|
108
|
+
9783834936011
|
109
|
+
9781420069129
|
110
|
+
0825672783
|
111
|
+
9789048139668
|
112
|
+
9788896319109
|
113
|
+
0582368863
|
114
|
+
9780203833001
|
115
|
+
0500018421
|
116
|
+
0761920846
|
117
|
+
9781455727551
|
118
|
+
9781118418420
|
119
|
+
9780132119856
|
120
|
+
1741144485
|
121
|
+
9781480328488
|
122
|
+
9781443842273
|
123
|
+
0882756702
|
124
|
+
2070112713
|
125
|
+
9789799101204
|
126
|
+
9783770544974
|
127
|
+
0205200095
|
128
|
+
1560003383
|
129
|
+
0205510701
|
130
|
+
9789251053003
|
131
|
+
9780415558938
|
132
|
+
014004227X
|
133
|
+
0766846822
|
134
|
+
9780415197342
|
135
|
+
0804737088
|
136
|
+
1878822624
|
137
|
+
9004104062
|
138
|
+
41062001
|
139
|
+
9788131775660
|
140
|
+
1604246014
|
141
|
+
9780801890048
|
142
|
+
0195208846
|
143
|
+
1884585388
|
144
|
+
1555835589
|
145
|
+
3540651160
|
146
|
+
041505818X
|
147
|
+
9287140553
|
148
|
+
0394538579
|
149
|
+
9780754628309
|
150
|
+
1566706688
|
151
|
+
3487072157
|
152
|
+
0226323145
|
153
|
+
9781559363303
|
154
|
+
0801438136
|
155
|
+
0787964344
|
156
|
+
9780807834343
|
157
|
+
0195300831
|
158
|
+
0323081665
|
159
|
+
9789814345477
|
160
|
+
3900627002
|
161
|
+
0393977757
|
162
|
+
0801831008
|
163
|
+
0844407291
|
164
|
+
0582418666
|
165
|
+
0863583016
|
166
|
+
9786074620191
|
167
|
+
0679750533
|
168
|
+
1569730431
|
169
|
+
1419551329
|
170
|
+
1405116994
|
171
|
+
080532402X
|
172
|
+
8879890913
|
173
|
+
9781616690106
|
174
|
+
8187586370
|
175
|
+
9789004255265
|
176
|
+
0205482252
|
177
|
+
0300049447
|
178
|
+
8772884746
|
179
|
+
0807824682
|
180
|
+
9992790768
|
181
|
+
9781598743036
|
182
|
+
0754655334
|
183
|
+
9783034312028
|
184
|
+
185437124X
|
185
|
+
9781439811283
|
186
|
+
9781412984652
|
187
|
+
9781579548896
|
188
|
+
0131975412
|
189
|
+
9781437720808
|
190
|
+
9781409469056
|
191
|
+
0816188734
|
192
|
+
9781629142708
|
193
|
+
027598169X
|
194
|
+
9780801452581
|
195
|
+
0596008678
|
196
|
+
0521855764
|
197
|
+
1934121320
|
198
|
+
0721679048
|
199
|
+
0824708709
|
200
|
+
9781606230220
|
@@ -14,7 +14,7 @@ module BorrowDirect
|
|
14
14
|
# BorrowDirect::Defaults.find_item_patron_barcode = "99999999999"
|
15
15
|
class Defaults
|
16
16
|
TEST_API_BASE = "https://bdtest.relais-host.com/"
|
17
|
-
PRODUCTION_API_BASE = "
|
17
|
+
PRODUCTION_API_BASE = "https://borrow-direct.relaisd2d.com/"
|
18
18
|
|
19
19
|
TEST_HTML_BASE = "https://bdtest.relaisd2d.com/service-proxy?command=query"
|
20
20
|
PRODUCTION_HTML_BASE = "https://borrow-direct.relaisd2d.com/service-proxy?command=query"
|
@@ -118,15 +118,23 @@ module BorrowDirect
|
|
118
118
|
end
|
119
119
|
|
120
120
|
# Items that are available locally, and thus not requestable via BD, can
|
121
|
-
# only be found by looking at the RequestMessage, bah
|
122
|
-
|
123
|
-
if h && h["RequestMessage"] == "This item is available locally"
|
121
|
+
# only be found by looking at the RequestMessage, bah
|
122
|
+
if locally_available?
|
124
123
|
return false
|
125
124
|
end
|
126
125
|
|
127
126
|
return response_hash["Item"]["Available"].to_s == "true"
|
128
127
|
end
|
129
128
|
|
129
|
+
# BD thinks the item is locally available at patron's home library,
|
130
|
+
# and it is not requestable for that reason.
|
131
|
+
# Items that are available locally, and thus not requestable via BD, can
|
132
|
+
# only be found by looking at the RequestMessage, bah
|
133
|
+
def locally_available?
|
134
|
+
h = response_hash["Item"]["RequestLink"]
|
135
|
+
return !! (h && h["RequestMessage"] == "This item is available locally")
|
136
|
+
end
|
137
|
+
|
130
138
|
# Returns the AuthorizationID returned by FindItem API call,
|
131
139
|
# or nil if none is available. Nil _can_ be returned, for
|
132
140
|
# instance when BD returns a NotFound error instead of a good
|
@@ -20,7 +20,7 @@ module BorrowDirect
|
|
20
20
|
self.url_base = (url_base || BorrowDirect::Defaults.html_base_url)
|
21
21
|
end
|
22
22
|
|
23
|
-
#
|
23
|
+
# build_query_with(:title => "one two", :author => "three four")
|
24
24
|
# valid keys are those supported by BD HTML interface:
|
25
25
|
# :title, :author, :isbn, :subject, :keyword, :isbn, :issn
|
26
26
|
#
|
@@ -28,7 +28,7 @@ module BorrowDirect
|
|
28
28
|
# fields are always 'and'd. We may enhance/expand later.
|
29
29
|
#
|
30
30
|
# Returns an un-escaped query, still needs to be put into a URL
|
31
|
-
def
|
31
|
+
def build_query_with(options)
|
32
32
|
clauses = []
|
33
33
|
|
34
34
|
options.each_pair do |field, value|
|
@@ -38,43 +38,98 @@ module BorrowDirect
|
|
38
38
|
|
39
39
|
raise ArgumentError.new("Don't recognize field code `#{field}`") unless code
|
40
40
|
|
41
|
-
clauses << %Q{#{code}="#{
|
41
|
+
clauses << %Q{#{code}="#{escape value}"}
|
42
42
|
end
|
43
43
|
|
44
44
|
return clauses.join(" and ")
|
45
45
|
end
|
46
46
|
|
47
|
-
# Pass in :title, :author, :isbn, etc -- if we have an isbn or issn,
|
48
|
-
# we'll use that alone, otherwise we'll use title and author
|
49
|
-
def best_known_item_query_with(options)
|
50
|
-
if options[:isbn]
|
51
|
-
return query_with(options.dup.delete_if {|k| k != :isbn})
|
52
|
-
elsif options[:issn]
|
53
|
-
return query_with(options.dup.delete_if {|k| k != :issn})
|
54
|
-
else
|
55
|
-
return query_with options
|
56
|
-
end
|
57
|
-
end
|
58
47
|
|
59
|
-
def query_url_with(options)
|
60
|
-
query = query_with(options)
|
61
48
|
|
49
|
+
def query_url_with(arg)
|
50
|
+
query = arg.kind_of?(Hash) ? build_query_with(arg) : arg.to_s
|
51
|
+
|
62
52
|
return add_query_param(self.url_base, "query", query).to_s
|
53
|
+
end
|
54
|
+
|
55
|
+
# Give it a :title and optionally an :author, we'll normalize
|
56
|
+
# them to our suggestion for a good BD author-title keyword
|
57
|
+
# search. Returns a hash suitable for passing to #query_url_with
|
58
|
+
#
|
59
|
+
# Additional option :max_title_words, default 5.
|
60
|
+
def normalized_author_title_params(options)
|
61
|
+
raise ArgumentError.new("Need a Hash argument, got #{options.inspect}") unless options.kind_of?(Hash)
|
62
|
+
|
63
|
+
# Symbolize keys please
|
64
|
+
#options = options.inject({}){ |h, (k, v)| hash.merge( (k.respond_to?(:to_sym) ? k.to_sym : k) => v ) }
|
65
|
+
|
66
|
+
title = options[:title].dup if options[:title]
|
67
|
+
author = options[:author].dup if options[:author]
|
68
|
+
|
69
|
+
# We don't do any normalization to author at present.
|
70
|
+
title = normalized_title(title, :max_title_words => options[:max_title_words])
|
71
|
+
|
72
|
+
results = {}
|
73
|
+
results[:title] = title if title && ! title.empty?
|
74
|
+
results[:author] = author if author && ! author.empty?
|
75
|
+
|
76
|
+
return results
|
77
|
+
end
|
63
78
|
|
79
|
+
# :title, :author and optionally :max_title_words.
|
80
|
+
#
|
81
|
+
# Returns a query with a suggested normalized author and title
|
82
|
+
# for best BD search. May return just BD base URL if no author/title
|
83
|
+
# given.
|
84
|
+
def normalized_author_title_query(options)
|
85
|
+
return self.query_url_with self.normalized_author_title_params(options)
|
64
86
|
end
|
65
87
|
|
66
|
-
def
|
67
|
-
|
88
|
+
def normalized_title(title, args = {})
|
89
|
+
return "" if title.nil? || title.empty?
|
68
90
|
|
69
|
-
|
91
|
+
max_title_words = args[:max_title_words] || 5
|
92
|
+
|
93
|
+
# Remove all things in parens at the END of the title, they tend
|
94
|
+
# to be weird addendums.
|
95
|
+
title.gsub!(/\([^)]*\)\s*$/, '')
|
96
|
+
|
97
|
+
# Strip the subtitle or other weird titles, just keep the text
|
98
|
+
# before the first colon OR semi-colon
|
99
|
+
title.sub!(/[\:\;](.*)$/, '')
|
100
|
+
|
101
|
+
# remove any remaining non-alphanumeric, excepting apostrophe, replacing
|
102
|
+
# with space. The punctuation doesn't help our queries.
|
103
|
+
title.gsub!(/[^[:alnum:][:space:]\']/, ' ')
|
104
|
+
|
105
|
+
# compress any remaining whitespace
|
106
|
+
title.strip!
|
107
|
+
title.gsub!(/\s+/, ' ')
|
108
|
+
|
109
|
+
# downcase
|
110
|
+
title.downcase!
|
111
|
+
|
112
|
+
# Limit to only first N words
|
113
|
+
if max_title_words && title.index(/((.+?[ ,.:\;]+){#{max_title_words}})/)
|
114
|
+
title = title.slice(0, $1.length).gsub(/[ ,.:\;]+$/, '')
|
115
|
+
end
|
116
|
+
|
117
|
+
return title
|
70
118
|
end
|
71
119
|
|
120
|
+
|
121
|
+
|
122
|
+
# Escape a query value.
|
72
123
|
# We don't really know how to escape, for now
|
73
124
|
# we just remove double quotes and parens, and replace with spaces.
|
74
125
|
# those seem to cause problems, and that seems to work.
|
75
|
-
def
|
126
|
+
def self.escape(str)
|
76
127
|
str.gsub(/[")()]/, ' ')
|
77
128
|
end
|
129
|
+
# Instance method version for convenience.
|
130
|
+
def escape(str)
|
131
|
+
self.class.escape(str)
|
132
|
+
end
|
78
133
|
|
79
134
|
def add_query_param(uri, key, value)
|
80
135
|
uri = URI.parse(uri) unless uri.kind_of? URI
|
@@ -100,9 +100,9 @@ module BorrowDirect
|
|
100
100
|
|
101
101
|
|
102
102
|
return response_hash
|
103
|
-
rescue HTTPClient::ReceiveTimeoutError => e
|
103
|
+
rescue HTTPClient::ReceiveTimeoutError, HTTPClient::ConnectTimeoutError, HTTPClient::SendTimeoutError => e
|
104
104
|
elapsed = Time.now - start_time
|
105
|
-
raise BorrowDirect::HttpTimeoutError.new("Timeout after #{elapsed}s connecting to BorrowDirect server at #{@api_base}")
|
105
|
+
raise BorrowDirect::HttpTimeoutError.new("Timeout after #{elapsed.round(1)}s connecting to BorrowDirect server at #{@api_base}")
|
106
106
|
end
|
107
107
|
|
108
108
|
def http_client
|
data/test/find_item_test.rb
CHANGED
@@ -121,6 +121,10 @@ describe "FindItem", :vcr do
|
|
121
121
|
assert_equal false, @find_item.find(:isbn => @locally_avail_item_isbn).requestable?
|
122
122
|
end
|
123
123
|
|
124
|
+
it "knows locally_available?" do
|
125
|
+
assert_equal true, @find_item.find(:isbn => @locally_avail_item_isbn).locally_available?
|
126
|
+
end
|
127
|
+
|
124
128
|
it "not requestable for item that does not exist in BD" do
|
125
129
|
assert_equal false, @find_item.find(:isbn => "NO_SUCH_THING").requestable?
|
126
130
|
end
|
data/test/generate_query_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# Encoding: UTF-8
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
require 'uri'
|
3
5
|
require 'cgi'
|
@@ -61,66 +63,67 @@ describe "GenerateQuery" do
|
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
|
-
describe "
|
65
|
-
it "
|
66
|
+
describe "with string arg" do
|
67
|
+
it "generates supplied query" do
|
66
68
|
generate_query = BorrowDirect::GenerateQuery.new(@test_base)
|
67
69
|
|
68
|
-
|
70
|
+
query = %Q{isbn="#{BorrowDirect::GenerateQuery.escape('1212')}" and (ti="#{BorrowDirect::GenerateQuery.escape('foo')}" or ti="#{BorrowDirect::GenerateQuery.escape('bar')}")}
|
69
71
|
|
70
|
-
|
72
|
+
url = generate_query.query_url_with(query)
|
71
73
|
|
72
74
|
parsed_url = URI.parse(url)
|
73
75
|
url_query = CGI.parse( parsed_url.query )
|
74
|
-
|
75
76
|
assert_present url_query
|
76
|
-
|
77
77
|
assert_length 1, url_query["query"]
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
parts = query_text.split(" and ")
|
82
|
-
|
83
|
-
assert_length 1, parts
|
84
|
-
|
85
|
-
assert_include parts, 'isbn="OUR_ISBN"'
|
79
|
+
assert_equal query, url_query["query"].first
|
86
80
|
end
|
81
|
+
end
|
87
82
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
parsed_url = URI.parse(url)
|
96
|
-
url_query = CGI.parse( parsed_url.query )
|
83
|
+
describe "#normalized_author_title_params" do
|
84
|
+
before do
|
85
|
+
@generator = BorrowDirect::GenerateQuery.new(@test_base)
|
86
|
+
end
|
87
|
+
it "raises without good arguments" do
|
88
|
+
assert_raises(ArgumentError) {@generator.normalized_author_title_params(nil)}
|
89
|
+
end
|
97
90
|
|
98
|
-
|
91
|
+
it "passes through simple author and title" do
|
92
|
+
author ="John Smith"
|
93
|
+
title = "Some Book"
|
94
|
+
assert_equal( {:title => "some book", :author => author}, @generator.normalized_author_title_params(:author => author, :title => title))
|
95
|
+
end
|
99
96
|
|
100
|
-
|
97
|
+
it "works with just a title" do
|
98
|
+
title = "Some Book"
|
99
|
+
expected = {:title => "some book"}
|
100
|
+
assert_equal expected, @generator.normalized_author_title_params(:title => title)
|
101
|
+
assert_equal expected, @generator.normalized_author_title_params(:title => title, :author => nil)
|
102
|
+
assert_equal expected, @generator.normalized_author_title_params(:title => title, :author => "")
|
103
|
+
end
|
101
104
|
|
102
|
-
|
105
|
+
it "title remove trailing parens" do
|
106
|
+
title = "A Book (really bad one)"
|
103
107
|
|
104
|
-
|
108
|
+
assert_equal( {:title => "a book"}, @generator.normalized_author_title_params(:title => title))
|
109
|
+
end
|
105
110
|
|
106
|
-
|
111
|
+
it "title strip subtitles" do
|
112
|
+
assert_equal({:title => "a book"}, @generator.normalized_author_title_params(:title => "A Book: Subtitle"))
|
113
|
+
assert_equal({:title => "a book"}, @generator.normalized_author_title_params(:title => "A Book; and more"))
|
114
|
+
end
|
107
115
|
|
108
|
-
|
109
|
-
|
116
|
+
it "limit to first 5 words" do
|
117
|
+
assert_equal({:title => "one two's three four five"}, @generator.normalized_author_title_params(:title => "One Two's Three Four Five Six Seven"))
|
110
118
|
end
|
111
119
|
|
112
|
-
it "
|
113
|
-
|
114
|
-
|
115
|
-
:title => 'the new international economic order',
|
116
|
-
:author => nil
|
117
|
-
)
|
120
|
+
it "okay with unicode, strip punct" do
|
121
|
+
assert_equal({:title => "el revolución"}, @generator.normalized_author_title_params(:title => "El Revolución!: Cuban poster art"))
|
122
|
+
end
|
118
123
|
|
124
|
+
it "full normalized_author_title_query" do
|
125
|
+
url = @generator.normalized_author_title_query(:title => "A Book: Subtitle", :author => "Smith, John" )
|
119
126
|
query = assert_bd_query_url(url)
|
120
|
-
|
121
|
-
parts = query.split(" and ")
|
122
|
-
|
123
|
-
assert_include parts, 'ti="the new international economic order"'
|
124
127
|
end
|
125
128
|
|
126
129
|
end
|
data/test/request_test.rb
CHANGED
@@ -76,6 +76,35 @@ describe "Request", :vcr => {:tag => :bd_request} do
|
|
76
76
|
assert_equal 5, http_client.connect_timeout
|
77
77
|
end
|
78
78
|
|
79
|
+
it "raises exception on timeout, live" do
|
80
|
+
request = {
|
81
|
+
"PartnershipId" => "BD",
|
82
|
+
"Credentials" => {
|
83
|
+
"LibrarySymbol" => VCRFilter[:bd_library_symbol],
|
84
|
+
"Barcode" => VCRFilter[:bd_patron]
|
85
|
+
},
|
86
|
+
"ExactSearch" => [
|
87
|
+
{
|
88
|
+
"Type" => "ISBN",
|
89
|
+
"Value" => @successful_item_isbn
|
90
|
+
}
|
91
|
+
]
|
92
|
+
}
|
93
|
+
bd = BorrowDirect::Request.new("/dws/item/available")
|
94
|
+
# tiny timeout, it'll def timeout, and on connect no less
|
95
|
+
bd.timeout = 0.00001
|
96
|
+
assert_raises(BorrowDirect::HttpTimeoutError) do
|
97
|
+
response = bd.request( request )
|
98
|
+
end
|
99
|
+
|
100
|
+
# little bit longer to get maybe a receive timeout instead
|
101
|
+
bd = BorrowDirect::Request.new("/dws/item/available")
|
102
|
+
bd.timeout = 0.10
|
103
|
+
assert_raises(BorrowDirect::HttpTimeoutError) do
|
104
|
+
response = bd.request( request )
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
79
108
|
describe "with expected errors" do
|
80
109
|
it "still returns result" do
|
81
110
|
request = {
|
data/test/support/assertions.rb
CHANGED
@@ -17,7 +17,7 @@ end
|
|
17
17
|
def assert_include(collection, item, msg = nil)
|
18
18
|
assert_respond_to(collection, :include?, "The collection must respond to :include?.")
|
19
19
|
|
20
|
-
msg ||= "#{collection.inspect
|
20
|
+
msg ||= "#{collection.inspect} expected to include #{item.inspect}"
|
21
21
|
|
22
22
|
assert collection.include?(item), msg
|
23
23
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
---
|
2
|
+
http_interactions:
|
3
|
+
- request:
|
4
|
+
method: post
|
5
|
+
uri: https://bdtest.relais-host.com/dws/item/available
|
6
|
+
body:
|
7
|
+
encoding: UTF-8
|
8
|
+
string: ! '{"PartnershipId":"BD","Credentials":{"LibrarySymbol":"DUMMY_BD_LIBRARY_SYMBOL","Barcode":"DUMMY_BD_PATRON"},"ExactSearch":[{"Type":"ISBN","Value":"0745649890"}]}'
|
9
|
+
headers:
|
10
|
+
User-Agent:
|
11
|
+
- ruby borrow_direct gem 0.9.1 (HTTPClient 2.5.3.3) https://github.com/jrochkind/borrow_direct
|
12
|
+
Accept:
|
13
|
+
- ! '*/*'
|
14
|
+
Date:
|
15
|
+
- Wed, 17 Dec 2014 16:29:43 GMT
|
16
|
+
Content-Type:
|
17
|
+
- application/json
|
18
|
+
Accept-Language:
|
19
|
+
- en
|
20
|
+
response:
|
21
|
+
status:
|
22
|
+
code: 200
|
23
|
+
message: OK
|
24
|
+
headers:
|
25
|
+
Server:
|
26
|
+
- nginx/1.4.1
|
27
|
+
Date:
|
28
|
+
- Wed, 17 Dec 2014 16:30:04 GMT
|
29
|
+
Content-Type:
|
30
|
+
- application/json;charset=UTF-8
|
31
|
+
Transfer-Encoding:
|
32
|
+
- chunked
|
33
|
+
Connection:
|
34
|
+
- keep-alive
|
35
|
+
body:
|
36
|
+
encoding: US-ASCII
|
37
|
+
string: ! '{"Item":{"Available":true,"SearchTerm":"isbn=0745649890","AuthorizationId":"OzHkQ_bUILh1Py-UdMtMRcbOnK0","RequestLink":{"RequestMessage":"This
|
38
|
+
item is available locally"},"PickupLocations":{"PickupLocation":["*Milton
|
39
|
+
S. Eisenhower Library","APL - EP STUDENTS - Ed Center","APL STAFF - Room 5-17","Arthur
|
40
|
+
Friedheim Library","Columbia Center","Harbor East","Harrison Medical Library-Bayview","Institute
|
41
|
+
of the History of Medicine","JH Medical Campus Office (faculty and staff)","JHMI
|
42
|
+
Pickup - Armstrong 306","JHMI Pickup - BSPH E4643","JHMI Pickup - Hampton
|
43
|
+
House 9th floor","JHMI Pickup - PCTB 115-116","JHMI Pickup - School of Nursing
|
44
|
+
313","JHU Homewood Office (faculty and grad students)","Montgomery Library
|
45
|
+
Resource Center","SAIS Library","Washington Library Resource Center","Welch
|
46
|
+
Medical Library","Wilmer Friedenwald Library"]}}}'
|
47
|
+
http_version:
|
48
|
+
recorded_at: Wed, 17 Dec 2014 16:30:04 GMT
|
49
|
+
recorded_with: VCR 2.9.3
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: borrow_direct
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Rochkind
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httpclient
|
@@ -113,6 +113,7 @@ files:
|
|
113
113
|
- README.md
|
114
114
|
- Rakefile
|
115
115
|
- bd_metrics/finditem_measure.rb
|
116
|
+
- bd_metrics/isbn-bd-test-200.txt
|
116
117
|
- bd_metrics/isbn.txt
|
117
118
|
- bd_metrics/lccn.txt
|
118
119
|
- bd_metrics/oclc.txt
|
@@ -155,6 +156,7 @@ files:
|
|
155
156
|
- test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml
|
156
157
|
- test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml
|
157
158
|
- test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml
|
159
|
+
- test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml
|
158
160
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml
|
159
161
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml
|
160
162
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml
|
@@ -204,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
204
206
|
version: '0'
|
205
207
|
requirements: []
|
206
208
|
rubyforge_project:
|
207
|
-
rubygems_version: 2.4.
|
209
|
+
rubygems_version: 2.4.4
|
208
210
|
signing_key:
|
209
211
|
specification_version: 4
|
210
212
|
summary: Ruby tools for interacting with the Borrow Direct consortial services
|
@@ -236,6 +238,7 @@ test_files:
|
|
236
238
|
- test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml
|
237
239
|
- test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml
|
238
240
|
- test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml
|
241
|
+
- test/vcr_cassettes/FindItem/find_with_Response/knows_locally_available_.yml
|
239
242
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml
|
240
243
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml
|
241
244
|
- test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml
|