sunspot 0.9.8 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (115) hide show
  1. data/History.txt +32 -0
  2. data/README.rdoc +40 -3
  3. data/TODO +10 -8
  4. data/VERSION.yml +2 -2
  5. data/bin/sunspot-configure-solr +22 -28
  6. data/bin/sunspot-solr +50 -29
  7. data/lib/sunspot/adapters.rb +1 -1
  8. data/lib/sunspot/composite_setup.rb +13 -15
  9. data/lib/sunspot/configuration.rb +14 -0
  10. data/lib/sunspot/data_extractor.rb +3 -0
  11. data/lib/sunspot/dsl/field_query.rb +33 -6
  12. data/lib/sunspot/dsl/fields.rb +14 -1
  13. data/lib/sunspot/dsl/fulltext.rb +168 -0
  14. data/lib/sunspot/dsl/query.rb +82 -5
  15. data/lib/sunspot/dsl/query_facet.rb +3 -3
  16. data/lib/sunspot/dsl/restriction.rb +7 -7
  17. data/lib/sunspot/dsl/scope.rb +17 -10
  18. data/lib/sunspot/dsl/search.rb +2 -2
  19. data/lib/sunspot/dsl.rb +2 -1
  20. data/lib/sunspot/facet.rb +9 -1
  21. data/lib/sunspot/facet_data.rb +56 -7
  22. data/lib/sunspot/facet_row.rb +2 -0
  23. data/lib/sunspot/field.rb +50 -26
  24. data/lib/sunspot/field_factory.rb +15 -0
  25. data/lib/sunspot/indexer.rb +6 -0
  26. data/lib/sunspot/instantiated_facet.rb +6 -9
  27. data/lib/sunspot/instantiated_facet_row.rb +7 -2
  28. data/lib/sunspot/query/boost_query.rb +20 -0
  29. data/lib/sunspot/query/connective.rb +98 -35
  30. data/lib/sunspot/query/dismax.rb +69 -0
  31. data/lib/sunspot/query/field_facet.rb +1 -22
  32. data/lib/sunspot/query/fulltext_base_query.rb +47 -0
  33. data/lib/sunspot/query/highlighting.rb +43 -0
  34. data/lib/sunspot/query/local.rb +24 -0
  35. data/lib/sunspot/query/pagination.rb +3 -4
  36. data/lib/sunspot/query/query.rb +93 -0
  37. data/lib/sunspot/query/query_facet.rb +14 -9
  38. data/lib/sunspot/query/query_facet_row.rb +3 -3
  39. data/lib/sunspot/query/query_field_facet.rb +10 -3
  40. data/lib/sunspot/query/restriction.rb +36 -15
  41. data/lib/sunspot/query/scope.rb +3 -159
  42. data/lib/sunspot/query/sort.rb +84 -15
  43. data/lib/sunspot/query/text_field_boost.rb +15 -0
  44. data/lib/sunspot/query.rb +2 -188
  45. data/lib/sunspot/schema.rb +7 -25
  46. data/lib/sunspot/search/highlight.rb +38 -0
  47. data/lib/sunspot/search/hit.rb +50 -3
  48. data/lib/sunspot/search.rb +51 -32
  49. data/lib/sunspot/session.rb +32 -12
  50. data/lib/sunspot/setup.rb +47 -10
  51. data/lib/sunspot/text_field_setup.rb +29 -0
  52. data/lib/sunspot/type.rb +4 -4
  53. data/lib/sunspot/util.rb +27 -1
  54. data/lib/sunspot.rb +8 -17
  55. data/solr/solr/conf/schema.xml +54 -40
  56. data/solr/solr/conf/solrconfig.xml +30 -0
  57. data/solr/solr/lib/geoapi-nogenerics-2.1-M2.jar +0 -0
  58. data/solr/solr/lib/gt2-referencing-2.3.1.jar +0 -0
  59. data/solr/solr/lib/jsr108-0.01.jar +0 -0
  60. data/solr/solr/lib/locallucene.jar +0 -0
  61. data/solr/solr/lib/localsolr.jar +0 -0
  62. data/spec/api/indexer/attributes_spec.rb +100 -0
  63. data/spec/api/indexer/batch_spec.rb +46 -0
  64. data/spec/api/indexer/dynamic_fields_spec.rb +33 -0
  65. data/spec/api/indexer/fixed_fields_spec.rb +57 -0
  66. data/spec/api/indexer/fulltext_spec.rb +43 -0
  67. data/spec/api/indexer/removal_spec.rb +46 -0
  68. data/spec/api/indexer/spec_helper.rb +1 -0
  69. data/spec/api/indexer_spec.rb +1 -308
  70. data/spec/api/query/connectives_spec.rb +162 -0
  71. data/spec/api/query/dsl_spec.rb +12 -0
  72. data/spec/api/query/dynamic_fields_spec.rb +149 -0
  73. data/spec/api/query/faceting_spec.rb +272 -0
  74. data/spec/api/query/fulltext_spec.rb +193 -0
  75. data/spec/api/query/highlighting_spec.rb +138 -0
  76. data/spec/api/query/local_spec.rb +54 -0
  77. data/spec/api/query/ordering_pagination_spec.rb +95 -0
  78. data/spec/api/query/scope_spec.rb +266 -0
  79. data/spec/api/query/spec_helper.rb +1 -0
  80. data/spec/api/query/text_field_scoping_spec.rb +30 -0
  81. data/spec/api/query/types_spec.rb +20 -0
  82. data/spec/api/search/dynamic_fields_spec.rb +27 -0
  83. data/spec/api/search/faceting_spec.rb +206 -0
  84. data/spec/api/search/highlighting_spec.rb +65 -0
  85. data/spec/api/search/hits_spec.rb +62 -0
  86. data/spec/api/search/results_spec.rb +52 -0
  87. data/spec/api/search/search_spec.rb +23 -0
  88. data/spec/api/search/spec_helper.rb +1 -0
  89. data/spec/api/spec_helper.rb +1 -1
  90. data/spec/helpers/indexer_helper.rb +29 -0
  91. data/spec/helpers/query_helper.rb +13 -0
  92. data/spec/helpers/search_helper.rb +78 -0
  93. data/spec/integration/faceting_spec.rb +1 -1
  94. data/spec/integration/highlighting_spec.rb +22 -0
  95. data/spec/integration/keyword_search_spec.rb +65 -0
  96. data/spec/integration/local_search_spec.rb +56 -0
  97. data/spec/integration/scoped_search_spec.rb +15 -1
  98. data/spec/integration/spec_helper.rb +3 -3
  99. data/spec/mocks/connection.rb +14 -1
  100. data/spec/mocks/photo.rb +1 -1
  101. data/spec/mocks/post.rb +5 -3
  102. data/spec/mocks/super_class.rb +2 -0
  103. data/spec/spec_helper.rb +13 -0
  104. data/tasks/gemspec.rake +18 -7
  105. data/tasks/schema.rake +1 -1
  106. data/tasks/spec.rake +1 -1
  107. data/templates/schema.xml.erb +36 -0
  108. metadata +117 -48
  109. data/lib/sunspot/query/base_query.rb +0 -90
  110. data/lib/sunspot/query/dynamic_query.rb +0 -69
  111. data/lib/sunspot/query/field_query.rb +0 -63
  112. data/spec/api/build_search_spec.rb +0 -1017
  113. data/spec/api/query_spec.rb +0 -153
  114. data/spec/api/search_retrieval_spec.rb +0 -362
  115. data/templates/schema.xml.haml +0 -24
