outoftime-sunspot 0.8.9 → 0.9.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 (73) hide show
  1. data/README.rdoc +13 -21
  2. data/Rakefile +0 -2
  3. data/TODO +2 -15
  4. data/VERSION.yml +2 -2
  5. data/bin/sunspot-configure-solr +46 -0
  6. data/bin/sunspot-solr +15 -7
  7. data/lib/sunspot/adapters.rb +5 -1
  8. data/lib/sunspot/composite_setup.rb +186 -0
  9. data/lib/sunspot/configuration.rb +7 -1
  10. data/lib/sunspot/data_extractor.rb +10 -0
  11. data/lib/sunspot/date_facet.rb +36 -0
  12. data/lib/sunspot/date_facet_row.rb +17 -0
  13. data/lib/sunspot/dsl/field_query.rb +72 -0
  14. data/lib/sunspot/dsl/fields.rb +30 -3
  15. data/lib/sunspot/dsl/query.rb +16 -35
  16. data/lib/sunspot/dsl/query_facet.rb +31 -0
  17. data/lib/sunspot/dsl/scope.rb +76 -20
  18. data/lib/sunspot/dsl/search.rb +30 -0
  19. data/lib/sunspot/dsl.rb +1 -1
  20. data/lib/sunspot/facet.rb +17 -3
  21. data/lib/sunspot/facet_row.rb +4 -4
  22. data/lib/sunspot/field.rb +130 -207
  23. data/lib/sunspot/field_factory.rb +126 -0
  24. data/lib/sunspot/indexer.rb +61 -14
  25. data/lib/sunspot/instantiated_facet.rb +38 -0
  26. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  27. data/lib/sunspot/query/base_query.rb +90 -0
  28. data/lib/sunspot/query/connective.rb +77 -0
  29. data/lib/sunspot/query/dynamic_query.rb +39 -56
  30. data/lib/sunspot/query/field_facet.rb +132 -4
  31. data/lib/sunspot/query/field_query.rb +57 -0
  32. data/lib/sunspot/query/pagination.rb +1 -1
  33. data/lib/sunspot/query/query_facet.rb +72 -0
  34. data/lib/sunspot/query/query_facet_row.rb +19 -0
  35. data/lib/sunspot/query/restriction.rb +9 -7
  36. data/lib/sunspot/query/scope.rb +165 -0
  37. data/lib/sunspot/query/sort.rb +17 -14
  38. data/lib/sunspot/query/sort_composite.rb +33 -0
  39. data/lib/sunspot/query.rb +162 -351
  40. data/lib/sunspot/query_facet.rb +33 -0
  41. data/lib/sunspot/query_facet_row.rb +21 -0
  42. data/lib/sunspot/schema.rb +165 -0
  43. data/lib/sunspot/search/hit.rb +62 -0
  44. data/lib/sunspot/search.rb +104 -41
  45. data/lib/sunspot/session.rb +64 -32
  46. data/lib/sunspot/setup.rb +119 -48
  47. data/lib/sunspot/type.rb +48 -2
  48. data/lib/sunspot.rb +74 -8
  49. data/solr/solr/conf/schema.xml +44 -225
  50. data/spec/api/build_search_spec.rb +557 -63
  51. data/spec/api/indexer_spec.rb +156 -74
  52. data/spec/api/query_spec.rb +55 -31
  53. data/spec/api/search_retrieval_spec.rb +210 -33
  54. data/spec/api/session_spec.rb +81 -26
  55. data/spec/api/sunspot_spec.rb +5 -7
  56. data/spec/integration/faceting_spec.rb +130 -0
  57. data/spec/integration/keyword_search_spec.rb +72 -31
  58. data/spec/integration/scoped_search_spec.rb +13 -0
  59. data/spec/integration/stored_fields_spec.rb +10 -0
  60. data/spec/mocks/blog.rb +3 -0
  61. data/spec/mocks/comment.rb +12 -23
  62. data/spec/mocks/connection.rb +84 -0
  63. data/spec/mocks/mock_adapter.rb +11 -3
  64. data/spec/mocks/mock_record.rb +41 -0
  65. data/spec/mocks/photo.rb +8 -0
  66. data/spec/mocks/post.rb +18 -23
  67. data/spec/spec_helper.rb +29 -14
  68. data/tasks/gemspec.rake +4 -3
  69. data/tasks/rdoc.rake +2 -2
  70. data/tasks/schema.rake +19 -0
  71. data/templates/schema.xml.haml +24 -0
  72. metadata +48 -7
  73. data/spec/mocks/base_class.rb +0 -2
