sunspot 2.3.0 → 2.6.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 (56) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +4 -4
  3. data/lib/sunspot/adapters.rb +15 -1
  4. data/lib/sunspot/data_extractor.rb +36 -6
  5. data/lib/sunspot/dsl/fields.rb +16 -0
  6. data/lib/sunspot/dsl/fulltext.rb +1 -1
  7. data/lib/sunspot/dsl/group.rb +10 -0
  8. data/lib/sunspot/dsl/scope.rb +17 -17
  9. data/lib/sunspot/dsl/standard_query.rb +29 -1
  10. data/lib/sunspot/dsl.rb +2 -2
  11. data/lib/sunspot/field.rb +15 -4
  12. data/lib/sunspot/indexer.rb +37 -8
  13. data/lib/sunspot/query/abstract_fulltext.rb +7 -3
  14. data/lib/sunspot/query/abstract_json_field_facet.rb +3 -0
  15. data/lib/sunspot/query/composite_fulltext.rb +21 -2
  16. data/lib/sunspot/query/date_field_json_facet.rb +2 -16
  17. data/lib/sunspot/query/dismax.rb +10 -4
  18. data/lib/sunspot/query/function_query.rb +25 -1
  19. data/lib/sunspot/query/group.rb +4 -5
  20. data/lib/sunspot/query/join.rb +3 -5
  21. data/lib/sunspot/query/range_json_facet.rb +5 -2
  22. data/lib/sunspot/query/restriction.rb +18 -13
  23. data/lib/sunspot/query/standard_query.rb +12 -0
  24. data/lib/sunspot/search/abstract_search.rb +1 -1
  25. data/lib/sunspot/search/field_json_facet.rb +14 -3
  26. data/lib/sunspot/search/hit.rb +6 -1
  27. data/lib/sunspot/session.rb +9 -1
  28. data/lib/sunspot/setup.rb +69 -0
  29. data/lib/sunspot/util.rb +4 -11
  30. data/lib/sunspot/version.rb +1 -1
  31. data/lib/sunspot.rb +9 -1
  32. data/spec/api/adapters_spec.rb +13 -0
  33. data/spec/api/data_extractor_spec.rb +39 -0
  34. data/spec/api/indexer/removal_spec.rb +87 -0
  35. data/spec/api/query/connective_boost_examples.rb +85 -0
  36. data/spec/api/query/fulltext_examples.rb +6 -12
  37. data/spec/api/query/join_spec.rb +2 -2
  38. data/spec/api/query/standard_spec.rb +10 -0
  39. data/spec/api/search/hits_spec.rb +14 -0
  40. data/spec/api/setup_spec.rb +99 -0
  41. data/spec/api/sunspot_spec.rb +3 -0
  42. data/spec/helpers/indexer_helper.rb +22 -0
  43. data/spec/integration/atomic_updates_spec.rb +169 -5
  44. data/spec/integration/faceting_spec.rb +68 -34
  45. data/spec/integration/field_grouping_spec.rb +19 -0
  46. data/spec/integration/field_lists_spec.rb +16 -0
  47. data/spec/integration/geospatial_spec.rb +15 -0
  48. data/spec/integration/join_spec.rb +64 -0
  49. data/spec/integration/scoped_search_spec.rb +78 -0
  50. data/spec/mocks/adapters.rb +33 -0
  51. data/spec/mocks/connection.rb +6 -0
  52. data/spec/mocks/photo.rb +19 -5
  53. data/spec/mocks/post.rb +35 -1
  54. data/sunspot.gemspec +0 -2
  55. metadata +14 -8
  56. data/gemfiles/.gitkeep +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 698b438f344a3e391c87d9f755dc227869fefad88555b261ef4dcd9cc9d9c1d1
4
- data.tar.gz: 2ba1b782a43a5eafd6f1bc6397ef221fe94bbbb92f1036505530719a50f07882
3
+ metadata.gz: c13ef43c3d9e3c45ca9d1ccc170ba55947797e788911fda1982b817d34207c5d
4
+ data.tar.gz: 8a2dc3da8797d0ff4945c99732c9808e1ff9d06f94d0611b932a20cc49210d08
5
5
  SHA512:
6
- metadata.gz: 8f89e196fd125f6c0b2bd8eda2aecf7c288860ff14ab128d695f61252344fc07b0011ed8c10d8c5578227eae814df368fecf3332d306ae2a79e9b5da593dfc97
7
- data.tar.gz: 780ca66b8d6e569a994670b2e6f990444333dc03f109bd015c7f26924673e801281f611f5d08b14b48afb35b6177099747113e0bbdc10a07aebe4cee5f93c8ea
6
+ metadata.gz: dbdf307d46210db9f3430a7a46d615709aa181c61c2400f6e42d23edd5dfa0c619ecb249cf148eb7215d4dd37d9907c630acf4d13ec83319cff1cbdd2652882e
7
+ data.tar.gz: f77be8b5ee7ea9226565214951f4af0930b994f9fe568b4c79ac7d0733d414644d32018b4bdfb0ebd23a19b30ecece2c45da5d29b8bd46e28aafaf33ff58796f
data/Appraisals CHANGED
@@ -1,7 +1,7 @@
1
- appraise 'rsolr-1.1.x' do
2
- gem 'rsolr', '~> 1.1.0'
1
+ appraise 'rsolr-1.x' do
2
+ gem 'rsolr', '>= 1', '< 2'
3
3
  end
4
4
 
5
- appraise 'rsolr-2.1.x' do
6
- gem 'rsolr', '~> 2.1.0'
5
+ appraise 'rsolr-2.x' do
6
+ gem 'rsolr', '>= 2', '< 3'
7
7
  end
@@ -55,6 +55,20 @@ module Sunspot
55
55
  @instance = instance
56
56
  end
57
57
 
58
+ #
59
+ # An ID prefix to be added to the index_id
60
+ #
61
+ # ==== Returns
62
+ #
63
+ # String:: ID prefix for use in index ID to determine
64
+ # the shard a document is sent to for indexing
65
+ #
66
+ def id_prefix
67
+ setup = Sunspot::Setup.for(@instance.class)
68
+
69
+ setup && setup.id_prefix_for(@instance)
70
+ end
71
+
58
72
  #
59
73
  # The universally-unique ID for this instance that will be stored in solr
60
74
  #
@@ -63,7 +77,7 @@ module Sunspot
63
77
  # String:: ID for use in Solr
64
78
  #
65
79
  def index_id #:nodoc:
66
- InstanceAdapter.index_id_for(@instance.class.name, id)
80
+ InstanceAdapter.index_id_for("#{id_prefix}#{@instance.class.name}", id)
67
81
  end
68
82
 
69
83
  class <<self
@@ -5,16 +5,46 @@ module Sunspot
5
5
  # method, which takes an object and returns the value extracted from it.
6
6
  #
7
7
  module DataExtractor #:nodoc: all
8
+ #
9
+ # Abstract extractor to perform common actions on extracted values
10
+ #
11
+ class AbstractExtractor
12
+ BLACKLIST_REGEXP = /[\x0-\x8\xB\xC\xE-\x1F\x7f\uFFFE-\uFFFF]/
13
+
14
+ def value_for(object)
15
+ extract_value_from(object)
16
+ end
17
+
18
+ private
19
+
20
+ def extract_value_from(object)
21
+ case object
22
+ when String
23
+ remove_blacklisted_chars(object)
24
+ when Array
25
+ object.map { |o| extract_value_from(o) }
26
+ when Hash
27
+ object.inject({}) { |h, (k, v)| h.merge(extract_value_from(k) => extract_value_from(v)) }
28
+ else
29
+ object
30
+ end
31
+ end
32
+
33
+ def remove_blacklisted_chars(object)
34
+ object.gsub(BLACKLIST_REGEXP, '')
35
+ end
36
+ end
37
+
8
38
  #
9
39
  # AttributeExtractors extract data by simply calling a method on the block.
10
40
  #
11
- class AttributeExtractor
41
+ class AttributeExtractor < AbstractExtractor
12
42
  def initialize(attribute_name)
13
43
  @attribute_name = attribute_name
14
44
  end
15
45
 
16
46
  def value_for(object)
17
- object.send(@attribute_name)
47
+ super object.send(@attribute_name)
18
48
  end
19
49
  end
20
50
 
@@ -24,26 +54,26 @@ module Sunspot
24
54
  # as the argument to the block. Either way, the return value of the block is
25
55
  # the value returned by the extractor.
26
56
  #