data/lib/sunspot/setup.rb CHANGED
@@ -57,6 +57,20 @@ module Sunspot
57
57
  @dynamic_field_factories_cache[field_factory.name] = field_factory
58
58
  end
59
59
 
60
+ #
61
+ # The coordinates field factory is used for populating the coordinate fields
62
+ # of documents during index, but does not actually generate fields (since
63
+ # the field names used in search are static).
64
+ #
65
+ def set_coordinates_field(name)
66
+ @coordinates_field_factory = FieldFactory::Coordinates.new(name)
67
+ end
68
+
69
+ #
70
+ # Add a document boost to documents at index time. Document boost can be
71
+ # static (the same for all documents of this class), or extracted on a per-
72
+ # document basis using either attribute or block extraction as per usual.
73
+ #
60
74
  def add_document_boost(attr_name, &block)
61
75
  @document_boost_extractor =
62
76
  if attr_name
@@ -77,6 +91,9 @@ module Sunspot
77
91
  @dsl.instance_eval(&block)
78
92
  end
79
93
 
94
+ #
95
+ # Return the Field with the given (public-facing) name
96
+ #
80
97
  def field(field_name)
81
98
  if field_factory = @field_factories_cache[field_name.to_sym]
82
99
  field_factory.build
@@ -88,17 +105,27 @@ module Sunspot
88
105
  end