data/README.rdoc CHANGED
@@ -3,7 +3,7 @@
3
3
  http://outoftime.github.com/sunspot
4
4
 
5
5
  Sunspot is a Ruby library for expressive, powerful interaction with the Solr search engine.
6
- Sunspot is built on top of the solr-ruby gem, which provides a low-level interface for Solr
6
+ Sunspot is built on top of the RSolr gem, which provides a low-level interface for Solr
7
7
  interaction; Sunspot provides a simple, intuitive, expressive DSL backed by powerful
8
8
  features for indexing objects and searching for them.
9
9
 
@@ -19,6 +19,7 @@ objects such as the filesystem.
19
19
  * Intuitive DSL for scoping searches, with all the usual boolean operators available
20
20
  * Intuitive interface for requesting facets on indexed fields
21
21
  * Extensible adapter architecture for easy integration of other ORMs or non-model classes
22
+ * Refine search using field facets, date range facets, or ultra-powerful query facets
22
23
  * Full compatibility with will_paginate
23
24
  * Ordering
24
25
 
@@ -87,6 +88,10 @@ to define your own. See Sunspot::Adapters for more information.
87
88
  with(:blog_id).any_of [2, 14]
88
89
  with(:category_ids).all_of [4, 10]
89
90
  with(:published_at).less_than Time.now
91
+ any_of do
92
+ with(:expired_at).greater_than(Time.now)
93
+ with(:expired_at, nil)
94
+ end
90
95
  without :title, 'Bad Title'
91
96
  without bad_instance # specifically exclude this instance from results
92
97
 
@@ -106,23 +111,6 @@ See Sunspot.search for more information.
106
111
  search.per_page
107
112
  search.facet(:blog_id)
108
113
 
109
- === Building searches manually:
110
-
111
- The search DSL is great for building searches from fairly static parameters,
112
- but a highly dynamic search might want to leverage an intermediate approach
113
- (such as an application of the Builder pattern). For these cases, Sunspot
114
- exposes direct access to the Query object:
115
-
116
- search = Sunspot.new_search(Post)
117
- search.query.keywords = 'great pizza'
118
- search.query.add_restriction(:author_name, :equal_to, 'Mark Twain')
119
- search.query.add_restriction(:title, :equal_to, 'Bad Title', true) # negate the restriction
120
- search.query.exclude_instance(bad_instance)
121
- search.query.paginate(3, 15)
122
- search.query.order_by(:average_rating, :desc)
123
- search.query.add_field_facet(:blog_id)
124
- search.execute!
125
-
126
114
  == About the API documentation
127
115
 
128
116
  All of the methods documented in the RDoc are considered part of Sunspot's
@@ -133,10 +121,14 @@ me so I can rectify the situation!
133
121
 
134
122
  == Dependencies
135
123
 
136
- 1. solr-ruby
137
- 2. Java
124
+ 1. RSolr
125
+ 2. Daemons
126
+ 3. OptiFlag
127
+ 4. Haml
128
+ 5. Java
138
129
 
139
- Sunspot has been tested with MRI 1.8.6, YARV 1.9.1, and JRuby 1.2.0
130
+ Sunspot has been tested with MRI 1.8.6 and 1.8.7, REE 1.8.6, YARV 1.9.1, and
131
+ JRuby 1.2.0
140
132
 
141
133
  == Bugs
142
134
 
data/Rakefile CHANGED
@@ -1,5 +1,3 @@
1
- require 'rubygems'
2
-
3
1
  ENV['RUBYOPT'] = '-W1'
4
2
 
5
3
  task :environment do
data/TODO CHANGED
@@ -1,17 +1,4 @@
1
1
  === 0.9 ===
2
- * Descriptive error messages during indexing
3
- * Date type
4
- * High-endian fields
5
- * Add omitNorms to typed fields
6
- * Instantiated facets!
7
- * Direct access to adapter
8
- * sunspot-configure-solr
9
- * Text fields allow multiple
10
- * Don't allow ordering on multiple-allowed fields
11
- * Detect Ranges in short-form #with()
12
- * Expose delete by ID
13
- * Support composite IDs
14
- * Transactions
15
- * Switch to RSolr
16
- * Facet by type (?)
17
2
  * Query-based faceting (?)
