outoftime-sunspot 0.8.9 → 0.9.0

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