89
106
  end
90
107
 
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
108
+ #
109
+ # Return one or more text fields with the given public-facing name. This
110
+ # implementation will always return a single field (in an array), but
111
+ # CompositeSetup objects might return more than one.
112
+ #
113
+ def text_fields(field_name)
114
+ text_field =
115
+ if field_factory = @text_field_factories_cache[field_name.to_sym]
116
+ field_factory.build
117
+ else
118
+ raise(
119
+ UnrecognizedFieldError,
120
+ "No text field configured for #{@clazz.name} with name '#{field_name}'"
121
+ )
122
+ end
123
+ [text_field]
100
124
  end
101
125
 
126
+ #
127
+ # Return the DynamicFieldFactory with the given base name
128
+ #
102
129
  def dynamic_field_factory(field_name)
103
130
  @dynamic_field_factories_cache[field_name.to_sym] || raise(
104
131
  UnrecognizedFieldError,
@@ -106,11 +133,17 @@ module Sunspot
106
133
  )
107
134
  end
108
135
 
136
+ #
137
+ # Return all attribute fields
138
+ #
109
139
  def fields
110
140
  field_factories.map { |field_factory| field_factory.build }
111
141
  end
112
142
 
113
- def text_fields
143
+ #
144
+ # Return all text fields
145
+ #
146
+ def all_text_fields
114
147
  text_field_factories.map { |text_field_factory| text_field_factory.build }
115
148
  end
116
149
 
@@ -148,6 +181,7 @@ module Sunspot
148
181
  def all_field_factories
149
182
  all_field_factories = []
150
183
  all_field_factories.concat(field_factories).concat(text_field_factories).concat(dynamic_field_factories)
184
+ all_field_factories << @coordinates_field_factory if @coordinates_field_factory
151
185
  all_field_factories
152
186
  end
153
187
 
@@ -173,6 +207,9 @@ module Sunspot
173
207
  Util.full_const_get(@class_name)
174
208
  end
175
209
 
210
+ #
211
+ # Get the document boost for a given model
212
+ #
176
213
  def document_boost_for(model)
177
214
  if @document_boost_extractor
178
215
  @document_boost_extractor.value_for(model)
@@ -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
data/lib/sunspot/type.rb CHANGED
@@ -147,7 +147,7 @@ module Sunspot
147
147
  end
148
148
  end
149
149
 
150
- def cast(string)
150
+ def cast(string) #:nodoc:
151
151
  time = Time.xmlschema(string)
152
152
  Date.civil(time.year, time.mon, time.mday)
153
153
  end
@@ -183,15 +183,15 @@ module Sunspot
183
183
 
184
184
  module ClassType
185
185
  class <<self
186
- def indexed_name(name)
186
+ def indexed_name(name) #:nodoc:
187
187
  'class_name'
188
188
  end
189
189
 
190
- def to_indexed(value)
190
+ def to_indexed(value) #:nodoc:
191
191
  value.name
192
192
  end
193
193
 
194
- def cast(string)
194
+ def cast(string) #:nodoc:
195
195
  Sunspot::Util.full_const_get(string)
196
196
  end
197
197
  end
data/lib/sunspot/util.rb CHANGED
@@ -66,7 +66,7 @@ module Sunspot
66
66
  #
67
67
  def full_const_get(string)
68
68
  string.split('::').inject(Object) do |context, const_name|
69
- context.const_get(const_name)
69
+ context.const_defined?(const_name) ? context.const_get(const_name) : context.const_missing(const_name)
70
70
  end
71
71
  end
72
72
 
@@ -160,5 +160,31 @@ module Sunspot
160
160
  destination
161
161
  end
162
162
  end
