gojee-sunspot 2.0.3 → 2.0.4

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 (178) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +5 -0
  3. data/History.txt +252 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +13 -0
  6. data/TODO +13 -0
  7. data/lib/light_config.rb +40 -0
  8. data/lib/sunspot/adapters.rb +265 -0
  9. data/lib/sunspot/batcher.rb +62 -0
  10. data/lib/sunspot/class_set.rb +23 -0
  11. data/lib/sunspot/composite_setup.rb +202 -0
  12. data/lib/sunspot/configuration.rb +53 -0
  13. data/lib/sunspot/data_extractor.rb +50 -0
  14. data/lib/sunspot/dsl/adjustable.rb +47 -0
  15. data/lib/sunspot/dsl/field_group.rb +57 -0
  16. data/lib/sunspot/dsl/field_query.rb +327 -0
  17. data/lib/sunspot/dsl/fields.rb +103 -0
  18. data/lib/sunspot/dsl/fulltext.rb +243 -0
  19. data/lib/sunspot/dsl/function.rb +27 -0
  20. data/lib/sunspot/dsl/functional.rb +44 -0
  21. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  22. data/lib/sunspot/dsl/paginatable.rb +32 -0
  23. data/lib/sunspot/dsl/query_facet.rb +36 -0
  24. data/lib/sunspot/dsl/restriction.rb +25 -0
  25. data/lib/sunspot/dsl/restriction_with_near.rb +160 -0
  26. data/lib/sunspot/dsl/scope.rb +217 -0
  27. data/lib/sunspot/dsl/search.rb +30 -0
  28. data/lib/sunspot/dsl/standard_query.rb +123 -0
  29. data/lib/sunspot/dsl.rb +5 -0
  30. data/lib/sunspot/field.rb +193 -0
  31. data/lib/sunspot/field_factory.rb +129 -0
  32. data/lib/sunspot/indexer.rb +136 -0
  33. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  34. data/lib/sunspot/query/bbox.rb +15 -0
  35. data/lib/sunspot/query/boost_query.rb +24 -0
  36. data/lib/sunspot/query/common_query.rb +96 -0
  37. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  38. data/lib/sunspot/query/connective.rb +206 -0
  39. data/lib/sunspot/query/date_field_facet.rb +14 -0
  40. data/lib/sunspot/query/dismax.rb +132 -0
  41. data/lib/sunspot/query/field_facet.rb +41 -0
  42. data/lib/sunspot/query/field_group.rb +36 -0
  43. data/lib/sunspot/query/filter.rb +38 -0
  44. data/lib/sunspot/query/function_query.rb +52 -0
  45. data/lib/sunspot/query/geo.rb +53 -0
  46. data/lib/sunspot/query/geofilt.rb +16 -0
  47. data/lib/sunspot/query/highlighting.rb +62 -0
  48. data/lib/sunspot/query/more_like_this.rb +61 -0
  49. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  50. data/lib/sunspot/query/pagination.rb +42 -0
  51. data/lib/sunspot/query/query_facet.rb +16 -0
  52. data/lib/sunspot/query/restriction.rb +262 -0
  53. data/lib/sunspot/query/scope.rb +9 -0
  54. data/lib/sunspot/query/sort.rb +109 -0
  55. data/lib/sunspot/query/sort_composite.rb +34 -0
  56. data/lib/sunspot/query/standard_query.rb +16 -0
  57. data/lib/sunspot/query/text_field_boost.rb +17 -0
  58. data/lib/sunspot/query.rb +11 -0
  59. data/lib/sunspot/schema.rb +151 -0
  60. data/lib/sunspot/search/abstract_search.rb +281 -0
  61. data/lib/sunspot/search/date_facet.rb +35 -0
  62. data/lib/sunspot/search/facet_row.rb +27 -0
  63. data/lib/sunspot/search/field_facet.rb +88 -0
  64. data/lib/sunspot/search/field_group.rb +32 -0
  65. data/lib/sunspot/search/group.rb +50 -0
  66. data/lib/sunspot/search/highlight.rb +38 -0
  67. data/lib/sunspot/search/hit.rb +150 -0
  68. data/lib/sunspot/search/hit_enumerable.rb +72 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +57 -0
  71. data/lib/sunspot/search/query_facet.rb +67 -0
  72. data/lib/sunspot/search/standard_search.rb +21 -0
  73. data/lib/sunspot/search.rb +9 -0
  74. data/lib/sunspot/session.rb +262 -0
  75. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  76. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  77. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  78. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -0
  79. data/lib/sunspot/session_proxy/multicore_session_proxy.rb +67 -0
  80. data/lib/sunspot/session_proxy/sharding_session_proxy.rb +222 -0
  81. data/lib/sunspot/session_proxy/silent_fail_session_proxy.rb +42 -0
  82. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  83. data/lib/sunspot/session_proxy.rb +95 -0
  84. data/lib/sunspot/setup.rb +350 -0
  85. data/lib/sunspot/text_field_setup.rb +29 -0
  86. data/lib/sunspot/type.rb +393 -0
  87. data/lib/sunspot/util.rb +252 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +579 -0
  90. data/log/.gitignore +1 -0
  91. data/pkg/.gitignore +1 -0
  92. data/script/console +10 -0
  93. data/spec/api/adapters_spec.rb +33 -0
  94. data/spec/api/batcher_spec.rb +112 -0
  95. data/spec/api/binding_spec.rb +50 -0
  96. data/spec/api/class_set_spec.rb +24 -0
  97. data/spec/api/hit_enumerable_spec.rb +47 -0
  98. data/spec/api/indexer/attributes_spec.rb +149 -0
  99. data/spec/api/indexer/batch_spec.rb +72 -0
  100. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  101. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  102. data/spec/api/indexer/fulltext_spec.rb +43 -0
  103. data/spec/api/indexer/removal_spec.rb +53 -0
  104. data/spec/api/indexer/spec_helper.rb +1 -0
  105. data/spec/api/indexer_spec.rb +14 -0
  106. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  107. data/spec/api/query/connectives_examples.rb +189 -0
  108. data/spec/api/query/dsl_spec.rb +18 -0
  109. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  110. data/spec/api/query/faceting_examples.rb +397 -0
  111. data/spec/api/query/fulltext_examples.rb +313 -0
  112. data/spec/api/query/function_spec.rb +79 -0
  113. data/spec/api/query/geo_examples.rb +68 -0
  114. data/spec/api/query/group_spec.rb +32 -0
  115. data/spec/api/query/highlighting_examples.rb +245 -0
  116. data/spec/api/query/more_like_this_spec.rb +140 -0
  117. data/spec/api/query/ordering_pagination_examples.rb +116 -0
  118. data/spec/api/query/scope_examples.rb +275 -0
  119. data/spec/api/query/spatial_examples.rb +27 -0
  120. data/spec/api/query/spec_helper.rb +1 -0
  121. data/spec/api/query/standard_spec.rb +29 -0
  122. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  123. data/spec/api/query/types_spec.rb +20 -0
  124. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  125. data/spec/api/search/faceting_spec.rb +360 -0
  126. data/spec/api/search/highlighting_spec.rb +69 -0
  127. data/spec/api/search/hits_spec.rb +131 -0
  128. data/spec/api/search/paginated_collection_spec.rb +36 -0
  129. data/spec/api/search/results_spec.rb +72 -0
  130. data/spec/api/search/search_spec.rb +23 -0
  131. data/spec/api/search/spec_helper.rb +1 -0
  132. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  133. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  134. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  135. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  136. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  137. data/spec/api/session_proxy/spec_helper.rb +9 -0
  138. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +39 -0
  139. data/spec/api/session_spec.rb +232 -0
  140. data/spec/api/spec_helper.rb +3 -0
  141. data/spec/api/sunspot_spec.rb +29 -0
  142. data/spec/ext.rb +11 -0
  143. data/spec/helpers/indexer_helper.rb +17 -0
  144. data/spec/helpers/integration_helper.rb +8 -0
  145. data/spec/helpers/mock_session_helper.rb +13 -0
  146. data/spec/helpers/query_helper.rb +26 -0
  147. data/spec/helpers/search_helper.rb +68 -0
  148. data/spec/integration/dynamic_fields_spec.rb +57 -0
  149. data/spec/integration/faceting_spec.rb +251 -0
  150. data/spec/integration/field_grouping_spec.rb +66 -0
  151. data/spec/integration/geospatial_spec.rb +85 -0
  152. data/spec/integration/highlighting_spec.rb +44 -0
  153. data/spec/integration/indexing_spec.rb +55 -0
  154. data/spec/integration/keyword_search_spec.rb +317 -0
  155. data/spec/integration/local_search_spec.rb +64 -0
  156. data/spec/integration/more_like_this_spec.rb +43 -0
  157. data/spec/integration/scoped_search_spec.rb +354 -0
  158. data/spec/integration/stored_fields_spec.rb +12 -0
  159. data/spec/integration/test_pagination.rb +43 -0
  160. data/spec/integration/unicode_spec.rb +15 -0
  161. data/spec/mocks/adapters.rb +32 -0
  162. data/spec/mocks/blog.rb +3 -0
  163. data/spec/mocks/comment.rb +21 -0
  164. data/spec/mocks/connection.rb +126 -0
  165. data/spec/mocks/mock_adapter.rb +30 -0
  166. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  167. data/spec/mocks/mock_record.rb +52 -0
  168. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  169. data/spec/mocks/photo.rb +11 -0
  170. data/spec/mocks/post.rb +86 -0
  171. data/spec/mocks/super_class.rb +2 -0
  172. data/spec/mocks/user.rb +13 -0
  173. data/spec/spec_helper.rb +40 -0
  174. data/sunspot.gemspec +42 -0
  175. data/tasks/rdoc.rake +27 -0
  176. data/tasks/schema.rake +19 -0
  177. data/tasks/todo.rake +4 -0
  178. metadata +261 -3
