searchr 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 000949f105a868a2f6e78e31ff582b1c37e08d8f
4
- data.tar.gz: 91a95ee98d16e7401a891f6e50ddd7a957d05aee
3
+ metadata.gz: de94e9fecb06325ad65d0dfd8830452010ced344
4
+ data.tar.gz: 929ca90c0a5f8950c5763f66e5b1ea25ae748bd0
5
5
  SHA512:
6
- metadata.gz: fae7f6d37acf030b967592ad7c0de6cfe9deb8f0f73686d0652f50752c45e2c34066649143de29c3cb3b5b04ed4d8fbb5f888d8b7a641b2c94da70edc107fbf6
7
- data.tar.gz: dd6608f95993537d2d12a6cd7371b1b34109db4ab72583766847a82578c7c0ee9dd86ca5dea06d867b8df7c2a1549a9887617718f85d6fb767f883d663c03f59
6
+ metadata.gz: 8c57aaced27c17568e3be0b6785e34d5c9a3fd4d3b8cd270ea3506fa0d15e57a0a94cef47c42bbbf5314b377a11aaec28d64aaa44d1cdee891fe8f8c9edae601
7
+ data.tar.gz: a249f23ccc42cb90cc308b79e5f8354f5b798f14697fb3e6a9ee8e26102d4fd9ecc772f46fa87f7e3f29777e9ea30bb4a513a28e2a2cad81624a51d49f5689f3
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Searchr
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/searchr`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ Diagnose search result weights. Currently supports solr. To be used with searchr-rails.
6
4
 
7
5
  ## Installation
8
6
 
@@ -1,11 +1,8 @@
1
1
  require 'net/http'
2
2
  require 'json'
3
3
 
4
- require "searchr/version"
5
- require 'searchr/query'
6
- require 'searchr/solr_query'
7
- require 'searchr/result'
8
- require 'searchr/solr_result'
4
+ require 'requirer'
5
+ Requirer.require_dir_tree 'searchr'
9
6
 
10
7
  module Searchr
11
8
  # Your code goes here...
@@ -0,0 +1,293 @@
1
+ module Searchr
2
+ Explanation = Struct.new(:str, :nested, :flattened)
3
+
4
+ class ExplanationParser
5
+ attr_reader :lines
6
+
7
+ def initialize(lines)
8
+ @lines = lines.select{ |line| !line.empty? }.collect{ |line| ExplainLine.new line }
9
+ end
10
+
11
+ def explanation
12
+ Explanation.new top_node.simple_string, top_node.simple_json, top_node.simple_array
13
+ end
14
+
15
+ def top_node
16
+ @top_node ||= scrunch
17
+ end
18
+
19
+ private
20
+
21
+ def scrunch
22
+ @_nodes = Array.new(20)
23
+ @_nodes[0] = @lines[0].node(nil)
24
+ @_node_depth = 0
25
+ @_line_num = 1
26
+ process_next_line until eof?
27
+
28
+ return @_nodes[0]
29
+ end
30
+
31
+ def parent_node
32
+ @_nodes[line.indent_level - 1]
33
+ end
34
+ def line
35
+ @lines[@_line_num]
36
+ end
37
+
38
+ def set_current_node(node, depth)
39
+ @_node_depth = depth
40
+ @_nodes[depth] = node
41
+ end
42
+
43
+ def process_next_line
44
+ node = line.node parent_node
45
+ set_current_node node, line.indent_level
46
+ bump_line
47
+ consume_subordinate_lines node if node.is_leaf
48
+ end
49
+
50
+ def bump_line
51
+ @_line_num += 1
52
+ end
53
+
54
+ def eof?
55
+ @_line_num >= @lines.size
56
+ end
57
+
58
+ def consume_subordinate_lines(node)
59
+ current_line = line
60
+ bump_line
61
+ while !eof? and (line.indent_level==0 or current_line.indent_level<line.indent_level)
62
+ node.add_line "#{' '*[line.indent_level-current_line.indent_level,0].max}#{line.line}"
63
+ bump_line
64
+ end
65
+ end
66
+ end
67
+
68
+ class ExplainLine
69
+ attr_reader :indent_level, :line
70
+
71
+ def self.node(str)
72
+ self.new(str).node
73
+ end
74
+
75
+ def initialize(line)
76
+ num_spaces = (/^\s*/.match line).to_s.size
77
+ @indent_level = num_spaces / 2
78
+ @line = line[num_spaces..line.size]
79
+ end
80
+
81
+ def node(parent)
82
+ line_type.new parent, self
83
+ end
84
+
85
+ def line_type
86
+ return ProductNode if @line.include? ') product of:'
87
+ return SumNode if @line.include? ') sum of:'
88
+ return MaxNode if @line.include? ') max of:'
89
+ return WeightNode if @line.include? ') weight('
90
+ return CoordNode if @line.include? '= coord('
91
+ msg = "Unknown line type: #{@line}"
92
+ msg.logit
93
+ raise msg
94
+ end
95
+
96
+ def score
97
+ @score ||= line.to_f
98
+ end
99
+ end
100
+
101
+ module Node
102
+ attr_reader :parent, :explain_line
103
+
104
+ def initialize(parent, explain_line)
105
+ set_parent parent
106
+ @explain_line = explain_line
107
+ end
108
+
109
+ def score
110
+ @explain_line.score
111
+ end
112
+
113
+ def children
114
+ @children ||= []
115
+ end
116
+
117
+ def is_leaf
118
+ false
119
+ end
120
+
121
+ private
122
+
123
+ def set_parent(parent)
124
+ @parent = parent
125
+ @parent.children << self if parent
126
+ end
127
+ end
128
+
129
+ class ProductNode
130
+ include Node
131
+
132
+ def simple_string
133
+ case children.size
134
+ when 0
135
+ ''
136
+ when 1
137
+ children[0].simple_string
138
+ else
139
+ "#{score}=(" + (children.collect {|child| child.simple_string}.join(" * ")) + ")"
140
+ end
141
+ end
142
+
143
+ def simple_json
144
+ case children.size
145
+ when 0
146
+ nil
147
+ when 1
148
+ children[0].simple_json
149
+ else
150
+ [score, '*', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
151
+ end
152
+ end
153
+
154
+ def simple_array
155
+ results = children.collect {|child| child.simple_array}
156
+ case results.size
157
+ when 0
158
+ []
159
+ when 1
160
+ results[0]
161
+ else
162
+ ['(', ['*', score]] + results.flatten(1) + [')']
163
+ end
164
+ end
165
+ end
166
+
167
+ class SumNode
168
+ include Node
169
+
170
+ def simple_string
171
+ case children.size
172
+ when 0
173
+ ''
174
+ when 1
175
+ children[0].simple_string
176
+ else
177
+ "#{score}=(" + (children.collect {|child| child.simple_string}.join(" + ")) + ")"
178
+ end
179
+ end
180
+
181
+
182
+ def simple_json
183
+ case children.size
184
+ when 0
185
+ nil
186
+ when 1
187
+ children[0].simple_json
188
+ else
189
+ [score, '+', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
190
+ end
191
+ end
192
+
193
+ def simple_array
194
+ results = children.collect {|child| child.simple_array}
195
+ case results.size
196
+ when 0
197
+ []
198
+ when 1
199
+ results[0]
200
+ else
201
+ ['(', ['+', score]] + results.flatten(1) + [')']
202
+ end
203
+ end
204
+ end
205
+
206
+ class MaxNode
207
+ include Node
208
+
209
+ def simple_string
210
+ case children.size
211
+ when 0
212
+ ''
213
+ when 1
214
+ children[0].simple_string
215
+ else
216
+ "#{score}=max(" + (children.collect {|child| child.simple_string}.join(", ")) + ")"
217
+ end
218
+ end
219
+
220
+
221
+ def simple_json
222
+ case children.size
223
+ when 0
224
+ nil
225
+ when 1
226
+ children[0].simple_json
227
+ else
228
+ [score, 'max', children.collect {|child| child.simple_json}.select {|ele| !ele.nil?}]
229
+ end
230
+ end
231
+
232
+ def simple_array
233
+ results = children.collect {|child| child.simple_array}
234
+ case results.size
235
+ when 0
236
+ []
237
+ when 1
238
+ results[0]
239
+ else
240
+ ['(', ['max', score]] + results.flatten(1) + [')']
241
+ end
242
+ end
243
+ end
244
+
245
+ class WeightNode
246
+ include Node
247
+
248
+ def is_leaf
249
+ true
250
+ end
251
+
252
+ def weight_name
253
+ m = /\) weight\(([^)]+)/.match explain_line.line
254
+ m[1]
255
+ end
256
+
257
+ def add_line(line)
258
+ subordinate_rows << line
259
+ end
260
+
261
+ def subordinate_rows
262
+ @subordinate_rows ||= []
263
+ end
264
+
265
+ def simple_string
266
+ "[#{score}=#{weight_name}]"
267
+ end
268
+
269
+ def simple_json
270
+ [score, weight_name, subordinate_rows]
271
+ end
272
+
273
+ def simple_array
274
+ [[weight_name, score, subordinate_rows]]
275
+ end
276
+ end
277
+
278
+ class CoordNode
279
+ include Node
280
+
281
+ def simple_string
282
+ "[#{score}=coord]"
283
+ end
284
+
285
+ def simple_json
286
+ [score, 'coord']
287
+ end
288
+
289
+ def simple_array
290
+ [['coord', score]]
291
+ end
292
+ end
293
+ end
@@ -2,13 +2,18 @@ require 'addressable/uri'
2
2
 
3
3
  module Searchr
4
4
  class Query
5
- attr_writer :query, :fields_to_query, :start_row, :num_rows
5
+ attr_writer :query, :fields_to_query
6
6
 
7
7
  def search
8
- result_with http_response
8
+ result_class.new self, http_response
9
9
  end
10
10
 
11
- def result_with
11
+ # There are more subclass responsibility methods in the protected section.
12
+ def result_class
13
+ subclass_responsibility
14
+ end
15
+
16
+ def query_parameters
12
17
  subclass_responsibility
13
18
  end
14
19
 
@@ -32,14 +37,43 @@ module Searchr
32
37
  @fields_to_query ||= default_fields_to_query
33
38
  end
34
39
 
40
+ def fields_to_return
41
+ @fields_to_return ||= default_fields_to_return
42
+ @fields_to_return.split(/\W+/)
43
+ end
44
+
45
+ def fields_to_return=(array_or_string)
46
+ @fields_to_return = if array_or_string.class==String
47
+ array_or_string
48
+ else
49
+ array_or_string.join(' ')
50
+ end
51
+ end
52
+
35
53
  def start_row
36
- @start_row ||= 0
54
+ @start_row ||= 1
55
+ end
56
+
57
+ def start_row=(num)
58
+ num_as_int = Integer(num)
59
+ raise "start_row must be a positive integer (#{num})" if num_as_int < 1
60
+ @start_row = num_as_int
37
61
  end
38
62
 
39
63
  def num_rows
40
64
  @num_rows ||= default_num_rows
41
65
  end
42
66
 
67
+ def num_rows=(num)
68
+ num_as_int = Integer(num)
69
+ raise "num_rows must be a positive integer (#{num})" if num_as_int < 0
70
+ @num_rows = num_as_int
71
+ end
72
+
73
+ def end_row
74
+ start_row + num_rows - 1
75
+ end
76
+
43
77
  def indent?
44
78
  @do_indent ||= default_indent?
45
79
  end
@@ -52,10 +86,6 @@ module Searchr
52
86
  @return_type ||= default_return_type
53
87
  end
54
88
 
55
- def query_parameters
56
- subclass_responsibility
57
- end
58
-
59
89
  def url
60
90
  uri = URI(base_query_url)
61
91
 
@@ -80,6 +110,10 @@ module Searchr
80
110
  subclass_responsibility
81
111
  end
82
112
 
113
+ def default_fields_to_return
114
+ subclass_responsibility
115
+ end
116
+
83
117
  def default_num_rows
84
118
  10
85
119
  end
@@ -90,7 +124,7 @@ module Searchr
90
124
  end
91
125
 
92
126
  def default_debug_query?
93
- # true required to diagnose result weights
127
+ # true required to retrieve result weights
94
128
  true
95
129
  end
96
130
 
@@ -1,12 +1,15 @@
1
1
  module Searchr
2
2
  class Result
3
- def initialize(http_response)
3
+ attr_reader :query
4
+
5
+ def initialize(query, http_response)
6
+ @query = query
4
7
  @http_response = http_response
5
8
  raise 'Http response for search did not succeed' if code!=200
6
9
  end
7
10
 
8
11
  def body
9
- @body ||= JSON.parse(http_response.body)
12
+ @body ||= JSON.parse(@http_response.body)
10
13
  end
11
14
 
12
15
  def code
@@ -1,12 +1,12 @@
1
1
  module Searchr
2
2
  class SolrQuery < Query
3
- def result_with(http_response)
4
- SolrResult.new http_response
3
+ def result_class
4
+ SolrResult
5
5
  end
6
6
 
7
7
  def query_parameters
8
8
  {
9
- start: start_row,
9
+ start: start_row-1,
10
10
  rows: num_rows,
11
11
  q: query,
12
12
  qf: fields_to_query,
@@ -1,4 +1,66 @@
1
1
  module Searchr
2
2
  class SolrResult < Result
3
+ def documents
4
+ @documents ||= body['response']['docs']
5
+ end
6
+
7
+ def num_documents_received
8
+ documents.size
9
+ end
10
+
11
+ def num_documents_requested
12
+ query.num_rows
13
+ end
14
+
15
+ def start_row
16
+ @start_row ||= body['response']['start']
17
+ end
18
+
19
+ def end_row
20
+ start_row + num_documents_received - 1
21
+ end
22
+
23
+ def num_matches
24
+ @num_found ||= body['response']['numFound']
25
+ end
26
+
27
+ def status
28
+ status ||= body['responseHeader']['status']
29
+ end
30
+
31
+ def parameters
32
+ @parameters ||= body['responseHeader']['params']
33
+ end
34
+
35
+ def explain
36
+ body['debug']['explain']
37
+ end
38
+
39
+ # calculated fields
40
+
41
+ def explanations
42
+ explanation_hash
43
+ end
44
+
45
+ def explanation_hash
46
+ unless @explanation_hash
47
+ @explanation_hash = extract_explanations
48
+ @explanation_hash.each do |key, rows|
49
+ parser = ExplanationParser.new rows
50
+ rows[0] = parser.explanation
51
+ end
52
+ end
53
+ @explanation_hash
54
+ end
55
+
56
+ protected
57
+
58
+ def extract_explanations
59
+ matches = {}
60
+ explain.each do |key, val|
61
+ matches[key] = val.split("\n")
62
+ end
63
+ matches
64
+ end
3
65
  end
4
66
  end
@@ -1,3 +1,3 @@
1
1
  module Searchr
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
  spec.require_paths = ["lib"]
21
21
 
22
22
  spec.add_dependency 'addressable'
23
+ spec.add_dependency 'requirer'
23
24
 
24
25
  spec.add_development_dependency "bundler", "~> 1.10"
25
26
  spec.add_development_dependency "rake", "~> 10.0"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Murphy-Dye
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2015-08-01 00:00:00.000000000 Z
11
+ date: 2015-08-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: requirer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -166,6 +180,7 @@ files:
166
180
  - bin/console
167
181
  - bin/setup
168
182
  - lib/searchr.rb
183
+ - lib/searchr/explanation_parser.rb
169
184
  - lib/searchr/query.rb
170
185
  - lib/searchr/result.rb
171
186
  - lib/searchr/solr_query.rb