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 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
@@ -0,0 +1,2 @@
1
+ =RSolr::Ext
2
+ A set of helper methods/modules to assist in building Solr queries and handling responses when using the RSolr library.
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,3 @@
1
+ class RSolr::Ext::Request::Dismax < RSolr::Ext::Request::Standard
2
+
3
+ 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,8 @@
1
+ module RSolr::Ext::Response
2
+
3
+ autoload :Base, 'rsolr-ext/response/base'
4
+ autoload :Select, 'rsolr-ext/response/select'
5
+ autoload :Luke, 'rsolr-ext/response/luke'
6
+ autoload :Update, 'rsolr-ext/response/update'
7
+
8
+ 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,13 @@
1
+ # for update responses
2
+ module RSolr::Ext::Response::Update
3
+
4
+ include Base
5
+
6
+ # converts to mash, then extends
7
+ def self.create(hash)
8
+ mash = hash.is_a?(Mash) ? hash : hash.to_mash
9
+ mash.extend self
10
+ mash
11
+ end
12
+
13
+ end
@@ -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