rsolr-ext 0.9.6.3

Sign up to get free protection for your applications and to get access to all the features.
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,119 @@
1
+ =RSolr::Ext
2
+ A set of helper methods/modules to assist in building Solr queries and handling responses when using the RSolr library.
3
+
4
+ ==Related Resources & Projects
5
+ * {RSolr}[http://github.com/mwmitchell/rsolr]
6
+
7
+ ==Requests
8
+ To use the RSolr::Ext connection instead of the normal RSolr connection:
9
+ solr = RSolr::Ext.connect
10
+
11
+ RSolr::Ext adds a #find and a #luke_admin method to the connection object.
12
+
13
+ ===#luke
14
+ The #luke method returns a Hash/Mash result of a /admin/luke?numTerms=0 request:
15
+ luke_response = solr.luke
16
+ luke_response['index']
17
+ luke_response['fields']
18
+ luke_response['info']
19
+
20
+
21
+ ===#find
22
+ The #find method listens for certain keys. All other keys are ignored, allowing the ability to mix-and-match special keys with normal Solr param keys. The recognized keys are describe below.
23
+
24
+
25
+ :page - This maps to the Solr "start" param. The current "page" in the results set. RSolr::Ext handles the page-to-rows math for you.
26
+
27
+
28
+ :per_page - This maps to the Solr "rows" param. How many "pages" in the result.
29
+
30
+
31
+ :queries - This key maps to the Solr "q" param. Accepts a string, array or hash. When an array is used, each value is joined by a space. When a hash is used, the keys are used as Solr fields.
32
+
33
+ * :queries => 'normal' BECOMES ?q=normal
34
+ * :queries => ['one', 'two'] BECOMES ?q=one two
35
+ * :queries => {:title=>'one'} BECOMES ?q=title:(one)
36
+ * :queries => ['red', {:title=>'one'}] BECOMES ?q=red title:(one)
37
+
38
+
39
+ :phrases - This value is mapped to the Solr "q" param. When this key is used, the value will become double-quoted, creating a Solr "phrase" based query.
40
+
41
+ * :phrases => 'normal' BECOMES ?q="normal"
42
+ * :phrases => ['one', 'two'] BECOMES ?q="one" "two"
43
+ * :phrases => {:title=>'one'} BECOMES ?q=title:("one")
44
+ * :phrases => ['red', {:title=>'one'}] BECOMES ?q="red" title:("one")
45
+
46
+
47
+ :filters - The :filters key maps to the Solr :fq param. This has the same behavior as the :queries key, except it's for the :fq param.
48
+
49
+ * :filters => 'normal' BECOMES ?fq=normal
50
+ * :filters => ['one', 'two'] BECOMES ?fq=one two
51
+ * :filters => {:title=>'one'} BECOMES ?fq=title:(one)
52
+ * :filters => ['red', {:title=>'one'}] BECOMES ?fq=red title:(one)
53
+
54
+
55
+ :phrase_filters - The :phrase_filters key maps to the Solr :fq param. This has the same behavior as the :phrases key, except it's for the :fq param.
56
+
57
+ * :phrase_filters => 'normal' BECOMES ?fq="normal"
58
+ * :phrase_filters => ['one', 'two'] BECOMES ?fq="one" "two"
59
+ * :phrase_filters => {:title=>'one'} BECOMES ?fq=title:("one")
60
+ * :phrase_filters => ['red', {:title=>'one'}] BECOMES ?fq="red" title:("one")
61
+
62
+
63
+ :facets - The :facets does a few different things. First, it sets the Solr param facet=true. It accepts a hash with a single key called :fields. This should be an array of field names to facet on.
64
+
65
+ * :facets=>{:fields=>['cat', 'blah']} BECOMES ?facet=true&facet.field=cat&facet.field=blah
66
+
67
+
68
+
69
+ ==Request Example
70
+ solr = RSolr::Ext.connect
71
+ solr_params = {
72
+ :page=>2,
73
+ :per_page=>10,
74
+ :phrases=>{:name=>'This is a phrase'},
75
+ :filters=>['test', {:price=>(1..10)}],
76
+ :phrase_filters=>{:manu=>['Apple']},
77
+ :queries=>'ipod',
78
+ :facets=>{:fields=>['cat', 'blah']},
79
+ :echoParams => 'EXPLICIT'
80
+ }
81
+ response = rsolr.find solr_params
82
+
83
+ ==Responses
84
+ RSolr::Ext decorates the normal output hash from RSolr and adds some helpful methods.
85
+
86
+ solr = RSolr::Ext.connect
87
+
88
+ response = solr.find :q=>'*:*'
89
+
90
+ response.ok?
91
+ response.params
92
+ response.docs
93
+ response.docs.previous_page
94
+ response.docs.next_page
95
+ response.facets.each do |facet|
96
+ puts facet.name
97
+ facet.items.each do |item|
98
+ puts "#{facet.name}::#{item.value} (#{item.hits})"
99
+ end
100
+ end
101
+
102
+ You can access values in the response hash using symbols or strings.
103
+
104
+ ===Documents/Pagination
105
+ If you wanna paginate, just throw the collection into the WillPaginate view helper.
106
+ <%= will_paginate response.docs %>
107
+
108
+ ==The "Model" Module
109
+ You can create your own <read-only> "models" using RSolr::Ext::Model
110
+
111
+ class Book
112
+ include RSolr::Ext::Model
113
+ def self.find_by_author(author)
114
+ find(:fq=>'object_type:"book"', :rows=>10, :phrase_filters=>{:author=>author})
115
+ end
116
+ end
117
+
118
+ all_books = Book.find('*:*')
119
+ hawk_books = Book.find_by_author('hawk')
data/lib/mash.rb ADDED
@@ -0,0 +1,143 @@
1
+ # This class has dubious semantics and we only have it so that people can write
2
+ # params[:key] instead of params['key'].
3
+ class Mash < Hash
4
+
5
+ # @param constructor<Object>
6
+ # The default value for the mash. Defaults to an empty hash.
7
+ #
8
+ # @details [Alternatives]
9
+ # If constructor is a Hash, a new mash will be created based on the keys of
10
+ # the hash and no default value will be set.
11
+ def initialize(constructor = {})
12
+ if constructor.is_a?(Hash)
13
+ super()
14
+ update(constructor)
15
+ else
16
+ super(constructor)
17
+ end
18
+ end
19
+
20
+ # @param key<Object> The default value for the mash. Defaults to nil.
21
+ #
22
+ # @details [Alternatives]
23
+ # If key is a Symbol and it is a key in the mash, then the default value will
24
+ # be set to the value matching the key.
25
+ def default(key = nil)
26
+ if key.is_a?(Symbol) && include?(key = key.to_s)
27
+ self[key]
28
+ else
29
+ super
30
+ end
31
+ end
32
+
33
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
34
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
35
+
36
+ # @param key<Object> The key to set.
37
+ # @param value<Object>
38
+ # The value to set the key to.
39
+ #
40
+ # @see Mash#convert_key
41
+ # @see Mash#convert_value
42
+ def []=(key, value)
43
+ regular_writer(convert_key(key), convert_value(value))
44
+ end
45
+
46
+ # @param other_hash<Hash>
47
+ # A hash to update values in the mash with. The keys and the values will be
48
+ # converted to Mash format.
49
+ #
50
+ # @return <Mash> The updated mash.
51
+ def update(other_hash)
52
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
53
+ self
54
+ end
55
+
56
+ alias_method :merge!, :update
57
+
58
+ # @param key<Object> The key to check for. This will be run through convert_key.
59
+ #
60
+ # @return <TrueClass, FalseClass> True if the key exists in the mash.
61
+ def key?(key)
62
+ super(convert_key(key))
63
+ end
64
+
65
+ # def include? def has_key? def member?
66
+ alias_method :include?, :key?
67
+ alias_method :has_key?, :key?
68
+ alias_method :member?, :key?
69
+
70
+ # @param key<Object> The key to fetch. This will be run through convert_key.
71
+ # @param *extras<Array> Default value.
72
+ #
73
+ # @return <Object> The value at key or the default value.
74
+ def fetch(key, *extras)
75
+ super(convert_key(key), *extras)
76
+ end
77
+
78
+ # @param *indices<Array>
79
+ # The keys to retrieve values for. These will be run through +convert_key+.
80
+ #
81
+ # @return <Array> The values at each of the provided keys
82
+ def values_at(*indices)
83
+ indices.collect {|key| self[convert_key(key)]}
84
+ end
85
+
86
+ # @return <Mash> A duplicate of this mash.
87
+ def dup
88
+ Mash.new(self)
89
+ end
90
+
91
+ # @param hash<Hash> The hash to merge with the mash.
92
+ #
93
+ # @return <Mash> A new mash with the hash values merged in.
94
+ def merge(hash)
95
+ self.dup.update(hash)
96
+ end
97
+
98
+ # @param key<Object>
99
+ # The key to delete from the mash.\
100
+ def delete(key)
101
+ super(convert_key(key))
102
+ end
103
+
104
+ # Used to provide the same interface as Hash.
105
+ #
106
+ # @return <Mash> This mash unchanged.
107
+ def stringify_keys!; self end
108
+
109
+ # @return <Hash> The mash as a Hash with string keys.
110
+ def to_hash
111
+ Hash.new(default).merge(self)
112
+ end
113
+
114
+ protected
115
+ # @param key<Object> The key to convert.
116
+ #
117
+ # @param <Object>
118
+ # The converted key. If the key was a symbol, it will be converted to a
119
+ # string.
120
+ #
121
+ # @api private
122
+ def convert_key(key)
123
+ key.kind_of?(Symbol) ? key.to_s : key
124
+ end
125
+
126
+ # @param value<Object> The value to convert.
127
+ #
128
+ # @return <Object>
129
+ # The converted value. A Hash or an Array of hashes, will be converted to
130
+ # their Mash equivalents.
131
+ #
132
+ # @api private
133
+ def convert_value(value)
134
+ case value
135
+ when Hash
136
+ value.to_mash
137
+ when Array
138
+ value.collect { |e| convert_value(e) }
139
+ else
140
+ value
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,41 @@
1
+ module RSolr::Ext::Connection
2
+
3
+ # TWO modes of arguments:
4
+ #
5
+ # <request-handler-path>, <solr-params-hash>
6
+ # OR
7
+ # <solr-params-hash>
8
+ #
9
+ # The default request-handler-path is /select
10
+ #
11
+ # If a hash is used for solr params, all of the normal RSolr::Ext::Request
12
+ # mappings are available (everything else gets passed to solr).
13
+ # Returns a new RSolr::Ext::Response::Base object.
14
+ def find *args
15
+ # remove the handler arg - the first, if it is a string OR set default
16
+ path = args.first.is_a?(String) ? args.shift : '/select'
17
+ # remove the params - the first, if it is a Hash OR set default
18
+ params = args.first.kind_of?(Hash) ? args.shift : {}
19
+ # send path, map params and send the rest of the args along
20
+ response = self.request path, RSolr::Ext::Request.map(params), *args
21
+ RSolr::Ext::Response::Base.new(response)
22
+ end
23
+
24
+ # TWO modes of arguments:
25
+ #
26
+ # <request-handler-path>, <solr-params-hash>
27
+ # OR
28
+ # <solr-params-hash>
29
+ #
30
+ # The default request-handler-path is /admin/luke
31
+ # The default params are numTerms=0
32
+ #
33
+ # Returns a new Mash object.
34
+ def luke *args
35
+ path = args.first.is_a?(String) ? args.shift : '/admin/luke'
36
+ params = args.pop || {}
37
+ params['numTerms'] ||= 0
38
+ self.request(path, params).to_mash
39
+ end
40
+
41
+ end
@@ -0,0 +1,44 @@
1
+ module RSolr::Ext::Doc
2
+
3
+ # for easy access to the solr id (route helpers etc..)
4
+ def id
5
+ self['id']
6
+ end
7
+
8
+ # Helper method to check if value/multi-values exist for a given key.
9
+ # The value can be a string, or a RegExp
10
+ # Multiple "values" can be given; only one needs to match.
11
+ #
12
+ # Example:
13
+ # doc.has?(:location_facet)
14
+ # doc.has?(:location_facet, 'Clemons')
15
+ # doc.has?(:id, 'h009', /^u/i)
16
+ def has?(k, *values)
17
+ return if self[k].nil?
18
+ return true if self.key?(k) and values.empty?
19
+ target = self[k]
20
+ if target.is_a?(Array)
21
+ values.each do |val|
22
+ return target.any?{|tv| val.is_a?(Regexp) ? (tv =~ val) : (tv==val)}
23
+ end
24
+ else
25
+ return values.any? {|val| val.is_a?(Regexp) ? (target =~ val) : (target == val)}
26
+ end
27
+ end
28
+
29
+ # helper
30
+ # key is the name of the field
31
+ # opts is a hash with the following valid keys:
32
+ # - :sep - a string used for joining multivalued field values
33
+ # - :default - a value to return when the key doesn't exist
34
+ # if :sep is nil and the field is a multivalued field, the array is returned
35
+ def get key, opts={:sep=>', ', :default=>nil}
36
+ if self.key? key
37
+ val = self[key]
38
+ (val.is_a?(Array) and opts[:sep]) ? val.join(opts[:sep]) : val
39
+ else
40
+ opts[:default]
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,99 @@
1
+ # include this module into a plain ruby class:
2
+ # class Book
3
+ # include RSolr::Ext::Model
4
+ # connection = RSolr::Ext.connect
5
+ # default_params = {:phrase_filters=>'type:book'}
6
+ # end
7
+ #
8
+ # Then:
9
+ # number_10 = Book.find_by_id(10)
10
+ #
11
+ module RSolr::Ext::Model
12
+
13
+ # Class level methods for altering object instances
14
+ module Callbacks
15
+
16
+ # method that only accepts a block
17
+ # The block is executed when an object is created via #new -> SolrDoc.new
18
+ # The blocks scope is the instance of the object.
19
+ def after_initialize(&blk)
20
+ hooks << blk
21
+ end
22
+
23
+ # Removes the current set of after_initialize blocks.
24
+ # You would use this if you wanted to open a class back up,
25
+ # but clear out the previously defined blocks.
26
+ def clear_after_initialize_blocks!
27
+ @hooks = []
28
+ end
29
+
30
+ # creates the @hooks container ("hooks" are blocks or procs).
31
+ # returns an array
32
+ def hooks
33
+ @hooks ||= []
34
+ end
35
+
36
+ end
37
+
38
+ #
39
+ # Findable is a module that gets mixed into the SolrDocument class object.
40
+ # These methods will be available through the class like: SolrDocument.find and SolrDocument.find_by_id
41
+ #
42
+ module Findable
43
+
44
+ attr_accessor :connection, :default_params
45
+
46
+ def connection
47
+ @connection ||= RSolr::Ext.connect
48
+ end
49
+
50
+ # this method decorates the connection find method
51
+ # and then creates new instance of the class that uses this module.
52
+ def find(*args)
53
+ decorate_response_docs connection.find(*args)
54
+ end
55
+
56
+ # this method decorates the connection find_by_id method
57
+ # and then creates new instance of the class that uses this module.
58
+ def find_by_id(id, solr_params={}, opts={})
59
+ decorate_response_docs connection.find_by_id(id, solr_params, opts)
60
+ end
61
+
62
+ protected
63
+
64
+ def decorate_response_docs response
65
+ response['response']['docs'].map!{|d| self.new d }
66
+ response
67
+ end
68
+
69
+ end
70
+
71
+ # Called by Ruby Module API
72
+ # extends this *class* object
73
+ def self.included(base)
74
+ base.extend Callbacks
75
+ base.extend Findable
76
+ base.send :include, RSolr::Ext::Doc
77
+ end
78
+
79
+ # The original object passed in to the #new method
80
+ attr :_source
81
+
82
+ # Constructor **for the class that is getting this module included**
83
+ # source_doc should be a hash or something similar
84
+ # calls each of after_initialize blocks
85
+ def initialize(source_doc={})
86
+ @_source = source_doc.to_mash
87
+ self.class.hooks.each do |h|
88
+ instance_eval &h
89
+ end
90
+ end
91
+
92
+ # the wrapper method to the @_source object.
93
+ # If a method is missing, it gets sent to @_source
94
+ # with all of the original params and block
95
+ def method_missing(m, *args, &b)
96
+ @_source.send(m, *args, &b)
97
+ end
98
+
99
+ end
@@ -0,0 +1,101 @@
1
+ module RSolr::Ext::Request
2
+
3
+ module Params
4
+
5
+ def map input
6
+ output = {}
7
+ if input[:per_page]
8
+ output[:rows] = input.delete :per_page
9
+ end
10
+
11
+ if page = input.delete(:page)
12
+ raise ':per_page must be set when using :page' unless output[:rows]
13
+ page = page.to_s.to_i-1
14
+ page = page < 1 ? 0 : page
15
+ output[:start] = page * output[:rows]
16
+ end
17
+
18
+ if queries = input.delete(:queries)
19
+ output[:q] = append_to_param output[:q], build_query(queries, false)
20
+ end
21
+ if phrases = input.delete(:phrases)
22
+ output[:q] = append_to_param output[:q], build_query(phrases, true)
23
+ end
24
+ if filters = input.delete(:filters)
25
+ output[:fq] = append_to_param output[:fq], build_query(filters), false
26
+ end
27
+ if phrase_filters = input.delete(:phrase_filters)
28
+ output[:fq] = append_to_param output[:fq], build_query(phrase_filters, true), false
29
+ end
30
+ if facets = input.delete(:facets)
31
+ output[:facet] = true
32
+ output['facet.field'] = append_to_param output['facet.field'], build_query(facets.values), false
33
+ end
34
+ output.merge input
35
+ end
36
+
37
+ end
38
+
39
+ module QueryHelpers
40
+
41
+ # Wraps a string around double quotes
42
+ def quote(value)
43
+ %("#{value}")
44
+ end
45
+
46
+ # builds a solr range query from a Range object
47
+ def build_range(r)
48
+ "[#{r.min} TO #{r.max}]"
49
+ end
50
+
51
+ # builds a solr query fragment
52
+ # if "quote_string" is true, the values will be quoted.
53
+ # if "value" is a string/symbol, the #to_s method is called
54
+ # if the "value" is an array, each item in the array is
55
+ # send to build_query (recursive)
56
+ # if the "value" is a Hash, a fielded query is built
57
+ # where the keys are used as the field names and
58
+ # the values are either processed as a Range or
59
+ # passed back into build_query (recursive)
60
+ def build_query(value, quote_string=false)
61
+ case value
62
+ when String,Symbol
63
+ quote_string ? quote(value.to_s) : value.to_s
64
+ when Array
65
+ value.collect do |v|
66
+ build_query(v, quote_string)
67
+ end.flatten
68
+ when Hash
69
+ return value.collect do |(k,v)|
70
+ if v.is_a?(Range)
71
+ "#{k}:#{build_range(v)}"
72
+ # If the value is an array, we want the same param, multiple times (not a query join)
73
+ elsif v.is_a?(Array)
74
+ v.collect do |vv|
75
+ "#{k}:#{build_query(vv, quote_string)}"
76
+ end
77
+ else
78
+ "#{k}:#{build_query(v, quote_string)}"
79
+ end
80
+ end.flatten
81
+ end
82
+ end
83
+
84
+ # creates an array where the "existing_value" param is first
85
+ # and the "new_value" is the last.
86
+ # All empty/nil items are removed.
87
+ # the return result is either the result of the
88
+ # array being joined on a space, or the array itself.
89
+ # "auto_join" should be true or false.
90
+ def append_to_param(existing_value, new_value, auto_join=true)
91
+ values = [existing_value, new_value]
92
+ values.delete_if{|v|v.nil?}
93
+ auto_join ? values.join(' ') : values.flatten
94
+ end
95
+
96
+ end
97
+
98
+ extend QueryHelpers
99
+ extend Params
100
+
101
+ end
@@ -0,0 +1,56 @@
1
+ module RSolr::Ext::Response::Docs
2
+
3
+ module Pageable
4
+
5
+ attr_accessor :start, :per_page, :total
6
+
7
+ # Returns the current page calculated from 'rows' and 'start'
8
+ # WillPaginate hook
9
+ def current_page
10
+ return 1 if start < 1
11
+ per_page_normalized = per_page < 1 ? 1 : per_page
12
+ @current_page ||= (start / per_page_normalized).ceil + 1
13
+ end
14
+
15
+ # Calcuates the total pages from 'numFound' and 'rows'
16
+ # WillPaginate hook
17
+ def total_pages
18
+ @total_pages ||= per_page > 0 ? (total / per_page.to_f).ceil : 1
19
+ end
20
+
21
+ # returns the previous page number or 1
22
+ # WillPaginate hook
23
+ def previous_page
24
+ @previous_page ||= (current_page > 1) ? current_page - 1 : 1
25
+ end
26
+
27
+ # returns the next page number or the last
28
+ # WillPaginate hook
29
+ def next_page
30
+ @next_page ||= (current_page == total_pages) ? total_pages : current_page+1
31
+ end
32
+
33
+ def has_next?
34
+ current_page < total_pages
35
+ end
36
+
37
+ def has_previous?
38
+ current_page > 1
39
+ end
40
+
41
+ end
42
+
43
+ def self.extended(base)
44
+ d = base['response']['docs']
45
+ d.each{|doc| doc.extend RSolr::Ext::Doc }
46
+ d.extend Pageable
47
+ d.per_page = base['responseHeader']['params']['rows'].to_s.to_i
48
+ d.start = base['response']['start'].to_s.to_i
49
+ d.total = base['response']['numFound'].to_s.to_i
50
+ end
51
+
52
+ def docs
53
+ response['docs']
54
+ end
55
+
56
+ end
@@ -0,0 +1,62 @@
1
+ module RSolr::Ext::Response::Facets
2
+
3
+ # represents a facet value; which is a field value and its hit count
4
+ FacetItem = Struct.new :value,:hits
5
+
6
+ # represents a facet; which is a field and its values
7
+ FacetField = Struct.new :name, :items do
8
+ def items; @items ||= [] end
9
+ end
10
+
11
+ # @response.facets.each do |facet|
12
+ # facet.field
13
+ # end
14
+ # "caches" the result in the @facets instance var
15
+ def facets
16
+ # memoize!
17
+ @facets ||= (
18
+ all = facet_fields.collect do |(facet_field_name,values_and_hits_list)|
19
+ facet = FacetField.new(facet_field_name)
20
+ # the values_and_hits_list is an array where a value is immediately followed by it's hit count
21
+ # so we shift off an item (the value)
22
+ while value = values_and_hits_list.shift
23
+ # and then shift off the next to get the hit value
24
+ facet.items << FacetItem.new(value, values_and_hits_list.shift)
25
+ # repeat until there are no more pairs in the values_and_hits_list array
26
+ end
27
+ facet
28
+ end
29
+ #all.extend RSolr::Ext::Response::Docs::Pageable
30
+ #all.start = header['params']['facet.offset'].to_s.to_i
31
+ #all.per_page = header['params']['facet.limit'].to_s.to_i - 1
32
+ #all.total = -1
33
+ ## override the has_next? method -- when paging through facets,
34
+ ## it's not possible to know how many "pages" there are
35
+ #all.instance_eval "def has_next?; #{all.size == all.per_page+1} end"
36
+ all
37
+ )
38
+ end
39
+
40
+ # pass in a facet field name and get back a Facet instance
41
+ def facet_by_field_name(name)
42
+ @facets_by_field_name ||= {}
43
+ @facets_by_field_name[name] ||= (
44
+ facets.detect{|facet|facet.name.to_s == name.to_s}
45
+ )
46
+ end
47
+
48
+ def facet_counts
49
+ @facet_counts ||= self['facet_counts'] || {}
50
+ end
51
+
52
+ # Returns the hash of all the facet_fields (ie: {'instock_b' => ['true', 123, 'false', 20]}
53
+ def facet_fields
54
+ @facet_fields ||= facet_counts['facet_fields'] || {}
55
+ end
56
+
57
+ # Returns all of the facet queries
58
+ def facet_queries
59
+ @facet_queries ||= facet_counts['facet_queries'] || {}
60
+ end
61
+
62
+ end # end Facets
@@ -0,0 +1,65 @@
1
+ # A mixin for making access to the spellcheck component data easy.
2
+ #
3
+ # response.spelling.words
4
+ #
5
+ module RSolr::Ext::Response::Spelling
6
+
7
+ def spelling
8
+ @spelling ||= Base.new(self)
9
+ end
10
+
11
+ class Base
12
+
13
+ attr :response
14
+
15
+ def initialize(response)
16
+ @response = response
17
+ end
18
+
19
+ # returns an array of spelling suggestion for specific query words,
20
+ # as provided in the solr response. Only includes words with higher
21
+ # frequency of occurrence than word in original query.
22
+ # can't do a full query suggestion because we only get info for each word;
23
+ # combination of words may not have results.
24
+ # Thanks to Naomi Dushay!
25
+ def words
26
+ @words ||= (
27
+ word_suggestions = []
28
+ spellcheck = self.response[:spellcheck]
29
+ if spellcheck && spellcheck[:suggestions]
30
+ suggestions = spellcheck[:suggestions]
31
+ unless suggestions.nil?
32
+ # suggestions is an array:
33
+ # (query term)
34
+ # (hash of term info and term suggestion)
35
+ # ...
36
+ # (query term)
37
+ # (hash of term info and term suggestion)
38
+ # 'correctlySpelled'
39
+ # true/false
40
+ # collation
41
+ # (suggestion for collation)
42
+ i_stop = suggestions.index("correctlySpelled")
43
+ # step through array in 2s to get info for each term
44
+ 0.step(i_stop-1, 2) do |i|
45
+ term = suggestions[i]
46
+ term_info = suggestions[i+1]
47
+ # term_info is a hash:
48
+ # numFound =>
49
+ # startOffset =>
50
+ # endOffset =>
51
+ # origFreq =>
52
+ # suggestion => { frequency =>, word => }
53
+ origFreq = term_info['origFreq']
54
+ suggFreq = term_info['suggestion']['frequency']
55
+ word_suggestions << term_info['suggestion']['word'] if suggFreq > origFreq
56
+ end
57
+ end
58
+ end
59
+ word_suggestions.uniq
60
+ )
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,51 @@
1
+ module RSolr::Ext::Response
2
+
3
+ autoload :Docs, 'rsolr-ext/response/docs'
4
+ autoload :Facets, 'rsolr-ext/response/facets'
5
+ autoload :Spelling, 'rsolr-ext/response/spelling'
6
+
7
+ class Base < Mash
8
+
9
+ attr :original_hash
10
+
11
+ def initialize hash
12
+ super hash
13
+ @original_hash = hash
14
+ extend Response# if self['response']
15
+ extend Docs# if self['response'] and self['response']['docs']
16
+ extend Facets# if self['facet_counts']
17
+ extend Spelling# if self['spellcheck']
18
+ end
19
+
20
+ def header
21
+ self['responseHeader']
22
+ end
23
+
24
+ def params
25
+ header['params']
26
+ end
27
+
28
+ def ok?
29
+ header['status'] == 0
30
+ end
31
+
32
+ def method_missing *args, &blk
33
+ self.original_hash.send *args, &blk
34
+ end
35
+
36
+ end
37
+
38
+ module Response
39
+
40
+ def response
41
+ self[:response]
42
+ end
43
+
44
+ # short cut to response['numFound']
45
+ def total
46
+ response[:numFound]
47
+ end
48
+
49
+ end
50
+
51
+ end
data/lib/rsolr-ext.rb ADDED
@@ -0,0 +1,42 @@
1
+ # add this directory to the load path if it hasn't already been added
2
+
3
+ lambda { |base|
4
+ $: << base unless $:.include?(base) || $:.include?(File.expand_path(base))
5
+ }.call(File.dirname(__FILE__))
6
+
7
+ require 'mash' unless defined?(Mash)
8
+
9
+ unless Hash.respond_to?(:to_mash)
10
+ class Hash
11
+ def to_mash
12
+ Mash.new(self)
13
+ end
14
+ end
15
+ end
16
+
17
+ require 'rubygems'
18
+ require 'rsolr'
19
+
20
+ module RSolr
21
+
22
+ module Ext
23
+
24
+ VERSION = '0.9.5'
25
+
26
+ autoload :Connection, 'rsolr-ext/connection.rb'
27
+ autoload :Doc, 'rsolr-ext/doc.rb'
28
+ autoload :Request, 'rsolr-ext/request.rb'
29
+ autoload :Response, 'rsolr-ext/response.rb'
30
+ autoload :Model, 'rsolr-ext/model.rb'
31
+
32
+ # c = RSolr::Ext.connect
33
+ # c.find(:q=>'*:*').docs.size
34
+ def self.connect(*args)
35
+ connection = RSolr.connect(*args)
36
+ connection.extend RSolr::Ext::Connection
37
+ connection
38
+ end
39
+
40
+ end
41
+
42
+ end
data/rsolr-ext.gemspec ADDED
@@ -0,0 +1,39 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "rsolr-ext"
3
+ s.version = "0.9.6.3"
4
+ s.date = "2009-09-30"
5
+
6
+ s.summary = "An extension lib for RSolr"
7
+ s.email = "goodieboy@gmail.com"
8
+ s.homepage = "http://github.com/mwmitchell/rsolr-ext"
9
+ s.description = "An extension lib for RSolr"
10
+ s.has_rdoc = true
11
+ s.authors = ["Matt Mitchell"]
12
+ s.files = [
13
+ "lib/mash.rb",
14
+ "lib/rsolr-ext/connection.rb",
15
+ "lib/rsolr-ext/doc.rb",
16
+ "lib/rsolr-ext/model.rb",
17
+ "lib/rsolr-ext/request.rb",
18
+ "lib/rsolr-ext/response/docs.rb",
19
+ "lib/rsolr-ext/response/facets.rb",
20
+ "lib/rsolr-ext/response/spelling.rb",
21
+ "lib/rsolr-ext/response.rb",
22
+ "lib/rsolr-ext.rb",
23
+ "LICENSE",
24
+ "README.rdoc",
25
+ "rsolr-ext.gemspec"
26
+ ]
27
+ s.test_files = [
28
+ 'test/connection_test.rb',
29
+ 'test/request_test.rb',
30
+ 'test/response_test.rb',
31
+ 'test/test_unit_test_case.rb',
32
+ 'test/helper.rb'
33
+ ]
34
+
35
+ s.extra_rdoc_files = %w(LICENSE README.rdoc)
36
+
37
+ s.add_dependency("mwmitchell-rsolr", [">= 0.9.6"])
38
+
39
+ end
@@ -0,0 +1,40 @@
1
+ require 'test_unit_test_case'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rsolr-ext')
3
+ require 'helper'
4
+
5
+ class RSolrExtConnectionTest < Test::Unit::TestCase
6
+
7
+ test 'the #connect method' do
8
+ connection = RSolr::Ext.connect
9
+ assert connection.respond_to?(:find)
10
+ end
11
+
12
+ test 'the #find method' do
13
+ connection = RSolr::Ext.connect
14
+ response = connection.find :q=>'*:*'
15
+ assert response.kind_of?(Mash)
16
+ end
17
+
18
+ test 'the #find method with a custom request handler' do
19
+ connection = RSolr::Ext.connect
20
+ response = connection.find '/select', :q=>'*:*'
21
+ assert response.adapter_response[:path]=~/\/select/
22
+ end
23
+
24
+ test 'the response' do
25
+ connection = RSolr::Ext.connect
26
+ response = connection.find :q=>'*:*'
27
+ assert response.respond_to?(:ok?)
28
+ assert response.ok?
29
+ assert_equal response.docs[0][:id], response.docs[0].id
30
+ end
31
+
32
+ test 'the #luke method' do
33
+ info = RSolr::Ext.connect.luke
34
+ assert info.kind_of?(Mash)
35
+ assert info.key?('fields')
36
+ assert info.key?('index')
37
+ assert info.key?('info')
38
+ end
39
+
40
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,3 @@
1
+ def mock_query_response
2
+ %({'responseHeader'=>{'status'=>0,'QTime'=>5,'params'=>{'facet.limit'=>'10','wt'=>'ruby','rows'=>'11','facet'=>'true','facet.field'=>['manu','cat'],'echoParams'=>'EXPLICIT','q'=>'*:*','facet.sort'=>'true'}},'response'=>{'numFound'=>26,'start'=>0,'docs'=>[{'id'=>'SP2514N','inStock'=>true,'manu'=>'Samsung Electronics Co. Ltd.','name'=>'Samsung SpinPoint P120 SP2514N - hard drive - 250 GB - ATA-133','popularity'=>6,'price'=>92.0,'sku'=>'SP2514N','timestamp'=>'2009-03-20T14:42:49.795Z','cat'=>['electronics','hard drive'],'spell'=>['Samsung SpinPoint P120 SP2514N - hard drive - 250 GB - ATA-133'],'features'=>['7200RPM, 8MB cache, IDE Ultra ATA-133','NoiseGuard, SilentSeek technology, Fluid Dynamic Bearing (FDB) motor']},{'id'=>'6H500F0','inStock'=>true,'manu'=>'Maxtor Corp.','name'=>'Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300','popularity'=>6,'price'=>350.0,'sku'=>'6H500F0','timestamp'=>'2009-03-20T14:42:49.877Z','cat'=>['electronics','hard drive'],'spell'=>['Maxtor DiamondMax 11 - hard drive - 500 GB - SATA-300'],'features'=>['SATA 3.0Gb/s, NCQ','8.5ms seek','16MB cache']},{'id'=>'F8V7067-APL-KIT','inStock'=>false,'manu'=>'Belkin','name'=>'Belkin Mobile Power Cord for iPod w/ Dock','popularity'=>1,'price'=>19.95,'sku'=>'F8V7067-APL-KIT','timestamp'=>'2009-03-20T14:42:49.937Z','weight'=>4.0,'cat'=>['electronics','connector'],'spell'=>['Belkin Mobile Power Cord for iPod w/ Dock'],'features'=>['car power adapter, white']},{'id'=>'IW-02','inStock'=>false,'manu'=>'Belkin','name'=>'iPod & iPod Mini USB 2.0 Cable','popularity'=>1,'price'=>11.5,'sku'=>'IW-02','timestamp'=>'2009-03-20T14:42:49.944Z','weight'=>2.0,'cat'=>['electronics','connector'],'spell'=>['iPod & iPod Mini USB 2.0 Cable'],'features'=>['car power adapter for iPod, white']},{'id'=>'MA147LL/A','inStock'=>true,'includes'=>'earbud headphones, USB cable','manu'=>'Apple Computer Inc.','name'=>'Apple 60 GB iPod with Video Playback Black','popularity'=>10,'price'=>399.0,'sku'=>'MA147LL/A','timestamp'=>'2009-03-20T14:42:49.962Z','weight'=>5.5,'cat'=>['electronics','music'],'spell'=>['Apple 60 GB iPod with Video Playback Black'],'features'=>['iTunes, Podcasts, Audiobooks','Stores up to 15,000 songs, 25,000 photos, or 150 hours of video','2.5-inch, 320x240 color TFT LCD display with LED backlight','Up to 20 hours of battery life','Plays AAC, MP3, WAV, AIFF, Audible, Apple Lossless, H.264 video','Notes, Calendar, Phone book, Hold button, Date display, Photo wallet, Built-in games, JPEG photo playback, Upgradeable firmware, USB 2.0 compatibility, Playback speed control, Rechargeable capability, Battery level indication']},{'id'=>'TWINX2048-3200PRO','inStock'=>true,'manu'=>'Corsair Microsystems Inc.','name'=>'CORSAIR XMS 2GB (2 x 1GB) 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) Dual Channel Kit System Memory - Retail','popularity'=>5,'price'=>185.0,'sku'=>'TWINX2048-3200PRO','timestamp'=>'2009-03-20T14:42:49.99Z','cat'=>['electronics','memory'],'spell'=>['CORSAIR XMS 2GB (2 x 1GB) 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) Dual Channel Kit System Memory - Retail'],'features'=>['CAS latency 2, 2-3-3-6 timing, 2.75v, unbuffered, heat-spreader']},{'id'=>'VS1GB400C3','inStock'=>true,'manu'=>'Corsair Microsystems Inc.','name'=>'CORSAIR ValueSelect 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - Retail','popularity'=>7,'price'=>74.99,'sku'=>'VS1GB400C3','timestamp'=>'2009-03-20T14:42:50Z','cat'=>['electronics','memory'],'spell'=>['CORSAIR ValueSelect 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - Retail']},{'id'=>'VDBDB1A16','inStock'=>true,'manu'=>'A-DATA Technology Inc.','name'=>'A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM','popularity'=>5,'sku'=>'VDBDB1A16','timestamp'=>'2009-03-20T14:42:50.004Z','cat'=>['electronics','memory'],'spell'=>['A-DATA V-Series 1GB 184-Pin DDR SDRAM Unbuffered DDR 400 (PC 3200) System Memory - OEM'],'features'=>['CAS latency 3, 2.7v']},{'id'=>'3007WFP','inStock'=>true,'includes'=>'USB cable','manu'=>'Dell, Inc.','name'=>'Dell Widescreen UltraSharp 3007WFP','popularity'=>6,'price'=>2199.0,'sku'=>'3007WFP','timestamp'=>'2009-03-20T14:42:50.017Z','weight'=>401.6,'cat'=>['electronics','monitor'],'spell'=>['Dell Widescreen UltraSharp 3007WFP'],'features'=>['30" TFT active matrix LCD, 2560 x 1600, .25mm dot pitch, 700:1 contrast']},{'id'=>'VA902B','inStock'=>true,'manu'=>'ViewSonic Corp.','name'=>'ViewSonic VA902B - flat panel display - TFT - 19"','popularity'=>6,'price'=>279.95,'sku'=>'VA902B','timestamp'=>'2009-03-20T14:42:50.034Z','weight'=>190.4,'cat'=>['electronics','monitor'],'spell'=>['ViewSonic VA902B - flat panel display - TFT - 19"'],'features'=>['19" TFT active matrix LCD, 8ms response time, 1280 x 1024 native resolution']},{'id'=>'0579B002','inStock'=>true,'manu'=>'Canon Inc.','name'=>'Canon PIXMA MP500 All-In-One Photo Printer','popularity'=>6,'price'=>179.99,'sku'=>'0579B002','timestamp'=>'2009-03-20T14:42:50.062Z','weight'=>352.0,'cat'=>['electronics','multifunction printer','printer','scanner','copier'],'spell'=>['Canon PIXMA MP500 All-In-One Photo Printer'],'features'=>['Multifunction ink-jet color photo printer','Flatbed scanner, optical scan resolution of 1,200 x 2,400 dpi','2.5" color LCD preview screen','Duplex Copying','Printing speed up to 29ppm black, 19ppm color','Hi-Speed USB','memory card: CompactFlash, Micro Drive, SmartMedia, Memory Stick, Memory Stick Pro, SD Card, and MultiMediaCard']}]},'facet_counts'=>{'facet_queries'=>{},'facet_fields'=>{'manu'=>['inc',8,'apach',2,'belkin',2,'canon',2,'comput',2,'corp',2,'corsair',2,'foundat',2,'microsystem',2,'softwar',2],'cat'=>['electronics',14,'memory',3,'card',2,'connector',2,'drive',2,'graphics',2,'hard',2,'monitor',2,'search',2,'software',2]},'facet_dates'=>{}}})
3
+ end
@@ -0,0 +1,33 @@
1
+ require 'test_unit_test_case'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rsolr-ext')
3
+
4
+ class RSolrExtRequestTest < Test::Unit::TestCase
5
+
6
+ test 'standard request' do
7
+ solr_params = RSolr::Ext::Request.map(
8
+ :page=>2,
9
+ :per_page=>10,
10
+ :phrases=>{:name=>'This is a phrase'},
11
+ :filters=>['test', {:price=>(1..10)}],
12
+ :phrase_filters=>{:manu=>['Apple']},
13
+ :queries=>'ipod',
14
+ :facets=>{:fields=>['cat', 'blah']},
15
+ :spellcheck => true
16
+ )
17
+ assert_equal ["test", "price:[1 TO 10]", "manu:\"Apple\""], solr_params[:fq]
18
+ assert_equal 10, solr_params[:start]
19
+ assert_equal 10, solr_params[:rows]
20
+ assert_equal "ipod name:\"This is a phrase\"", solr_params[:q]
21
+ assert_equal ['cat', 'blah'], solr_params['facet.field']
22
+ assert_equal true, solr_params[:facet]
23
+ end
24
+
25
+ test 'fq param using the phrase_filters mapping' do
26
+ solr_params = RSolr::Ext::Request.map(
27
+ :phrase_filters=>{:manu=>['Apple', 'ASG'], :color=>['red', 'blue']}
28
+ )
29
+ expected = {:fq=>["color:\"red\"", "color:\"blue\"", "manu:\"Apple\"", "manu:\"ASG\""]}
30
+ assert expected, solr_params
31
+ end
32
+
33
+ end
@@ -0,0 +1,105 @@
1
+ require 'test_unit_test_case'
2
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'rsolr-ext')
3
+ require 'helper'
4
+
5
+ class RSolrExtResponseTest < Test::Unit::TestCase
6
+
7
+ test 'base response class' do
8
+ raw_response = eval(mock_query_response)
9
+ r = RSolr::Ext::Response::Base.new(raw_response)
10
+ assert r.respond_to?(:header)
11
+ assert r.ok?
12
+ end
13
+
14
+ test 'standard response class' do
15
+ raw_response = eval(mock_query_response)
16
+ r = RSolr::Ext::Response::Base.new(raw_response)
17
+ assert r.respond_to?(:response)
18
+ assert r.ok?
19
+ assert_equal 11, r.docs.size
20
+ assert_equal 'EXPLICIT', r.params[:echoParams]
21
+ assert_equal 1, r.docs.previous_page
22
+ assert_equal 2, r.docs.next_page
23
+ #
24
+ assert r.kind_of?(RSolr::Ext::Response::Docs)
25
+ assert r.kind_of?(RSolr::Ext::Response::Facets)
26
+ end
27
+
28
+ test 'standard response doc ext methods' do
29
+ raw_response = eval(mock_query_response)
30
+ r = RSolr::Ext::Response::Base.new(raw_response)
31
+ doc = r.docs.first
32
+ assert doc.has?(:cat, /^elec/)
33
+ assert ! doc.has?(:cat, 'elec')
34
+ assert doc.has?(:cat, 'electronics')
35
+
36
+ assert 'electronics', doc.get(:cat)
37
+ assert_nil doc.get(:xyz)
38
+ assert_equal 'def', doc.get(:xyz, :default=>'def')
39
+ end
40
+
41
+ test 'Response::Standard facets' do
42
+ raw_response = eval(mock_query_response)
43
+ r = RSolr::Ext::Response::Base.new(raw_response)
44
+ assert_equal 2, r.facets.size
45
+
46
+ field_names = r.facets.collect{|facet|facet.name}
47
+ assert field_names.include?('cat')
48
+ assert field_names.include?('manu')
49
+
50
+ first_facet = r.facets.first
51
+ assert_equal 'cat', first_facet.name
52
+ assert_equal 10, first_facet.items.size
53
+
54
+ expected = first_facet.items.collect do |item|
55
+ item.value + ' - ' + item.hits.to_s
56
+ end.join(', ')
57
+ assert_equal "electronics - 14, memory - 3, card - 2, connector - 2, drive - 2, graphics - 2, hard - 2, monitor - 2, search - 2, software - 2", expected
58
+
59
+ r.facets.each do |facet|
60
+ assert facet.respond_to?(:name)
61
+ facet.items.each do |item|
62
+ assert item.respond_to?(:value)
63
+ assert item.respond_to?(:hits)
64
+ end
65
+ end
66
+
67
+ end
68
+
69
+ test 'response::standard facet_by_field_name' do
70
+ raw_response = eval(mock_query_response)
71
+ r = RSolr::Ext::Response::Base.new(raw_response)
72
+ facet = r.facet_by_field_name('cat')
73
+ assert_equal 'cat', facet.name
74
+ end
75
+
76
+ =begin
77
+
78
+ # pagination for facets has been commented out in the response/facets module.
79
+ # ...need to think more about how this can be handled
80
+
81
+ test 'response::standard facets.paginate' do
82
+ raw_response = eval(mock_query_response)
83
+ raw_response['responseHeader']['params']['facet.offset'] = 1
84
+ raw_response['responseHeader']['params']['facet.limit'] = 2
85
+
86
+ r = RSolr::Ext::Response::Standard.new(raw_response)
87
+
88
+ assert_equal 2, r.facets.current_page
89
+
90
+ # always 1 less than facet.limit
91
+ assert_equal 1, r.facets.per_page
92
+
93
+ assert_equal 3, r.facets.next_page
94
+
95
+ assert_equal 1, r.facets.previous_page
96
+
97
+ # can't know how many pages there are with facets.... so we set it to -1
98
+ assert_equal -1, r.facets.total_pages
99
+
100
+ assert r.facets.has_next?
101
+ assert r.facets.has_previous?
102
+ end
103
+ =end
104
+
105
+ 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,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rsolr-ext
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.6.3
5
+ platform: ruby
6
+ authors:
7
+ - Matt Mitchell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-09-30 00:00:00 -04:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: mwmitchell-rsolr
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.6
24
+ version:
25
+ description: An extension lib for RSolr
26
+ email: goodieboy@gmail.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.rdoc
34
+ files:
35
+ - lib/mash.rb
36
+ - lib/rsolr-ext/connection.rb
37
+ - lib/rsolr-ext/doc.rb
38
+ - lib/rsolr-ext/model.rb
39
+ - lib/rsolr-ext/request.rb
40
+ - lib/rsolr-ext/response/docs.rb
41
+ - lib/rsolr-ext/response/facets.rb
42
+ - lib/rsolr-ext/response/spelling.rb
43
+ - lib/rsolr-ext/response.rb
44
+ - lib/rsolr-ext.rb
45
+ - LICENSE
46
+ - README.rdoc
47
+ - rsolr-ext.gemspec
48
+ has_rdoc: true
49
+ homepage: http://github.com/mwmitchell/rsolr-ext
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options: []
54
+
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.4
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: An extension lib for RSolr
76
+ test_files:
77
+ - test/connection_test.rb
78
+ - test/request_test.rb
79
+ - test/response_test.rb
80
+ - test/test_unit_test_case.rb
81
+ - test/helper.rb