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
@@ -0,0 +1,168 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # This DSL exposes the functionality provided by Solr's fulltext Dismax
5
+ # handler.
6
+ #
7
+ class Fulltext
8
+ def initialize(query, setup) #:nodoc:
9
+ @query, @setup = query, setup
10
+ @fields_added = false
11
+ end
12
+
13
+ #
14
+ # Specify which fields to search. Field names specified as arguments are
15
+ # given default boost; field boosts can be specified by passing a hash of
16
+ # field names keyed to boost values as the last argument.
17
+ #
18
+ # If you wish to boost certain fields without restricting which fields are
19
+ # searched, use #boost_fields
20
+ #
21
+ # === Example
22
+ #
23
+ # Sunspot.search(Post) do
24
+ # keywords 'search is cool' do
25
+ # fields(:body, :title => 2.0)
26
+ # end
27
+ # end
28
+ #
29
+ # This would search the :body field with default boost (1.0), and the :title
30
+ # field with a boost of 2.0
31
+ #
32
+ def fields(*field_names)
33
+ @fields_added = true
34
+ boosted_fields = field_names.pop if field_names.last.is_a?(Hash)
35
+ field_names.each do |field_name|
36
+ @setup.text_fields(field_name).each do |field|
37
+ @query.add_fulltext_field(field, field.default_boost)
38
+ end
39
+ end
40
+ if boosted_fields
41
+ boosted_fields.each_pair do |field_name, boost|
42
+ @setup.text_fields(field_name).each do |field|
43
+ @query.add_fulltext_field(field, boost)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ #
50
+ # Enable keyword highlighting for this search. By default, the fields
51
+ # under search will be highlighted; you may also may pass one or more
52
+ # symbol arguments indicating fields to be highlighted (they don't even
53
+ # have to be the same fields you're searching).
54
+ #
55
+ # === Example
56
+ #
57
+ # Sunspot.search(Post) do
58
+ # keywords 'show me the highlighting' do
59
+ # highlight :title, :body
60
+ # end
61
+ # end
62
+ #
63
+ # You may also pass a hash of options as the last argument. Options are
64
+ # the following:
65
+ #
66
+ # Full disclosure: I barely understand what these options actually do;
67
+ # this documentation is pretty much just copied from the
68
+ # (http://wiki.apache.org/solr/HighlightingParameters#head-23ecd5061bc2c86a561f85dc1303979fe614b956)[Solr Wiki]
69
+ #
70
+ # :max_snippets::
71
+ # The maximum number of highlighted snippets to generate per field
72
+ # :fragment_size::
73
+ # The number of characters to consider for a highlighted fragment
74
+ # :merge_continuous_fragments::
75
+ # Collapse continuous fragments into a single fragment
76
+ # :phrase_highlighter::
77
+ # Highlight phrase terms only when they appear within the query phrase
78
+ # in the document
79
+ # :require_field_match::
80
+ # If true, a field will only be highlighted if the query matched in
81
+ # this particular field (only has an effect if :phrase_highlighter is
82
+ # true as well)
83
+ #
84
+ def highlight(*args)
85
+ options = args.last.kind_of?(Hash) ? args.pop : {}
86
+ fields = []
87
+ args.each { |field_name| fields.concat(@setup.text_fields(field_name)) }
88
+
89
+ @query.set_highlight(fields, options)
90
+ end
91
+
92
+ #
93
+ # Phrase fields are an awesome dismax feature that adds extra boost to
94
+ # documents for which all the fulltext keywords appear in close proximity
95
+ # in one of the given fields. Excellent for titles, headlines, etc.
96
+ #
97
+ # Boosted fields are specified in a hash of field names to a boost, as
98
+ # with the #fields and #boost_fields methods.
99
+ #
100
+ # === Example
101
+ #
102
+ # Sunspot.search(Post) do
103
+ # keywords 'nothing reveals like relevance' do
104
+ # phrase_fields :title => 2.0
105
+ # end
106
+ # end
107
+ #
108
+ def phrase_fields(boosted_fields)
109
+ if boosted_fields
110
+ boosted_fields.each_pair do |field_name, boost|
111
+ @setup.text_fields(field_name).each do |field|
112
+ @query.add_phrase_field(field, boost)
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ #
119
+ # Boost queries allow specification of an arbitrary scope for which
120
+ # matching documents should receive an extra boost. The block is evaluated
121
+ # in the usual scope DSL, and field names are attribute fields, not text
122
+ # fields, as in other scope.
123
+ #
124
+ # === Example
125
+ #
126
+ # Sunspot.search(Post) do
127
+ # keywords 'super fan' do
128
+ # boost(2.0) do
129
+ # with(:featured, true)
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # In the above search, featured posts will receive a boost of 2.0.
135
+ #
136
+ def boost(factor, &block)
137
+ Sunspot::Util.instance_eval_or_call(
138
+ Scope.new(@query.create_boost_query(factor), @setup),
139
+ &block
140
+ )
141
+ end
142
+
143
+ #
144
+ # Add boost to certain fields, without restricting which fields are
145
+ # searched.
146
+ #
147
+ # === Example
148
+ #
149
+ # Sunspot.search(Post) do
150
+ # keywords('pork sandwich') do
151
+ # boost_fields :title => 1.5
152
+ # end
153
+ # end
154
+ #
155
+ def boost_fields(boosts)
156
+ boosts.each_pair do |field_name, boost|
157
+ @setup.text_fields(field_name).each do |field|
158
+ @query.add_fulltext_field(field, boost)
159
+ end
160
+ end
161
+ end
162
+
163
+ def fields_added? #:nodoc:
164
+ @fields_added
165
+ end
166
+ end
167
+ end
168
+ end
@@ -18,6 +18,9 @@ module Sunspot
18
18
  # + and - modifiers work as expected. All other special Solr boolean