3
+ === 0.10 ===
4
+ * Intelligently decide whether to instantiate all facet rows at once
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
+ :patch: 0
2
3
  :major: 0
3
- :minor: 8
4
- :patch: 9
4
+ :minor: 9
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ using_gems = false
4
+ begin
5
+ require 'fileutils'
6
+ require 'optiflag'
7
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'sunspot', 'schema')
8
+ rescue LoadError => e
9
+ if using_gems
10
+ raise(e)
11
+ else
12
+ using_gems = true
13
+ require 'rubygems'
14
+ retry
15
+ end
16
+ end
17
+
18
+ module ConfigureSolrFlags extend OptiFlagSet
19
+ optional_flag 'tokenizer'
20
+ optional_flag 'extra_filters'
21
+ optional_flag 'dir'
22
+ and_process!
23
+ end
24
+
25
+ solr_directory = ARGV.flags.dir || FileUtils.pwd
26
+ conf_directory = File.join(solr_directory, 'conf')
27
+ schema_file = File.join(conf_directory, 'schema.xml')
28
+ FileUtils.mkdir_p(conf_directory)
29
+
30
+ schema = Sunspot::Schema.new
31
+ schema.tokenizer = ARGV.flags.tokenizer if ARGV.flags.tokenizer
32
+ if ARGV.flags.extra_filters
33
+ for filter in ARGV.flags.extra_filters.split(',')
34
+ schema.add_filter(filter)
35
+ end
36
+ end
37
+
38
+ if File.exist?(schema_file)
39
+ backup_file = File.join(conf_directory, "schema-#{File.mtime(schema_file).strftime('%Y%m%d%H%M%S')}.xml")
40
+ STDERR.puts("Backing up current schema file to #{File.expand_path(backup_file)}")
41
+ FileUtils.mv(schema_file, backup_file)
42
+ end
43
+
44
+ File.open(File.join(conf_directory, 'schema.xml'), 'w') do |file|
45
+ file << schema.to_xml
46
+ end
data/bin/sunspot-solr CHANGED
@@ -1,11 +1,19 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- gem 'daemons', '~> 1.0'
4
- gem 'optiflag', '~> 0.6.5'
5
- require 'fileutils'
6
- require 'tmpdir'
7
- require 'daemons'
8
- require 'optiflag'
2
+ using_gems = false
3
+ begin
4
+ require 'fileutils'
5
+ require 'tmpdir'
6
+ require 'daemons'
7
+ require 'optiflag'
8
+ rescue LoadError => e
9
+ if using_gems
10
+ raise(e)
11
+ else
12
+ using_gems = true
13
+ require 'rubygems'
14
+ retry
15
+ end
16
+ end
9
17
 
10
18
  working_directory = FileUtils.pwd
11
19
  solr_home = File.join(File.dirname(__FILE__), '..', 'solr')
@@ -61,7 +61,7 @@ module Sunspot
61
61
  # String:: ID for use in Solr
62
62
  #
63
63
  def index_id #:nodoc:
64
- "#{@instance.class.name} #{id}"
64
+ InstanceAdapter.index_id_for(@instance.class.name, id)
65
65
  end
66
66
 
67
67
  class <<self
@@ -127,6 +127,10 @@ module Sunspot
127
127
  "No adapter is configured for #{original_class_name} or its superclasses. See the documentation for Sunspot::Adapters")
128
128
  end
129
129
 
130
+ def index_id_for(class_name, id)
131
+ "#{class_name} #{id}"
132
+ end
133
+
130
134
  protected
131
135
 
132
136
  # Lazy-initialize the hash of registered instance adapters