163
+
164
+ class Coordinates #:nodoc:
165
+ def initialize(coords)
166
+ @coords = coords
167
+ end
168
+
169
+ def lat
170
+ if @coords.respond_to?(:[])
171
+ @coords[0]
172
+ else
173
+ @coords.lat
174
+ end.to_f
175
+ end
176
+
177
+ def lng
178
+ if @coords.respond_to?(:[])
179
+ @coords[1]
180
+ elsif @coords.respond_to?(:lng)
181
+ @coords.lng
182
+ elsif @coords.respond_to?(:lon)
183
+ @coords.lon
184
+ elsif @coords.respond_to?(:long)
185
+ @coords.long
186
+ end.to_f
187
+ end
188
+ end
163
189
  end
164
190
  end
data/lib/sunspot.rb CHANGED
@@ -9,9 +9,10 @@ end
9
9
 
10
10
  require File.join(File.dirname(__FILE__), 'light_config')
11
11
 
12
- %w(util adapters configuration setup composite_setup field field_factory
13
- data_extractor indexer query search facet facet_row instantiated_facet
14
- instantiated_facet_row facet_data session type dsl).each do |filename|
12
+ %w(util adapters configuration setup composite_setup text_field_setup field
13
+ field_factory data_extractor indexer query search facet facet_row
14
+ instantiated_facet instantiated_facet_row facet_data session type
15
+ dsl).each do |filename|
15
16
  require File.join(File.dirname(__FILE__), 'sunspot', filename)
16
17
  end
17
18
 
@@ -35,6 +36,7 @@ module Sunspot
35
36
  UnrecognizedRestrictionError = Class.new(Exception)
36
37
  NoAdapterError = Class.new(Exception)
37
38
  NoSetupError = Class.new(Exception)
39
+ IllegalSearchError = Class.new(Exception)
38
40
 
39
41
  class <<self
40
42
  # Configures indexing and search for a given class.
@@ -221,18 +223,6 @@ module Sunspot
221
223
  # Zero, one, or more types to search for. If no types are passed, all
222
224
  # configured types will be searched.
223
225
  #
224
- # ==== Options (last argument, optional)
225
- #
226
- # :keywords<String>:: Fulltext search string
227
- # :conditions<Hash>::
228
- # Hash of key-value pairs to be used as restrictions. Keys are field
229
- # names. Scalar values are used as equality restrictions; arrays are used
230
- # as "any of" restrictions; and Ranges are used as range restrictions.
231
- # :order<String>:: order field and direction (e.g., 'updated_at desc')
232
- # :page<Integer>:: Page to start on for pagination
233
- # :per_page<Integer>::
234
- # Number of results to use per page. Ignored if :page is not specified.
235
- #
236
226
  # ==== Returns
237
227
  #
238
228
  # Sunspot::Search:: Object containing results, facets, count, etc.
@@ -241,9 +231,10 @@ module Sunspot
241
231
  # the following criteria:
242
232
  #
243
233
  # * They are not of type +text+.
244
- # * They are defined for all of the classes being searched
245
- # * They have the same data type for all of the classes being searched
234
+ # * They are defined for at least one of the classes being searched
235
+ # * They have the same data type for all of the classes being searched.
246
236
  # * They have the same multiple flag for all of the classes being searched.
237
+ # * They have the same stored flag for all of the classes being searched.
247
238
  #
248
239
  # The restrictions available are the constants defined in the
249
240
  # Sunspot::Restriction class. The standard restrictions are:
@@ -1,50 +1,64 @@
1
- <?xml version='1.0' encoding='utf-8' ?>
2
- <schema name='sunspot' version='0.9'>
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <schema version="0.9" name="sunspot">
3
3
  <types>
4
- <fieldtype class='solr.TextField' name='text' positionIncrementGap='100'>
4
+ <fieldtype class="solr.TextField" positionIncrementGap="100" name="text">
5
5
  <analyzer>
6
- <tokenizer class='solr.StandardTokenizerFactory' />
7
- <filter class='solr.StandardFilterFactory' />
8
- <filter class='solr.LowerCaseFilterFactory' />
6
+ <tokenizer class="solr.StandardTokenizerFactory"/>
7
+ <filter class="solr.StandardFilterFactory"/>
8
+ <filter class="solr.LowerCaseFilterFactory"/>
9
9
  </analyzer>
10
10
  </fieldtype>