19
19
  # syntax is escaped, and mismatched quotes are ignored entirely.
20
20
  #
21
+ # This method can optionally take a block, which is evaluated by the
22
+ # Fulltext DSL class, and exposes several powerful dismax features.
23
+ #
21
24
  # ==== Parameters
22
25
  #
23
26
  # keywords<String>:: phrase to perform fulltext search on
@@ -27,10 +30,49 @@ module Sunspot
27
30
  # :fields<Array>::
28
31
  # List of fields that should be searched for keywords. Defaults to all
29
32
  # fields configured for the types under search.
33
+ # :highlight<Boolean,Array>::
34
+ # If true, perform keyword highlighting on all searched fields. If an
35
+ # array of field names, perform highlighting on the specified fields.
36
+ # This can also be called from within the fulltext block.
30
37
  #
31
- def keywords(keywords, options = {})
32
- @query.set_keywords(keywords, options)
38
+ def fulltext(keywords, options = {}, &block)
39
+ if keywords && !(keywords.to_s =~ /^\s*$/)
40
+ fulltext_query = @query.set_fulltext(keywords)
41
+ if field_names = options.delete(:fields)
42
+ Array(field_names).each do |field_name|
43
+ @setup.text_fields(field_name).each do |field|
44
+ fulltext_query.add_fulltext_field(field, field.default_boost)
45
+ end
46
+ end
47
+ end
48
+ if highlight_field_names = options.delete(:highlight)
49
+ if highlight_field_names == true
50
+ fulltext_query.set_highlight
51
+ else
52
+ highlight_fields = []
53
+ Array(highlight_field_names).each do |field_name|
54
+ highlight_fields.concat(@setup.text_fields(field_name))
55
+ end
56
+ fulltext_query.set_highlight(highlight_fields)
57
+ end
58
+ end
59
+ if block && fulltext_query
60
+ fulltext_dsl = Fulltext.new(fulltext_query, @setup)
61
+ Util.instance_eval_or_call(
62
+ fulltext_dsl,
63
+ &block
64
+ )
65
+ end
66
+ if !field_names && (!fulltext_dsl || !fulltext_dsl.fields_added?)
67
+ @setup.all_text_fields.each do |field|
68
+ unless fulltext_query.has_fulltext_field?(field)
69
+ fulltext_query.add_fulltext_field(field, field.default_boost)
70
+ end
71
+ end
72
+ end
73
+ end
33
74
  end
75
+ alias_method :keywords, :fulltext
34
76
 
35
77
  # Paginate your search. This works the same way as WillPaginate's
36
78
  # paginate().
@@ -42,18 +84,53 @@ module Sunspot
42
84
  #
43
85
  # ==== Options (options)
44
86
  #
45
- # :page<Integer>:: The requested page (required)
87
+ # :page<Integer,String>:: The requested page. The default is 1.
46
88
  #
47
- # :per_page<Integer>::
89
+ # :per_page<Integer,String>::
48
90
  # How many results to return per page. The default is the value in