27
- class BlockExtractor
57
+ class BlockExtractor < AbstractExtractor
28
58
  def initialize(&block)
29
59
  @block = block
30
60
  end
31
61
 
32
62
  def value_for(object)
33
- Util.instance_eval_or_call(object, &@block)
63
+ super Util.instance_eval_or_call(object, &@block)
34
64
  end
35
65
  end
36
66
 
37
67
  #
38
68
  # Constant data extractors simply return the same value for every object.
39
69
  #
40
- class Constant
70
+ class Constant < AbstractExtractor
41
71
  def initialize(value)
42
72
  @value = value
43
73
  end
44
74
 
45
75
  def value_for(object)
46
- @value
76
+ super @value
47
77
  end
48
78
  end
49
79
  end
@@ -55,6 +55,22 @@ module Sunspot
55
55
  @setup.add_document_boost(attr_name, &block)
56
56
  end
57
57
 
58
+ #
59
+ # If you use the compositeId router for shards, you can send documents
60
+ # with a prefix in the document ID which will be used to calculate the
61
+ # hash Solr uses to determine the shard a document is sent to for indexing.
62
+ # The prefix can be anything you’d like it to be (it doesn’t have to be
63
+ # the shard name, for example), but it must be consistent so Solr
64
+ # behaves consistently.
65
+ #
66
+ # ==== Parameters
67
+ #
68
+ # attr_name<Symbol,String>:: Attribute name to call or a string constant
69
+ #
70
+ def id_prefix(attr_name = nil, &block)
71
+ @setup.add_id_prefix(attr_name, &block)
72
+ end
73
+
58
74
  # method_missing is used to provide access to typed fields, because
59
75
  # developers should be able to add new Sunspot::Type implementations
60
76
  # dynamically and have them recognized inside the Fields DSL. Like #text,
@@ -176,7 +176,7 @@ module Sunspot
176
176
  @query.add_additive_boost_function(factor_or_function)
177
177
  else
178
178
  Sunspot::Util.instance_eval_or_call(
179
- Scope.new(@query.create_boost_query(factor_or_function), @setup),
179
+ Scope.new(@query.add_boost_query(factor_or_function), @setup),
180
180
  &block
181
181
  )
182
182
  end
@@ -59,6 +59,16 @@ module Sunspot
59
59
  @group.truncate = true
60
60
  end
61
61
 
62
+ #
63
+ # The group.ngroups option true return the total number of groups
64
+ # this is expensive and sometimes you don't need it!
65
+ # If ngroups is false paginated_collection last_page? and total_pages wont't work.
66
+ # Defaults to true.
67
+ #
68
+ def ngroups(enabled)
69
+ @group.ngroups = enabled
70
+ end
71
+
62
72
  # Specify the order that results should be returned in. This method can
63
73
  # be called multiple times; precedence will be in the order given.
64
74
  #
@@ -199,25 +199,25 @@ module Sunspot
199
199
 
200
200
  def add_restriction(negated, *args)
201
201
  case args.first