@@ -0,0 +1,186 @@
1
+ module Sunspot
2
+ #
3
+ # The CompositeSetup class encapsulates a collection of setups, and responds
4
+ # to a subset of the methods that Setup responds to (in particular, the
5
+ # methods required to build queries).
6
+ #
7
+ class CompositeSetup #:nodoc:
8
+ class << self
9
+ alias_method :for, :new
10
+ end
11
+
12
+ def initialize(types)
13
+ @types = types
14
+ end
15
+
16
+ #
17
+ # Collection of Setup objects for the enclosed types
18
+ #
19
+ # ==== Returns
20
+ #
21
+ # Array:: Collection of Setup objects
22
+ #
23
+ def setups
24
+ @setups ||= @types.map { |type| Setup.for(type) }
25
+ end
26
+
27
+ #
28
+ # Return the names of the encapsulated types
29
+ #
30
+ # ==== Returns
31
+ #
32
+ # Array:: Collection of class names
33
+ #
34
+ def type_names
35
+ @type_names ||= @types.map { |clazz| clazz.name }
36
+ end
37
+
38
+ #
39
+ # Get a text field object by its public name. A field will be returned if
40
+ # it is configured for any of the enclosed types.
41
+ #
42
+ # ==== Returns
43
+ #
44
+ # Sunspot::FulltextField:: Text field with the given public name
45
+ #
46
+ # ==== Raises
47
+ #
48
+ # UnrecognizedFieldError::
49
+ # If no field with that name is configured for any of the enclosed types.
50
+ #
51
+ def text_field(field_name)
52
+ text_fields_hash[field_name.to_sym] || raise(
53
+ UnrecognizedFieldError,
54
+ "No text field configured for #{@types * ', '} with name '#{field_name}'"
55
+ )
56
+ end
57
+
58
+ #
59
+ # Get a Sunspot::AttributeField instance corresponding to the given field name
60
+ #
61
+ # ==== Parameters
62
+ #
63
+ # field_name<Symbol>:: The public field name for which to find a field
64
+ #
65
+ # ==== Returns
66
+ #
67
+ # Sunspot::AttributeField The field object corresponding to the given name
68
+ #
69
+ # ==== Raises
70
+ #
71
+ # ArgumentError::
72
+ # If the given field name is not configured for the types being queried
73
+ #
74
+ def field(field_name) #:nodoc:
75
+ fields_hash[field_name.to_sym] || raise(
76
+ UnrecognizedFieldError,
77
+ "No field configured for #{@types * ', '} with name '#{field_name}'"
78
+ )
79
+ end
80
+
81
+ #
82
+ # Get a dynamic field factory for the given base name.
83
+ #
84
+ # ==== Returns
85
+ #
86
+ # DynamicFieldFactory:: Factory for dynamic fields with the given base name
87
+ #
88
+ # ==== Raises
89
+ #
90
+ # UnrecognizedFieldError::
91
+ # If the given base name is not configured as a dynamic field for the types being queried
92
+ #
93
+ def dynamic_field_factory(field_name)
94
+ dynamic_field_factories_hash[field_name.to_sym] || raise(
95
+ UnrecognizedFieldError,
96
+ "No dynamic field configured for #{@types * ', '} with name #{field_name.inspect}"
97
+ )
98
+ end
99
+
100
+ #
101
+ # Collection of all text fields configured for any of the enclosed types.
102
+ #
103
+ # === Returns
104
+ #
105
+ # Array:: Text fields configured for the enclosed types
106
+ #
107
+ def text_fields
108
+ @text_fields ||= text_fields_hash.values
109
+ end
110
+
111
+ private
112
+
113
+ #
114
+ # Return a hash of field names to text field objects, containing all fields
115
+ # that are configured for any of the types enclosed.
116
+ #
117
+ # ==== Returns
118
+ #
119
+ # Hash:: Hash of field names to text field objects.
120
+ #
121
+ def text_fields_hash
122
+ @text_fields_hash ||=
123
+ setups.inject({}) do |hash, setup|
124
+ setup.text_fields.each do |text_field|
125
+ hash[text_field.name] ||= text_field
126
+ end
127
+ hash
128
+ end
129
+ end
130
+
131
+ #
132
+ # Return a hash of field names to field objects, containing all fields
133
+ # that are common to all of the classes enclosed. In order for fields
134
+ # to be common, they must be of the same type and have the same
135
+ # value for allow_multiple? and stored?. This method is memoized.
136
+ #
137
+ # ==== Returns
138
+ #
139
+ # Hash:: field names keyed to field objects
140
+ #
141
+ def fields_hash
142
+ @fields_hash ||=
143
+ begin
144
+ fields_hash = @types.inject({}) do |hash, type|
145
+ Setup.for(type).fields.each do |field|
146
+ (hash[field.name.to_sym] ||= {})[type.name] = field
147
+ end
148
+ hash
149
+ end
150
+ fields_hash.each_pair do |field_name, field_configurations_hash|
151
+ if @types.any? { |type| field_configurations_hash[type.name].nil? } # at least one type doesn't have this field configured
152
+ fields_hash.delete(field_name)
153
+ elsif field_configurations_hash.values.map { |configuration| configuration.indexed_name }.uniq.length != 1 # fields with this name have different configs
154
+ fields_hash.delete(field_name)
155
+ else
156
+ fields_hash[field_name] = field_configurations_hash.values.first
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ #
163
+ # Return a hash of dynamic field base names to dynamic field factories for
164
+ # those base names. Criteria for the inclusion are the same as for
165
+ # #fields_hash()
166
+ #
167
+ def dynamic_field_factories_hash
168
+ @dynamic_field_factories_hash ||=
169
+ begin
170
+ dynamic_field_factories_hash = @types.inject({}) do |hash, type|
171
+ Setup.for(type).dynamic_field_factories.each do |field_factory|
172
+ (hash[field_factory.name.to_sym] ||= {})[type.name] = field_factory
173
+ end
174
+ hash
175
+ end
176
+ dynamic_field_factories_hash.each_pair do |field_name, field_configurations_hash|
177
+ if @types.any? { |type| field_configurations_hash[type.name].nil? }
178
+ dynamic_field_factories_hash.delete(field_name)
179
+ else
180
+ dynamic_field_factories_hash[field_name] = field_configurations_hash.values.first
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -2,6 +2,11 @@ module Sunspot
2
2
  # The Sunspot::Configuration module provides a factory method for Sunspot