@@ -0,0 +1,16 @@
1
+ module Sunspot
2
+ module Query
3
+ class Geofilt
4
+ def initialize(field, lat, lon, radius, options = {})
5
+ @field, @lat, @lon, @radius, @options = field, lat, lon, radius, options
6
+ end
7
+
8
+ def to_params
9
+ func = @options[:bbox] ? "bbox" : "geofilt"
10
+
11
+ filter = "{!#{func} sfield=#{@field.indexed_name} pt=#{@lat},#{@lon} d=#{@radius}}"
12
+ {:fq => filter}
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,62 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A query component that builds parameters for requesting highlights
5
+ #
6
+ class Highlighting #:nodoc:
7
+ def initialize(fields=[], options={})
8
+ @fields = fields
9
+ @options = options
10
+ end
11
+
12
+ #
13
+ # Return Solr highlighting params
14
+ #
15
+ def to_params
16
+ params = {
17
+ :hl => 'on',
18
+ :"hl.simple.pre" => '@@@hl@@@',
19
+ :"hl.simple.post" => '@@@endhl@@@'
20
+ }
21
+ unless @fields.empty?
22
+ params[:"hl.fl"] = @fields.map { |field| field.indexed_name }
23
+ end
24
+ if max_snippets = @options[:max_snippets]
25
+ params.merge!(make_params('snippets', max_snippets))
26
+ end
27
+ if fragment_size = @options[:fragment_size]
28
+ params.merge!(make_params('fragsize', fragment_size))
29
+ end
30
+ if @options[:merge_contiguous_fragments]
31
+ params.merge!(make_params('mergeContiguous', 'true'))
32
+ end
33
+ if @options[:phrase_highlighter]
34
+ params.merge!(make_params('usePhraseHighlighter', 'true'))
35
+ if @options[:require_field_match]
36
+ params.merge!(make_params('requireFieldMatch', 'true'))
37
+ end
38
+ end
39
+ if formatter = @options[:formatter]
40
+ params.merge!(make_params('formatter', formatter))
41
+ end
42
+ if fragmenter = @options[:fragmenter]
43
+ params.merge!(make_params('fragmenter', fragmenter))
44
+ end
45
+
46
+ params
47
+ end
48
+
49
+ private
50
+
51
+ def make_params(name, value)
52
+ if @fields.empty?
53
+ { :"hl.#{name}" => value }
54
+ else
55
+ @fields.inject({}) do |hash, field|
56
+ hash.merge!(:"f.#{field.indexed_name}.hl.#{name}" => value)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ module Sunspot
2
+ module Query
3
+ class MoreLikeThis
4
+ attr_reader :fields
5
+
6
+ def initialize(document)
7
+ @document_scope = Restriction::EqualTo.new(
8
+ false,
9
+ IdField.instance,
10
+ Adapters::InstanceAdapter.adapt(document).index_id
11
+ )
12
+ @fields = {}
13
+ @params = {}
14
+ end
15
+
16
+ def add_field(field, boost = nil)
17
+ raise(ArgumentError, "Field #{field.name} is not set up for more_like_this") unless field.more_like_this?
18
+ @fields[field.indexed_name] = TextFieldBoost.new(field, boost)
19
+ end
20
+
21
+ def minimum_term_frequency=(mintf)
22
+ @params[:"mlt.mintf"] = mintf
23
+ end
24
+
25
+ def minimum_document_frequency=(mindf)
26
+ @params[:"mlt.mindf"] = mindf
27
+ end
28
+
29
+ def minimum_word_length=(minwl)
30
+ @params[:"mlt.minwl"] = minwl
31
+ end
32
+
33
+ def maximum_word_length=(maxwl)
34
+ @params[:"mlt.maxwl"] = maxwl
35
+ end
36
+
37
+ def maximum_query_terms=(maxqt)
38
+ @params[:"mlt.maxqt"] = maxqt
39
+ end
40
+
41
+ def boost_by_relevance=(should_boost)
42
+ @params[:"mlt.boost"] = should_boost
43
+ end
44
+
45
+ def to_params
46
+ params = Sunspot::Util.deep_merge(
47
+ @params,
48
+ :q => @document_scope.to_boolean_phrase
49
+ )
50
+ params[:"mlt.fl"] = @fields.keys.join(",")
51
+ boosted_fields = @fields.values.select { |field| field.boost }
52
+ unless boosted_fields.empty?
53
+ params[:qf] = boosted_fields.map do |field|
54
+ field.to_boosted_field
55
+ end.join(' ')
56
+ end
57
+ params
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,12 @@
1
+ module Sunspot
2
+ module Query
3
+ class MoreLikeThisQuery < CommonQuery
4
+ attr_accessor :scope, :more_like_this
5
+
6
+ def initialize(document, types)
7
+ super(types)
8
+ @components << @more_like_this = MoreLikeThis.new(document)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,42 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # A query component that holds information about pagination. Unlike other
5
+ # query components, this one is mutable, because the query itself holds a
6
+ # reference to it and updates it if pagination is changed.
7
+ #
8
+ class Pagination #:nodoc:
9
+ attr_reader :page, :per_page, :offset
10
+
11
+ def initialize(page = nil, per_page = nil, offset = nil)
12
+ self.offset, self.page, self.per_page = offset, page, per_page
13
+ end
14
+
15
+ def to_params
16
+ { :start => start, :rows => rows }
17
+ end
18
+
19
+ def page=(page)
20
+ @page = page.to_i if page
21
+ end
22
+
23
+ def per_page=(per_page)
24
+ @per_page = per_page.to_i if per_page
25
+ end
26
+
27
+ def offset=(offset)
28
+ @offset = offset.to_i
29
+ end
30
+
31
+ private
32
+
33
+ def start
34
+ (@page - 1) * @per_page + @offset
35
+ end
36
+
37
+ def rows
38
+ @per_page
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ module Sunspot
2
+ module Query
3
+ class QueryFacet < Connective::Conjunction
4
+ def to_params
5
+ if @components.empty?
6
+ {}
7
+ else
8
+ {
9
+ :facet => 'true',
10
+ :"facet.query" => to_boolean_phrase
11
+ }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,262 @@
1
+ module Sunspot
2
+ module Query
3
+ module Restriction #:nodoc:
4
+ class <<self
5
+ #
6
+ # Return the names of all of the restriction classes that should be made
7
+ # available to the DSL.
8
+ #
9
+ # ==== Returns
10
+ #
11
+ # Array:: Collection of restriction class names
12
+ #
13
+ def names
14
+ constants - %w(Base) #XXX this seems ugly
15
+ end
16
+
17
+ #
18
+ # Convenience method to access a restriction class by an underscored
19
+ # symbol or string
20
+ #
21
+ def [](restriction_name)
22
+ @types ||= {}
23
+ @types[restriction_name.to_sym] ||= const_get(Sunspot::Util.camel_case(restriction_name.to_s))
24
+ end
25
+ end
26
+
27
+ #
28
+ # Subclasses of this class represent restrictions that can be applied to
29
+ # a Sunspot query. The Sunspot::DSL::Restriction class presents a builder
30
+ # API for instances of this class.
31
+ #
32
+ # Implementations of this class must respond to #to_params and
33
+ # #to_negated_params. Instead of implementing those methods, they may
34
+ # choose to implement any of:
35
+ #
36
+ # * #to_positive_boolean_phrase, and optionally #to_negated_boolean_phrase
37
+ # * #to_solr_conditional
38
+ #
39
+ class Base #:nodoc:
40
+ include Filter
41
+ include RSolr::Char
42
+
43
+ RESERVED_WORDS = Set['AND', 'OR', 'NOT']
44
+
45
+ def initialize(negated, field, value)
46
+ raise ArgumentError.new("RFCTR") unless [true, false].include?(negated)
47
+ @negated, @field, @value = negated, field, value
48
+ end
49
+
50
+ #
51
+ # A hash representing this restriction in solr-ruby's parameter format.
52
+ # All restriction implementations must respond to this method; however,
53
+ # the base implementation delegates to the #to_positive_boolean_phrase method, so
54
+ # subclasses may (and probably should) choose to implement that method
55
+ # instead.
56
+ #
57
+ # ==== Returns
58
+ #
59
+ # Hash:: Representation of this restriction as solr-ruby parameters
60
+ #
61
+ def to_params
62
+ { :fq => [to_filter_query] }
63
+ end
64
+
65
+ #
66
+ # Return the boolean phrase associated with this restriction object.
67
+ # Differentiates between positive and negated boolean phrases depending
68
+ # on whether this restriction is negated.
69
+ #
70
+ def to_boolean_phrase
71
+ unless negated?
72
+ to_positive_boolean_phrase
73
+ else
74
+ to_negated_boolean_phrase
75
+ end
76
+ end
77
+
78
+ #
79
+ # Boolean phrase representing this restriction in the positive. Subclasses
80
+ # may choose to implement this method rather than #to_params; however,
81
+ # this method delegates to the abstract #to_solr_conditional method, which
82
+ # in most cases will be what subclasses will want to implement.
83
+ # #to_solr_conditional contains the boolean phrase representing the
84
+ # condition but leaves out the field name (see built-in implementations
85
+ # for examples)
86
+ #
87
+ # ==== Returns
88
+ #
89
+ # String:: Boolean phrase for restriction in the positive
90
+ #
91
+ def to_positive_boolean_phrase
92
+ "#{escape(@field.indexed_name)}:#{to_solr_conditional}"
93
+ end
94
+
95
+ #
96
+ # Boolean phrase representing this restriction in the negated. Subclasses
97
+ # may choose to implement this method, but it is not necessary, as the
98
+ # base implementation delegates to #to_positive_boolean_phrase.
99
+ #
100
+ # ==== Returns
101
+ #
102
+ # String:: Boolean phrase for restriction in the negated
103
+ #
104
+ def to_negated_boolean_phrase
105
+ "-#{to_positive_boolean_phrase}"
106
+ end
107
+
108
+ #
109
+ # Whether this restriction should be negated from its original meaning
110
+ #
111
+ def negated? #:nodoc:
112
+ !!@negated
113
+ end
114
+
115
+ #
116
+ # Return a new restriction that is the negated version of this one. It
117
+ # is used by disjunction denormalization.
118
+ #
119
+ def negate
120
+ self.class.new(!@negated, @field, @value)
121
+ end
122
+
123
+ protected
124
+
125
+ #
126
+ # Return escaped Solr API representation of given value
127
+ #
128
+ # ==== Parameters
129
+ #
130
+ # value<Object>::
131
+ # value to convert to Solr representation (default: @value)
132
+ #
133
+ # ==== Returns
134
+ #
135
+ # String:: Solr API representation of given value
136
+ #
137
+ def solr_value(value = @value)
138
+ solr_value = escape(@field.to_indexed(value))
139
+ if RESERVED_WORDS.include?(solr_value)
140
+ %Q("#{solr_value}")
141
+ else
142
+ solr_value
143
+ end
144
+ end
145
+ end
146
+
147
+ #
148
+ # Results must have field with value equal to given value. If the value
149
+ # is nil, results must have no value for the given field.
150
+ #
151
+ class EqualTo < Base
152
+ def to_positive_boolean_phrase
153
+ unless @value.nil?
154
+ super
155
+ else
156
+ "#{escape(@field.indexed_name)}:[* TO *]"
157
+ end
158
+ end
159
+
160
+ def negated?
161
+ if @value.nil?
162
+ !super
163
+ else
164
+ super
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ def to_solr_conditional
171
+ "#{solr_value}"
172
+ end
173
+ end
174
+
175
+ #
176
+ # Results must have field with value less than given value
177
+ #
178
+ class LessThan < Base
179
+ private
180
+
181
+ def solr_value(value = @value)
182
+ solr_value = super
183
+ solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
184
+ solr_value
185
+ end
186
+
187
+ def to_solr_conditional
188
+ "[* TO #{solr_value}]"
189
+ end
190
+ end
191
+
192
+ #
193
+ # Results must have field with value greater than given value
194
+ #
195
+ class GreaterThan < Base
196
+ private
197
+
198
+ def solr_value(value = @value)
199
+ solr_value = super
200
+ solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
201
+ solr_value
202
+ end
203
+
204
+ def to_solr_conditional
205
+ "[#{solr_value} TO *]"
206
+ end
207
+ end
208
+
209
+ #
210
+ # Results must have field with value in given range
211
+ #
212
+ class Between < Base
213
+ private
214
+
215
+ def solr_value(value = @value)
216
+ solr_value = super
217
+ solr_value = "\"#{solr_value}\"" if solr_value.index(' ')
218
+ solr_value
219
+ end
220
+
221
+ def to_solr_conditional
222
+ "[#{solr_value(@value.first)} TO #{solr_value(@value.last)}]"
223
+ end
224
+ end
225
+
226
+ #
227
+ # Results must have field with value included in given collection
228
+ #
229
+ class AnyOf < Base
230
+ private
231
+
232
+ def to_solr_conditional
233
+ "(#{@value.map { |v| solr_value v } * ' OR '})"
234
+ end
235
+ end
236
+
237
+ #
238
+ # Results must have field with values matching all values in given
239
+ # collection (only makes sense for fields with multiple values)
240
+ #
241
+ class AllOf < Base
242
+ private
243
+
244
+ def to_solr_conditional
245
+ "(#{@value.map { |v| solr_value v } * ' AND '})"
246
+ end
247
+ end
248
+
249
+ #
250
+ # Results must have a field with a value that begins with the argument.
251
+ # Most useful for strings, but in theory will work with anything.
252
+ #
253
+ class StartingWith < Base
254
+ private
255
+
256
+ def to_solr_conditional
257
+ "#{solr_value(@value)}*"
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,9 @@
1
+ module Sunspot
2
+ module Query
3
+ class Scope < Connective::Conjunction
4
+ def to_params
5
+ { :fq => @components.map { |component| component.to_filter_query }}
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,109 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The classes in this module implement query components that build sort
5
+ # parameters for Solr. As well as regular sort on fields, there are several
6
+ # "special" sorts that allow ordering for metrics calculated during the
7
+ # search.
8
+ #
9
+ module Sort #:nodoc: all
10
+ DIRECTIONS = {
11
+ :asc => 'asc',
12
+ :ascending => 'asc',
13
+ :desc => 'desc',
14
+ :descending => 'desc'
15
+ }
16
+
17
+ class <<self
18
+ #
19
+ # Certain field names are "special", referring to specific non-field
20
+ # sorts, which are generally by other metrics associated with hits.
21
+ #
22
+ # XXX I'm not entirely convinced it's a good idea to prevent anyone from
23
+ # ever sorting by a field named 'score', etc.
24
+ #
25
+ def special(name)
26
+ special_class_name = "#{Util.camel_case(name.to_s)}Sort"
27
+ if const_defined?(special_class_name) && special_class_name != 'FieldSort'
28
+ const_get(special_class_name)
29
+ end
30
+ end
31
+ end
32
+
33
+ #
34
+ # Base class for sorts. All subclasses should implement the #to_param
35
+ # method, which is a string that is then concatenated with other sort
36
+ # strings by the SortComposite to form the sort parameter.
37
+ #
38
+ class Abstract
39
+ def initialize(direction)
40
+ @direction = (direction || :asc).to_sym
41
+ end
42
+
43
+ private
44
+
45
+ #
46
+ # Translate fairly forgiving direction argument into solr direction
47
+ #
48
+ def direction_for_solr
49
+ DIRECTIONS[@direction] ||
50
+ raise(
51
+ ArgumentError,
52
+ "Unknown sort direction #{@direction}. Acceptable input is: #{DIRECTIONS.keys.map { |input| input.inspect } * ', '}"
53
+ )
54
+ end
55
+ end
56
+
57
+ #
58
+ # A FieldSort is the usual kind of sort, by the value of a particular
59
+ # field, ascending or descending
60
+ #
61
+ class FieldSort < Abstract
62
+ def initialize(field, direction = nil)
63
+ if field.multiple?
64
+ raise(ArgumentError, "#{field.name} cannot be used for ordering because it is a multiple-value field")
65
+ end
66
+ @field, @direction = field, (direction || :asc).to_sym
67
+ end
68
+
69
+ def to_param
70
+ "#{@field.indexed_name.to_sym} #{direction_for_solr}"
71
+ end
72
+ end
73
+
74
+ #
75
+ # A RandomSort uses Solr's random field functionality to sort results
76
+ # (usually) randomly.
77
+ #
78
+ class RandomSort < Abstract
79
+ def to_param
80
+ "random_#{rand(1<<16)} #{direction_for_solr}"
81
+ end
82
+ end
83
+
84
+ #
85
+ # A ScoreSort sorts by keyword relevance score. This is only useful when
86
+ # performing fulltext search.
87
+ #
88
+ class ScoreSort < Abstract
89
+ def to_param
90
+ "score #{direction_for_solr}"
91
+ end
92
+ end
93
+
94
+ #
95
+ # A GeodistSort sorts by distance from a given point.
96
+ #
97
+ class GeodistSort < FieldSort
98
+ def initialize(field, lat, lon, direction)
99
+ @lat, @lon = lat, lon
100
+ super(field, direction)
101
+ end
102
+
103
+ def to_param
104
+ "geodist(#{@field.indexed_name.to_sym},#{@lat},#{@lon}) #{direction_for_solr}"
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,34 @@
1
+ module Sunspot
2
+ module Query
3
+ #
4
+ # The SortComposite class encapsulates an ordered collection of Sort
5
+ # objects. It's necessary to keep this as a separate class as Solr takes
6
+ # the sort as a single parameter, so adding sorts as regular components
7
+ # would not merge correctly in the #to_params method.
8
+ #
9
+ class SortComposite #:nodoc:
10
+ def initialize
11
+ @sorts = []
12
+ end
13
+
14
+ #
15
+ # Add a sort to the composite
16
+ #
17
+ def <<(sort)
18
+ @sorts << sort
19
+ end
20
+
21
+ #
22
+ # Combine the sorts into a single param by joining them
23
+ #
24
+ def to_params(prefix = "")
25
+ unless @sorts.empty?
26
+ key = "#{prefix}sort".to_sym
27
+ { key => @sorts.map { |sort| sort.to_param } * ', ' }
28
+ else
29
+ {}
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,16 @@
1
+ module Sunspot
2
+ module Query
3
+ class StandardQuery < CommonQuery
4
+ attr_accessor :scope, :fulltext
5
+
6
+ def initialize(types)
7
+ super
8
+ @components << @fulltext = CompositeFulltext.new
9
+ end
10
+
11
+ def add_fulltext(keywords)
12
+ @fulltext.add(keywords)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,17 @@
1
+ module Sunspot
2
+ module Query
3
+ class TextFieldBoost #:nodoc:
4
+ attr_reader :boost
5
+
6
+ def initialize(field, boost = nil)
7
+ @field, @boost = field, boost
8
+ end
9
+
10
+ def to_boosted_field
11
+ boosted_field = @field.indexed_name
12
+ boosted_field.concat("^#{@boost}") if @boost
13
+ boosted_field
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ %w(filter abstract_field_facet connective boost_query date_field_facet dismax
2
+ field_facet highlighting pagination restriction common_query
3
+ standard_query more_like_this more_like_this_query geo geofilt bbox query_facet
4
+ scope sort sort_composite text_field_boost function_query
5
+ composite_fulltext field_group).each do |file|
6
+ require(File.join(File.dirname(__FILE__), 'query', file))
7
+ end
8
+ module Sunspot
9
+ module Query #:nodoc:all
10
+ end
11
+ end