UnderpantsGnome-sunspot 0.9.1.1

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 (103) hide show
  1. data/History.txt +39 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +4 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +470 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/date_facet.rb +36 -0
  16. data/lib/sunspot/date_facet_row.rb +17 -0
  17. data/lib/sunspot/dsl.rb +3 -0
  18. data/lib/sunspot/dsl/field_query.rb +72 -0
  19. data/lib/sunspot/dsl/fields.rb +86 -0
  20. data/lib/sunspot/dsl/query.rb +59 -0
  21. data/lib/sunspot/dsl/query_facet.rb +31 -0
  22. data/lib/sunspot/dsl/restriction.rb +25 -0
  23. data/lib/sunspot/dsl/scope.rb +193 -0
  24. data/lib/sunspot/dsl/search.rb +30 -0
  25. data/lib/sunspot/facet.rb +51 -0
  26. data/lib/sunspot/facet_row.rb +34 -0
  27. data/lib/sunspot/field.rb +157 -0
  28. data/lib/sunspot/field_factory.rb +126 -0
  29. data/lib/sunspot/indexer.rb +127 -0
  30. data/lib/sunspot/instantiated_facet.rb +38 -0
  31. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  32. data/lib/sunspot/query.rb +190 -0
  33. data/lib/sunspot/query/base_query.rb +90 -0
  34. data/lib/sunspot/query/connective.rb +77 -0
  35. data/lib/sunspot/query/dynamic_query.rb +69 -0
  36. data/lib/sunspot/query/field_facet.rb +149 -0
  37. data/lib/sunspot/query/field_query.rb +57 -0
  38. data/lib/sunspot/query/pagination.rb +39 -0
  39. data/lib/sunspot/query/query_facet.rb +72 -0
  40. data/lib/sunspot/query/query_facet_row.rb +19 -0
  41. data/lib/sunspot/query/restriction.rb +225 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/query_facet.rb +33 -0
  46. data/lib/sunspot/query_facet_row.rb +21 -0
  47. data/lib/sunspot/schema.rb +165 -0
  48. data/lib/sunspot/search.rb +222 -0
  49. data/lib/sunspot/search/hit.rb +62 -0
  50. data/lib/sunspot/session.rb +201 -0
  51. data/lib/sunspot/setup.rb +271 -0
  52. data/lib/sunspot/type.rb +200 -0
  53. data/lib/sunspot/util.rb +164 -0
  54. data/solr/etc/jetty.xml +212 -0
  55. data/solr/etc/webdefault.xml +379 -0
  56. data/solr/lib/jetty-6.1.3.jar +0 -0
  57. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  58. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  59. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  60. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  61. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  62. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  63. data/solr/solr/conf/elevate.xml +36 -0
  64. data/solr/solr/conf/protwords.txt +21 -0
  65. data/solr/solr/conf/schema.xml +50 -0
  66. data/solr/solr/conf/solrconfig.xml +696 -0
  67. data/solr/solr/conf/stopwords.txt +57 -0
  68. data/solr/solr/conf/synonyms.txt +31 -0
  69. data/solr/start.jar +0 -0
  70. data/solr/webapps/solr.war +0 -0
  71. data/spec/api/adapters_spec.rb +33 -0
  72. data/spec/api/build_search_spec.rb +918 -0
  73. data/spec/api/indexer_spec.rb +311 -0
  74. data/spec/api/query_spec.rb +153 -0
  75. data/spec/api/search_retrieval_spec.rb +325 -0
  76. data/spec/api/session_spec.rb +157 -0
  77. data/spec/api/spec_helper.rb +1 -0
  78. data/spec/api/sunspot_spec.rb +18 -0
  79. data/spec/integration/dynamic_fields_spec.rb +55 -0
  80. data/spec/integration/faceting_spec.rb +169 -0
  81. data/spec/integration/keyword_search_spec.rb +83 -0
  82. data/spec/integration/scoped_search_spec.rb +188 -0
  83. data/spec/integration/spec_helper.rb +1 -0
  84. data/spec/integration/stored_fields_spec.rb +10 -0
  85. data/spec/integration/test_pagination.rb +32 -0
  86. data/spec/mocks/adapters.rb +32 -0
  87. data/spec/mocks/blog.rb +3 -0
  88. data/spec/mocks/comment.rb +19 -0
  89. data/spec/mocks/connection.rb +84 -0
  90. data/spec/mocks/mock_adapter.rb +30 -0
  91. data/spec/mocks/mock_record.rb +41 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +70 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +25 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +21 -0
  99. data/tasks/schema.rake +19 -0
  100. data/tasks/spec.rake +24 -0
  101. data/tasks/todo.rake +4 -0
  102. data/templates/schema.xml.haml +24 -0
  103. metadata +245 -0