11
- <fieldtype class='solr.RandomSortField' name='rand'></fieldtype>
12
- <fieldtype class='solr.BoolField' name='boolean' omitNorms='true' />
13
- <fieldtype class='solr.SortableFloatField' name='sfloat' omitNorms='true' />
14
- <fieldtype class='solr.DateField' name='date' omitNorms='true' />
15
- <fieldtype class='solr.SortableIntField' name='sint' omitNorms='true' />
16
- <fieldtype class='solr.StrField' name='string' omitNorms='true' />
11
+ <fieldtype class="solr.RandomSortField" name="rand"/>
12
+ <fieldtype name="boolean" class="solr.BoolField" omitNorms="true"/>
13
+ <fieldtype name="sfloat" class="solr.SortableFloatField" omitNorms="true"/>
14
+ <fieldtype name="date" class="solr.DateField" omitNorms="true"/>
15
+ <fieldtype name="sint" class="solr.SortableIntField" omitNorms="true"/>
16
+ <fieldtype name="string" class="solr.StrField" omitNorms="true"/>
17
+ <fieldtype name="sdouble" class="solr.SortableDoubleField" omitNorms="true"/>
18
+ <fieldtype name="slong" class="solr.SortableLongField" omitNorms="true"/>
17
19
  </types>
18
20
  <fields>
