nuatt_sunspot 1.1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (187) hide show
  1. data/History.txt +198 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +239 -0
  4. data/Rakefile +11 -0
  5. data/TODO +13 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-installer +19 -0
  8. data/bin/sunspot-solr +74 -0
  9. data/installer/config/schema.yml +95 -0
  10. data/lib/light_config.rb +40 -0
  11. data/lib/sunspot.rb +529 -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.rb +5 -0
  17. data/lib/sunspot/dsl/adjustable.rb +47 -0
  18. data/lib/sunspot/dsl/field_query.rb +266 -0
  19. data/lib/sunspot/dsl/fields.rb +113 -0
  20. data/lib/sunspot/dsl/fulltext.rb +243 -0
  21. data/lib/sunspot/dsl/function.rb +14 -0
  22. data/lib/sunspot/dsl/functional.rb +41 -0
  23. data/lib/sunspot/dsl/more_like_this_query.rb +56 -0
  24. data/lib/sunspot/dsl/paginatable.rb +28 -0
  25. data/lib/sunspot/dsl/query_facet.rb +36 -0
  26. data/lib/sunspot/dsl/restriction.rb +25 -0
  27. data/lib/sunspot/dsl/scope.rb +229 -0
  28. data/lib/sunspot/dsl/search.rb +30 -0
  29. data/lib/sunspot/dsl/standard_query.rb +125 -0
  30. data/lib/sunspot/field.rb +192 -0
  31. data/lib/sunspot/field_factory.rb +147 -0
  32. data/lib/sunspot/indexer.rb +131 -0
  33. data/lib/sunspot/installer.rb +31 -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 +106 -0
  37. data/lib/sunspot/installer/task_helper.rb +18 -0
  38. data/lib/sunspot/query.rb +10 -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 +31 -0
  43. data/lib/sunspot/query/connective.rb +191 -0
  44. data/lib/sunspot/query/date_field_facet.rb +14 -0
  45. data/lib/sunspot/query/dismax.rb +127 -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/highlighting.rb +55 -0
  50. data/lib/sunspot/query/local.rb +26 -0
  51. data/lib/sunspot/query/more_like_this.rb +60 -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 +20 -0
  60. data/lib/sunspot/query/text_field_boost.rb +17 -0
  61. data/lib/sunspot/schema.rb +151 -0
  62. data/lib/sunspot/search.rb +9 -0
  63. data/lib/sunspot/search/abstract_search.rb +302 -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/query_facet.rb +62 -0
  71. data/lib/sunspot/search/standard_search.rb +21 -0
  72. data/lib/sunspot/server.rb +152 -0
  73. data/lib/sunspot/session.rb +252 -0
  74. data/lib/sunspot/session_proxy.rb +71 -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/sharding_session_proxy.rb +215 -0
  80. data/lib/sunspot/session_proxy/thread_local_session_proxy.rb +37 -0
  81. data/lib/sunspot/setup.rb +366 -0
  82. data/lib/sunspot/text_field_setup.rb +29 -0
  83. data/lib/sunspot/type.rb +340 -0
  84. data/lib/sunspot/util.rb +253 -0
  85. data/lib/sunspot/version.rb +3 -0
  86. data/solr/etc/jetty.xml +214 -0
  87. data/solr/etc/webdefault.xml +379 -0
  88. data/solr/lib/jetty-6.1.3.jar +0 -0
  89. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  90. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  91. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  92. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  93. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  94. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  95. data/solr/solr/conf/admin-extra.html +31 -0
  96. data/solr/solr/conf/elevate.xml +36 -0
  97. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +246 -0
  98. data/solr/solr/conf/protwords.txt +21 -0
  99. data/solr/solr/conf/schema.xml +240 -0
  100. data/solr/solr/conf/scripts.conf +24 -0
  101. data/solr/solr/conf/solrconfig.xml +938 -0
  102. data/solr/solr/conf/spellings.txt +2 -0
  103. data/solr/solr/conf/stopwords.es.txt +345 -0
  104. data/solr/solr/conf/stopwords.txt +58 -0
  105. data/solr/solr/conf/synonyms.txt +31 -0
  106. data/solr/solr/lib/lucene-spatial-2.9.1.jar +0 -0
  107. data/solr/solr/lib/solr-spatial-light-0.0.6.jar +0 -0
  108. data/solr/start.jar +0 -0
  109. data/solr/webapps/solr.war +0 -0
  110. data/spec/api/adapters_spec.rb +33 -0
  111. data/spec/api/binding_spec.rb +38 -0
  112. data/spec/api/indexer/attributes_spec.rb +149 -0
  113. data/spec/api/indexer/batch_spec.rb +46 -0
  114. data/spec/api/indexer/dynamic_fields_spec.rb +42 -0
  115. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  116. data/spec/api/indexer/fulltext_spec.rb +43 -0
  117. data/spec/api/indexer/removal_spec.rb +53 -0
  118. data/spec/api/indexer/spec_helper.rb +1 -0
  119. data/spec/api/indexer_spec.rb +14 -0
  120. data/spec/api/query/advanced_manipulation_examples.rb +35 -0
  121. data/spec/api/query/connectives_examples.rb +176 -0
  122. data/spec/api/query/dsl_spec.rb +18 -0
  123. data/spec/api/query/dynamic_fields_examples.rb +165 -0
  124. data/spec/api/query/faceting_examples.rb +399 -0
  125. data/spec/api/query/fulltext_examples.rb +315 -0
  126. data/spec/api/query/function_spec.rb +70 -0
  127. data/spec/api/query/highlighting_examples.rb +225 -0
  128. data/spec/api/query/local_examples.rb +38 -0
  129. data/spec/api/query/more_like_this_spec.rb +140 -0
  130. data/spec/api/query/ordering_pagination_examples.rb +97 -0
  131. data/spec/api/query/scope_examples.rb +256 -0
  132. data/spec/api/query/spec_helper.rb +1 -0
  133. data/spec/api/query/standard_spec.rb +28 -0
  134. data/spec/api/query/text_field_scoping_examples.rb +30 -0
  135. data/spec/api/query/types_spec.rb +20 -0
  136. data/spec/api/search/dynamic_fields_spec.rb +33 -0
  137. data/spec/api/search/faceting_spec.rb +356 -0
  138. data/spec/api/search/highlighting_spec.rb +69 -0
  139. data/spec/api/search/hits_spec.rb +138 -0
  140. data/spec/api/search/results_spec.rb +79 -0
  141. data/spec/api/search/search_spec.rb +23 -0
  142. data/spec/api/search/spec_helper.rb +1 -0
  143. data/spec/api/server_spec.rb +91 -0
  144. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +85 -0
  145. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +30 -0
  146. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +41 -0
  147. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +77 -0
  148. data/spec/api/session_proxy/spec_helper.rb +9 -0
  149. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +50 -0
  150. data/spec/api/session_spec.rb +198 -0
  151. data/spec/api/spec_helper.rb +3 -0
  152. data/spec/api/sunspot_spec.rb +18 -0
  153. data/spec/ext.rb +11 -0
  154. data/spec/helpers/indexer_helper.rb +29 -0
  155. data/spec/helpers/query_helper.rb +38 -0
  156. data/spec/helpers/search_helper.rb +80 -0
  157. data/spec/integration/dynamic_fields_spec.rb +55 -0
  158. data/spec/integration/faceting_spec.rb +238 -0
  159. data/spec/integration/highlighting_spec.rb +22 -0
  160. data/spec/integration/indexing_spec.rb +33 -0
  161. data/spec/integration/keyword_search_spec.rb +317 -0
  162. data/spec/integration/local_search_spec.rb +91 -0
  163. data/spec/integration/more_like_this_spec.rb +43 -0
  164. data/spec/integration/scoped_search_spec.rb +324 -0
  165. data/spec/integration/spec_helper.rb +7 -0
  166. data/spec/integration/stored_fields_spec.rb +10 -0
  167. data/spec/integration/test_pagination.rb +32 -0
  168. data/spec/mocks/adapters.rb +32 -0
  169. data/spec/mocks/blog.rb +3 -0
  170. data/spec/mocks/comment.rb +21 -0
  171. data/spec/mocks/connection.rb +122 -0
  172. data/spec/mocks/mock_adapter.rb +30 -0
  173. data/spec/mocks/mock_class_sharding_session_proxy.rb +24 -0
  174. data/spec/mocks/mock_record.rb +52 -0
  175. data/spec/mocks/mock_sharding_session_proxy.rb +15 -0
  176. data/spec/mocks/photo.rb +12 -0
  177. data/spec/mocks/post.rb +76 -0
  178. data/spec/mocks/super_class.rb +2 -0
  179. data/spec/mocks/user.rb +8 -0
  180. data/spec/spec_helper.rb +52 -0
  181. data/tasks/gemspec.rake +33 -0
  182. data/tasks/rcov.rake +28 -0
  183. data/tasks/rdoc.rake +27 -0
  184. data/tasks/schema.rake +19 -0
  185. data/tasks/spec.rake +24 -0
  186. data/tasks/todo.rake +4 -0
  187. metadata +355 -0