49
91
  # +Sunspot.config.pagination.default_per_page+
50
92
  #
51
93
  def paginate(options = {})
52
- page = options.delete(:page) || raise(ArgumentError, "paginate requires a :page argument")
94
+ page = options.delete(:page)
53
95
  per_page = options.delete(:per_page)
54
96
  raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
55
97
  @query.paginate(page, per_page)
56
98
  end
99
+
100
+ #
101
+ # Scope the search by geographical distance from a given point.
102
+ # +coordinates+ should either respond to #first and #last (e.g. a
103
+ # two-element array), or to #lat and one of #lng, #lon, or #long.
104
+ # +miles+ is the radius around the point for which to return documents.
105
+ #
106
+ def near(coordinates, miles)
107
+ @query.add_location_restriction(coordinates, miles)
108
+ end
109
+
110
+ #
111
+ # Apply scope-type restrictions on fulltext fields. In certain situations,
112
+ # it may be desirable to place logical restrictions on text fields.
113
+ # Remember that text fields are tokenized; your mileage may very.
114
+ #
115
+ # The block works exactly like a normal scope, except that the field names
116
+ # refer to text fields instead of attribute fields.
117
+ #
118
+ # === Example
119
+ #
120
+ # Sunspot.search(Post) do
121
+ # text_fields do
122
+ # with :body, nil
123
+ # end
124
+ # end
125
+ #
126
+ # This will return all documents that do not have a body.
127
+ #
128
+ def text_fields(&block)
129
+ Sunspot::Util.instance_eval_or_call(
130
+ Scope.new(@scope, TextFieldSetup.new(@setup)),
131
+ &block
132
+ )
133
+ end
57
134
  end
58
135
  end
59
136
  end
@@ -5,8 +5,8 @@ module Sunspot
5
5
  # method.
6
6
  #
7
7
  class QueryFacet
8
- def initialize(query_facet) #:nodoc:
9
- @query_facet = query_facet
8
+ def initialize(query_facet, setup) #:nodoc:
9
+ @query_facet, @setup = query_facet, setup
10
10
  end
11
11
 
12
12
  #
@@ -24,7 +24,7 @@ module Sunspot
24
24
  # An object used to identify this facet row in the results.
25
25
  #
26
26
  def row(label, &block)
27
- Scope.new(@query_facet.add_row(label)).instance_eval(&block)
27
+ Scope.new(@query_facet.add_row(label), @setup).instance_eval(&block)
28
28
  end
29
29
  end
30
30
  end
@@ -1,22 +1,22 @@
1
1
  module Sunspot
2
- module DSL #:nodoc:
2
+ module DSL
3
3
  #
4
4
  # This class presents an API for building restrictions in the query DSL. The
5
5
  # methods exposed are the snake-cased names of the classes defined in the
6
- # Restriction module, with the exception of Base and SameAs. All methods
7
- # take a single argument, which is the value to be applied to the
6
+ # Sunspot::Restriction module, with the exception of Base. All
7
+ # methods take a single argument, which is the value to be applied to the
8
8
  # restriction.
9
9
  #
10
- class Restriction #:nodoc:
11
- def initialize(field_name, query, negative)
12
- @field_name, @query, @negative = field_name, query, negative
10
+ class Restriction
11
+ def initialize(field_name, query, negative) #:nodoc:
12
+ @field_name, @scope, @negative = field_name, query, negative
13
13
  end
14
14
 
15
15
  Sunspot::Query::Restriction.names.each do |class_name|
16
16
  method_name = Util.snake_case(class_name.to_s)
17
17
  module_eval(<<-RUBY, __FILE__, __LINE__ + 1)
18
18
  def #{method_name}(value)