19
- <field indexed='true' multiValued='false' name='id' stored='true' type='string' />
20
- <field indexed='true' multiValued='true' name='type' stored='false' type='string' />
21
- <field indexed='true' multiValued='false' name='class_name' stored='false' type='string' />
22
- <field indexed='true' multiValued='true' name='text' stored='false' type='text' />
23
- <dynamicField indexed='true' multiValued='true' name='*_text' stored='false' type='text' />
24
- <dynamicField indexed='true' name='random_*' stored='false' type='rand' />
25
- <dynamicField indexed='true' multiValued='false' name='*_b' stored='false' type='boolean' />
26
- <dynamicField indexed='true' multiValued='false' name='*_f' stored='false' type='sfloat' />
27
- <dynamicField indexed='true' multiValued='false' name='*_d' stored='false' type='date' />
28
- <dynamicField indexed='true' multiValued='false' name='*_i' stored='false' type='sint' />
29
- <dynamicField indexed='true' multiValued='false' name='*_s' stored='false' type='string' />
30
- <dynamicField indexed='true' multiValued='true' name='*_bm' stored='false' type='boolean' />
31
- <dynamicField indexed='true' multiValued='true' name='*_fm' stored='false' type='sfloat' />
32
- <dynamicField indexed='true' multiValued='true' name='*_dm' stored='false' type='date' />
33
- <dynamicField indexed='true' multiValued='true' name='*_im' stored='false' type='sint' />
34
- <dynamicField indexed='true' multiValued='true' name='*_sm' stored='false' type='string' />
35
- <dynamicField indexed='true' multiValued='false' name='*_bs' stored='true' type='boolean' />
36
- <dynamicField indexed='true' multiValued='false' name='*_fs' stored='true' type='sfloat' />
37
- <dynamicField indexed='true' multiValued='false' name='*_ds' stored='true' type='date' />
38
- <dynamicField indexed='true' multiValued='false' name='*_is' stored='true' type='sint' />
39
- <dynamicField indexed='true' multiValued='false' name='*_ss' stored='true' type='string' />
40
- <dynamicField indexed='true' multiValued='true' name='*_bms' stored='true' type='boolean' />
41
- <dynamicField indexed='true' multiValued='true' name='*_fms' stored='true' type='sfloat' />
42
- <dynamicField indexed='true' multiValued='true' name='*_dms' stored='true' type='date' />
43
- <dynamicField indexed='true' multiValued='true' name='*_ims' stored='true' type='sint' />
44
- <dynamicField indexed='true' multiValued='true' name='*_sms' stored='true' type='string' />
21
+ <field name="id" type="string" indexed="true" stored="true" multiValued="false" />
22
+ <field name="type" type="string" indexed="true" stored="false" multiValued="true" />
23
+ <field name="class_name" type="string" indexed="true" stored="false" multiValued="false" />
24
+ <field name="text" type="text" indexed="true" stored="false" multiValued="true" />
25
+ <field name="lat" type="sdouble" indexed="true" stored="true" multiValued="false" />
26
+ <field name="long" type="sdouble" indexed="true" stored="true" multiValued="false" />
27
+ <dynamicField name="*_text" type="text" indexed="true" stored="false" multiValued="true" />
28
+ <dynamicField name="*_texts" type="text" indexed="true" stored="true" multiValued="true" />
29
+ <dynamicField name="random_*" type="rand" indexed="true" stored="false" multiValued="false" />
30
+ <dynamicField name="_local*" type="sdouble" indexed="true" stored="false" multiValued="false" />
31
+ <dynamicField name="*_b" type="boolean" indexed="true" stored="false" multiValued="false" />
32
+ <dynamicField name="*_f" type="sfloat" indexed="true" stored="false" multiValued="false" />
33
+ <dynamicField name="*_d" type="date" indexed="true" stored="false" multiValued="false" />
34
+ <dynamicField name="*_i" type="sint" indexed="true" stored="false" multiValued="false" />
35
+ <dynamicField name="*_s" type="string" indexed="true" stored="false" multiValued="false" />
36
+ <dynamicField name="*_e" type="sdouble" indexed="true" stored="false" multiValued="false" />
37
+ <dynamicField name="*_l" type="slong" indexed="true" stored="false" multiValued="false" />
38
+ <dynamicField name="*_bm" type="boolean" indexed="true" stored="false" multiValued="true" />
39
+ <dynamicField name="*_fm" type="sfloat" indexed="true" stored="false" multiValued="true" />
40
+ <dynamicField name="*_dm" type="date" indexed="true" stored="false" multiValued="true" />
41
+ <dynamicField name="*_im" type="sint" indexed="true" stored="false" multiValued="true" />
42
+ <dynamicField name="*_sm" type="string" indexed="true" stored="false" multiValued="true" />
43
+ <dynamicField name="*_em" type="sdouble" indexed="true" stored="false" multiValued="true" />
44
+ <dynamicField name="*_lm" type="slong" indexed="true" stored="false" multiValued="true" />
45
+ <dynamicField name="*_bs" type="boolean" indexed="true" stored="true" multiValued="false" />
46
+ <dynamicField name="*_fs" type="sfloat" indexed="true" stored="true" multiValued="false" />
47
+ <dynamicField name="*_ds" type="date" indexed="true" stored="true" multiValued="false" />
48
+ <dynamicField name="*_is" type="sint" indexed="true" stored="true" multiValued="false" />
49
+ <dynamicField name="*_ss" type="string" indexed="true" stored="true" multiValued="false" />
50
+ <dynamicField name="*_es" type="sdouble" indexed="true" stored="true" multiValued="false" />
51
+ <dynamicField name="*_ls" type="slong" indexed="true" stored="true" multiValued="false" />
52
+ <dynamicField name="*_bms" type="boolean" indexed="true" stored="true" multiValued="true" />
53
+ <dynamicField name="*_fms" type="sfloat" indexed="true" stored="true" multiValued="true" />
54
+ <dynamicField name="*_dms" type="date" indexed="true" stored="true" multiValued="true" />
55
+ <dynamicField name="*_ims" type="sint" indexed="true" stored="true" multiValued="true" />
56
+ <dynamicField name="*_sms" type="string" indexed="true" stored="true" multiValued="true" />
57
+ <dynamicField name="*_ems" type="sdouble" indexed="true" stored="true" multiValued="true" />
58
+ <dynamicField name="*_lms" type="slong" indexed="true" stored="true" multiValued="true" />
45
59
  </fields>
46
60
  <uniqueKey>id</uniqueKey>
47
61
  <defaultSearchField>text</defaultSearchField>
48
- <solrQueryParser defaultOperator='AND' />
49
- <copyField dest='text' source='*_text' />
62
+ <solrQueryParser defaultOperator="AND"/>
63
+ <copyField dest="text" source="*_text"/>
50
64
  </schema>
@@ -34,6 +34,12 @@
34
34
 
35
35
  <indexDefaults>
36
36
  <!-- Values here affect all index writers and act as a default unless overridden. -->
37
+ %arr{ :name => 'components' }
38
+ %str localsolr
39
+ %str facet
40
+ %str mlt
41
+ %str highlight
42
+ %str debug
37
43
  <useCompoundFile>false</useCompoundFile>
38
44
 
39
45
  <mergeFactor>10</mergeFactor>
@@ -693,4 +699,28 @@
693
699
  -->
694
700
  </admin>
695
701
 
