sunspot_rbg 1.3.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.
Files changed (222) hide show
  1. data/.gitignore +12 -0
  2. data/Gemfile +4 -0
  3. data/History.txt +222 -0
  4. data/LICENSE +18 -0
  5. data/Rakefile +17 -0
  6. data/TODO +13 -0
  7. data/VERSION.yml +4 -0
  8. data/bin/sunspot-installer +19 -0
  9. data/bin/sunspot-solr +74 -0
  10. data/installer/config/schema.yml +95 -0
  11. data/lib/light_config.rb +40 -0
  12. data/lib/sunspot/adapters.rb +265 -0
  13. data/lib/sunspot/composite_setup.rb +202 -0
  14. data/lib/sunspot/configuration.rb +46 -0
  15. data/lib/sunspot/data_extractor.rb +50 -0
  16. data/lib/sunspot/dsl/adjustable.rb +47 -0
  17. data/lib/sunspot/dsl/field_query.rb +279 -0
  18. data/lib/sunspot/dsl/fields.rb +103 -0
  19. data/lib/sunspot/dsl/fulltext.rb +243 -0
  20. data/lib/sunspot/dsl/function.rb +14 -0
  21. data/lib/sunspot/dsl/functional.rb +44 -0
  22. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  23. data/lib/sunspot/dsl/paginatable.rb +28 -0
  24. data/lib/sunspot/dsl/query_facet.rb +36 -0
  25. data/lib/sunspot/dsl/restriction.rb +25 -0
  26. data/lib/sunspot/dsl/restriction_with_near.rb +121 -0
  27. data/lib/sunspot/dsl/scope.rb +217 -0
  28. data/lib/sunspot/dsl/search.rb +30 -0
  29. data/lib/sunspot/dsl/standard_query.rb +121 -0
  30. data/lib/sunspot/dsl.rb +5 -0
  31. data/lib/sunspot/field.rb +193 -0
  32. data/lib/sunspot/field_factory.rb +129 -0
  33. data/lib/sunspot/indexer.rb +131 -0
  34. data/lib/sunspot/installer/library_installer.rb +45 -0
  35. data/lib/sunspot/installer/schema_builder.rb +219 -0
  36. data/lib/sunspot/installer/solrconfig_updater.rb +76 -0
  37. data/lib/sunspot/installer/task_helper.rb +18 -0
  38. data/lib/sunspot/installer.rb +31 -0
  39. data/lib/sunspot/query/abstract_field_facet.rb +52 -0
  40. data/lib/sunspot/query/boost_query.rb +24 -0
  41. data/lib/sunspot/query/common_query.rb +85 -0
  42. data/lib/sunspot/query/composite_fulltext.rb +36 -0
  43. data/lib/sunspot/query/connective.rb +206 -0
  44. data/lib/sunspot/query/date_field_facet.rb +14 -0
  45. data/lib/sunspot/query/dismax.rb +128 -0
  46. data/lib/sunspot/query/field_facet.rb +41 -0
  47. data/lib/sunspot/query/filter.rb +38 -0
  48. data/lib/sunspot/query/function_query.rb +52 -0
  49. data/lib/sunspot/query/geo.rb +53 -0
  50. data/lib/sunspot/query/highlighting.rb +55 -0
  51. data/lib/sunspot/query/more_like_this.rb +61 -0
  52. data/lib/sunspot/query/more_like_this_query.rb +12 -0
  53. data/lib/sunspot/query/pagination.rb +38 -0
  54. data/lib/sunspot/query/query_facet.rb +16 -0
  55. data/lib/sunspot/query/restriction.rb +262 -0
  56. data/lib/sunspot/query/scope.rb +9 -0
  57. data/lib/sunspot/query/sort.rb +95 -0
  58. data/lib/sunspot/query/sort_composite.rb +33 -0
  59. data/lib/sunspot/query/standard_query.rb +16 -0
  60. data/lib/sunspot/query/text_field_boost.rb +17 -0
  61. data/lib/sunspot/query.rb +11 -0
  62. data/lib/sunspot/schema.rb +151 -0
  63. data/lib/sunspot/search/abstract_search.rb +293 -0
  64. data/lib/sunspot/search/date_facet.rb +35 -0
  65. data/lib/sunspot/search/facet_row.rb +27 -0
  66. data/lib/sunspot/search/field_facet.rb +88 -0
  67. data/lib/sunspot/search/highlight.rb +38 -0
  68. data/lib/sunspot/search/hit.rb +136 -0
  69. data/lib/sunspot/search/more_like_this_search.rb +31 -0
  70. data/lib/sunspot/search/paginated_collection.rb +55 -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/server.rb +152 -0
  75. data/lib/sunspot/session.rb +260 -0
  76. data/lib/sunspot/session_proxy/abstract_session_proxy.rb +29 -0
  77. data/lib/sunspot/session_proxy/class_sharding_session_proxy.rb +66 -0
  78. data/lib/sunspot/session_proxy/id_sharding_session_proxy.rb +89 -0
  79. data/lib/sunspot/session_proxy/master_slave_session_proxy.rb +43 -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 +87 -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 +372 -0
  87. data/lib/sunspot/util.rb +243 -0
  88. data/lib/sunspot/version.rb +3 -0
  89. data/lib/sunspot.rb +569 -0
  90. data/lib/sunspot_rbg.rb +7 -0
  91. data/log/.gitignore +1 -0
  92. data/pkg/.gitignore +1 -0
  93. data/script/console +10 -0
  94. data/solr/README.txt +42 -0
  95. data/solr/etc/jetty.xml +218 -0
  96. data/solr/etc/webdefault.xml +379 -0
  97. data/solr/lib/jetty-6.1.3.jar +0 -0
  98. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  99. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  100. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  101. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  102. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  103. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  104. data/solr/logs/.gitignore +1 -0
  105. data/solr/solr/.gitignore +1 -0
  106. data/solr/solr/README.txt +54 -0
  107. data/solr/solr/conf/admin-extra.html +31 -0
  108. data/solr/solr/conf/elevate.xml +36 -0
  109. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  110. data/solr/solr/conf/protwords.txt +21 -0
  111. data/solr/solr/conf/schema.xml +238 -0
  112. data/solr/solr/conf/scripts.conf +24 -0
  113. data/solr/solr/conf/solrconfig.xml +934 -0
  114. data/solr/solr/conf/spellings.txt +2 -0
  115. data/solr/solr/conf/stopwords.txt +58 -0
  116. data/solr/solr/conf/synonyms.txt +31 -0
  117. data/solr/solr/conf/xslt/example.xsl +132 -0
  118. data/solr/solr/conf/xslt/example_atom.xsl +67 -0
  119. data/solr/solr/conf/xslt/example_rss.xsl +66 -0
  120. data/solr/solr/conf/xslt/luke.xsl +337 -0
  121. data/solr/start.jar +0 -0
  122. data/solr/webapps/solr.war +0 -0
  123. data/solr-1.3/etc/jetty.xml +212 -0
  124. data/solr-1.3/etc/webdefault.xml +379 -0
  125. data/solr-1.3/lib/jetty-6.1.3.jar +0 -0
  126. data/solr-1.3/lib/jetty-util-6.1.3.jar +0 -0
  127. data/solr-1.3/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  128. data/solr-1.3/lib/jsp-2.1/core-3.1.1.jar +0 -0
  129. data/solr-1.3/lib/jsp-2.1/jsp-2.1.jar +0 -0
  130. data/solr-1.3/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  131. data/solr-1.3/lib/servlet-api-2.5-6.1.3.jar +0 -0
  132. data/solr-1.3/solr/conf/elevate.xml +36 -0
  133. data/solr-1.3/solr/conf/protwords.txt +21 -0
  134. data/solr-1.3/solr/conf/schema.xml +64 -0
  135. data/solr-1.3/solr/conf/solrconfig.xml +725 -0
  136. data/solr-1.3/solr/conf/stopwords.txt +57 -0
  137. data/solr-1.3/solr/conf/synonyms.txt +31 -0
  138. data/solr-1.3/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  139. data/solr-1.3/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  140. data/solr-1.3/solr/lib/jsr108-0.01.jar +0 -0
  141. data/solr-1.3/solr/lib/locallucene.jar +0 -0
  142. data/solr-1.3/solr/lib/localsolr.jar +0 -0
  143. data/solr-1.3/start.jar +0 -0
  144. data/solr-1.3/webapps/solr.war +0 -0
  145. data/spec/api/adapters_spec.rb +33 -0
  146. data/spec/api/binding_spec.rb +50 -0
  147. data/spec/api/indexer/attributes_spec.rb +149 -0
  148. data/spec/api/indexer/batch_spec.rb +46 -0
  149. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  150. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  151. data/spec/api/indexer/fulltext_spec.rb +43 -0
  152. data/spec/api/indexer/removal_spec.rb +53 -0
  153. data/spec/api/indexer/spec_helper.rb +1 -0
  154. data/spec/api/indexer_spec.rb +14 -0
  155. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  156. data/spec/api/query/connectives_examples.rb +189 -0
  157. data/spec/api/query/dsl_spec.rb +18 -0
  158. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  159. data/spec/api/query/faceting_examples.rb +397 -0
  160. data/spec/api/query/fulltext_examples.rb +313 -0
  161. data/spec/api/query/function_spec.rb +70 -0
  162. data/spec/api/query/geo_examples.rb +68 -0
  163. data/spec/api/query/highlighting_examples.rb +223 -0
  164. data/spec/api/query/more_like_this_spec.rb +140 -0
  165. data/spec/api/query/ordering_pagination_examples.rb +95 -0
  166. data/spec/api/query/scope_examples.rb +275 -0
  167. data/spec/api/query/spec_helper.rb +1 -0
  168. data/spec/api/query/standard_spec.rb +28 -0
  169. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  170. data/spec/api/query/types_spec.rb +20 -0
  171. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  172. data/spec/api/search/faceting_spec.rb +360 -0
  173. data/spec/api/search/highlighting_spec.rb +69 -0
  174. data/spec/api/search/hits_spec.rb +120 -0
  175. data/spec/api/search/paginated_collection_spec.rb +26 -0
  176. data/spec/api/search/results_spec.rb +66 -0
  177. data/spec/api/search/search_spec.rb +23 -0
  178. data/spec/api/search/spec_helper.rb +1 -0
  179. data/spec/api/server_spec.rb +91 -0
  180. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  181. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  182. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  183. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  184. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +24 -0
  185. data/spec/api/session_proxy/spec_helper.rb +9 -0
  186. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +50 -0
  187. data/spec/api/session_spec.rb +220 -0
  188. data/spec/api/spec_helper.rb +3 -0
  189. data/spec/api/sunspot_spec.rb +18 -0
  190. data/spec/ext.rb +11 -0
  191. data/spec/helpers/indexer_helper.rb +29 -0
  192. data/spec/helpers/query_helper.rb +38 -0
  193. data/spec/helpers/search_helper.rb +80 -0
  194. data/spec/integration/dynamic_fields_spec.rb +55 -0
  195. data/spec/integration/faceting_spec.rb +238 -0
  196. data/spec/integration/highlighting_spec.rb +22 -0
  197. data/spec/integration/indexing_spec.rb +33 -0
  198. data/spec/integration/keyword_search_spec.rb +317 -0
  199. data/spec/integration/local_search_spec.rb +64 -0
  200. data/spec/integration/more_like_this_spec.rb +43 -0
  201. data/spec/integration/scoped_search_spec.rb +354 -0
  202. data/spec/integration/spec_helper.rb +7 -0
  203. data/spec/integration/stored_fields_spec.rb +10 -0
  204. data/spec/integration/test_pagination.rb +32 -0
  205. data/spec/mocks/adapters.rb +32 -0
  206. data/spec/mocks/blog.rb +3 -0
  207. data/spec/mocks/comment.rb +21 -0
  208. data/spec/mocks/connection.rb +126 -0
  209. data/spec/mocks/mock_adapter.rb +30 -0
  210. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  211. data/spec/mocks/mock_record.rb +52 -0
  212. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  213. data/spec/mocks/photo.rb +11 -0
  214. data/spec/mocks/post.rb +85 -0
  215. data/spec/mocks/super_class.rb +2 -0
  216. data/spec/mocks/user.rb +13 -0
  217. data/spec/spec_helper.rb +30 -0
  218. data/sunspot.gemspec +40 -0
  219. data/tasks/rdoc.rake +27 -0
  220. data/tasks/schema.rake +19 -0
  221. data/tasks/todo.rake +4 -0
  222. metadata +457 -0