3
3
  # configuration objects. Available properties are:
4
4
  #
5
+ # Sunspot.config.http_client::
6
+ # The client to use for HTTP communication with Solr. Available options are
7
+ # :net_http, which is the default and uses Ruby's built-in pure-Ruby HTTP
8
+ # library; and :curb, which uses Ruby's libcurl bindings and requires
9
+ # installation of the 'curb' gem.
5
10
  # Sunspot.config.solr.url::
6
11
  # The URL at which to connect to Solr
7
12
  # (default: 'http://localhost:8983/solr')
@@ -19,8 +24,9 @@ module Sunspot
19
24
  #
20
25
  def build #:nodoc:
21
26
  LightConfig.build do
27
+ http_client :net_http
22
28
  solr do
23
- url 'http://localhost:8983/solr'
29
+ url 'http://127.0.0.1:8983/solr'
24
30
  end
25
31
  pagination do
26
32
  default_per_page 30
@@ -33,5 +33,15 @@ module Sunspot
33
33
  Util.instance_eval_or_call(object, &@block)
34
34
  end
35
35
  end
36
+
37
+ class Constant
38
+ def initialize(value)
39
+ @value = value
40
+ end
41
+
42
+ def value_for(object)
43
+ @value
44
+ end
45
+ end
36
46
  end
37
47
  end
