mwmitchell-rsolr-ext 0.4.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.
- data/LICENSE +13 -0
- data/README.rdoc +2 -0
- data/lib/rsolr-ext.rb +25 -0
- data/lib/rsolr-ext/request.rb +56 -0
- data/lib/rsolr-ext/request/dismax.rb +3 -0
- data/lib/rsolr-ext/request/standard.rb +38 -0
- data/lib/rsolr-ext/response.rb +8 -0
- data/lib/rsolr-ext/response/base.rb +35 -0
- data/lib/rsolr-ext/response/luke.rb +53 -0
- data/lib/rsolr-ext/response/select.rb +216 -0
- data/lib/rsolr-ext/response/update.rb +13 -0
- data/test/test_unit_test_case.rb +18 -0
- metadata +68 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2008-2009 Matt Mitchell
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.rdoc
ADDED
data/lib/rsolr-ext.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# add this directory to the load path if it hasn't already been added
|
2
|
+
lambda {|base|
|
3
|
+
$: << base unless $:.include?(base) || $:.include?(File.expand_path(base))
|
4
|
+
}.call(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
unless defined?(Mash)
|
7
|
+
require 'mash'
|
8
|
+
end
|
9
|
+
|
10
|
+
unless Hash.respond_to?(:to_mash)
|
11
|
+
require 'core_ext'
|
12
|
+
end
|
13
|
+
|
14
|
+
module RSolr
|
15
|
+
|
16
|
+
module Ext
|
17
|
+
|
18
|
+
VERSION = '0.2.1'
|
19
|
+
|
20
|
+
autoload :Request, 'rsolr-ext/request.rb'
|
21
|
+
autoload :Response, 'rsolr-ext/response.rb'
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RSolr::Ext::Request
|
2
|
+
|
3
|
+
module Mapable
|
4
|
+
|
5
|
+
def map(input)
|
6
|
+
result = input.dup
|
7
|
+
self.class::MAPPED_PARAMS.each do |meth|
|
8
|
+
input_value = result.delete(meth)
|
9
|
+
next if input_value.to_s.empty?
|
10
|
+
send("map_#{meth}", input_value, result)
|
11
|
+
end
|
12
|
+
result
|
13
|
+
end
|
14
|
+
|
15
|
+
def append_to_param(existing_value, new_value)
|
16
|
+
values = [existing_value, new_value]
|
17
|
+
values.delete_if{|v|v.nil?}
|
18
|
+
values.join(' ')
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
module Queryable
|
24
|
+
|
25
|
+
def quote(value)
|
26
|
+
%("#{value}")
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_range(r)
|
30
|
+
"[#{r.min} TO #{r.max}]"
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_query(value, quote_string=false)
|
34
|
+
case value
|
35
|
+
when String,Symbol
|
36
|
+
return quote_string ? quote(value.to_s) : value.to_s
|
37
|
+
when Array
|
38
|
+
value.collect do |v|
|
39
|
+
build_query(v, quote_string)
|
40
|
+
end.flatten
|
41
|
+
when Hash
|
42
|
+
return value.collect do |(k,v)|
|
43
|
+
if v.is_a?(Range)
|
44
|
+
"#{k}:#{build_range(v)}"
|
45
|
+
else
|
46
|
+
"#{k}:#{build_query(v, quote_string)}"
|
47
|
+
end
|
48
|
+
end.flatten
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
autoload :Standard, 'rsolr-ext/request/standard.rb'
|
54
|
+
autoload :Dismax, 'rsolr-ext/request/dismax.rb'
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class RSolr::Ext::Request::Standard
|
2
|
+
|
3
|
+
include RSolr::Ext::Request::Mapable
|
4
|
+
include RSolr::Ext::Request::Queryable
|
5
|
+
|
6
|
+
MAPPED_PARAMS = [
|
7
|
+
:per_page,
|
8
|
+
:page,
|
9
|
+
:phrases, # quoted q param
|
10
|
+
:filters, # fq params
|
11
|
+
:phrase_filters # quoted fq params
|
12
|
+
]
|
13
|
+
|
14
|
+
def map_per_page(value,output)
|
15
|
+
output[:rows] = value.to_i
|
16
|
+
end
|
17
|
+
|
18
|
+
def map_page(value,output)
|
19
|
+
raise ':per_page must be set when using :page' unless output[:rows]
|
20
|
+
page = value.to_s.to_i-1
|
21
|
+
page = page < 1 ? 0 : page
|
22
|
+
output[:start] = page * output[:rows]
|
23
|
+
end
|
24
|
+
|
25
|
+
def map_phrases(value,output)
|
26
|
+
output[:q] = append_to_param(output[:q], build_query(value, true))
|
27
|
+
end
|
28
|
+
|
29
|
+
def map_filters(value,output)
|
30
|
+
output[:fq] = append_to_param(output[:fq], build_query(value))
|
31
|
+
end
|
32
|
+
|
33
|
+
def map_phrase_filters(value,output)
|
34
|
+
output[:fq] = append_to_param(output[:fq], build_query(value, true))
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#
|
2
|
+
# my_solr_hash.extend RSolrExt::Response::Base
|
3
|
+
# my_solr_hash.header
|
4
|
+
# my_solr_hash.ok?
|
5
|
+
#
|
6
|
+
module RSolr::Ext::Response::Base
|
7
|
+
|
8
|
+
def header
|
9
|
+
self[:responseHeader]
|
10
|
+
end
|
11
|
+
|
12
|
+
def params
|
13
|
+
header[:params]
|
14
|
+
end
|
15
|
+
|
16
|
+
def status
|
17
|
+
header[:status].to_i
|
18
|
+
end
|
19
|
+
|
20
|
+
def query_time
|
21
|
+
header[:QTime]
|
22
|
+
end
|
23
|
+
|
24
|
+
def ok?
|
25
|
+
self.status == 0
|
26
|
+
end
|
27
|
+
|
28
|
+
# converts to mash, then extends
|
29
|
+
def self.create(hash)
|
30
|
+
mash = hash.is_a?(Mash) ? hash : hash.to_mash
|
31
|
+
mash.extend self
|
32
|
+
mash
|
33
|
+
end
|
34
|
+
|
35
|
+
end # end Base
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module RSolr::Ext::Response::Luke
|
2
|
+
|
3
|
+
include Base
|
4
|
+
|
5
|
+
def index
|
6
|
+
self[:index]
|
7
|
+
end
|
8
|
+
|
9
|
+
def directory
|
10
|
+
index[:directory]
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_deletions
|
14
|
+
index[:hasDeletions]
|
15
|
+
end
|
16
|
+
|
17
|
+
def current
|
18
|
+
index[:current]
|
19
|
+
end
|
20
|
+
|
21
|
+
def max_doc
|
22
|
+
index[:max_doc]
|
23
|
+
end
|
24
|
+
|
25
|
+
def num_docs
|
26
|
+
index[:numDocs]
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
index[:version]
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :has_deletions? :has_deletions
|
34
|
+
alias :optimized? :optimized
|
35
|
+
alias :current? :current
|
36
|
+
|
37
|
+
# Returns an array of fields from the index
|
38
|
+
# An optional rule can be used for "grepping" field names:
|
39
|
+
# field_list(/_facet$/)
|
40
|
+
def field_list(rule=nil)
|
41
|
+
self[:fields].select do |k,v|
|
42
|
+
rule ? k =~ rule : true
|
43
|
+
end.collect{|k,v|k}
|
44
|
+
end
|
45
|
+
|
46
|
+
# converts to mash, then extends
|
47
|
+
def self.create(hash)
|
48
|
+
mash = hash.is_a?(Mash) ? hash : hash.to_mash
|
49
|
+
mash.extend self
|
50
|
+
mash
|
51
|
+
end
|
52
|
+
|
53
|
+
end# end Luke
|
@@ -0,0 +1,216 @@
|
|
1
|
+
module RSolr::Ext::Response::Select
|
2
|
+
|
3
|
+
# module for adding helper methods to each solr response[:docs] object
|
4
|
+
module DocExt
|
5
|
+
|
6
|
+
# Helper method to check if value/multi-values exist for a given key.
|
7
|
+
# The value can be a string, or a RegExp
|
8
|
+
# Example:
|
9
|
+
# doc.has?(:location_facet)
|
10
|
+
# doc.has?(:location_facet, 'Clemons')
|
11
|
+
# doc.has?(:id, 'h009', /^u/i)
|
12
|
+
def has?(k, *values)
|
13
|
+
return if self[k].nil?
|
14
|
+
return true if self.key?(k) and values.empty?
|
15
|
+
target = self[k]
|
16
|
+
if target.is_a?(Array)
|
17
|
+
values.each do |val|
|
18
|
+
return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
|
19
|
+
end
|
20
|
+
else
|
21
|
+
return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# helper
|
26
|
+
# key is the name of the field
|
27
|
+
# opts is a hash with the following valid keys:
|
28
|
+
# - :sep - a string used for joining multivalued field values
|
29
|
+
# - :default - a value to return when the key doesn't exist
|
30
|
+
# if :sep is nil and the field is a multivalued field, the array is returned
|
31
|
+
def get(key, opts={:sep=>', ', :default=>nil})
|
32
|
+
if self.key? key
|
33
|
+
val = self[key]
|
34
|
+
(val.is_a?(Array) and opts[:sep]) ? val.join(opts[:sep]) : val
|
35
|
+
else
|
36
|
+
opts[:default]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
module Facets
|
43
|
+
|
44
|
+
# represents a facet value; which is a field value and its hit count
|
45
|
+
class FacetValue
|
46
|
+
attr_reader :value,:hits
|
47
|
+
def initialize(value,hits)
|
48
|
+
@value,@hits=value,hits
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# represents a facet; which is a field and its values
|
53
|
+
class Facet
|
54
|
+
attr_reader :field
|
55
|
+
attr_accessor :values
|
56
|
+
def initialize(field)
|
57
|
+
@field=field
|
58
|
+
@values=[]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# @response.facets.each do |facet|
|
63
|
+
# facet.field
|
64
|
+
# end
|
65
|
+
# "caches" the result in the @facets instance var
|
66
|
+
def facets
|
67
|
+
# memoize!
|
68
|
+
@facets ||= (
|
69
|
+
facet_fields.inject([]) do |acc,(facet_field_name,values_and_hits_list)|
|
70
|
+
acc << facet = Facet.new(facet_field_name)
|
71
|
+
# the values_and_hits_list is an array where a value is immediately followed by it's hit count
|
72
|
+
# so we shift off an item (the value)
|
73
|
+
while value = values_and_hits_list.shift
|
74
|
+
# and then shift off the next to get the hit value
|
75
|
+
facet.values << FacetValue.new(value, values_and_hits_list.shift)
|
76
|
+
# repeat until there are no more pairs in the values_and_hits_list array
|
77
|
+
end
|
78
|
+
acc
|
79
|
+
end
|
80
|
+
)
|
81
|
+
end
|
82
|
+
|
83
|
+
# pass in a facet field name and get back a Facet instance
|
84
|
+
def facet_by_field_name(name)
|
85
|
+
@facets_by_field_name ||= {}
|
86
|
+
@facets_by_field_name[name] ||= (
|
87
|
+
facets.detect{|facet|facet.field.to_s == name.to_s}
|
88
|
+
)
|
89
|
+
end
|
90
|
+
|
91
|
+
def facet_counts
|
92
|
+
@facet_counts ||= self[:facet_counts] || {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
|
96
|
+
def facet_fields
|
97
|
+
@facet_fields ||= facet_counts[:facet_fields] || {}
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns all of the facet queries
|
101
|
+
def facet_queries
|
102
|
+
@facet_queries ||= facet_counts[:facet_queries] || {}
|
103
|
+
end
|
104
|
+
|
105
|
+
end # end Facets
|
106
|
+
|
107
|
+
#
|
108
|
+
#
|
109
|
+
#
|
110
|
+
class FacetPaginator
|
111
|
+
|
112
|
+
attr_reader :total, :items, :previous_offset, :next_offset
|
113
|
+
|
114
|
+
def initialize(all_facet_values, offset, limit)
|
115
|
+
offset = offset.to_s.to_i
|
116
|
+
limit = limit.to_s.to_i
|
117
|
+
total = all_facet_values.size
|
118
|
+
@items = all_facet_values.slice(0, limit-1)
|
119
|
+
@has_next = total == limit
|
120
|
+
@has_previous = offset > 0
|
121
|
+
@next_offset = offset + (limit-1)
|
122
|
+
@previous_offset = offset - (limit-1)
|
123
|
+
end
|
124
|
+
|
125
|
+
def has_next?
|
126
|
+
@has_next
|
127
|
+
end
|
128
|
+
|
129
|
+
def has_previous?
|
130
|
+
@has_previous
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
#
|
137
|
+
#
|
138
|
+
class Paginator
|
139
|
+
|
140
|
+
attr_reader :start, :per_page, :total
|
141
|
+
|
142
|
+
def initialize(start, per_page, total)
|
143
|
+
@start = start.to_s.to_i
|
144
|
+
@per_page = per_page.to_s.to_i
|
145
|
+
@total = total.to_s.to_i
|
146
|
+
end
|
147
|
+
|
148
|
+
# Returns the current page calculated from 'rows' and 'start'
|
149
|
+
# WillPaginate hook
|
150
|
+
def current_page
|
151
|
+
return 1 if start < 1
|
152
|
+
@current_page ||= (start / per_page).ceil + 1
|
153
|
+
end
|
154
|
+
|
155
|
+
# Calcuates the total pages from 'numFound' and 'rows'
|
156
|
+
# WillPaginate hook
|
157
|
+
def total_pages
|
158
|
+
@total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
|
159
|
+
end
|
160
|
+
|
161
|
+
# returns the previous page number or 1
|
162
|
+
# WillPaginate hook
|
163
|
+
def previous_page
|
164
|
+
@previous_page ||= (current_page > 1) ? current_page - 1 : 1
|
165
|
+
end
|
166
|
+
|
167
|
+
# returns the next page number or the last
|
168
|
+
# WillPaginate hook
|
169
|
+
def next_page
|
170
|
+
@next_page ||= (current_page < total_pages) ? current_page + 1 : total_pages
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def paginator
|
175
|
+
@paginator ||= Paginator.new(start, rows, total)
|
176
|
+
end
|
177
|
+
|
178
|
+
# The main select response class.
|
179
|
+
# Includes the top level Response::Base module
|
180
|
+
# Includes the Pagination module.
|
181
|
+
# Each solr hash doc is extended by the DocExt module.
|
182
|
+
|
183
|
+
include RSolr::Ext::Response::Base
|
184
|
+
include Facets
|
185
|
+
|
186
|
+
def response
|
187
|
+
self[:response]
|
188
|
+
end
|
189
|
+
|
190
|
+
def num_found
|
191
|
+
response[:numFound]
|
192
|
+
end
|
193
|
+
|
194
|
+
def start
|
195
|
+
response[:start]
|
196
|
+
end
|
197
|
+
|
198
|
+
def rows
|
199
|
+
params[:rows]
|
200
|
+
end
|
201
|
+
|
202
|
+
alias :total :num_found
|
203
|
+
alias :offset :start
|
204
|
+
|
205
|
+
def docs
|
206
|
+
@docs ||= response[:docs].collect{ |d| d=d.to_mash; d.extend(DocExt); d }
|
207
|
+
end
|
208
|
+
|
209
|
+
# converts to mash, then extends
|
210
|
+
def self.create(hash)
|
211
|
+
mash = hash.is_a?(Mash) ? hash : hash.to_mash
|
212
|
+
mash.extend self
|
213
|
+
mash
|
214
|
+
end
|
215
|
+
|
216
|
+
end # end Select
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
|
3
|
+
class Test::Unit::TestCase
|
4
|
+
|
5
|
+
def self.test(name, &block)
|
6
|
+
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
|
7
|
+
defined = instance_method(test_name) rescue false
|
8
|
+
raise "#{test_name} is already defined in #{self}" if defined
|
9
|
+
if block_given?
|
10
|
+
define_method(test_name, &block)
|
11
|
+
else
|
12
|
+
define_method(test_name) do
|
13
|
+
flunk "No implementation provided for #{name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mwmitchell-rsolr-ext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matt Mitchell
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-15 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: An extension lib for RSolr
|
17
|
+
email: goodieboy@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- LICENSE
|
24
|
+
- README.rdoc
|
25
|
+
files:
|
26
|
+
- lib/rsolr-ext.rb
|
27
|
+
- lib/rsolr-ext/params.rb
|
28
|
+
- lib/rsolr-ext/request.rb
|
29
|
+
- lib/rsolr-ext/request/standard.rb
|
30
|
+
- lib/rsolr-ext/request/dismax.rb
|
31
|
+
- lib/rsolr-ext/response.rb
|
32
|
+
- lib/rsolr-ext/response/base.rb
|
33
|
+
- lib/rsolr-ext/response/luke.rb
|
34
|
+
- lib/rsolr-ext/response/select.rb
|
35
|
+
- lib/rsolr-ext/response/update.rb
|
36
|
+
- LICENSE
|
37
|
+
- README.rdoc
|
38
|
+
- rsolr_ext.gemspec
|
39
|
+
has_rdoc: true
|
40
|
+
homepage: http://github.com/mwmitchell/rsolr_ext
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
|
44
|
+
require_paths:
|
45
|
+
- lib
|
46
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: "0"
|
51
|
+
version:
|
52
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: "0"
|
57
|
+
version:
|
58
|
+
requirements: []
|
59
|
+
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 1.2.0
|
62
|
+
signing_key:
|
63
|
+
specification_version: 2
|
64
|
+
summary: An extension lib for RSolr
|
65
|
+
test_files:
|
66
|
+
- test/rsolr_ext_standard_test.rb
|
67
|
+
- test/rsolr_ext_test.rb
|
68
|
+
- test/test_unit_test_case.rb
|