@@ -0,0 +1,279 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # Provides an API for areas of the query DSL that operate on specific
5
+ # fields. This functionality is provided by the query DSL and the dynamic
6
+ # query DSL.
7
+ #
8
+ class FieldQuery < Scope
9
+ def initialize(search, query, setup) #:nodoc:
10
+ @search, @query = search, query
11
+ super(query.scope, setup)
12
+ end
13
+
14
+ # Specify the order that results should be returned in. This method can
15
+ # be called multiple times; precedence will be in the order given.
16
+ #
17
+ # ==== Parameters
18
+ #
19
+ # field_name<Symbol>:: the field to use for ordering
20
+ # direction<Symbol>:: :asc or :desc (default :asc)
21
+ #
22
+ def order_by(field_name, direction = nil)
23
+ sort =
24
+ if special = Sunspot::Query::Sort.special(field_name)
25
+ special.new(direction)
26
+ else
27
+ Sunspot::Query::Sort::FieldSort.new(
28
+ @setup.field(field_name), direction
29
+ )
30
+ end
31
+ @query.add_sort(sort)
32
+ end
33
+
34
+ #
35
+ # DEPRECATED Use <code>order_by(:random)</code>
36
+ #
37
+ def order_by_random
38
+ order_by(:random)
39
+ end
40
+
41
+ #
42
+ # Request a facet on the search query. A facet is a feature of Solr that
43
+ # determines the number of documents that match the existing search *and*
44
+ # an additional criterion. This allows you to build powerful drill-down
45
+ # interfaces for search, at each step presenting the searcher with a set
46
+ # of refinements that are known to return results.
47
+ #
48
+ # In Sunspot, each facet returns zero or more rows, each of which
49
+ # represents a particular criterion conjoined with the actual query being
50
+ # performed. For _field_ _facets_, each row represents a particular value
51
+ # for a given field. For _query_ _facets_, each row represents an
52
+ # arbitrary scope; the facet itself is just a means of logically grouping
53
+ # the scopes.
54
+ #
55
+ # === Examples
56
+ #
57
+ # ==== Field Facets
58
+ #
59
+ # A field facet is specified by passing one or more Symbol arguments to
60
+ # this method:
61
+ #
62
+ # Sunspot.search(Post) do
63
+ # with(:blog_id, 1)
64
+ # facet(:category_id)
65
+ # end
66
+ #
67
+ # The facet specified above will have a row for each category_id that is
68
+ # present in a document which also has a blog_id of 1.
69
+ #
70
+ # ==== Multiselect Facets
71
+ #
72
+ # In certain circumstances, it is beneficial to exclude certain query
73
+ # scopes from a facet; the most common example is multi-select faceting,
74
+ # where the user has selected a certain value, but the facet should still
75
+ # show all options that would be available if they had not:
76
+ #
77
+ # Sunspot.search(Post) do
78
+ # with(:blog_id, 1)
79
+ # category_filter = with(:category_id, 2)
80
+ # facet(:category_id, :exclude => category_filter)
81
+ # end
82
+ #
83
+ # Although the results of the above search will be restricted to those
84
+ # with a category_id of 2, the category_id facet will operate as if a
85
+ # category had not been selected, allowing the user to select additional
86
+ # categories (which will presumably be ORed together).
87
+ #
88
+ # It possible to exclude multiple filters by passing an array:
89
+ #
90
+ # Sunspot.search(Post) do
91
+ # with(:blog_id, 1)
92
+ # category_filter = with(:category_id, 2)
93
+ # author_filter = with(:author_id, 3)
94
+ # facet(:category_id,
95
+ # :exclude => [category_filter, author_filter].compact)
96
+ # end
97
+ #
98
+ # You should consider using +.compact+ to ensure that the array does not
99
+ # contain any nil values.
100
+ #
101
+ # <strong>As far as I can tell, Solr only supports multi-select with
102
+ # field facets; if +:exclude+ is passed to a query facet, this method will
103
+ # raise an error. Also, the +:only+ and +:extra+ options use query
104
+ # faceting under the hood, so these can't be used with +:extra+ either.
105
+ # </strong>
106
+ #
107
+ # ==== Query Facets
108
+ #
109
+ # A query facet is a collection of arbitrary scopes, each of which
110
+ # represents a row. This is specified by passing a block into the #facet
111
+ # method; the block then contains one or more +row+ blocks, each of which
112
+ # creates a query facet row. The +row+ blocks follow the usual Sunspot
113
+ # scope DSL.
114
+ #
115
+ # For example, a query facet can be used to facet over a set of ranges:
116
+ #
117
+ # Sunspot.search(Post) do
118
+ # facet(:average_rating) do
119
+ # row(1.0..2.0) do
120
+ # with(:average_rating, 1.0..2.0)
121
+ # end
122
+ # row(2.0..3.0) do
123
+ # with(:average_rating, 2.0..3.0)
124
+ # end
125
+ # row(3.0..4.0) do
126
+ # with(:average_rating, 3.0..4.0)
127
+ # end
128
+ # row(4.0..5.0) do
129
+ # with(:average_rating, 4.0..5.0)
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # Note that the arguments to the +facet+ and +row+ methods simply provide
135
+ # labels for the facet and its rows, so that they can be retrieved and
136
+ # identified from the Search object. They are not passed to Solr and no
137
+ # semantic meaning is attached to them. The label for +facet+ should be
138
+ # a symbol; the label for +row+ can be whatever you'd like.
139
+ #
140
+ # ==== Parameters
141
+ #
142
+ # field_names...<Symbol>:: fields for which to return field facets
143
+ #
144
+ # ==== Options
145
+ #
146
+ # :sort<Symbol>::
147
+ # Either :count (values matching the most terms first) or :index (lexical)
148
+ # :limit<Integer>::
149
+ # The maximum number of facet rows to return
150
+ # :minimum_count<Integer>::
151
+ # The minimum count a facet row must have to be returned
152
+ # :zeros<Boolean>::
153
+ # Return facet rows for which there are no matches (equivalent to
154
+ # :minimum_count => 0). Default is false.
155
+ # :exclude<Object,Array>::
156
+ # Exclude one or more filters when performing the faceting (see
157
+ # Multiselect Faceting above). The object given for this argument should
158
+ # be the return value(s) of a scoping method (+with+, +any_of+,
159
+ # +all_of+, etc.). <strong>Only can be used for field facets that do not
160
+ # use the +:extra+ or +:only+ options.</strong>
161
+ # :name<Symbol>::
162
+ # Give a custom name to a field facet. The main use case for this option
163
+ # is for requesting the same field facet multiple times, using different
164
+ # filter exclusions (see Multiselect Faceting above). If you pass this
165
+ # option, it is also the argument that should be passed to Search#facet
166
+ # when retrieving the facet result.
167
+ # :only<Array>::
168
+ # Only return facet rows for the given values. Useful if you are only
169
+ # interested in faceting on a subset of values for a given field.
170
+ # <strong>Only applies to field facets.</strong>
171
+ # :extra<Symbol,Array>::
172
+ # One or more of :any and :none. :any returns a facet row with a count
173
+ # of all matching documents that have some value for this field. :none
174
+ # returns a facet row with a count of all matching documents that have
175
+ # no value for this field. The facet row(s) corresponding to the extras
176
+ # have a value of the symbol passed. <strong>Only applies to field
177
+ # facets.</strong>
178
+ #
179
+ def facet(*field_names, &block)
180
+ options = Sunspot::Util.extract_options_from(field_names)
181
+
182
+ if block
183
+ if field_names.length != 1
184
+ raise(
185
+ ArgumentError,
186
+ "wrong number of arguments (#{field_names.length} for 1)"
187
+ )
188
+ end
189
+ if options.has_key?(:exclude)
190
+ raise(
191
+ ArgumentError,
192
+ "can't use :exclude with query facets"
193
+ )
194
+ end
195
+ search_facet = @search.add_query_facet(field_names.first, options)
196
+ Sunspot::Util.instance_eval_or_call(
197
+ QueryFacet.new(@query, @setup, search_facet),
198
+ &block
199
+ )
200
+ elsif options[:only]
201
+ if options.has_key?(:exclude)
202
+ raise(
203
+ ArgumentError,
204
+ "can't use :exclude with :only (see documentation)"
205
+ )
206
+ end
207
+ field_names.each do |field_name|
208
+ field = @setup.field(field_name)
209
+ search_facet = @search.add_field_facet(field, options)
210
+ Util.Array(options[:only]).each do |value|
211
+ facet = Sunspot::Query::QueryFacet.new
212
+ facet.add_positive_restriction(field, Sunspot::Query::Restriction::EqualTo, value)
213
+ @query.add_query_facet(facet)
214
+ search_facet.add_row(value, facet.to_boolean_phrase)
215
+ end
216
+ end
217
+ else
218
+ field_names.each do |field_name|
219
+ search_facet = nil
220
+ field = @setup.field(field_name)
221
+ facet =
222
+ if options[:time_range]
223
+ unless field.type.is_a?(Sunspot::Type::TimeType)
224
+ raise(
225
+ ArgumentError,
226
+ ':time_range can only be specified for Date or Time fields'
227
+ )
228
+ end
229
+ search_facet = @search.add_date_facet(field, options)
230
+ Sunspot::Query::DateFieldFacet.new(field, options)
231
+ else
232
+ search_facet = @search.add_field_facet(field, options)
233
+ Sunspot::Query::FieldFacet.new(field, options)
234
+ end
235
+ @query.add_field_facet(facet)
236
+ Util.Array(options[:extra]).each do |extra|
237
+ if options.has_key?(:exclude)
238
+ raise(
239
+ ArgumentError,
240
+ "can't use :exclude with :extra (see documentation)"
241
+ )
242
+ end
243
+ extra_facet = Sunspot::Query::QueryFacet.new
244
+ case extra
245
+ when :any
246
+ extra_facet.add_negated_restriction(
247
+ field,
248
+ Sunspot::Query::Restriction::EqualTo,
249
+ nil
250
+ )
251
+ when :none
252
+ extra_facet.add_positive_restriction(
253
+ field,
254
+ Sunspot::Query::Restriction::EqualTo,
255
+ nil
256
+ )
257
+ else
258
+ raise(
259
+ ArgumentError,
260
+ "Allowed values for :extra are :any and :none"
261
+ )
262
+ end
263
+ search_facet.add_row(extra, extra_facet.to_boolean_phrase)
264
+ @query.add_query_facet(extra_facet)
265
+ end
266
+ end
267
+ end
268
+ end
269
+
270
+ def dynamic(base_name, &block)
271
+ dynamic_field_factory = @setup.dynamic_field_factory(base_name)
272
+ Sunspot::Util.instance_eval_or_call(
273
+ FieldQuery.new(@search, @query, dynamic_field_factory),
274
+ &block
275
+ )
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,103 @@
1
+ module Sunspot
2
+ module DSL #:nodoc:
3
+ # The Fields class provides a DSL for specifying field definitions in the
4
+ # Sunspot.setup block. As well as the #text method, which creates fulltext
5
+ # fields, uses #method_missing to allow definition of typed fields. The
6
+ # available methods are determined by the constants defined in
7
+ # Sunspot::Type - in theory (though this is untested), plugin developers
8
+ # should be able to add support for new types simply by creating new
9
+ # implementations in Sunspot::Type
10
+ #
11
+ class Fields
12
+ def initialize(setup) #:nodoc:
13
+ @setup = setup
14
+ end
15
+
16
+ # Add a text field. Text fields are tokenized before indexing and are
17
+ # the only fields searched in fulltext searches. If a block is passed,
18
+ # create a virtual field; otherwise create an attribute field.
19
+ #
20
+ # If options are passed, they will be applied to all the given fields.
21
+ #
22
+ # ==== Parameters
23
+ #
24
+ # names...<Symbol>:: One or more field names
25
+ #
26
+ # ==== Options
27
+ #
28
+ # :boost<Float>::
29
+ # Index-time boost that should be applied to this field for keyword search
30
+ # :default_boost<Float>::
31
+ # Default search-time boost to apply to this field during keyword
32
+ # search. Can be overriden with DSL::Fulltext#fields or
33
+ # DSL::Fulltext#boost_fields method.
34
+ #
35
+ def text(*names, &block)
36
+ options = names.pop if names.last.is_a?(Hash)
37
+ names.each do |name|
38
+ @setup.add_text_field_factory(
39
+ name,
40
+ options || {},
41
+ &block
42
+ )
43
+ end
44
+ end
45
+
46
+ #
47
+ # Specify a document-level boost. As with fields, you have the option of
48
+ # passing an attribute name which will be called on each model, or a block
49
+ # to be evaluated in the model's context. As well as these two options,
50
+ # this method can also take a constant number, meaning that all indexed
51
+ # documents of this class will have the specified boost.
52
+ #
53
+ # ==== Parameters
54
+ #
55
+ # attr_name<Symbol,~.to_f>:: Attribute name to call or a numeric constant
56
+ #
57
+ def boost(attr_name = nil, &block)
58
+ @setup.add_document_boost(attr_name, &block)
59
+ end
60
+
61
+ # method_missing is used to provide access to typed fields, because
62
+ # developers should be able to add new Sunspot::Type implementations
63
+ # dynamically and have them recognized inside the Fields DSL. Like #text,
64
+ # these methods will create a VirtualField if a block is passed, or an
65
+ # AttributeField if not.
66
+ #
67
+ # ==== Example
68
+ #
69
+ # Sunspot.setup(File) do
70
+ # time :mtime
71
+ # end
72
+ #
73
+ # The call to +time+ will create a field of type Sunspot::Types::TimeType
74
+ #
75
+ def method_missing(method, *args, &block)
76
+ options = Util.extract_options_from(args)
77
+ type_const_name = "#{Util.camel_case(method.to_s.sub(/^dynamic_/, ''))}Type"
78
+ trie = options.delete(:trie)
79
+ type_const_name = "Trie#{type_const_name}" if trie
80
+ begin
81
+ type_class = Type.const_get(type_const_name)
82
+ rescue(NameError)
83
+ if trie
84
+ raise ArgumentError, "Trie fields are only valid for numeric and time types"
85
+ else
86
+ super(method, *args, &block)
87
+ end
88
+ end
89
+ type = type_class.instance
90
+ name = args.shift
91
+ if method.to_s =~ /^dynamic_/
92
+ if type.accepts_dynamic?
93
+ @setup.add_dynamic_field_factory(name, type, options, &block)
94
+ else
95
+ super(method, *args, &block)
96
+ end
97
+ else
98
+ @setup.add_field_factory(name, type, options, &block)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,243 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # This DSL exposes the functionality provided by Solr's fulltext Dismax
5
+ # handler.
6
+ #
7
+ class Fulltext
8
+ attr_reader :exclude_fields #:nodoc:
9
+
10
+ # accept function in boost
11
+ include Functional
12
+
13
+ def initialize(query, setup) #:nodoc:
14
+ @query, @setup = query, setup
15
+ @fields_added = false
16
+ @exclude_fields = []
17
+ end
18
+
19
+ #
20
+ # Specify which fields to search. Field names specified as arguments are
21
+ # given default boost; field boosts can be specified by passing a hash of
22
+ # field names keyed to boost values as the last argument.
23
+ #
24
+ # If you wish to boost certain fields without restricting which fields are
25
+ # searched, use #boost_fields
26
+ #
27
+ # === Example
28
+ #
29
+ # Sunspot.search(Post) do
30
+ # keywords 'search is cool' do
31
+ # fields(:body, :title => 2.0)
32
+ # end
33
+ # end
34
+ #
35
+ # This would search the :body field with default boost (1.0), and the :title
36
+ # field with a boost of 2.0
37
+ #
38
+ def fields(*field_names)
39
+ @fields_added = true
40
+ boosted_fields = field_names.pop if field_names.last.is_a?(Hash)
41
+ field_names.each do |field_name|
42
+ @setup.text_fields(field_name).each do |field|
43
+ @query.add_fulltext_field(field, field.default_boost)
44
+ end
45
+ end
46
+ if boosted_fields
47
+ boosted_fields.each_pair do |field_name, boost|
48
+ @setup.text_fields(field_name).each do |field|
49
+ @query.add_fulltext_field(field, boost)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ #
56
+ # Exclude the given fields from the search. All fields that are configured
57
+ # for the types under search and not listed here will be searched.
58
+ #
59
+ def exclude_fields(*field_names)
60
+ @exclude_fields.concat(field_names)
61
+ end
62
+
63
+ #
64
+ # Enable keyword highlighting for this search. By default, the fields
65
+ # under search will be highlighted; you may also may pass one or more
66
+ # symbol arguments indicating fields to be highlighted (they don't even
67
+ # have to be the same fields you're searching).
68
+ #
69
+ # === Example
70
+ #
71
+ # Sunspot.search(Post) do
72
+ # keywords 'show me the highlighting' do
73
+ # highlight :title, :body
74
+ # end
75
+ # end
76
+ #
77
+ # You may also pass a hash of options as the last argument. Options are
78
+ # the following:
79
+ #
80
+ # Full disclosure: I barely understand what these options actually do;
81
+ # this documentation is pretty much just copied from the
82
+ # (http://wiki.apache.org/solr/HighlightingParameters#head-23ecd5061bc2c86a561f85dc1303979fe614b956)[Solr Wiki]
83
+ #
84
+ # :max_snippets::
85
+ # The maximum number of highlighted snippets to generate per field
86
+ # :fragment_size::
87
+ # The number of characters to consider for a highlighted fragment
88
+ # :merge_continuous_fragments::
89
+ # Collapse continuous fragments into a single fragment
90
+ # :phrase_highlighter::
91
+ # Highlight phrase terms only when they appear within the query phrase
92
+ # in the document
93
+ # :require_field_match::
94
+ # If true, a field will only be highlighted if the query matched in
95
+ # this particular field (only has an effect if :phrase_highlighter is
96
+ # true as well)
97
+ #
98
+ def highlight(*args)
99
+ options = args.last.kind_of?(Hash) ? args.pop : {}
100
+ fields = []
101
+ args.each { |field_name| fields.concat(@setup.text_fields(field_name)) }
102
+
103
+ @query.add_highlight(fields, options)
104
+ end
105
+
106
+ #
107
+ # Phrase fields are an awesome dismax feature that adds extra boost to
108
+ # documents for which all the fulltext keywords appear in close proximity
109
+ # in one of the given fields. Excellent for titles, headlines, etc.
110
+ #
111
+ # Boosted fields are specified in a hash of field names to a boost, as
112
+ # with the #fields and #boost_fields methods.
113
+ #
114
+ # === Example
115
+ #
116
+ # Sunspot.search(Post) do
117
+ # keywords 'nothing reveals like relevance' do
118
+ # phrase_fields :title => 2.0
119
+ # end
120
+ # end
121
+ #
122
+ def phrase_fields(boosted_fields)
123
+ if boosted_fields
124
+ boosted_fields.each_pair do |field_name, boost|
125
+ @setup.text_fields(field_name).each do |field|
126
+ @query.add_phrase_field(field, boost)
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ #
133
+ # The maximum number of words that can appear between search terms for a
134
+ # field to qualify for phrase field boost. See #query_phrase_slop for
135
+ # examples. Phrase slop is only meaningful if phrase fields are specified
136
+ # (see #phrase_fields), and it does not have an effect on which results
137
+ # are returned; only on what their respective boosts are.
138
+ #
139
+ def phrase_slop(slop)
140
+ @query.phrase_slop = slop
141
+ end
142
+
143
+ #
144
+ # Boost queries allow specification of an arbitrary scope for which
145
+ # matching documents should receive an extra boost. You can either specify
146
+ # a boost factor and a block, or a boost function. The block is evaluated
147
+ # in the usual scope DSL, and field names are attribute fields, not text
148
+ # fields, as in other scope.
149
+ #
150
+ # The boost function can be a constant (numeric or string literal),
151
+ # a field name or another function. You can build arbitrarily complex
152
+ # functions, which are passed transparently to solr.
153
+ #
154
+ # This method can be called more than once for different boost queries
155
+ # with different boosts.
156
+ #
157
+ # === Example
158
+ #
159
+ # Sunspot.search(Post) do
160
+ # keywords 'super fan' do
161
+ # boost(2.0) do
162
+ # with(:featured, true)
163
+ # end
164
+ #
165
+ # boost(function { sum(:average_rating, product(:popularity, 10)) })
166
+ # end
167
+ # end
168
+ #
169
+ # In the above search, featured posts will receive a boost of 2.0 and all posts
170
+ # will be boosted by (average_rating + popularity * 10).
171
+ #
172
+ def boost(factor_or_function, &block)
173
+ if factor_or_function.is_a?(Sunspot::Query::FunctionQuery)
174
+ @query.add_boost_function(factor_or_function)
175
+ else
176
+ Sunspot::Util.instance_eval_or_call(
177
+ Scope.new(@query.create_boost_query(factor_or_function), @setup),
178
+ &block
179
+ )
180
+ end
181
+ end
182
+
183
+ #
184
+ # Add boost to certain fields, without restricting which fields are
185
+ # searched.
186
+ #
187
+ # === Example
188
+ #
189
+ # Sunspot.search(Post) do
190
+ # keywords('pork sandwich') do
191
+ # boost_fields :title => 1.5
192
+ # end
193
+ # end
194
+ #
195
+ def boost_fields(boosts)
196
+ boosts.each_pair do |field_name, boost|
197
+ begin
198
+ @setup.text_fields(field_name).each do |field|
199
+ @query.add_fulltext_field(field, boost)
200
+ end
201
+ rescue Sunspot::UnrecognizedFieldError
202
+ # We'll let this one slide.
203
+ end
204
+ end
205
+ end
206
+
207
+ #
208
+ # The minimum number of search terms that a result must match. By
209
+ # default, all search terms must match; if the number of search terms
210
+ # is less than this number, the default behavior applies.
211
+ #
212
+ def minimum_match(minimum_match)
213
+ @query.minimum_match = minimum_match
214
+ end
215
+
216
+ #
217
+ # The number of words that can appear between the words in a
218
+ # user-entered phrase (i.e., keywords in quotes) and still match. For
219
+ # instance, in a search for "\"great pizza\"" with a query phrase slop of
220
+ # 1, "great pizza" and "great big pizza" will match, but "great monster of
221
+ # a pizza" will not. Default behavior is a query phrase slop of zero.
222
+ #
223
+ def query_phrase_slop(slop)
224
+ @query.query_phrase_slop = slop
225
+ end
226
+
227
+ #
228
+ # A tiebreaker coefficient for scores derived from subqueries that are
229
+ # lower-scoring than the maximum score subquery. Typically a near-zero
230
+ # value is useful. See
231
+ # http://wiki.apache.org/solr/DisMaxRequestHandler#tie_.28Tie_breaker.29
232
+ # for more information.
233
+ #
234
+ def tie(tie)
235
+ @query.tie = tie
236
+ end
237
+
238
+ def fields_added? #:nodoc:
239
+ @fields_added
240
+ end
241
+ end
242
+ end
243
+ end
@@ -0,0 +1,14 @@
1
+ module Sunspot
2
+ module DSL
3
+ class Function #:nodoc:
4
+ def initialize(functional) #:nodoc:
5
+ @functional = functional
6
+ end
7
+
8
+ def method_missing(method, *args, &block)
9
+ function_args = args.map { |arg| @functional.create_function_query(arg) }
10
+ Sunspot::Query::FunctionalFunctionQuery.new(method, function_args)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,44 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # Mixin DSL to accept functions.
5
+ #
6
+ module Functional
7
+
8
+ #
9
+ # Specify a function query with a block that returns an expression.
10
+ #
11
+ # === Examples
12
+ #
13
+ # function { 10 }
14
+ # function { :average_rating }
15
+ # function { sum(:average_rating, 10) }
16
+ #
17
+ # See http://wiki.apache.org/solr/FunctionQuery for a list of all
18
+ # applicable functions
19
+ #
20
+ def function(&block)
21
+ expression = Sunspot::Util.instance_eval_or_call(
22
+ Function.new(self),
23
+ &block
24
+ )
25
+ create_function_query(expression)
26
+ end
27
+
28
+ #
29
+ # Creates an AbstractFunctionQuery from an expression, also called by
30
+ # Sunspot::DSL::Function
31
+ #
32
+ def create_function_query(expression) #:nodoc:
33
+ if expression.is_a?(Sunspot::Query::FunctionQuery)
34
+ expression
35
+ elsif expression.is_a?(Symbol)
36
+ Sunspot::Query::FieldFunctionQuery.new(@setup.field(expression))
37
+ else
38
+ Sunspot::Query::ConstantFunctionQuery.new(expression)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+