borrow_direct 0.9.1 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|