202
- when String, Symbol
203
- raise ArgumentError if args.length > 2
204
- field = @setup.field(args[0].to_sym)
205
- if args.length > 1
206
- value = args[1]
207
- @scope.add_shorthand_restriction(negated, field, value)
208
- else # NONE
209
- DSL::Restriction.new(field, @scope, negated)
210
- end
211
- else # args are instances
212
- @scope.add_restriction(
213
- negated,
214
- IdField.instance,
215
- Sunspot::Query::Restriction::AnyOf,
216
- args.flatten.map { |instance|
217
- Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
218
- )
202
+ when String, Symbol
203
+ raise ArgumentError if args.length > 2
204
+ field = @setup.field(args[0].to_sym)
205
+ if args.length > 1
206
+ value = args[1]
207
+ @scope.add_shorthand_restriction(negated, field, value)
208
+ else # NONE
209
+ DSL::Restriction.new(field, @scope, negated)
219
210
  end
211
+ else # args are instances
212
+ @scope.add_restriction(
213
+ negated,
214
+ IdField.instance,
215
+ Sunspot::Query::Restriction::AnyOf,
216
+ args.flatten.map { |instance|
217
+ Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id }
218
+ )
220
219
  end
220
+ end
221
221
  end
222
222
  end
223
223
  end
@@ -9,7 +9,7 @@ module Sunspot
9
9
  # See Sunspot.search for usage examples
10
10
  #
11
11
  class StandardQuery < FieldQuery
12
- include Paginatable, Adjustable, Spellcheckable
12
+ include Paginatable, Adjustable, Spellcheckable, Functional
13
13
 
14
14
  # Specify a phrase that should be searched as fulltext. Only +text+
15
15
  # fields are searched - see DSL::Fields.text
@@ -123,6 +123,34 @@ module Sunspot
123
123
  end
124
124
  end
125
125
 
126
+ #
127
+ # Defines a boost query
128
+ #
129
+ # === Examples
130
+ #
131
+ # Sunspot.search(Post) do
132
+ # with(:blog_id, 1)
133
+ #
134
+ # boost(10) do
135
+ # with(:category_id, 2)
136
+ # end
137
+ # end
138
+ #
139
+ def boost(factor_or_function, &block)
140
+ if factor_or_function.is_a?(Sunspot::Query::FunctionQuery)
141
+ @query.add_boost_function(factor_or_function)
142
+ else
143
+ Sunspot::Util.instance_eval_or_call(
144
+ Scope.new(@query.add_boost_query(factor_or_function), @setup),
145
+ &block
146
+ )
147
+ end
148
+ end
149
+
150
+ def boost_multiplicative(factor_or_function)
151
+ @query.add_multiplicative_boost_function(factor_or_function)
152
+ end
153
+
126
154
  private
127
155
 
128
156
  def add_fulltext(keywords, field_names)
data/lib/sunspot/dsl.rb CHANGED
@@ -1,5 +1,5 @@
1
- %w(spellcheckable fields scope paginatable adjustable field_query
2
- standard_query query_facet functional fulltext restriction
1
+ %w(spellcheckable fields functional scope paginatable adjustable field_query
2
+ standard_query query_facet fulltext restriction
3
3
  restriction_with_near search more_like_this_query function
4
4
  group field_stats).each do |file|
5
5
  require File.join(File.dirname(__FILE__), 'dsl', file)
data/lib/sunspot/field.rb CHANGED
@@ -195,7 +195,7 @@ module Sunspot
195
195
  # Could be of any type
196
196
  #
197
197
  class JoinField < Field #:nodoc:
198
- attr_reader :default_boost, :target
198
+ attr_reader :default_boost
199
199
 
200
200
  def initialize(name, type, options = {})
201
201
  @multiple = !!options.delete(:multiple)
@@ -213,15 +213,21 @@ module Sunspot
213
213
  end
214
214
 
215
215
  def from
216
- Sunspot::Setup.for(@target).field(@join[:from]).indexed_name
216
+ Sunspot::Setup.for(target).field(@join[:from]).indexed_name
217
217
  end
218
218
 
219
219
  def to
220
220
  Sunspot::Setup.for(@clazz).field(@join[:to]).indexed_name
221
221
  end
222
222
 
223
- def local_params
224
- "{!join from=#{from} to=#{to}}"
223
+ def local_params(value)
224
+ query = ["type:\"#{target.name}\""] | Util.Array(value)
225
+
226
+ "{!join from=#{from} to=#{to} v='#{query.join(' AND ')}'}"
227
+ end
228
+
229
+ def to_solr_conditional(value)
230
+ "\"#{value}\""
225
231
  end
226
232
 
227
233
  def eql?(field)
@@ -229,6 +235,11 @@ module Sunspot
229
235
  end
230
236
 
231
237
  alias_method :==, :eql?
238
+
239
+ def target
240
+ @target = Util.full_const_get(@target) if @target.is_a?(String)
241
+ @target
242
+ end
232
243
  end
233
244
 
234
245
  class TypeField #:nodoc:
@@ -54,9 +54,24 @@ module Sunspot
54
54
  # Remove the model from the Solr index by specifying the class and ID
55
55
  #
56
56
  def remove_by_id(class_name, *ids)
57
+ if class_name.is_a?(String) and class_name.index("!")
58
+ partition = class_name.rpartition("!")
59
+ id_prefix = partition[0..1].join
60
+ class_name = partition[2]
61
+ else
62
+ clazz_setup = setup_for_class(Util.full_const_get(class_name))
63
+ id_prefix = if clazz_setup.id_prefix_defined?
64
+ if clazz_setup.id_prefix_requires_instance?
65
+ warn(Sunspot::RemoveByIdNotSupportCompositeIdMessage.call(class_name))
66
+ else
67
+ clazz_setup.id_prefix_for_class
68
+ end
69
+ end
70
+ end
71
+
57
72
  ids.flatten!
58
73
  @connection.delete_by_id(
59
- ids.map { |id| Adapters::InstanceAdapter.index_id_for(class_name, id) }
74
+ ids.map { |id| Adapters::InstanceAdapter.index_id_for("#{id_prefix}#{class_name}", id) }
60
75
  )
61
76
  end
62
77
 
@@ -147,13 +162,27 @@ module Sunspot
147
162
  )
148
163
  end
149
164
 
150
- def document_for_atomic_update(clazz, id)
151
- if Adapters::InstanceAdapter.for(clazz)
152
- RSolr::Xml::Document.new(
153
- id: Adapters::InstanceAdapter.index_id_for(clazz.name, id),
154
- type: Util.superclasses_for(clazz).map(&:name)
155
- )
156
- end
165
+ def document_for_atomic_update(clazz, key)
166
+ return unless Adapters::InstanceAdapter.for(clazz)
167
+
168
+ clazz_setup = setup_for_class(clazz)
169
+ id_prefix = if clazz_setup.id_prefix_defined?
170
+ if clazz_setup.id_prefix_requires_instance?
171
+ if key.respond_to?(:id)
172
+ clazz_setup.id_prefix_for(key)
173
+ else
174
+ warn(Sunspot::AtomicUpdateRequireInstanceForCompositeIdMessage.call(clazz.name))
175
+ end
176
+ else
177
+ clazz_setup.id_prefix_for_class
178
+ end
179
+ end
180
+
181
+ instance_id = key.respond_to?(:id) ? key.id : key
182
+ RSolr::Xml::Document.new(
183
+ id: Adapters::InstanceAdapter.index_id_for("#{id_prefix}#{clazz.name}", instance_id),
184
+ type: Util.superclasses_for(clazz).map(&:name)
185
+ )
157
186
  end
158
187
  #
159
188
  # Get the Setup object for the given object's class.
@@ -10,7 +10,7 @@ module Sunspot
10
10
  #
11
11
  # Assign a new boost query and return it.
12
12
  #
13
- def create_boost_query(factor)
13
+ def add_boost_query(factor)
14
14
  @boost_queries << boost_query = BoostQuery.new(factor)
15
15
  boost_query
16
16
  end
@@ -19,14 +19,18 @@ module Sunspot
19
19
  # Add a boost function
20
20
  #
21
21
  def add_additive_boost_function(function_query)
22
- @additive_boost_functions << function_query
22
+ unless @additive_boost_functions.include?(function_query)
23
+ @additive_boost_functions << function_query
24
+ end
23
25
  end
24
26
 
25
27
  #
26
28
  # Add a multiplicative boost function
27
29
  #
28
30
  def add_multiplicative_boost_function(function_query)
29
- @multiplicative_boost_functions << function_query
31
+ unless @multiplicative_boost_functions.include?(function_query)
32
+ @multiplicative_boost_functions << function_query
33
+ end
30
34
  end
31
35
 
32
36
  #
@@ -17,6 +17,9 @@ module Sunspot
17
17
  params[:sort] = { @options[:sort] => @options[:sort_type]||'desc' } unless @options[:sort].nil?
18
18
  params[:prefix] = @options[:prefix] unless @options[:prefix].nil?
19
19
  params[:offset] = @options[:offset] unless @options[:offset].nil?
20
+ params[:allBuckets] = @options[:all_buckets] unless @options[:all_buckets].nil?
21
+ params[:missing] = @options[:missing] unless @options[:missing].nil?
22
+ params[:method] = @options[:method] unless @options[:method].nil?
20
23
 
21
24
  if !@options[:distinct].nil?
22
25
  dist_opts = @options[:distinct]
@@ -3,11 +3,30 @@ module Sunspot
3
3
  class CompositeFulltext
4
4
  def initialize
5
5
  @components = []
6
+ @dismax = nil
6
7
  end
7
8
 
8
9
  def add_fulltext(keywords)
9
- @components << dismax = Dismax.new(keywords)
10
- dismax
10
+ @components << @dismax = Dismax.new(keywords)
11
+ @dismax
12
+ end
13
+
14
+ def add_boost_query(factor)
15
+ @dismax ||= Dismax.new("*:*")
16
+ @components << @dismax unless @components.include?(@dismax)
17
+ @dismax.add_boost_query(factor)
18
+ end
19
+
20
+ def add_boost_function(function)
21
+ @dismax ||= Dismax.new("*:*")
22
+ @components << @dismax unless @components.include?(@dismax)
23
+ @dismax.add_additive_boost_function(function)
24
+ end
25
+
26
+ def add_multiplicative_boost_function(function)
27
+ @dismax ||= Dismax.new("*:*")
28
+ @components << @dismax unless @components.include?(@dismax)
29
+ @dismax.add_multiplicative_boost_function(function)
11
30
  end
12
31
 
13
32
  def add_join(keywords, target, from, to)
@@ -1,24 +1,10 @@
1
1
  module Sunspot
2
2
  module Query
3
- class DateFieldJsonFacet < AbstractJsonFieldFacet
3
+ class DateFieldJsonFacet < RangeJsonFacet
4
4
 
5
5
  def initialize(field, options, setup)
6
- raise Exception.new('Need to specify a time_range') if options[:time_range].nil?
7
- @start = options[:time_range].first
8
- @end = options[:time_range].last
9
- @gap = "+#{options[:gap] || 86400}SECONDS"
10
6
  super
11
- end
12
-
13
- def field_name_with_local_params
14
- params = {}
15
- params[:type] = 'range'
16
- params[:field] = @field.indexed_name
17
- params[:start] = @field.to_indexed(@start)
18
- params[:end] = @field.to_indexed(@end)
19
- params[:gap] = @gap
20
- params.merge!(init_params)
21
- { @field.name => params }
7
+ @gap = "+#{@gap}#{options[:gap_unit] || 'SECONDS'}"
22
8
  end
23
9
  end
24
10
  end
@@ -28,10 +28,16 @@ module Sunspot
28
28
  # The query as Solr parameters
29
29
  #
30
30
  def to_params
31
- params = { :q => @keywords }
32
- params[:fl] = '* score'
33
- params[:qf] = @fulltext_fields.values.map { |field| field.to_boosted_field }.join(' ')
34
- params[:defType] = 'edismax'
31
+ params = {
32
+ :q => @keywords,
33
+ :defType => 'edismax',
34
+ :fl => '* score'
35
+ }
36
+
37
+ if @fulltext_fields.any?
38
+ params[:qf] = @fulltext_fields.values.map { |field| field.to_boosted_field }.join(' ')
39
+ end
40
+
35
41
  params[:mm] = @minimum_match if @minimum_match
36
42
  params[:ps] = @phrase_slop if @phrase_slop
37
43
  params[:qs] = @query_phrase_slop if @query_phrase_slop
@@ -3,18 +3,25 @@ module Sunspot
3
3
  #
4
4
  # Abstract class for function queries.
5
5
  #
6
- class FunctionQuery
6
+ class FunctionQuery
7
+ attr_reader :boost_amount
7
8
 
8
9
  def ^(y)
9
10
  @boost_amount = y
10
11
  self
11
12
  end
13
+
14
+ def ==(other)
15
+ @boost_amount == other.boost_amount
16
+ end
12
17
  end
13
18
 
14
19
  #
15
20
  # Function query which represents a constant.
16
21
  #
17
22
  class ConstantFunctionQuery < FunctionQuery
23
+ attr_reader :constant
24
+
18
25
  def initialize(constant)
19
26
  @constant = constant
20
27
  end
@@ -22,12 +29,18 @@ module Sunspot
22
29
  def to_s
23
30
  Type.to_literal(@constant) << (@boost_amount ? "^#{@boost_amount}" : "")
24
31
  end
32
+
33
+ def ==(other)
34
+ super and @constant == other.constant
35
+ end
25
36
  end
26
37
 
27
38
  #
28
39
  # Function query which represents a field.
29
40
  #
30
41
  class FieldFunctionQuery < FunctionQuery
42
+ attr_reader :field
43
+
31
44
  def initialize(field)
32
45
  @field = field
33
46
  end
@@ -35,6 +48,10 @@ module Sunspot
35
48
  def to_s
36
49
  "#{Util.escape(@field.indexed_name)}" << (@boost_amount ? "^#{@boost_amount}" : "")
37
50
  end
51
+
52
+ def ==(other)
53
+ super and @field == other.field
54
+ end
38
55
  end
39
56
 
40
57
  #
@@ -43,6 +60,8 @@ module Sunspot
43
60
  # Arguments are in turn FunctionQuery objects.
44
61
  #
45
62
  class FunctionalFunctionQuery < FunctionQuery
63
+ attr_reader :function_name, :function_args
64
+
46
65
  def initialize(function_name, function_args)
47
66
  @function_name, @function_args = function_name, function_args
48
67
  end
@@ -51,6 +70,11 @@ module Sunspot
51
70
  params = @function_args.map { |arg| arg.to_s }.join(",")
52
71
  "#{@function_name}(#{params})" << (@boost_amount ? "^#{@boost_amount}" : "")
53
72
  end
73
+
74
+ def ==(other)
75
+ super and
76
+ @function_name == other.function_name and @function_args == other.function_args
77
+ end
54
78
  end
55
79
  end
56
80
  end
@@ -4,7 +4,7 @@ module Sunspot
4
4
  # A Group groups by the unique values of a given field, or by given queries.
5
5
  #
6
6
  class Group
7
- attr_accessor :limit, :truncate
7
+ attr_accessor :limit, :truncate, :ngroups
8
8
  attr_reader :fields, :queries
9
9
 
10
10
  def initialize
@@ -30,16 +30,15 @@ module Sunspot
30
30
 
31
31
  def to_params
32
32
  params = {
33
- :group => "true",
34
- :"group.ngroups" => "true",
33
+ :group => 'true',
34
+ :"group.ngroups" => @ngroups.nil? ? 'true' : @ngroups.to_s
35
35
  }
36
36
 
37
- params.merge!(@sort.to_params("group."))
37
+ params.merge!(@sort.to_params('group.'))
38
38
  params[:"group.field"] = @fields.map(&:indexed_name) if @fields.any?
39
39
  params[:"group.query"] = @queries.map(&:to_boolean_phrase) if @queries.any?
40
40
  params[:"group.limit"] = @limit if @limit
41
41
  params[:"group.truncate"] = @truncate if @truncate
42
-
43
42
  params
44
43
  end
45
44
  end
@@ -42,19 +42,17 @@ module Sunspot
42
42
  keywords = escape_quotes(params.delete(:q))
43
43
  options = params.map { |key, value| escape_param(key, value) }.join(' ')
44
44
  q_name = "q#{@target.name}#{self.object_id}"
45
- fq_name = "f#{q_name}"
46
45
 
47
46
  {
48
- :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name} fq=$#{fq_name}}\"",
49
- q_name => "_query_:\"{!edismax #{options}}#{keywords}\"",
50
- fq_name => "type:#{@target.name}"
47
+ :q => "_query_:\"{!join from=#{@from} to=#{@to} v=$#{q_name}}\"",
48
+ q_name => "_query_:\"{!field f=type}#{@target.name}\"+_query_:\"{!edismax #{options}}#{keywords}\""
51
49
  }
52
50
  end
53
51
 
54
52
  #
55
53
  # Assign a new boost query and return it.
56
54
  #
57
- def create_boost_query(factor)
55
+ def add_boost_query(factor)
58
56
  end
59
57
 
60
58
  #
@@ -5,10 +5,12 @@ module Sunspot
5
5
  SECONDS_IN_DAY = 86400
6
6
 
7
7
  def initialize(field, options, setup)
8
- raise Exception.new("Need to specify a range") if options[:range].nil?
8
+ options[:range] ||= options[:time_range]
9
+ raise Exception.new("Need to specify a range") if options[:range].nil? && options[:time_range].nil?
9
10
  @start = options[:range].first
10
11
  @end = options[:range].last
11
12
  @gap = options[:gap] || SECONDS_IN_DAY
13
+ @other = options[:other]
12
14
  super
13
15
  end
14
16
 
@@ -19,7 +21,8 @@ module Sunspot
19
21
  field: @field.indexed_name,
20
22
  start: @field.to_indexed(@start),
21
23
  end: @field.to_indexed(@end),
22
- gap: @gap
24
+ gap: @gap,
25
+ other: @other
23
26
  }.merge!(init_params)
24
27
  }
25
28
  end