borrow_direct 0.9.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 (76) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +14 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +183 -0
  6. data/Rakefile +11 -0
  7. data/bd_metrics/finditem_measure.rb +61 -0
  8. data/bd_metrics/isbn.txt +1088 -0
  9. data/bd_metrics/lccn.txt +1668 -0
  10. data/bd_metrics/oclc.txt +167 -0
  11. data/borrow_direct.gemspec +30 -0
  12. data/lib/borrow_direct/authentication.rb +55 -0
  13. data/lib/borrow_direct/defaults.rb +38 -0
  14. data/lib/borrow_direct/error.rb +17 -0
  15. data/lib/borrow_direct/find_item.rb +149 -0
  16. data/lib/borrow_direct/generate_query.rb +78 -0
  17. data/lib/borrow_direct/request.rb +185 -0
  18. data/lib/borrow_direct/request_item.rb +119 -0
  19. data/lib/borrow_direct/request_query.rb +124 -0
  20. data/lib/borrow_direct/util.rb +18 -0
  21. data/lib/borrow_direct/version.rb +3 -0
  22. data/lib/borrow_direct.rb +19 -0
  23. data/test/authentication_test.rb +79 -0
  24. data/test/find_item_test.rb +159 -0
  25. data/test/generate_query_test.rb +91 -0
  26. data/test/request_item_test.rb +109 -0
  27. data/test/request_query_test.rb +113 -0
  28. data/test/request_test.rb +141 -0
  29. data/test/support/assertions.rb +23 -0
  30. data/test/support/vcr_filter.rb +45 -0
  31. data/test/test_helper.rb +39 -0
  32. data/test/util_test.rb +32 -0
  33. data/test/vcr_cassettes/Authentication/Makes_a_request_succesfully.yml +52 -0
  34. data/test/vcr_cassettes/Authentication/Raises_for_bad_library_symbol.yml +52 -0
  35. data/test/vcr_cassettes/Authentication/Raises_for_bad_patron_barcode.yml +53 -0
  36. data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_library_symbol.yml +52 -0
  37. data/test/vcr_cassettes/Authentication/get_auth_id/raises_for_a_bad_patron_barcode.yml +53 -0
  38. data/test/vcr_cassettes/Authentication/get_auth_id/returns_an_auth_id_for_a_good_request.yml +52 -0
  39. data/test/vcr_cassettes/Authentication/raw_request_to_verify_HTTP_api/.yml +52 -0
  40. data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_locally_available_item.yml +49 -0
  41. data/test/vcr_cassettes/FindItem/_find_item_request/finds_a_requestable_item.yml +49 -0
  42. data/test/vcr_cassettes/FindItem/_find_item_request/finds_an_item_that_does_not_exist_in_BD.yml +50 -0
  43. data/test/vcr_cassettes/FindItem/_find_item_request/with_expected_error_PUBFI002/returns_result.yml +40 -0
  44. data/test/vcr_cassettes/FindItem/_find_item_request/works_with_multiple_values.yml +49 -0
  45. data/test/vcr_cassettes/FindItem/find_with_Response/has_an_auth_id.yml +49 -0
  46. data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_auth_id_when_BD_doesn_t_want_to_give_us_one.yml +40 -0
  47. data/test/vcr_cassettes/FindItem/find_with_Response/has_nil_pickup_locations_when_BD_doesn_t_want_to_give_us_them.yml +40 -0
  48. data/test/vcr_cassettes/FindItem/find_with_Response/has_pickup_locations.yml +49 -0
  49. data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_BD_returns_PUBFI002.yml +40 -0
  50. data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_does_not_exist_in_BD.yml +50 -0
  51. data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_item_that_no_libraries_will_lend.yml +50 -0
  52. data/test/vcr_cassettes/FindItem/find_with_Response/not_requestable_for_locally_available_item.yml +49 -0
  53. data/test/vcr_cassettes/FindItem/find_with_Response/requestable_for_requestable_item.yml +49 -0
  54. data/test/vcr_cassettes/FindItem/find_with_Response/requestable_with_multiple_items_if_at_least_one_is_requestable.yml +49 -0
  55. data/test/vcr_cassettes/Request/authentication_id/automatically_fetches_one_when_needed.yml +52 -0
  56. data/test/vcr_cassettes/Request/authentication_id/can_refetch_when_instructed.yml +52 -0
  57. data/test/vcr_cassettes/Request/can_make_a_succesful_request.yml +49 -0
  58. data/test/vcr_cassettes/Request/gets_BD_error_info.yml +41 -0
  59. data/test/vcr_cassettes/Request/raises_on_bad_path.yml +53 -0
  60. data/test/vcr_cassettes/Request/raises_on_bad_request_hash.yml +63 -0
  61. data/test/vcr_cassettes/Request/with_expected_errors/still_returns_result.yml +41 -0
  62. data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_locally_available_item.yml +90 -0
  63. data/test/vcr_cassettes/RequestItem/make_request/make_request_for_a_requestable_item.yml +89 -0
  64. data/test/vcr_cassettes/RequestItem/make_request/make_request_for_an_unrequestable_item.yml +91 -0
  65. data/test/vcr_cassettes/RequestItem/make_request/raises_for_unrequestable.yml +91 -0
  66. data/test/vcr_cassettes/RequestItem/make_request/returns_number_for_succesful_request.yml +89 -0
  67. data/test/vcr_cassettes/RequestItem/make_request/says_no_for_item_that_BD_returns_PUBRI004.yml +89 -0
  68. data/test/vcr_cassettes/RequestItem/raw_requests_an_unrequestable_item.yml +91 -0
  69. data/test/vcr_cassettes/RequestItem/uses_manually_set_auth_id.yml +89 -0
  70. data/test/vcr_cassettes/RequestItem/with_pickup_location_and_requestable_item/still_works.yml +90 -0
  71. data/test/vcr_cassettes/RequestQuery/raw_request_query_request/returns_results.yml +381 -0
  72. data/test/vcr_cassettes/RequestQuery/raw_request_to_verify_the_BD_HTTP_API.yml +381 -0
  73. data/test/vcr_cassettes/RequestQuery/requests/fetches_default_records.yml +384 -0
  74. data/test/vcr_cassettes/RequestQuery/requests/fetches_full_records.yml +481 -0
  75. data/test/vcr_cassettes/top_level_describe/an_inner_describe/.yml +76 -0
  76. metadata +262 -0