@@ -0,0 +1,366 @@
1
+ module Sunspot
2
+ #
3
+ # This class encapsulates the search/indexing setup for a given class. Its
4
+ # contents are built using the Sunspot.setup method.
5
+ #
6
+ class Setup #:nodoc:
7
+ attr_reader :class_object_id
8
+ def initialize(clazz)
9
+ @class_object_id = clazz.object_id
10
+ @class_name = clazz.name
11
+ @field_factories, @text_field_factories, @dynamic_field_factories,
12
+ @field_factories_cache, @text_field_factories_cache,
13
+ @dynamic_field_factories_cache = *Array.new(6) { Hash.new }
14
+ @stored_field_factories_cache = Hash.new { |h, k| h[k] = [] }
15
+ @more_like_this_field_factories_cache = Hash.new { |h, k| h[k] = [] }
16
+ @dsl = DSL::Fields.new(self)
17
+ add_field_factory(:class, Type::ClassType.instance)
18
+ end
19
+
20
+ def type_names
21
+ [@class_name]
22
+ end
23
+
24
+ #
25
+ # Add field factory for scope/ordering
26
+ #
27
+ def add_field_factory(name, type, options = {}, &block)
28
+ stored, more_like_this = options[:stored], options[:more_like_this]
29
+ field_factory = FieldFactory::Static.new(name, type, options, &block)
30
+ @field_factories[field_factory.signature] = field_factory
31
+ @field_factories_cache[field_factory.name] = field_factory
32
+ if stored
33
+ @stored_field_factories_cache[field_factory.name] << field_factory
34
+ end
35
+ if more_like_this
36
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
37
+ end
38
+ end
39
+
40
+ #
41
+ # Add field_factories for fulltext search
42
+ #
43
+ # ==== Parameters
44
+ #
45
+ # field_factories<Array>:: Array of Sunspot::Field objects
46
+ #
47
+ def add_text_field_factory(name, options = {}, &block)
48
+ stored, more_like_this = options[:stored], options[:more_like_this]
49
+ field_factory = FieldFactory::Static.new(name, Type::TextType.instance, options, &block)
50
+ @text_field_factories[name] = field_factory
51
+ @text_field_factories_cache[field_factory.name] = field_factory
52
+ if stored
53
+ @stored_field_factories_cache[field_factory.name] << field_factory
54
+ end
55
+ if more_like_this
56
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
57
+ end
58
+ end
59
+
60
+ #
61
+ # Add dynamic field_factories
62
+ #
63
+ # ==== Parameters
64
+ #
65
+ # field_factories<Array>:: Array of dynamic field objects
66
+ #
67
+ def add_dynamic_field_factory(name, type, options = {}, &block)
68
+ stored, more_like_this = options[:stored], options[:more_like_this]
69
+ field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
70
+ @dynamic_field_factories[field_factory.signature] = field_factory
71
+ @dynamic_field_factories_cache[field_factory.name] = field_factory
72
+ if stored
73
+ @stored_field_factories_cache[field_factory.name] << field_factory
74
+ end
75
+ if more_like_this
76
+ @more_like_this_field_factories_cache[field_factory.name] << field_factory
77
+ end
78
+ end
79
+
80
+ #
81
+ # The coordinates field factory is used for populating the coordinate fields
82
+ # of documents during index, but does not actually generate fields (since
83
+ # the field names used in search are static).
84
+ #
85
+ def set_coordinates_field(name = nil, &block)
86
+ @coordinates_field_factory = FieldFactory::Coordinates.new(name, &block)
87
+ end
88
+
89
+ #
90
+ # Add a document boost to documents at index time. Document boost can be
91
+ # static (the same for all documents of this class), or extracted on a per-
92
+ # document basis using either attribute or block extraction as per usual.
93
+ #
94
+ def add_document_boost(attr_name, &block)
95
+ @document_boost_extractor =
96
+ if attr_name
97
+ if attr_name.respond_to?(:to_f)
98
+ DataExtractor::Constant.new(attr_name)
99
+ else
100
+ DataExtractor::AttributeExtractor.new(attr_name)
101
+ end
102
+ else
103
+ DataExtractor::BlockExtractor.new(&block)
104
+ end
105
+ end
106
+
107
+ #
108
+ # Builder method for evaluating the setup DSL
109
+ #
110
+ def setup(&block)
111
+ Util.instance_eval_or_call(@dsl, &block)
112
+ end
113
+
114
+ #
115
+ # Return the Field with the given (public-facing) name
116
+ #
117
+ def field(field_name)
118
+ if field_factory = @field_factories_cache[field_name.to_sym]
119
+ field_factory.build
120
+ else
121
+ raise(
122
+ UnrecognizedFieldError,
123
+ "No field configured for #{@class_name} with name '#{field_name}'"
124
+ )
125
+ end
126
+ end
127
+
128
+ #
129
+ # Return one or more text fields with the given public-facing name. This
130
+ # implementation will always return a single field (in an array), but
131
+ # CompositeSetup objects might return more than one.
132
+ #
133
+ def text_fields(field_name)
134
+ text_field =
135
+ if field_factory = @text_field_factories_cache[field_name.to_sym]
136
+ field_factory.build
137
+ else
138
+ raise(
139
+ UnrecognizedFieldError,
140
+ "No text field configured for #{@class_name} with name '#{field_name}'"
141
+ )
142
+ end
143
+ [text_field]
144
+ end
145
+
146
+ #
147
+ # Return one or more stored fields (can be either attribute or text fields)
148
+ # for the given name.
149
+ #
150
+ def stored_fields(field_name, dynamic_field_name = nil)
151
+ @stored_field_factories_cache[field_name.to_sym].map do |field_factory|
152
+ if dynamic_field_name
153
+ field_factory.build(dynamic_field_name)
154
+ else
155
+ field_factory.build
156
+ end
157
+ end
158
+ end
159
+
160
+ #
161
+ # Return one or more more_like_this fields (can be either attribute or text fields)
162
+ # for the given name.
163
+ #
164
+ def more_like_this_fields(field_name)
165
+ @more_like_this_field_factories_cache[field_name.to_sym].map do |field_factory|
166
+ field_factory.build
167
+ end
168
+ end
169
+
170
+ #
171
+ # Return the DynamicFieldFactory with the given base name
172
+ #
173
+ def dynamic_field_factory(field_name)
174
+ @dynamic_field_factories_cache[field_name.to_sym] || raise(
175
+ UnrecognizedFieldError,
176
+ "No dynamic field configured for #{@class_name} with name '#{field_name}'"
177
+ )
178
+ end
179
+
180
+ #
181
+ # Return all attribute fields
182
+ #
183
+ def fields
184
+ field_factories.map { |field_factory| field_factory.build }
185
+ end
186
+
187
+ #
188
+ # Return all text fields
189
+ #
190
+ def all_text_fields
191
+ text_field_factories.map { |text_field_factory| text_field_factory.build }
192
+ end
193
+
194
+ #
195
+ # Return all more_like_this fields
196
+ #
197
+ def all_more_like_this_fields
198
+ @more_like_this_field_factories_cache.values.map do |field_factories|
199
+ field_factories.map { |field_factory| field_factory.build }
200
+ end.flatten
201
+ end
202
+
203
+ #
204
+ # Get the field_factories associated with this setup as well as all inherited field_factories
205
+ #
206
+ # ==== Returns
207
+ #
208
+ # Array:: Collection of all field_factories associated with this setup
209
+ #
210
+ def field_factories
211
+ collection_from_inheritable_hash(:field_factories)
212
+ end
213
+
214
+ #
215
+ # Get the text field_factories associated with this setup as well as all inherited
216
+ # text field_factories
217
+ #
218
+ # ==== Returns
219
+ #
220
+ # Array:: Collection of all text field_factories associated with this setup
221
+ #
222
+ def text_field_factories
223
+ collection_from_inheritable_hash(:text_field_factories)
224
+ end
225
+
226
+ #
227
+ # Get all static, dynamic, and text field_factories associated with this setup as
228
+ # well as all inherited field_factories
229
+ #
230
+ # ==== Returns
231
+ #
232
+ # Array:: Collection of all text and scope field_factories associated with this setup
233
+ #
234
+ def all_field_factories
235
+ all_field_factories = []
236
+ all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
237
+ all_field_factories << @coordinates_field_factory if @coordinates_field_factory
238
+ all_field_factories
239
+ end
240
+
241
+ #
242
+ # Get all dynamic field_factories for this and parent setups
243
+ #
244
+ # ==== Returns
245
+ #
246
+ # Array:: Dynamic field_factories
247
+ #
248
+ def dynamic_field_factories
249
+ collection_from_inheritable_hash(:dynamic_field_factories)
250
+ end
251
+
252
+ #
253
+ # Return the class associated with this setup.
254
+ #
255
+ # ==== Returns
256
+ #
257
+ # clazz<Class>:: Class setup is configured for
258
+ #
259
+ def clazz
260
+ Util.full_const_get(@class_name)
261
+ end
262
+
263
+ #
264
+ # Get the document boost for a given model
265
+ #
266
+ def document_boost_for(model)
267
+ if @document_boost_extractor
268
+ @document_boost_extractor.value_for(model)
269
+ end
270
+ end
271
+
272
+ protected
273
+
274
+ #
275
+ # Get the nearest inherited setup, if any
276
+ #
277
+ # ==== Returns
278
+ #
279
+ # Sunspot::Setup:: Setup for the nearest ancestor of this setup's class
280
+ #
281
+ def parent
282
+ Setup.for(clazz.superclass)
283
+ end
284
+
285
+ def get_inheritable_hash(name)
286
+ hash = instance_variable_get(:"@#{name}")
287
+ parent.get_inheritable_hash(name).each_pair do |key, value|
288
+ hash[key] = value unless hash.has_key?(key)
289
+ end if parent
290
+ hash
291
+ end
292
+
293
+ private
294
+
295
+ def collection_from_inheritable_hash(name)
296
+ get_inheritable_hash(name).values
297
+ end
298
+
299
+ class <<self
300
+ #
301
+ # Retrieve or create the Setup instance for the given class, evaluating
302
+ # the given block to add to the setup's configuration
303
+ #
304
+ def setup(clazz, &block) #:nodoc:
305
+ self.for!(clazz).setup(&block)
306
+ end
307
+
308
+ #
309
+ # Retrieve the setup instance for the given class, or for the nearest
310
+ # ancestor that has a setup, if any.
311
+ #
312
+ # ==== Parameters
313
+ #
314
+ # clazz<Class>:: Class for which to retrieve a setup
315
+ #
316
+ # ==== Returns
317
+ #
318
+ # Sunspot::Setup::
319
+ # Setup instance associated with the given class or its nearest ancestor
320
+ #
321
+ def for(clazz) #:nodoc:
322
+ class_name =
323
+ if clazz.respond_to?(:name)
324
+ clazz.name
325
+ else
326
+ clazz
327
+ end
328
+ setups[class_name.to_sym] || self.for(clazz.superclass) if clazz
329
+ end
330
+
331
+ protected
332
+
333
+ #
334
+ # Retrieve or create a Setup instance for this class
335
+ #
336
+ # ==== Parameters
337
+ #
338
+ # clazz<Class>:: Class for which to retrieve a setup
339
+ #
340
+ # ==== Returns
341
+ #
342
+ # Sunspot::Setup:: New or existing setup for this class
343
+ #
344
+ def for!(clazz) #:nodoc:
345
+ setup = setups[clazz.name.to_sym]
346
+ if setup && setup.class_object_id == clazz.object_id
347
+ setup
348
+ else
349
+ setups[clazz.name.to_sym] = new(clazz)
350
+ end
351
+ end
352
+
353
+ private
354
+
355
+ # Singleton hash of class names to Setup instances
356
+ #
357
+ # ==== Returns
358
+ #
359
+ # Hash:: Class names keyed to Setup instances
360
+ #
361
+ def setups
362
+ @setups ||= {}
363
+ end
364
+ end
365
+ end
366
+ end
@@ -0,0 +1,29 @@
1
+ module Sunspot
2
+ #
3
+ # A TextFieldSetup encapsulates a regular (or composite) setup, and exposes
4
+ # the #field() method returning text fields instead of attribute fields.
5
+ #
6
+ class TextFieldSetup #:nodoc:
7
+ def initialize(setup)
8
+ @setup = setup
9
+ end
10
+
11
+ #
12
+ # Return a text field with the given name. Duck-type compatible with
13
+ # Setup and CompositeSetup, but return text fields instead.
14
+ #
15
+ def field(name)
16
+ fields = @setup.text_fields(name)
17
+ if fields
18
+ if fields.length == 1
19
+ fields.first
20
+ else
21
+ raise(
22
+ Sunspot::UnrecognizedFieldError,
23
+ "The text field with name #{name} has incompatible configurations for the classes #{@setup.type_names.join(', ')}"
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,340 @@
1
+ module Sunspot
2
+ #
3
+ # This module contains singleton objects that represent the types that can be
4
+ # indexed and searched using Sunspot. Plugin developers should be able to
5
+ # add new constants to the Type module; as long as they implement the
6
+ # appropriate methods, Sunspot should be able to integrate them (note that
7
+ # this capability is untested at the moment). The required methods are:
8
+ #
9
+ # +indexed_name+::
10
+ # Convert a given field name into its form as stored in Solr. This
11
+ # generally means adding a suffix to match a Solr dynamicField definition.
12
+ # +to_indexed+::
13
+ # Convert a value of this type into the appropriate Solr string
14
+ # representation.
15
+ # +cast+::
16
+ # Convert a Solr string representation of a value into the appropriate
17
+ # Ruby type.
18
+ #
19
+ module Type
20
+ class <<self
21
+ def register(sunspot_type, *classes)
22
+ classes.each do |clazz|
23
+ ruby_type_map[clazz.name.to_sym] = sunspot_type.instance
24
+ end
25
+ end
26
+
27
+ def for_class(clazz)
28
+ if clazz
29
+ ruby_type_map[clazz.name.to_sym] || for_class(clazz.superclass)
30
+ end
31
+ end
32
+
33
+ def for(object)
34
+ for_class(object.class)
35
+ end
36
+
37
+ def to_indexed(object)
38
+ if type = self.for(object)
39
+ type.to_indexed(object)
40
+ else
41
+ object.to_s
42
+ end
43
+ end
44
+
45
+ def to_literal(object)
46
+ if type = self.for(object)
47
+ type.to_literal(object)
48
+ else
49
+ raise ArgumentError, "Can't use #{object.inspect} as Solr literal: #{object.class} has no registered Solr type"
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def ruby_type_map
56
+ @ruby_type_map ||= {}
57
+ end
58
+ end
59
+
60
+ class AbstractType #:nodoc:
61
+ class <<self
62
+ def instance
63
+ @instance ||= new
64
+ end
65
+ private :new
66
+ end
67
+
68
+ def accepts_dynamic?
69
+ true
70
+ end
71
+
72
+ def accepts_more_like_this?
73
+ false
74
+ end
75
+
76
+ def to_literal(object)
77
+ raise(
78
+ ArgumentError,
79
+ "#{self.class.name} cannot be used as a Solr literal"
80
+ )
81
+ end
82
+ end
83
+
84
+ #
85
+ # Text is a special type that stores data for fulltext search. Unlike other
86
+ # types, Text fields are tokenized and are made available to the keyword
87
+ # search phrase. Text fields cannot be faceted, ordered upon, or used in
88
+ # restrictions. Similarly, text fields are the only fields that are made
89
+ # available to keyword search.
90
+ #
91
+ class TextType < AbstractType
92
+ def indexed_name(name) #:nodoc:
93
+ "#{name}_text"
94
+ end
95
+
96
+ def to_indexed(value) #:nodoc:
97
+ value.to_s if value
98
+ end
99
+
100
+ def cast(text)
101
+ text
102
+ end
103
+
104
+ def accepts_dynamic?
105
+ false
106
+ end
107
+
108
+ def accepts_more_like_this?
109
+ true
110
+ end
111
+ end
112
+
113
+ #
114
+ # The String type represents string data.
115
+ #
116
+ class StringType < AbstractType
117
+ def indexed_name(name) #:nodoc:
118
+ "#{name}_s"
119
+ end
120
+
121
+ def to_indexed(value) #:nodoc:
122
+ value.to_s if value
123
+ end
124
+
125
+ def cast(string) #:nodoc:
126
+ string
127
+ end
128
+ end
129
+ register(StringType, String)
130
+
131
+ #
132
+ # The Integer type represents integers.
133
+ #
134
+ class IntegerType < AbstractType
135
+ def indexed_name(name) #:nodoc:
136
+ "#{name}_i"
137
+ end
138
+
139
+ def to_indexed(value) #:nodoc:
140
+ value.to_i.to_s if value
141
+ end
142
+
143
+ def to_literal(value)
144
+ to_indexed(value)
145
+ end
146
+
147
+ def cast(string) #:nodoc:
148
+ string.to_i
149
+ end
150
+ end
151
+ register(IntegerType, Integer)
152
+
153
+ #
154
+ # The Long type indexes Ruby Fixnum and Bignum numbers into Java Longs
155
+ #
156
+ class LongType < IntegerType
157
+ def indexed_name(name) #:nodoc:
158
+ "#{name}_l"
159
+ end
160
+ end
161
+
162
+ #
163
+ # The Float type represents floating-point numbers.
164
+ #
165
+ class FloatType < AbstractType
166
+ def indexed_name(name) #:nodoc:
167
+ "#{name}_f"
168
+ end
169
+
170
+ def to_indexed(value) #:nodoc:
171
+ value.to_f.to_s if value
172
+ end
173
+
174
+ def to_literal(value)
175
+ to_indexed(value)
176
+ end
177
+
178
+ def cast(string) #:nodoc:
179
+ string.to_f
180
+ end
181
+ end
182
+ register(FloatType, Float)
183
+
184
+ #
185
+ # The Double type indexes Ruby Floats (which are in fact doubles) into Java
186
+ # Double fields
187
+ #
188
+ class DoubleType < FloatType
189
+ def indexed_name(name)
190
+ "#{name}_e"
191
+ end
192
+ end
193
+
194
+ #
195
+ # The time type represents times. Note that times are always converted to
196
+ # UTC before indexing, and facets of Time fields always return times in UTC.
197
+ #
198
+ class TimeType < AbstractType
199
+ XMLSCHEMA = "%Y-%m-%dT%H:%M:%SZ"
200
+
201
+ def indexed_name(name) #:nodoc:
202
+ "#{name}_d"
203
+ end
204
+
205
+ def to_indexed(value) #:nodoc:
206
+ if value
207
+ value_to_utc_time(value).strftime(XMLSCHEMA)
208
+ end
209
+ end
210
+
211
+ def to_literal(value)
212
+ to_indexed(value)
213
+ end
214
+
215
+ def cast(string) #:nodoc:
216
+ begin
217
+ Time.xmlschema(string)
218
+ rescue ArgumentError
219
+ DateTime.strptime(string, XMLSCHEMA)
220
+ end
221
+ end
222
+
223
+ private
224
+
225
+ def value_to_utc_time(value)
226
+ if value.respond_to?(:utc)
227
+ value.utc
228
+ elsif value.respond_to?(:new_offset)
229
+ value.new_offset
230
+ else
231
+ begin
232
+ Time.parse(value.to_s).utc
233
+ rescue ArgumentError
234
+ DateTime.parse(value.to_s).new_offset
235
+ end
236
+ end
237
+ end
238
+ end
239
+ register TimeType, Time, DateTime
240
+
241
+ #
242
+ # The DateType encapsulates dates (without time information). Internally,
243
+ # Solr does not have a date-only type, so this type indexes data using
244
+ # Solr's DateField type (which is actually date/time), midnight UTC of the
245
+ # indexed date.
246
+ #
247
+ class DateType < TimeType
248
+ def to_indexed(value) #:nodoc:
249
+ if value
250
+ time =
251
+ if %w(year mon mday).all? { |method| value.respond_to?(method) }
252
+ Time.utc(value.year, value.mon, value.mday)
253
+ else
254
+ date = Date.parse(value.to_s)
255
+ Time.utc(date.year, date.mon, date.mday)
256
+ end
257
+ super(time)
258
+ end
259
+ end
260
+
261
+ def cast(string) #:nodoc:
262
+ time = super
263
+ Date.civil(time.year, time.mon, time.mday)
264
+ end
265
+ end
266
+ register DateType, Date
267
+
268
+ #
269
+ # Store integers in a TrieField, which makes range queries much faster.
270
+ #
271
+ class TrieIntegerType < IntegerType
272
+ def indexed_name(name)
273
+ "#{super}t"
274
+ end
275
+ end
276
+
277
+ #
278
+ # Store floats in a TrieField, which makes range queries much faster.
279
+ #
280
+ class TrieFloatType < FloatType
281
+ def indexed_name(name)
282
+ "#{super}t"
283
+ end
284
+ end
285
+
286
+ #
287
+ # Index times using a TrieField. Internally, trie times are indexed as
288
+ # Unix timestamps in a trie integer field, as TrieField does not support
289
+ # datetime types natively. This distinction should have no effect from the
290
+ # standpoint of the library's API.
291
+ #
292
+ class TrieTimeType < TimeType
293
+ def indexed_name(name)
294
+ "#{super}t"
295
+ end
296
+ end
297
+
298
+
299
+ #
300
+ # The boolean type represents true/false values. Note that +nil+ will not be
301
+ # indexed at all; only +false+ will be indexed with a false value.
302
+ #
303
+ class BooleanType < AbstractType
304
+ def indexed_name(name) #:nodoc:
305
+ "#{name}_b"
306
+ end
307
+
308
+ def to_indexed(value) #:nodoc:
309
+ unless value.nil?
310
+ value ? 'true' : 'false'
311
+ end
312
+ end
313
+
314
+ def cast(string) #:nodoc:
315
+ case string
316
+ when 'true'
317
+ true
318
+ when 'false'
319
+ false
320
+ end
321
+ end
322
+ end
323
+ register BooleanType, TrueClass, FalseClass
324
+
325
+ class ClassType < AbstractType
326
+ def indexed_name(name) #:nodoc:
327
+ 'class_name'
328
+ end
329
+
330
+ def to_indexed(value) #:nodoc:
331
+ value.name
332
+ end
333
+
334
+ def cast(string) #:nodoc:
335
+ Sunspot::Util.full_const_get(string)
336
+ end
337
+ end
338
+ register ClassType, Class
339
+ end
340
+ end