@@ -0,0 +1,271 @@
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
+ def initialize(clazz)
8
+ @clazz = clazz
9
+ @class_name = clazz.name
10
+ @field_factories, @text_field_factories, @dynamic_field_factories,
11
+ @field_factories_cache, @text_field_factories_cache,
12
+ @dynamic_field_factories_cache = *Array.new(6) { Hash.new }
13
+ @dsl = DSL::Fields.new(self)
14
+ add_field_factory(:class, Type::ClassType)
15
+ end
16
+
17
+ def type_names
18
+ [@class_name]
19
+ end
20
+
21
+ #
22
+ # Add field_factories for scope/ordering
23
+ #
24
+ # ==== Parameters
25
+ #
26
+ # field_factories<Array>:: Array of Sunspot::Field objects
27
+ #
28
+ def add_field_factory(name, type, options = {}, &block)
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
+ end
33
+
34
+ #
35
+ # Add field_factories for fulltext search
36
+ #
37
+ # ==== Parameters
38
+ #
39
+ # field_factories<Array>:: Array of Sunspot::Field objects
40
+ #
41
+ def add_text_field_factory(name, options = {}, &block)
42
+ field_factory = FieldFactory::Static.new(name, Type::TextType, options, &block)
43
+ @text_field_factories[name] = field_factory
44
+ @text_field_factories_cache[field_factory.name] = field_factory
45
+ end
46
+
47
+ #
48
+ # Add dynamic field_factories
49
+ #
50
+ # ==== Parameters
51
+ #
52
+ # field_factories<Array>:: Array of dynamic field objects
53
+ #
54
+ def add_dynamic_field_factory(name, type, options = {}, &block)
55
+ field_factory = FieldFactory::Dynamic.new(name, type, options, &block)
56
+ @dynamic_field_factories[field_factory.signature] = field_factory
57
+ @dynamic_field_factories_cache[field_factory.name] = field_factory
58
+ end
59
+
60
+ def add_document_boost(attr_name, &block)
61
+ @document_boost_extractor =
62
+ if attr_name
63
+ if attr_name.respond_to?(:to_f)
64
+ DataExtractor::Constant.new(attr_name)
65
+ else
66
+ DataExtractor::AttributeExtractor.new(attr_name)
67
+ end
68
+ else
69
+ DataExtractor::BlockExtractor.new(&block)
70
+ end
71
+ end
72
+
73
+ #
74
+ # Builder method for evaluating the setup DSL
75
+ #
76
+ def setup(&block)
77
+ @dsl.instance_eval(&block)
78
+ end
79
+
80
+ def field(field_name)
81
+ if field_factory = @field_factories_cache[field_name.to_sym]
82
+ field_factory.build
83
+ else
84
+ raise(
85
+ UnrecognizedFieldError,
86
+ "No field configured for #{@clazz.name} with name '#{field_name}'"
87
+ )
88
+ end
89
+ end
90
+
91
+ def text_field(field_name)
92
+ if field_factory = @text_field_factories_cache[field_name.to_sym]
93
+ field_factory.build
94
+ else
95
+ raise(
96
+ UnrecognizedFieldError,
97
+ "No text field configured for #{@clazz.name} with name '#{field_name}'"
98
+ )
99
+ end
100
+ end
101
+
102
+ def dynamic_field_factory(field_name)
103
+ @dynamic_field_factories_cache[field_name.to_sym] || raise(
104
+ UnrecognizedFieldError,
105
+ "No dynamic field configured for #{@clazz.name} with name '#{field_name}'"
106
+ )
107
+ end
108
+
109
+ def fields
110
+ field_factories.map { |field_factory| field_factory.build }
111
+ end
112
+
113
+ def text_fields
114
+ text_field_factories.map { |text_field_factory| text_field_factory.build }
115
+ end
116
+
117
+ #
118
+ # Get the field_factories associated with this setup as well as all inherited field_factories
119
+ #
120
+ # ==== Returns
121
+ #
122
+ # Array:: Collection of all field_factories associated with this setup
123
+ #
124
+ def field_factories
125
+ collection_from_inheritable_hash(:field_factories)
126
+ end
127
+
128
+ #
129
+ # Get the text field_factories associated with this setup as well as all inherited
130
+ # text field_factories
131
+ #
132
+ # ==== Returns
133
+ #
134
+ # Array:: Collection of all text field_factories associated with this setup
135
+ #
136
+ def text_field_factories
137
+ collection_from_inheritable_hash(:text_field_factories)
138
+ end
139
+
140
+ #
141
+ # Get all static, dynamic, and text field_factories associated with this setup as
142
+ # well as all inherited field_factories
143
+ #
144
+ # ==== Returns
145
+ #
146
+ # Array:: Collection of all text and scope field_factories associated with this setup
147
+ #
148
+ def all_field_factories
149
+ all_field_factories = []
150
+ all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
151
+ all_field_factories
152
+ end
153
+
154
+ #
155
+ # Get all dynamic field_factories for this and parent setups
156
+ #
157
+ # ==== Returns
158
+ #
159
+ # Array:: Dynamic field_factories
160
+ #
161
+ def dynamic_field_factories
162
+ collection_from_inheritable_hash(:dynamic_field_factories)
163
+ end
164
+
165
+ #
166
+ # Return the class associated with this setup.
167
+ #
168
+ # ==== Returns
169
+ #
170
+ # clazz<Class>:: Class setup is configured for
171
+ #
172
+ def clazz
173
+ Util.full_const_get(@class_name)
174
+ end
175
+
176
+ def document_boost_for(model)
177
+ if @document_boost_extractor
178
+ @document_boost_extractor.value_for(model)
179
+ end
180
+ end
181
+
182
+ protected
183
+
184
+ #
185
+ # Get the nearest inherited setup, if any
186
+ #
187
+ # ==== Returns
188
+ #
189
+ # Sunspot::Setup:: Setup for the nearest ancestor of this setup's class
190
+ #
191
+ def parent
192
+ Setup.for(clazz.superclass)
193
+ end
194
+
195
+ def get_inheritable_hash(name)
196
+ hash = instance_variable_get(:"@#{name}")
197
+ parent.get_inheritable_hash(name).each_pair do |key, value|
198
+ hash[key] = value unless hash.has_key?(key)
199
+ end if parent
200
+ hash
201
+ end
202
+
203
+ private
204
+
205
+ def collection_from_inheritable_hash(name)
206
+ get_inheritable_hash(name).values
207
+ end
208
+
209
+ class <<self
210
+ #
211
+ # Retrieve or create the Setup instance for the given class, evaluating
212
+ # the given block to add to the setup's configuration
213
+ #
214
+ def setup(clazz, &block) #:nodoc:
215
+ self.for!(clazz).setup(&block)
216
+ end
217
+
218
+ #
219
+ # Retrieve the setup instance for the given class, or for the nearest
220
+ # ancestor that has a setup, if any.
221
+ #
222
+ # ==== Parameters
223
+ #
224
+ # clazz<Class>:: Class for which to retrieve a setup
225
+ #
226
+ # ==== Returns
227
+ #
228
+ # Sunspot::Setup::
229
+ # Setup instance associated with the given class or its nearest ancestor
230
+ #
231
+ def for(clazz) #:nodoc:
232
+ class_name =
233
+ if clazz.respond_to?(:name)
234
+ clazz.name
235
+ else
236
+ clazz
237
+ end
238
+ setups[class_name.to_sym] || self.for(clazz.superclass) if clazz
239
+ end
240
+
241
+ protected
242
+
243
+ #
244
+ # Retrieve or create a Setup instance for this class
245
+ #
246
+ # ==== Parameters
247
+ #
248
+ # clazz<Class>:: Class for which to retrieve a setup
249
+ #
250
+ # ==== Returns
251
+ #
252
+ # Sunspot::Setup:: New or existing setup for this class
253
+ #
254
+ def for!(clazz) #:nodoc:
255
+ setups[clazz.name.to_sym] ||= new(clazz)
256
+ end
257
+
258
+ private
259
+
260
+ # Singleton hash of class names to Setup instances
261
+ #
262
+ # ==== Returns
263
+ #
264
+ # Hash:: Class names keyed to Setup instances
265
+ #
266
+ def setups
267
+ @setups ||= {}
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,200 @@
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
+ #
21
+ # Text is a special type that stores data for fulltext search. Unlike other
22
+ # types, Text fields are tokenized and are made available to the keyword
23
+ # search phrase. Text fields cannot be faceted, ordered upon, or used in
24
+ # restrictions. Similarly, text fields are the only fields that are made
25
+ # available to keyword search.
26
+ #
27
+ module TextType
28
+ class <<self
29
+ def indexed_name(name) #:nodoc:
30
+ "#{name}_text"
31
+ end
32
+
33
+ def to_indexed(value) #:nodoc:
34
+ value.to_s if value
35
+ end
36
+ end
37
+ end
38
+
39
+ #
40
+ # The String type represents string data.
41
+ #
42
+ module StringType
43
+ class <<self
44
+ def indexed_name(name) #:nodoc:
45
+ "#{name}_s"
46
+ end
47
+
48
+ def to_indexed(value) #:nodoc:
49
+ value.to_s if value
50
+ end
51
+
52
+ def cast(string) #:nodoc:
53
+ string
54
+ end
55
+ end
56
+ end
57
+
58
+ #
59
+ # The Integer type represents integers.
60
+ #
61
+ module IntegerType
62
+ class <<self
63
+ def indexed_name(name) #:nodoc:
64
+ "#{name}_i"
65
+ end
66
+
67
+ def to_indexed(value) #:nodoc:
68
+ value.to_i.to_s if value
69
+ end
70
+
71
+ def cast(string) #:nodoc:
72
+ string.to_i
73
+ end
74
+ end
75
+ end
76
+
77
+ #
78
+ # The Float type represents floating-point numbers.
79
+ #
80
+ module FloatType
81
+ class <<self
82
+ def indexed_name(name) #:nodoc:
83
+ "#{name}_f"
84
+ end
85
+
86
+ def to_indexed(value) #:nodoc:
87
+ value.to_f.to_s if value
88
+ end
89
+
90
+ def cast(string) #:nodoc:
91
+ string.to_f
92
+ end
93
+ end
94
+ end
95
+
96
+ #
97
+ # The time type represents times. Note that times are always converted to
98
+ # UTC before indexing, and facets of Time fields always return times in UTC.
99
+ #
100
+ module TimeType
101
+
102
+ class <<self
103
+ def indexed_name(name) #:nodoc:
104
+ "#{name}_d"
105
+ end
106
+
107
+ def to_indexed(value) #:nodoc:
108
+ if value
109
+ time =
110
+ if value.respond_to?(:utc)
111
+ value
112
+ else
113
+ Time.parse(value.to_s)
114
+ end
115
+ time.utc.xmlschema
116
+ end
117
+ end
118
+
119
+ def cast(string) #:nodoc:
120
+ Time.xmlschema(string)
121
+ end
122
+ end
123
+ end
124
+
125
+ #
126
+ # The DateType encapsulates dates (without time information). Internally,
127
+ # Solr does not have a date-only type, so this type indexes data using
128
+ # Solr's DateField type (which is actually date/time), midnight UTC of the
129
+ # indexed date.
130
+ #
131
+ module DateType
132
+ class <<self
133
+ def indexed_name(name) #:nodoc:
134
+ "#{name}_d"
135
+ end
136
+
137
+ def to_indexed(value) #:nodoc:
138
+ if value
139
+ time =
140
+ if %w(year mon mday).all? { |method| value.respond_to?(method) }
141
+ Time.utc(value.year, value.mon, value.mday)
142
+ else
143
+ date = Date.parse(value.to_s)
144
+ Time.utc(date.year, date.mon, date.mday)
145
+ end
146
+ time.utc.xmlschema
147
+ end
148
+ end
149
+
150
+ def cast(string)
151
+ time = Time.xmlschema(string)
152
+ Date.civil(time.year, time.mon, time.mday)
153
+ end
154
+ end
155
+ end
156
+
157
+ #
158
+ # The boolean type represents true/false values. Note that +nil+ will not be
159
+ # indexed at all; only +false+ will be indexed with a false value.
160
+ #
161
+ module BooleanType
162
+ class <<self
163
+ def indexed_name(name) #:nodoc:
164
+ "#{name}_b"
165
+ end
166
+
167
+ def to_indexed(value) #:nodoc:
168
+ unless value.nil?
169
+ value ? 'true' : 'false'
170
+ end
171
+ end
172
+
173
+ def cast(string) #:nodoc:
174
+ case string
175
+ when 'true'
176
+ true
177
+ when 'false'
178
+ false
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ module ClassType
185
+ class <<self
186
+ def indexed_name(name)
187
+ 'class_name'
188
+ end
189
+
190
+ def to_indexed(value)
191
+ value.name
192
+ end
193
+
194
+ def cast(string)
195
+ Sunspot::Util.full_const_get(string)
196
+ end
197
+ end
198
+ end
199
+ end
200
+ end