19
- @query.add_restriction(@field_name, Sunspot::Query::Restriction::#{class_name}, value, @negative)
19
+ @scope.add_restriction(@field_name, Sunspot::Query::Restriction::#{class_name}, value, @negative)
20
20
  end
21
21
  RUBY
22
22
  end
@@ -10,8 +10,8 @@ module Sunspot
10
10
  class Scope
11
11
  NONE = Object.new
12
12
 
13
- def initialize(query) #:nodoc:
14
- @query = query
13
+ def initialize(scope, setup) #:nodoc:
14
+ @scope, @setup = scope, setup
15
15
  end
16
16
 
17
17
  #
@@ -62,9 +62,9 @@ module Sunspot
62
62
  #
63
63
  def with(field_name, value = NONE)
64
64
  if value == NONE
65
- DSL::Restriction.new(field_name.to_sym, @query, false)
65
+ DSL::Restriction.new(@setup.field(field_name.to_sym), @scope, false)
66
66
  else
67
- @query.add_shorthand_restriction(field_name, value)
67
+ @scope.add_shorthand_restriction(@setup.field(field_name), value)
68
68
  end
69
69
  end
70
70
 
@@ -111,14 +111,18 @@ module Sunspot
111
111
  field_name = args[0]
112
112
  value = args.length > 1 ? args[1] : NONE
113
113
  if value == NONE
114
- DSL::Restriction.new(field_name.to_sym, @query, true)
114
+ DSL::Restriction.new(@setup.field(field_name.to_sym), @scope, true)
115
115
  else
116
- @query.add_negated_shorthand_restriction(field_name, value)
116
+ @scope.add_negated_shorthand_restriction(@setup.field(field_name.to_sym), value)
117
117
  end
118
118
  else
119
119
  instances = args
120
120
  for instance in instances.flatten
121
- @query.exclude_instance(instance)
121
+ @scope.add_negated_restriction(
122
+ IdField.instance,
123
+ Sunspot::Query::Restriction::EqualTo,
124
+ Sunspot::Adapters::InstanceAdapter.adapt(instance).index_id
125
+ )
122
126
  end
123
127
  end
124
128
  end
@@ -140,7 +144,7 @@ module Sunspot
140
144
  # future, or who do not have any expiration time at all.
141
145
  #
142
146
  def any_of(&block)
143
- Util.instance_eval_or_call(Scope.new(@query.add_disjunction), &block)
147
+ Util.instance_eval_or_call(Scope.new(@scope.add_disjunction, @setup), &block)
144
148
  end
145
149
 
146
150
  #
@@ -162,7 +166,7 @@ module Sunspot
162
166
  # end
163
167
  #
164
168
  def all_of(&block)
165
- Util.instance_eval_or_call(Scope.new(@query.add_conjunction), &block)
169
+ Util.instance_eval_or_call(Scope.new(@scope.add_conjunction, @setup), &block)
166
170
  end
167
171
 
168
172
  #
@@ -186,7 +190,10 @@ module Sunspot
186
190
  # end
187
191
  #
188
192
  def dynamic(base_name, &block)
189
- FieldQuery.new(@query.dynamic_query(base_name)).instance_eval(&block)
193
+ Sunspot::Util.instance_eval_or_call(
194
+ Scope.new(@scope, @setup.dynamic_field_factory(base_name)),
195
+ &block
196
+ )
190
197
  end
191
198
  end
192
199
  end
@@ -6,9 +6,9 @@ module Sunspot
6
6
  # Sunspot::DSL::Scope for the full API presented.
7
7
  #
8
8
  class Search < Query
9
- def initialize(search) #:nodoc:
9
+ def initialize(search, setup) #:nodoc:
10
10
  @search = search
11
- @query = search.query
11
+ super(search.query, setup)
12
12
  end
13
13
 
14
14
  #
data/lib/sunspot/dsl.rb CHANGED
@@ -1,3 +1,4 @@
1
- %w(fields scope field_query query query_facet restriction search).each do |file|
1
+ %w(fields scope field_query query fulltext query_facet restriction
2
+ search).each do |file|
2
3
  require File.join(File.dirname(__FILE__), 'dsl', file)
3
4
  end
data/lib/sunspot/facet.rb CHANGED
@@ -1,14 +1,22 @@
1
1
  module Sunspot
2
2
  class Facet
3
- def initialize(facet_data)
3
+ def initialize(facet_data) #:nodoc:
4
4
  @facet_data = facet_data
5
5
  end
6
6
 
7
+ #
8
+ # For field facets, this is the field name. For query facets, this is the
9
+ # name given to the #facet method in the DSL.
10
+ #
7
11
  def name
8
12
  @facet_data.name
9
13
  end
10
14
  alias_method :field_name, :name
11
15
 
16
+ #
17
+ # Collection of FacetRow objects containing the individual values returned
18
+ # by the facet.
19
+ #
12
20
  def rows
13
21
  @facet_data.rows { |value, count| FacetRow.new(value, count) }
14
22
  end
@@ -1,14 +1,29 @@
1
1
  require 'enumerator'
2
2
 
3
3
  module Sunspot
4
- module FacetData
4
+ #
5
+ # The FacetData classes encapsulate various sources of facet data (field
6
+ # facet, # date facet, query facet), presenting a polymorphic API to the Facet
7
+ # class.
8
+ #
9
+ module FacetData #:nodoc:all
10
+ #
11
+ # Base class for facet data.
12
+ #
5
13
  class Abstract
6
14
  attr_reader :field #:nodoc:
7
15
 
16
+ #
17
+ # The class that the field references, if any.
18
+ #
8
19
  def reference
9
20
  @field.reference if @field
10
21
  end
11
22
 
23
+ #
24
+ # Cast the given value to the field's type, if it has one; otherwise just
25
+ # return the string.
26
+ #
12
27
  def cast(value)
13
28
  if @field
14
29
  @field.cast(value)
@@ -17,13 +32,20 @@ module Sunspot
17
32
  end
18
33
  end
19
34
 
35
+ #
36
+ # Given the raw facet row value, return what the Sunspot facet row object
37
+ # should present as the value. This can be overridden by subclasses.
38
+ #
20
39
  def row_value(value)
21
40
  cast(value)
22
41
  end
23
42
  end
24
43
 
44
+ #
45
+ # FieldFacetData encapsulates the data returned by field facets
46
+ #
25
47
  class FieldFacetData < Abstract
26
- def initialize(facet_values, field) #:nodoc:
48
+ def initialize(facet_values, field)
27
49
  @facet_values, @field = facet_values, field
28
50
  end
29
51
 
@@ -55,8 +77,11 @@ module Sunspot
55
77
  end
56
78
  end
57
79
 
80
+ #
81
+ # DateFacetData encapsulates facet date for date-range facets.
82
+ #
58
83
  class DateFacetData < FieldFacetData
59
- def initialize(facet_values, field) #:nodoc:
84
+ def initialize(facet_values, field)
60
85
  @gap = facet_values.delete('gap')[/\+(\d+)SECONDS/,1].to_i
61
86
  %w(start end).each { |key| facet_values.delete(key) }
62
87
  super(facet_values.to_a.flatten, field)
@@ -84,12 +109,18 @@ module Sunspot
84
109
  end
85
110
  end
86
111
 
112
+ #
113
+ # QueryFacetData encapsulates the data returned by a query facet.
114
+ #
87
115
  class QueryFacetData < Abstract
88
116
  def initialize(outgoing_query_facet, row_data) #:nodoc:
89
117
  @outgoing_query_facet, @row_data = outgoing_query_facet, row_data
90
118
  @field = @outgoing_query_facet.field
91
119
  end
92
120
 
121
+ #
122
+ # Use the name defined by the user
123
+ #
93
124
  def name
94
125
  outgoing_query_facet.name
95
126
  end
@@ -106,13 +137,31 @@ module Sunspot
106
137
  @rows ||=
107
138
  begin
108
139
  rows = []
109
- for row in @outgoing_query_facet.rows
110
- row_query = row.to_boolean_phrase
140
+ options = @outgoing_query_facet.options
141
+ minimum_count =
142
+ if options[:zeros] then 0
143
+ elsif options[:minimum_count] then options[:minimum_count]
144
+ else 1
145
+ end
146
+ for outgoing_row in @outgoing_query_facet.rows
147
+ row_query = outgoing_row.to_boolean_phrase
111
148
  if @row_data.has_key?(row_query)
112
- rows << yield(row.label, @row_data[row_query])
149
+ row = yield(outgoing_row.label, @row_data[row_query])
150
+ rows << row if row.count >= minimum_count
113
151
  end
114
152
  end
115
- rows.sort! { |x, y| y.count <=> x.count }
153
+ if options[:sort] == :index || !options[:limit] && options[:sort] != :count
154
+ if rows.all? { |row| row.value.respond_to?(:<=>) }
155
+ rows.sort! { |x, y| x.value <=> y.value }
156
+ end
157
+ else
158
+ rows.sort! { |x, y| y.count <=> x.count }
159
+ end
160
+ if limit = options[:limit]
161
+ rows[0, limit]
162
+ else
163
+ rows
164
+ end
116
165
  end
117
166
  end
118
167
  end
@@ -1,5 +1,7 @@
1
1
  module Sunspot
2
+ #
2
3
  # This class encapsulates a facet row (value) for a facet.
4
+ #
3
5
  class FacetRow
4
6
  attr_reader :value, :count
5
7