@@ -0,0 +1,36 @@
1
+ module Sunspot
2
+ #
3
+ # Date facets are retrieved by passing a :time_range key into the
4
+ # DSL::FieldQuery#facet options. They are only available for Date and Time
5
+ # type fields. The #value for date facet rows is a Range object encapsulating
6
+ # the time range covered by the row.
7
+ #
8
+ class DateFacet < Facet
9
+ def initialize(facet_values, field) #:nodoc:
10
+ @gap = facet_values.delete('gap')[/\+(\d+)SECONDS/,1].to_i
11
+ %w(start end).each { |key| facet_values.delete(key) }
12
+ super(facet_values.to_a.flatten, field)
13
+ end
14
+
15
+ #
16
+ # Get the rows of this date facet, which are instances of DateFacetRow.
17
+ # The rows will always be sorted in chronological order.
18
+ #
19
+ #--
20
+ #
21
+ # The date facet info comes back from Solr as a hash, so we need to sort
22
+ # it manually. FIXME this currently assumes we want to do a "lexical"
23
+ # sort, but we should support count sort as well, even if it's not a
24
+ # common use case.
25
+ #
26
+ def rows
27
+ super.sort { |a, b| a.value.first <=> b.value.first }
28
+ end
29
+
30
+ private
31
+
32
+ def new_row(pair)
33
+ DateFacetRow.new(pair, @gap, self)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,17 @@
1
+ module Sunspot
2
+ #TODO document
3
+ class DateFacetRow < FacetRow
4
+ def initialize(pair, gap, facet)
5
+ @gap = gap
6
+ super(pair, facet)
7
+ end
8
+
9
+ def value
10
+ @value ||=
11
+ begin
12
+ start_date = @facet.field.cast(@pair[0])
13
+ Range.new(start_date, start_date + @gap)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,72 @@
1
+ module Sunspot
2
+ module DSL
3
+ #
4
+ # Provides an API for areas of the query DSL that operate on specific
5
+ # fields. This functionality is provided by the query DSL and the dynamic
6
+ # query DSL.
7
+ #
8
+ class FieldQuery < Scope
9
+ # Specify the order that results should be returned in. This method can
10
+ # be called multiple times; precedence will be in the order given.
11
+ #
12
+ # ==== Parameters
13
+ #
14
+ # field_name<Symbol>:: the field to use for ordering
15
+ # direction<Symbol>:: :asc or :desc (default :asc)
16
+ #
17
+ def order_by(field_name, direction = nil)
18
+ @query.order_by(field_name, direction)
19
+ end
20
+
21
+ #
22
+ # Order results randomly. This will (generally) return the results in a
23
+ # different order each time a search is called.
24
+ #
25
+ def order_by_random
26
+ @query.order_by_random
27
+ end
28
+
29
+ # Request facets on the given field names. If the last argument is a hash,
30
+ # the given options will be applied to all specified fields. See
31
+ # Sunspot::Search#facet and Sunspot::Facet for information on what is
32
+ # returned.
33
+ #
34
+ # ==== Parameters
35
+ #
36
+ # field_names...<Symbol>:: fields for which to return field facets
37
+ #
38
+ # ==== Options
39
+ #
40
+ # :sort<Symbol>::
41
+ # Either :count (values matching the most terms first) or :index (lexical)
42
+ # :limit<Integer>::
43
+ # The maximum number of facet rows to return
44
+ # :minimum_count<Integer>::
45
+ # The minimum count a facet row must have to be returned
46
+ # :zeros<Boolean>::
47
+ # Return facet rows for which there are no matches (equivalent to
48
+ # :minimum_count => 0). Default is false.
49
+ #
50
+ def facet(*field_names, &block)
51
+ if block
52
+ if field_names.length != 1
53
+ raise(
54
+ ArgumentError,
55
+ "wrong number of arguments (#{field_names.length} for 1)"
56
+ )
57
+ end
58
+ name = field_names.first
59
+ DSL::QueryFacet.new(@query.add_query_facet(name)).instance_eval(&block)
60
+ else
61
+ options =
62
+ if field_names.last.is_a?(Hash)
63
+ field_names.pop
64
+ end
65
+ for field_name in field_names
66
+ @query.add_field_facet(field_name, options)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -17,16 +17,43 @@ module Sunspot
17
17
  # the only fields searched in fulltext searches. If a block is passed,
18
18
  # create a virtual field; otherwise create an attribute field.
19
19
  #
20
+ # If options are passed, they will be applied to all the given fields.
21
+ #
20
22
  # ==== Parameters
21
23
  #
22
24
  # names...<Symbol>:: One or more field names
23
25
  #
26
+ # ==== Options
27
+ #
28
+ # :boost<Float>::
29
+ # Boost that should be applied to this field for keyword search
30
+ #
24
31
  def text(*names, &block)
32
+ options = names.pop if names.last.is_a?(Hash)
25
33
  for name in names
26
- @setup.add_text_fields(Field::StaticField.build(name, Type::TextType, &block))
34
+ @setup.add_text_field_factory(
35
+ name,
36
+ options || {},
37
+ &block
38
+ )
27
39
  end
28
40
  end
29
41
 