@@ -0,0 +1,167 @@
1
+ 10065659
2
+ 10586552
3
+ 1081684
4
+ 10904204
5
+ 1124917
6
+ 11803073
7
+ 11910543
8
+ 123077828
9
+ 123209896
10
+ 12748813
11
+ 1331857
12
+ 1379728
13
+ 1424222
14
+ 1434991
15
+ 1478791
16
+ 1480801
17
+ 14814735
18
+ 1536398
19
+ 1564931
20
+ 1565627
21
+ 1567377
22
+ 1586310
23
+ 1638942
24
+ 1639814
25
+ 16402936
26
+ 1643323
27
+ 1644869
28
+ 1645522
29
+ 1714985
30
+ 1751778
31
+ 1751795
32
+ 1752305
33
+ 1755507
34
+ 1763246
35
+ 1764190
36
+ 1765691
37
+ 1767509
38
+ 1779849
39
+ 18141357
40
+ 18654422
41
+ 190859513
42
+ 2033274
43
+ 20400968
44
+ 22167445
45
+ 2251554
46
+ 226114275
47
+ 2264017
48
+ 232115955
49
+ 232117723
50
+ 24072815
51
+ 2410175
52
+ 2445140
53
+ 2464767
54
+ 247189142
55
+ 2520764
56
+ 29874562
57
+ 2993219
58
+ 300267691
59
+ 30404911
60
+ 31042626
61
+ 31218891
62
+ 31949646
63
+ 3306860
64
+ 34085858
65
+ 34134679
66
+ 34601690
67
+ 36289392
68
+ 36491673
69
+ 36818836
70
+ 36950362
71
+ 37320198
72
+ 37939040
73
+ 38161063
74
+ 38309698
75
+ 38537165
76
+ 38589589
77
+ 38866515
78
+ 38871666
79
+ 38911795
80
+ 38912661
81
+ 38945637
82
+ 39003731
83
+ 3900884
84
+ 39148292
85
+ 39224682
86
+ 41070319
87
+ 41092744
88
+ 41407365
89
+ 41882904
90
+ 41973550
91
+ 41978558
92
+ 42635201
93
+ 4339970
94
+ 43437977
95
+ 43573821
96
+ 43829445
97
+ 44178619
98
+ 44519004
99
+ 44520978
100
+ 44685364
101
+ 44715152
102
+ 45267760
103
+ 45421405
104
+ 4652347
105
+ 46822658
106
+ 47075834
107
+ 47076058
108
+ 47377635
109
+ 47671164
110
+ 47727849
111
+ 47810818
112
+ 47815833
113
+ 48017624
114
+ 48151618
115
+ 48801390
116
+ 48880094
117
+ 48887313
118
+ 49286131
119
+ 49604992
120
+ 498003413
121
+ 49849538
122
+ 49920040
123
+ 50167015
124
+ 50245479
125
+ 50427037
126
+ 50732308
127
+ 50784557
128
+ 51244236
129
+ 51761890
130
+ 52616475
131
+ 54449599
132
+ 5461534
133
+ 55201024
134
+ 5520474
135
+ 55737776
136
+ 55803866
137
+ 57004362
138
+ 57378025
139
+ 60616144
140
+ 60620970
141
+ 60623349
142
+ 60624664
143
+ 60626878
144
+ 60627706
145
+ 60628611
146
+ 607405987
147
+ 61125729
148
+ 611639238
149
+ 61311169
150
+ 613617204
151
+ 61523343
152
+ 6291542
153
+ 6294105
154
+ 641269190
155
+ 6415579
156
+ 67618577
157
+ 696271299
158
+ 702602240
159
+ 762029593
160
+ 809393
161
+ 8287652
162
+ 830989328
163
+ 84844215
164
+ 85489765
165
+ 94139525
166
+ 9438862
167
+
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'borrow_direct/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "borrow_direct"
8
+ spec.version = BorrowDirect::VERSION
9
+ spec.authors = ["Jonathan Rochkind"]
10
+ spec.email = ["jonathan@dnil.net"]
11
+ spec.summary = %q{Ruby tools for interacting with the Borrow Direct consortial services}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "httpclient", "~> 2.4"
21
+
22
+ #spec.add_development_dependency "bundler", ">= 1.6.2", "< 2"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+
25
+ spec.add_development_dependency "minitest", "~> 5.0"
26
+ spec.add_development_dependency "minitest-vcr", ">= 1.0.2", "< 2"
27
+ spec.add_development_dependency "vcr", "~> 2.9"
28
+ spec.add_development_dependency "webmock", "~> 1.11"
29
+
30
+ end
@@ -0,0 +1,55 @@
1
+ require 'borrow_direct/request'
2
+
3
+ module BorrowDirect
4
+
5
+ # The BD Authorization API
6
+ # http://borrowdirect.pbworks.com/w/file/83346673/Authorization%20Service.docx
7
+ #
8
+ # For now, always calls with 'patron' type.
9
+ class Authentication < Request
10
+ attr_reader :patron_barcode, :patron_library_symbol
11
+
12
+ @@api_path = "/portal-service/user/authentication/patron"
13
+
14
+ # BorrowDirect::Authentication.new(barcode)
15
+ # BorrowDirect::Authentication.new(barcode, library_symbol)
16
+ def initialize(patron_barcode,
17
+ patron_library_symbol = Defaults.library_symbol)
18
+ super(@@api_path)
19
+
20
+ @patron_barcode = patron_barcode
21
+ @patron_library_symbol = patron_library_symbol
22
+ end
23
+
24
+ # Returns raw Hash results of the Authentication request
25
+ # See also #get_auth_id
26
+ def authentication_request
27
+ self.request authentication_request_hash(self.patron_barcode, self.patron_library_symbol)
28
+ end
29
+
30
+ # Makes a request and returns the "AId" value which is used as input
31
+ # "AuthorizationId" in other API calls.
32
+ #
33
+ # If one can't be obtained for some reason, will raise BorrowDirect::Error
34
+ def get_auth_id
35
+ response = authentication_request
36
+
37
+ if response["Authentication"] && response["Authentication"]["AuthnUserInfo"] && response["Authentication"]["AuthnUserInfo"]["AId"]
38
+ return response["Authentication"]["AuthnUserInfo"]["AId"]
39
+ else
40
+ raise BorrowDirect::Error.new("Could not obtain AId from Authorization API call: #{response.inspect}")
41
+ end
42
+ end
43
+
44
+ def authentication_request_hash(patron_barcode, library_symbol)
45
+ {
46
+ "AuthenticationInformation" => {
47
+ "LibrarySymbol" => library_symbol,
48
+ "PatronId" => patron_barcode
49
+ }
50
+ }
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,38 @@
1
+ require 'borrow_direct'
2
+
3
+ module BorrowDirect
4
+ # Some defaults for BD requests, including some you might def want to set
5
+ # at app boot, perhaps in a Rails initializer.
6
+ #
7
+ # To use the production BD system instead of test system:
8
+ # BorrowDirect::Defaults.api_base = BorrowDirect::Defaults::PRODUCTION_API_BASE
9
+ #
10
+ # To set your library's BD symbol as a default:
11
+ # BorrowDirect::Defaults.library_symbol = "YOURSYMBOL"
12
+ #
13
+ # To set a default generic patron barcode to use for FindItem requests
14
+ # BorrowDirect::Defaults.find_item_patron_barcode = "99999999999"
15
+ class Defaults
16
+ TEST_API_BASE = "https://bdtest.relais-host.com/"
17
+ PRODUCTION_API_BASE = "NOT_YET_AVAILABLE"
18
+
19
+ TEST_HTML_BASE = "https://bdtest.relaisd2d.com/service-proxy?command=query"
20
+ PRODUCTION_HTML_BASE = "https://borrow-direct.relaisd2d.com/service-proxy?command=query"
21
+
22
+ class << self
23
+ attr_accessor :api_base, :partnership_id, :find_item_patron_barcode, :library_symbol
24
+ attr_accessor :html_base_url
25
+
26
+ # used for HTTPClient send, connection, AND receive timeouts, so
27
+ # theoretically could take 3x this, but unlikely, usually it's just
28
+ # receive that might timeout. In seconds. Default 30s.
29
+ attr_accessor :timeout
30
+ end
31
+
32
+ self.api_base = BorrowDirect::Defaults::TEST_API_BASE
33
+ self.partnership_id = "BD"
34
+ self.timeout = 30
35
+ self.html_base_url = TEST_HTML_BASE
36
+
37
+ end
38
+ end
@@ -0,0 +1,17 @@
1
+ module BorrowDirect
2
+ class Error < StandardError
3
+ attr_reader :bd_code
4
+
5
+ def initialize(msg, bd_code = nil)
6
+ @bd_code = bd_code
7
+ if @bd_code
8
+ msg = "#{@bd_code}: #{msg}"
9
+ end
10
+ super(msg)
11
+ end
12
+
13
+ end
14
+
15
+ class HttpError < Error ; end
16
+ class HttpTimeoutError < HttpError ; end
17
+ end
@@ -0,0 +1,149 @@
1
+ require 'borrow_direct'
2
+ require 'borrow_direct/request'
3
+
4
+ module BorrowDirect
5
+ # The BorrowDirect FindItem service, for discovering item availability
6
+ # http://borrowdirect.pbworks.com/w/file/83346676/Find%20Item%20Service.docx
7
+ #
8
+ # BorrowDirect::FindItem.new(patron_barcode).bd_requestability?(:isbn => isbn)
9
+ # # or set BorrowDirect::Defaults.find_item_patron_barcode to make patron barcode
10
+ # # optional and use a default patron barcode
11
+ #
12
+ # You can also use #find_item_request to get the raw BD response as a ruby hash
13
+ class FindItem < Request
14
+ attr_reader :patron_barcode, :patron_library_symbol
15
+
16
+ @@api_path = "/dws/item/available"
17
+ @@valid_search_types = %w{ISBN ISSN LCCN OCLC PHRASE}
18
+
19
+
20
+ def initialize(patron_barcode = Defaults.find_item_patron_barcode,
21
+ patron_library_symbol = Defaults.library_symbol)
22
+ super(@@api_path)
23
+
24
+ @patron_barcode = patron_barcode
25
+ @patron_library_symbol = patron_library_symbol
26
+
27
+ # BD sometimes unpredictably returns this error when it means
28
+ # "no results", other times it doens't. We don't want to raise on it.
29
+ self.expected_error_codes << "PUBFI002"
30
+ end
31
+
32
+ # need to send a key and value for a valid exact_search type
33
+ # type can be string or symbol, lowercase or uppercase.
34
+ #
35
+ # Returns the actual complete BD response hash. You may want
36
+ # #bd_requestable? instead
37
+ #
38
+ # finder.find_item_request(:isbn => "12345545456")
39
+ #
40
+ # You can request multiple values which BD will treat as an 'OR'/union -- sort
41
+ # of. BD does unpredictable things here, be careful.
42
+ #
43
+ # finder.find_item_request(:isbn => ["12345545456", "99999999"])
44
+ def find_item_request(options)
45
+ search_type, search_value = nil, nil
46
+ options.each_pair do |key, value|
47
+ if @@valid_search_types.include? key.to_s.upcase
48
+ if search_type || search_value
49
+ raise ArgumentError.new("Only one search criteria at a time is allowed: '#{options}'")
50
+ end
51
+
52
+ search_type, search_value = key, value
53
+ end
54
+ end
55
+ unless search_type && search_value
56
+ raise ArgumentError.new("Missing valid search type and value: '#{options}'")
57
+ end
58
+
59
+ request exact_search_request_hash(search_type, search_value)
60
+ end
61
+
62
+ # need to send a key and value for a valid exact_search type
63
+ # type can be string or symbol, lowercase or uppercase.
64
+ #
65
+ # Returns a BorrowDirect::FindItem::Response object, from which you
66
+ # can find out requestability, list of pickup locations, etc.
67
+ def find(options)
68
+ BorrowDirect::FindItem::Response.new find_item_request(options)
69
+ end
70
+
71
+ protected
72
+
73
+ # Produce BD request hash for exact search of type eg "ISBN"
74
+ # value can be a singel value, or an array of values. For array,
75
+ # BD will "OR" them.
76
+ def exact_search_request_hash(type, value)
77
+ # turn it into an array if it's not one already
78
+ values = Array(value)
79
+
80
+ hash = {
81
+ "PartnershipId" => Defaults.partnership_id,
82
+ "Credentials" => {
83
+ "LibrarySymbol" => self.patron_library_symbol,
84
+ "Barcode" => self.patron_barcode
85
+ },
86
+ "ExactSearch" => []
87
+ }
88
+
89
+ values.each do |value|
90
+ hash["ExactSearch"] << {
91
+ "Type" => type.to_s.upcase,
92
+ "Value" => value
93
+ }
94
+ end
95
+
96
+ return hash
97
+ end
98
+
99
+ class Response
100
+ include BorrowDirect::Util
101
+
102
+ attr_reader :response_hash
103
+
104
+ def initialize(hash)
105
+ @response_hash = hash
106
+ end
107
+
108
+
109
+ # Returns true or false -- can the item actually be requested
110
+ # via BorrowDirect.
111
+ #
112
+ # finder.find(:isbn => "12345545456").requestable?
113
+ def requestable?
114
+ # Sometimes a PUBFI002 error code isn't really an error,
115
+ # but just means not available.
116
+ if response_hash && response_hash["Error"] && (response_hash["Error"]["ErrorNumber"] == "PUBFI002")
117
+ return false
118
+ end
119
+
120
+ # Items that are available locally, and thus not requestable via BD, can
121
+ # only be found by looking at the RequestMessage, bah
122
+ h = response_hash["Item"]["RequestLink"]
123
+ if h && h["RequestMessage"] == "This item is available locally"
124
+ return false
125
+ end
126
+
127
+ return response_hash["Item"]["Available"].to_s == "true"
128
+ end
129
+
130
+ # Returns the AuthorizationID returned by FindItem API call,
131
+ # or nil if none is available. Nil _can_ be returned, for
132
+ # instance when BD returns a NotFound error instead of a good
133
+ # response.
134
+ def auth_id
135
+ hash_key_path response_hash, "Item", "AuthorizationId"
136
+ end
137
+
138
+ # Can be nil in some cases if not requestable?
139
+ # if requestable?, should be an array of Strings.
140
+ def pickup_locations
141
+ hash_key_path response_hash, "Item", "PickupLocations", "PickupLocation"
142
+ end
143
+
144
+
145
+ end
146
+
147
+
148
+ end
149
+ end
@@ -0,0 +1,78 @@
1
+ require 'cgi'
2
+
3
+ module BorrowDirect
4
+ # Generate a "deep link" to query results in BD's native
5
+ # HTML interface.
6
+ class GenerateQuery
7
+ attr_accessor :url_base
8
+
9
+ # Hash from our own API argument to BD field code
10
+ @@fields = {
11
+ :keyword => "term",
12
+ :title => "ti",
13
+ :author => "au",
14
+ :subject => "su",
15
+ :isbn => "isbn",
16
+ :issn => "issn"
17
+ }
18
+
19
+ def initialize(url_base = nil)
20
+ self.url_base = (url_base || BorrowDirect::Defaults.html_base_url)
21
+ end
22
+
23
+ # query_with(:title => "one two", :author => "three four")
24
+ # valid keys are those supported by BD HTML interface:
25
+ # :title, :author, :isbn, :subject, :keyword, :isbn, :issn
26
+ #
27
+ # For now, the value is always searched as a phrase, and multiple
28
+ # fields are always 'and'd. We may enhance/expand later.
29
+ #
30
+ # Returns an un-escaped query, still needs to be put into a URL
31
+ def query_with(options)
32
+ clauses = []
33
+
34
+ options.each_pair do |field, value|
35
+ code = @@fields[field]
36
+
37
+ raise ArgumentError.new("Don't recognize field code `#{field}`") unless code
38
+
39
+ clauses << %Q{#{code}="#{escape_query_value value}"}
40
+ end
41
+
42
+ return clauses.join(" and ")
43
+ end
44
+
45
+ # Pass in :title, :author, :isbn, etc -- if we have an isbn or issn,
46
+ # we'll use that alone, otherwise we'll use title and author
47
+ def best_known_item_query_with(options)
48
+ if options[:isbn]
49
+ return query_with(options.dup.delete_if {|k| k != :isbn})
50
+ elsif options[:issn]
51
+ return query_with(options.dup.delete_if {|k| k != :issn})
52
+ else
53
+ return query_with options
54
+ end
55
+ end
56
+
57
+ def query_url_with(options)
58
+ query = query_with(options)
59
+
60
+ return self.url_base + '?' + "query=#{CGI.escape query}"
61
+ end
62
+
63
+ def best_known_item_query_url_with(options)
64
+ query = best_known_item_query_with(options)
65
+
66
+ return self.url_base + '?' + "query=#{CGI.escape query}"
67
+ end
68
+
69
+ # We don't really know how to escape, for now
70
+ # we just remove double quotes and parens, and replace with spaces.
71
+ # those seem to cause problems, and that seems to work.
72
+ def escape_query_value(str)
73
+ str.gsub(/[")()]/, ' ')
74
+ end
75
+
76
+
77
+ end
78
+ end