sunspot 2.3.0 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
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