42
+ #
43
+ # Specify a document-level boost. As with fields, you have the option of
44
+ # passing an attribute name which will be called on each model, or a block
45
+ # to be evaluated in the model's context. As well as these two options,
46
+ # this method can also take a constant number, meaning that all indexed
47
+ # documents of this class will have the specified boost.
48
+ #
49
+ # ==== Parameters
50
+ #
51
+ # attr_name<Symbol,~.to_f>:: Attribute name to call or a numeric constant
52
+ #
53
+ def boost(attr_name = nil, &block)
54
+ @setup.add_document_boost(attr_name, &block)
55
+ end
56
+
30
57
  # method_missing is used to provide access to typed fields, because
31
58
  # developers should be able to add new Sunspot::Type implementations
32
59
  # dynamically and have them recognized inside the Fields DSL. Like #text,
@@ -49,9 +76,9 @@ module Sunspot
49
76
  end
50
77
  name = args.shift
51
78
  if method.to_s =~ /^dynamic_/
52
- @setup.add_dynamic_fields(Field::DynamicField.build(name, type, *args, &block))
79
+ @setup.add_dynamic_field_factory(name, type, *args, &block)
53
80
  else
54
- @setup.add_fields(Field::StaticField.build(name, type, *args, &block))
81
+ @setup.add_field_factory(name, type, *args, &block)
55
82
  end
56
83
  end
57
84
  end
@@ -3,28 +3,33 @@ module Sunspot
3
3
  #
4
4
  # This class presents a DSL for constructing queries using the
5
5
  # Sunspot.search method. Methods of this class are available inside the
6
- # search block. Methods that take field names as arguments are implemented
7
- # in the superclass Sunspot::DSL::Scope, as that DSL is also available in
8
- # the #dynamic() block.
6
+ # search block. Much of the DSL's functionality is implemented by this
7
+ # class's superclasses, Sunspot::DSL::FieldQuery and Sunspot::DSL::Scope
9
8
  #
10
9
  # See Sunspot.search for usage examples
11
10
  #
12
- class Query < Scope
11
+ class Query < FieldQuery
13
12
  # Specify a phrase that should be searched as fulltext. Only +text+
14
13
  # fields are searched - see DSL::Fields.text
15
14
  #
16
- # Note that the keywords are passed directly to Solr unadulterated. The
17
- # advantage of this is that users can potentially use boolean logic to
18
- # make advanced searches. The disadvantage is that syntax errors are
19
- # possible. This may get better in a future version; suggestions are
20
- # welcome.
15
+ # Keyword search is executed using Solr's dismax handler, which strikes
16
+ # a good balance between powerful and foolproof. In particular,
17
+ # well-matched quotation marks can be used to group phrases, and the
18
+ # + and - modifiers work as expected. All other special Solr boolean
19
+ # syntax is escaped, and mismatched quotes are ignored entirely.
21
20
  #
22
21
  # ==== Parameters
23
22
  #
24
23
  # keywords<String>:: phrase to perform fulltext search on
25
24
  #
26
- def keywords(keywords)
27
- @query.keywords = keywords
25
+ # ==== Options
26
+ #
27
+ # :fields<Array>::
28
+ # List of fields that should be searched for keywords. Defaults to all
29
+ # fields configured for the types under search.
30
+ #
31
+ def keywords(keywords, options = {})
32
+ @query.set_keywords(keywords, options)
28
33
  end
29
34
 
30
35
  # Paginate your search. This works the same way as WillPaginate's
@@ -49,30 +54,6 @@ module Sunspot
49
54
  raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
50
55
  @query.paginate(page, per_page)
51
56
  end
52
-
53
- #
54
- # Apply restrictions, facets, and ordering to dynamic field instances.
55
- # The block API is implemented by Sunspot::DSL::Scope, which is a
56
- # superclass of the Query DSL (thus providing a subset of the API, in
57
- # particular only methods that refer to particular fields).
58
- #
59
- # ==== Parameters
60
- #
61
- # base_name<Symbol>:: The base name for the dynamic field definition
62
- #
63
- # ==== Example
64
- #
65
- # Sunspot.search Post do
66
- # dynamic :custom do
67
- # with :cuisine, 'Pizza'
68
- # facet :atmosphere
69
- # order_by :chef_name
70
- # end
71
- # end
72
- #
73
- def dynamic(base_name, &block)
74
- Scope.new(@query.dynamic_query(base_name)).instance_eval(&block)
75
- end
76
57
  end
77
58
  end
78
59
  end