702
+ <!-- configuration for LocalSolr -->
703
+ <updateRequestProcessorChain>
704
+ <processor class='com.pjaol.search.solr.update.LocalUpdateProcessorFactory'>
705
+ <str name='latField'>lat</str>
706
+ <str name='lngField'>long</str>
707
+ <int name='startTier'>9</int>
708
+ <int name='endTier'>16</int>
709
+ </processor>
710
+ <processor class='solr.RunUpdateProcessorFactory'></processor>
711
+ <processor class='solr.LogUpdateProcessorFactory'></processor>
712
+ </updateRequestProcessorChain>
713
+ <searchComponent class='com.pjaol.search.solr.component.LocalSolrQueryComponent' name='localsolr'>
714
+ <str name='latField'>lat</str>
715
+ <str name='lngField'>long</str>
716
+ </searchComponent>
717
+ <requestHandler class='org.apache.solr.handler.component.SearchHandler' name='geo'>
718
+ <arr name='components'>
719
+ <str>localsolr</str>
720
+ <str>facet</str>
721
+ <str>mlt</str>
722
+ <str>highlight</str>
723
+ <str>debug</str>
724
+ </arr>
725
+ </requestHandler>
696
726
  </config>
Binary file
Binary file
Binary file
@@ -0,0 +1,100 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'indexing attribute fields', :type => :indexer do
4
+ it 'should correctly index a stored string attribute field' do
5
+ session.index(post(:title => 'A Title'))
6
+ connection.should have_add_with(:title_ss => 'A Title')
7
+ end
8
+
9
+ it 'should correctly index an integer attribute field' do
10
+ session.index(post(:blog_id => 4))
11
+ connection.should have_add_with(:blog_id_i => '4')
12
+ end
13
+
14
+ it 'should correctly index a float attribute field' do
15
+ session.index(post(:ratings_average => 2.23))
16
+ connection.should have_add_with(:average_rating_f => '2.23')
17
+ end
18
+
19
+ it 'should allow indexing by a multiple-value field' do
20
+ session.index(post(:category_ids => [3, 14]))
21
+ connection.should have_add_with(:category_ids_im => ['3', '14'])
22
+ end
23
+
24
+ it 'should correctly index a time field' do
25
+ session.index(
26
+ post(:published_at => Time.parse('1983-07-08 05:00:00 -0400'))
27
+ )
28
+ connection.should have_add_with(:published_at_d => '1983-07-08T09:00:00Z')
29
+ end
30
+
31
+ it 'should correctly index a date field' do
32
+ session.index(post(:expire_date => Date.new(2009, 07, 13)))
33
+ connection.should have_add_with(:expire_date_d => '2009-07-13T00:00:00Z')
34
+ end
35
+
36
+ it 'should correctly index a boolean field' do
37
+ session.index(post(:featured => true))
38
+ connection.should have_add_with(:featured_b => 'true')
39
+ end
40
+
41
+ it 'should correctly index a false boolean field' do
42
+ session.index(post(:featured => false))
43
+ connection.should have_add_with(:featured_b => 'false')
44
+ end
45
+
46
+ it 'should not index a nil boolean field' do
47
+ session.index(post)
48
+ connection.should_not have_add_with(:featured_b)
49
+ end
50
+
51
+ it 'should index latitude and longitude as a pair' do
52
+ session.index(post(:coordinates => [40.7, -73.5]))
53
+ connection.should have_add_with(:lat => 40.7, :long => -73.5)
54
+ end
55
+
56
+ [
57
+ [:lat, :lng],
58
+ [:lat, :lon],
59
+ [:lat, :long]
60
+ ].each do |lat_attr, lng_attr|
61
+ it "should index latitude and longitude from #{lat_attr.inspect}, #{lng_attr.inspect}" do
62
+ session.index(post(
63
+ :coordinates => OpenStruct.new(lat_attr => 40.7, lng_attr => -73.5)
64
+ ))
65
+ connection.should have_add_with(:lat => 40.7, :long => -73.5)
66
+ end
67
+ end
68
+
69
+ it 'should correctly index an attribute field with block access' do
70
+ session.index(post(:title => 'The Blog Post'))
71
+ connection.should have_add_with(:sort_title_s => 'blog post')
72
+ end
73
+
74
+ it 'should correctly index an attribute field with instance-external block access' do
75
+ session.index(post(:category_ids => [1, 2, 3]))
76
+ connection.should have_add_with(:primary_category_id_i => '1')
77
+ end
78
+
79
+ it 'should correctly index a field that is defined on a superclass' do
80
+ Sunspot.setup(SuperClass) { string :author_name }
81
+ session.index(post(:author_name => 'Mat Brown'))
82
+ connection.should have_add_with(:author_name_s => 'Mat Brown')
83
+ end
84
+
85
+ it 'should throw a NoMethodError only if a nonexistent type is defined' do
86
+ lambda { Sunspot.setup(Post) { string :author_name }}.should_not raise_error
87
+ lambda { Sunspot.setup(Post) { bogus :journey }}.should raise_error(NoMethodError)
88
+ end
89
+
90
+ it 'should throw a NoMethodError if a nonexistent field argument is passed' do
91
+ lambda { Sunspot.setup(Post) { string :author_name, :bogus => :argument }}.should raise_error(ArgumentError)
92
+ end
93
+
94
+ it 'should throw an ArgumentError if single-value field tries to index multiple values' do
95
+ lambda do
96
+ Sunspot.setup(Post) { string :author_name }
97
+ session.index(post(:author_name => ['Mat Brown', 'Matthew Brown']))
98
+ end.should raise_error(ArgumentError)
99
+ end
100
+ end
@@ -0,0 +1,46 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'batch indexing', :type => :indexer do
4
+ it 'should send all batched adds in a single request' do
5
+ posts = Array.new(2) { Post.new }
6
+ session.batch do
7
+ for post in posts
8
+ session.index(post)
9
+ end
10
+ end
11
+ connection.adds.length.should == 1
12
+ end
13
+
14
+ it 'should add all batched adds' do
15
+ posts = Array.new(2) { Post.new }
16
+ session.batch do
17
+ for post in posts
18
+ session.index(post)
19
+ end
20
+ end
21
+ add = connection.adds.last
22
+ connection.adds.first.map { |add| add.field_by_name(:id).value }.should ==
23
+ posts.map { |post| "Post #{post.id}" }
24
+ end
25
+
26
+ it 'should not index changes to models that happen after index call' do
27
+ post = Post.new
28
+ session.batch do
29
+ session.index(post)
30
+ post.title = 'Title'
31
+ end
32
+ connection.adds.first.first.field_by_name(:title_ss).should be_nil
33
+ end
34
+
35
+ it 'should batch an add and a delete' do
36
+ pending 'batching all operations'
37
+ connection.should_not_receive(:add)
38
+ connection.should_not_receive(:remove)
39
+ posts = Array.new(2) { Post.new }
40
+ session.batch do
41
+ session.index(posts[0])
42
+ session.remove(posts[1])
43
+ end
44
+ connection.adds
45
+ end
46
+ end
@@ -0,0 +1,33 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'indexing dynamic fields' do
4
+ it 'indexes string data' do
5
+ session.index(post(:custom_string => { :test => 'string' }))
6
+ connection.should have_add_with(:"custom_string:test_s" => 'string')
7
+ end
8
+
9
+ it 'indexes integer data with virtual accessor' do
10
+ session.index(post(:category_ids => [1, 2]))
11
+ connection.should have_add_with(:"custom_integer:1_i" => '1', :"custom_integer:2_i" => '1')
12
+ end
13
+
14
+ it 'indexes float data' do
15
+ session.index(post(:custom_fl => { :test => 1.5 }))
16
+ connection.should have_add_with(:"custom_float:test_fm" => '1.5')
17
+ end
18
+
19
+ it 'indexes time data' do
20
+ session.index(post(:custom_time => { :test => Time.parse('2009-05-18 18:05:00 -0400') }))
21
+ connection.should have_add_with(:"custom_time:test_d" => '2009-05-18T22:05:00Z')
22
+ end
23
+
24
+ it 'indexes boolean data' do
25
+ session.index(post(:custom_boolean => { :test => false }))
26
+ connection.should have_add_with(:"custom_boolean:test_b" => 'false')
27
+ end
28
+
29
+ it 'indexes multiple values for a field' do
30
+ session.index(post(:custom_fl => { :test => [1.0, 2.1, 3.2] }))
31
+ connection.should have_add_with(:"custom_float:test_fm" => %w(1.0 2.1 3.2))
32
+ end
33
+ end