sunspot 1.2.1 → 1.3.0.rc6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. data/.gitignore +11 -0
  2. data/Gemfile +3 -8
  3. data/History.txt +17 -0
  4. data/Rakefile +9 -7
  5. data/lib/sunspot.rb +13 -3
  6. data/lib/sunspot/class_set.rb +23 -0
  7. data/lib/sunspot/dsl/field_query.rb +13 -0
  8. data/lib/sunspot/dsl/function.rb +13 -0
  9. data/lib/sunspot/dsl/functional.rb +3 -0
  10. data/lib/sunspot/dsl/paginatable.rb +5 -1
  11. data/lib/sunspot/dsl/scope.rb +4 -4
  12. data/lib/sunspot/dsl/search.rb +2 -2
  13. data/lib/sunspot/dsl/standard_query.rb +2 -0
  14. data/lib/sunspot/indexer.rb +1 -1
  15. data/lib/sunspot/query/common_query.rb +3 -2
  16. data/lib/sunspot/query/highlighting.rb +8 -1
  17. data/lib/sunspot/query/pagination.rb +8 -4
  18. data/lib/sunspot/search/abstract_search.rb +9 -12
  19. data/lib/sunspot/search/hit.rb +21 -7
  20. data/lib/sunspot/search/paginated_collection.rb +55 -0
  21. data/lib/sunspot/util.rb +9 -0
  22. data/lib/sunspot/version.rb +1 -1
  23. data/script/console +10 -0
  24. data/spec/api/adapters_spec.rb +1 -1
  25. data/spec/api/binding_spec.rb +1 -1
  26. data/spec/api/class_set_spec.rb +24 -0
  27. data/spec/api/indexer/attributes_spec.rb +1 -1
  28. data/spec/api/indexer/batch_spec.rb +1 -1
  29. data/spec/api/indexer/dynamic_fields_spec.rb +1 -1
  30. data/spec/api/indexer/fixed_fields_spec.rb +1 -1
  31. data/spec/api/indexer/fulltext_spec.rb +1 -1
  32. data/spec/api/indexer/removal_spec.rb +1 -1
  33. data/spec/api/indexer/spec_helper.rb +1 -1
  34. data/spec/api/indexer_spec.rb +1 -1
  35. data/spec/api/query/advanced_manipulation_examples.rb +1 -1
  36. data/spec/api/query/dsl_spec.rb +1 -1
  37. data/spec/api/query/faceting_examples.rb +0 -2
  38. data/spec/api/query/fulltext_examples.rb +0 -2
  39. data/spec/api/query/function_spec.rb +10 -1
  40. data/spec/api/query/geo_examples.rb +0 -1
  41. data/spec/api/query/highlighting_examples.rb +22 -2
  42. data/spec/api/query/more_like_this_spec.rb +1 -1
  43. data/spec/api/query/ordering_pagination_examples.rb +14 -2
  44. data/spec/api/query/spec_helper.rb +1 -1
  45. data/spec/api/query/standard_spec.rb +1 -1
  46. data/spec/api/search/dynamic_fields_spec.rb +1 -1
  47. data/spec/api/search/faceting_spec.rb +1 -1
  48. data/spec/api/search/highlighting_spec.rb +1 -1
  49. data/spec/api/search/hits_spec.rb +23 -32
  50. data/spec/api/search/paginated_collection_spec.rb +26 -0
  51. data/spec/api/search/results_spec.rb +6 -19
  52. data/spec/api/search/search_spec.rb +1 -1
  53. data/spec/api/search/spec_helper.rb +1 -1
  54. data/spec/api/session_proxy/class_sharding_session_proxy_spec.rb +1 -1
  55. data/spec/api/session_proxy/id_sharding_session_proxy_spec.rb +1 -1
  56. data/spec/api/session_proxy/master_slave_session_proxy_spec.rb +1 -1
  57. data/spec/api/session_proxy/sharding_session_proxy_spec.rb +1 -1
  58. data/spec/api/session_proxy/silent_fail_session_proxy_spec.rb +1 -1
  59. data/spec/api/session_proxy/spec_helper.rb +1 -1
  60. data/spec/api/session_proxy/thread_local_session_proxy_spec.rb +1 -12
  61. data/spec/api/session_spec.rb +1 -1
  62. data/spec/api/spec_helper.rb +1 -1
  63. data/spec/api/sunspot_spec.rb +12 -1
  64. data/spec/helpers/indexer_helper.rb +0 -12
  65. data/spec/helpers/integration_helper.rb +7 -0
  66. data/spec/helpers/mock_session_helper.rb +13 -0
  67. data/spec/helpers/query_helper.rb +0 -12
  68. data/spec/helpers/search_helper.rb +0 -12
  69. data/spec/integration/dynamic_fields_spec.rb +2 -0
  70. data/spec/integration/faceting_spec.rb +1 -1
  71. data/spec/integration/highlighting_spec.rb +2 -0
  72. data/spec/integration/indexing_spec.rb +1 -1
  73. data/spec/integration/keyword_search_spec.rb +1 -1
  74. data/spec/integration/local_search_spec.rb +1 -1
  75. data/spec/integration/more_like_this_spec.rb +1 -1
  76. data/spec/integration/scoped_search_spec.rb +1 -1
  77. data/spec/integration/stored_fields_spec.rb +2 -0
  78. data/spec/integration/test_pagination.rb +13 -2
  79. data/spec/mocks/connection.rb +4 -4
  80. data/spec/spec_helper.rb +23 -25
  81. data/sunspot.gemspec +38 -0
  82. metadata +100 -65
  83. data/Gemfile.lock +0 -32
  84. data/README.rdoc +0 -252
  85. data/bin/sunspot-installer +0 -19
  86. data/bin/sunspot-solr +0 -74
  87. data/installer/config/schema.yml +0 -95
  88. data/lib/sunspot/installer.rb +0 -31
  89. data/lib/sunspot/installer/library_installer.rb +0 -45
  90. data/lib/sunspot/installer/schema_builder.rb +0 -219
  91. data/lib/sunspot/installer/solrconfig_updater.rb +0 -76
  92. data/lib/sunspot/installer/task_helper.rb +0 -18
  93. data/lib/sunspot/server.rb +0 -152
  94. data/solr/etc/jetty.xml +0 -214
  95. data/solr/etc/webdefault.xml +0 -379
  96. data/solr/lib/jetty-6.1.3.jar +0 -0
  97. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  98. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  99. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  100. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  101. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  102. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  103. data/solr/solr/conf/admin-extra.html +0 -31
  104. data/solr/solr/conf/elevate.xml +0 -36
  105. data/solr/solr/conf/mapping-ISOLatin1Accent.txt +0 -246
  106. data/solr/solr/conf/protwords.txt +0 -21
  107. data/solr/solr/conf/schema.xml +0 -238
  108. data/solr/solr/conf/scripts.conf +0 -24
  109. data/solr/solr/conf/solrconfig.xml +0 -934
  110. data/solr/solr/conf/spellings.txt +0 -2
  111. data/solr/solr/conf/stopwords.txt +0 -58
  112. data/solr/solr/conf/synonyms.txt +0 -31
  113. data/solr/start.jar +0 -0
  114. data/solr/webapps/solr.war +0 -0
  115. data/spec/api/server_spec.rb +0 -91
  116. data/spec/integration/spec_helper.rb +0 -7
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ sunspot-solr.pid
2
+ *.swp
3
+ coverage
4
+ pkg
5
+ /doc
6
+ .DS_Store
7
+
8
+ .yardoc
9
+ README.rdoc
10
+ .bundle
11
+ Gemfile.lock
data/Gemfile CHANGED
@@ -1,10 +1,5 @@
1
- source :rubygems
1
+ source "http://rubygems.org"
2
2
 
3
- require File.expand_path('../lib/sunspot/version', __FILE__)
3
+ gem 'sunspot_solr', :path => File.expand_path('../../sunspot_solr', __FILE__)
4
4
 
5
- gem 'sunspot', Sunspot::VERSION, :path => File.expand_path('..', __FILE__)
6
-
7
- group :test do
8
- gem 'rspec', '~> 1.3'
9
- gem 'ruby-debug'
10
- end
5
+ gemspec
data/History.txt CHANGED
@@ -1,3 +1,20 @@
1
+ == 1.3.0
2
+ * Requests to Solr use HTTP POST verb by default to avoid issues when the query string grows too large for GET (Johan Van Ryseghem)
3
+ * `sunspot.yml` supports ERB (Andrew Cholakian)
4
+ * An error is raised when attempting to run the packaged Solr if Java is not available (Lucas Parry)
5
+ * Pagination operates correctly without dependency on `will_paginate` (Larry Sprock, Dave Krupinski)
6
+ * Stored boolean fields with `false` value are returned correctly (yipdw)
7
+ * `highlight` accepts `:formatter` and `:fragmenter` parameters (Jeremy McNevin)
8
+ * Default `headerBufferSize` is increased for packaged Solr make sure the limit is not reached during large queries (gjb83)
9
+ * Packaged Solr respects the `data_path` configuration setting from `sunspot.yml` (djmaze)
10
+ * Packaged Solr correctly uses the `pid_dir` configuration option from `sunspot.yml` (Russen Guggemos)
11
+ * Specs run correctly in 1.9.2 (Larry Sprock)
12
+ * Documentation improvements (Thibaut Barrère, gjb83, Breno Santos Salgado)
13
+ * Adds :offset option to paginate method (Benjamin Fleischer)
14
+
15
+ == 1.2.1 2010-12-28
16
+ * Decreased default reindexing batch size from 500 to 50
17
+
1
18
  == 1.2.0 2010-12-28
2
19
  * Replace solr-spatial-light with client-side geohash-based spatial search
3
20
  * Override Solr field naming conventions using :as option
data/Rakefile CHANGED
@@ -1,11 +1,13 @@
1
- ENV['RUBYOPT'] = '-W1'
1
+ # encoding: UTF-8
2
2
 
3
- task :environment do
4
- require File.dirname(__FILE__) + '/lib/sunspot'
5
- end
6
-
7
- require File.join(File.dirname(__FILE__), 'lib', 'sunspot', 'version')
3
+ require 'rspec/core/rake_task'
8
4
 
9
5
  Dir['tasks/**/*.rake'].each { |t| load t }
10
6
 
11
- task :default => 'spec:api'
7
+ desc "Run all examples"
8
+ RSpec::Core::RakeTask.new(:spec) do |t|
9
+ t.rspec_opts = '--format documentation'
10
+ t.ruby_opts = "-W1"
11
+ end
12
+
13
+ task :default => :spec
data/lib/sunspot.rb CHANGED
@@ -14,7 +14,7 @@ require File.join(File.dirname(__FILE__), 'light_config')
14
14
 
15
15
  %w(util adapters configuration setup composite_setup text_field_setup field
16
16
  field_factory data_extractor indexer query search session session_proxy
17
- type dsl).each do |filename|
17
+ type dsl class_set).each do |filename|
18
18
  require File.join(File.dirname(__FILE__), 'sunspot', filename)
19
19
  end
20
20
 
@@ -41,9 +41,12 @@ module Sunspot
41
41
  IllegalSearchError = Class.new(StandardError)
42
42
  NotImplementedError = Class.new(StandardError)
43
43
 
44
- autoload :Server, File.join(File.dirname(__FILE__), 'sunspot', 'server')
45
44
  autoload :Installer, File.join(File.dirname(__FILE__), 'sunspot', 'installer')
46
45
 
46
+ # Array to track classes that have been set up for searching.
47
+ # Used by, e.g., Sunspot::Rails for reindexing all searchable classes.
48
+ @searchable = ClassSet.new
49
+
47
50
  class <<self
48
51
  #
49
52
  # Clients can inject a session proxy, allowing them to implement custom
@@ -53,6 +56,11 @@ module Sunspot
53
56
  #
54
57
  attr_writer :session
55
58
 
59
+ #
60
+ # Access the list of classes set up to be searched.
61
+ #
62
+ attr_reader :searchable
63
+
56
64
  # Configures indexing and search for a given class.
57
65
  #
58
66
  # ==== Parameters
@@ -154,6 +162,7 @@ module Sunspot
154
162
  # the dynamic name, which is the part that is specified at indexing time.
155
163
  #
156
164
  def setup(clazz, &block)
165
+ Sunspot.searchable << clazz
157
166
  Setup.setup(clazz, &block)
158
167
  end
159
168
 
@@ -329,7 +338,8 @@ module Sunspot
329
338
  # end
330
339
  #
331
340
  # See Sunspot::DSL::Search, Sunspot::DSL::Scope, Sunspot::DSL::FieldQuery
332
- # and Sunspot::DSL::Query for the full API presented inside the block.
341
+ # and Sunspot::DSL::StandardQuery for the full API presented inside the
342
+ # block.
333
343
  #
334
344
  def search(*types, &block)
335
345
  session.search(*types, &block)
@@ -0,0 +1,23 @@
1
+ module Sunspot
2
+ class ClassSet
3
+ include Enumerable
4
+
5
+ def initialize
6
+ @name_to_klass = {}
7
+ end
8
+
9
+ def <<(klass)
10
+ @name_to_klass[klass.name.to_sym] = klass
11
+ self
12
+ end
13
+ alias_method :add, :<<
14
+
15
+ def each(&block)
16
+ @name_to_klass.values.each(&block)
17
+ end
18
+
19
+ def empty?
20
+ @name_to_klass.empty?
21
+ end
22
+ end
23
+ end
@@ -84,6 +84,19 @@ module Sunspot
84
84
  # with a category_id of 2, the category_id facet will operate as if a
85
85
  # category had not been selected, allowing the user to select additional
86
86
  # categories (which will presumably be ORed together).
87
+ #
88
+ # It possible to exclude multiple filters by passing an array:
89
+ #
90
+ # Sunspot.search(Post) do
91
+ # with(:blog_id, 1)
92
+ # category_filter = with(:category_id, 2)
93
+ # author_filter = with(:author_id, 3)
94
+ # facet(:category_id,
95
+ # :exclude => [category_filter, author_filter].compact)
96
+ # end
97
+ #
98
+ # You should consider using +.compact+ to ensure that the array does not
99
+ # contain any nil values.
87
100
  #
88
101
  # <strong>As far as I can tell, Solr only supports multi-select with
89
102
  # field facets; if +:exclude+ is passed to a query facet, this method will
@@ -5,7 +5,20 @@ module Sunspot
5
5
  @functional = functional
6
6
  end
7
7
 
8
+ # Special case to handle <http://wiki.apache.org/solr/FunctionQuery#sub>
9
+ # because `Kernel#sub` exists so `method_missing` will not be called
10
+ # for this function.
11
+ def sub(*args) #:nodoc:
12
+ create_function_query(:sub, *args)
13
+ end
14
+
8
15
  def method_missing(method, *args, &block)
16
+ create_function_query(method, *args)
17
+ end
18
+
19
+ private
20
+
21
+ def create_function_query(method, *args)
9
22
  function_args = args.map { |arg| @functional.create_function_query(arg) }
10
23
  Sunspot::Query::FunctionalFunctionQuery.new(method, function_args)
11
24
  end
@@ -14,6 +14,9 @@ module Sunspot
14
14
  # function { :average_rating }
15
15
  # function { sum(:average_rating, 10) }
16
16
  #
17
+ # See http://wiki.apache.org/solr/FunctionQuery for a list of all
18
+ # applicable functions
19
+ #
17
20
  def function(&block)
18
21
  expression = Sunspot::Util.instance_eval_or_call(
19
22
  Function.new(self),
@@ -17,11 +17,15 @@ module Sunspot
17
17
  # How many results to return per page. The default is the value in
18
18
  # +Sunspot.config.pagination.default_per_page+
19
19
  #
20
+ # :offset<Integer,String>::
21
+ # Applies a shift to paginated records. The default is 0.
22
+ #
20
23
  def paginate(options = {})
21
24
  page = options.delete(:page)
22
25
  per_page = options.delete(:per_page)
26
+ offset = options.delete(:offset)
23
27
  raise ArgumentError, "unknown argument #{options.keys.first.inspect} passed to paginate" unless options.empty?
24
- @query.paginate(page, per_page)
28
+ @query.paginate(page, per_page, offset)
25
29
  end
26
30
  end
27
31
  end
@@ -3,9 +3,9 @@ module Sunspot
3
3
  #
4
4
  # This DSL presents methods for constructing restrictions and other query
5
5
  # elements that are specific to fields. As well as being a superclass of
6
- # Sunspot::DSL::Query, which presents the main query block, this DSL class
7
- # is also used directly inside the #dynamic() block, which only allows
8
- # operations on specific fields.
6
+ # Sunspot::DSL::StandardQuery, which presents the main query block, this
7
+ # DSL class is also used directly inside the #dynamic() block, which only
8
+ # allows operations on specific fields.
9
9
  #
10
10
  class Scope
11
11
  NONE = Object.new
@@ -41,7 +41,7 @@ module Sunspot
41
41
  #
42
42
  # ==== Returns
43
43
  #
44
- # Sunspot::DSL::Query::Restriction::
44
+ # Sunspot::DSL::Restriction::
45
45
  # Restriction DSL object (if only one argument is passed which is a
46
46
  # field name)
47
47
  #
@@ -2,8 +2,8 @@ module Sunspot
2
2
  module DSL
3
3
  #
4
4
  # This top-level DSL class is the context in which the block passed to
5
- # Sunspot.query. See Sunspot::DSL::Query, Sunspot::DSL::FieldQuery, and
6
- # Sunspot::DSL::Scope for the full API presented.
5
+ # Sunspot.query. See Sunspot::DSL::StandardQuery, Sunspot::DSL::FieldQuery,
6
+ # and Sunspot::DSL::Scope for the full API presented.
7
7
  #
8
8
  class Search < StandardQuery
9
9
  def initialize(search, setup) #:nodoc:
@@ -35,6 +35,8 @@ module Sunspot
35
35
  # :highlight<Boolean,Array>::
36
36
  # If true, perform keyword highlighting on all searched fields. If an
37
37
  # array of field names, perform highlighting on the specified fields.
38
+ # Note that for highlighting to work, the desired fields have to be set
39
+ # up with :stored => true.
38
40
  # This can also be called from within the fulltext block.
39
41
  # :minimum_match<Integer>::
40
42
  # The minimum number of search terms that a result must match. By
@@ -107,7 +107,7 @@ module Sunspot
107
107
  # pairs.
108
108
  #
109
109
  def document_for(model)
110
- RSolr::Message::Document.new(
110
+ RSolr::Xml::Document.new(
111
111
  :id => Adapters::InstanceAdapter.adapt(model).index_id,
112
112
  :type => Util.superclasses_for(model.class).map { |clazz| clazz.name }
113
113
  )
@@ -35,12 +35,13 @@ module Sunspot
35
35
  function
36
36
  end
37
37
 
38
- def paginate(page, per_page)
38
+ def paginate(page, per_page, offset = nil)
39
39
  if @pagination
40
+ @pagination.offset = offset
40
41
  @pagination.page = page
41
42
  @pagination.per_page = per_page
42
43
  else
43
- @components << @pagination = Pagination.new(page, per_page)
44
+ @components << @pagination = Pagination.new(page, per_page, offset)
44
45
  end
45
46
  end
46
47
 
@@ -9,7 +9,7 @@ module Sunspot
9
9
  @options = options
10
10
  end
11
11
 
12
- #
12
+ #
13
13
  # Return Solr highlighting params
14
14
  #
15
15
  def to_params
@@ -36,6 +36,13 @@ module Sunspot
36
36
  params.merge!(make_params('requireFieldMatch', 'true'))
37
37
  end
38
38
  end
39
+ if formatter = @options[:formatter]
40
+ params.merge!(make_params('formatter', formatter))
41
+ end
42
+ if fragmenter = @options[:fragmenter]
43
+ params.merge!(make_params('fragmenter', fragmenter))
44
+ end
45
+
39
46
  params
40
47
  end
41
48
 
@@ -6,10 +6,10 @@ module Sunspot
6
6
  # reference to it and updates it if pagination is changed.
7
7
  #
8
8
  class Pagination #:nodoc:
9
- attr_reader :page, :per_page
9
+ attr_reader :page, :per_page, :offset
10
10
 
11
- def initialize(page = nil, per_page = nil)
12
- self.page, self.per_page = page, per_page
11
+ def initialize(page = nil, per_page = nil, offset = nil)
12
+ self.offset, self.page, self.per_page = offset, page, per_page
13
13
  end
14
14
 
15
15
  def to_params
@@ -24,10 +24,14 @@ module Sunspot
24
24
  @per_page = per_page.to_i if per_page
25
25
  end
26
26
 
27
+ def offset=(offset)
28
+ @offset = offset.to_i
29
+ end
30
+
27
31
  private
28
32
 
29
33
  def start
30
- (@page - 1) * @per_page
34
+ (@page - 1) * @per_page + @offset
31
35
  end
32
36
 
33
37
  def rows
@@ -1,5 +1,8 @@
1
+ require 'sunspot/search/paginated_collection'
2
+
1
3
  module Sunspot
2
4
  module Search #:nodoc:
5
+
3
6
  #
4
7
  # This class encapsulates the results of a Solr search. It provides access
5
8
  # to search results, total result count, facets, and pagination information.
@@ -32,7 +35,7 @@ module Sunspot
32
35
  def execute
33
36
  reset
34
37
  params = @query.to_params
35
- @solr_result = @connection.request("/#{request_handler}", params)
38
+ @solr_result = @connection.post "#{request_handler}", :data => params
36
39
  self
37
40
  end
38
41
 
@@ -53,7 +56,7 @@ module Sunspot
53
56
  # WillPaginate::Collection or Array:: Instantiated result objects
54
57
  #
55
58
  def results
56
- @results ||= maybe_will_paginate(verified_hits.map { |hit| hit.instance })
59
+ @results ||= paginate_collection(verified_hits.map { |hit| hit.instance })
57
60
  end
58
61
 
59
62
  #
@@ -84,7 +87,7 @@ module Sunspot
84
87
  Hit.new(doc, highlights_for(doc), self)
85
88
  end
86
89
  end
87
- maybe_will_paginate(hits || [])
90
+ paginate_collection(hits || [])
88
91
  end
89
92
  end
90
93
  end
@@ -269,17 +272,11 @@ module Sunspot
269
272
  end
270
273
 
271
274
  def verified_hits
272
- @verified_hits ||= maybe_will_paginate(hits.select { |hit| hit.instance })
275
+ @verified_hits ||= paginate_collection(hits.select { |hit| hit.instance })
273
276
  end
274
277
 
275
- def maybe_will_paginate(collection)
276
- if defined?(WillPaginate::Collection)
277
- WillPaginate::Collection.create(@query.page, @query.per_page, total) do |pager|
278
- pager.replace(collection)
279
- end
280
- else
281
- collection
282
- end
278
+ def paginate_collection(collection)
279
+ PaginatedCollection.new(collection, @query.page, @query.per_page, total)
283
280
  end
284
281
 
285
282
  def add_facet(name, facet)
@@ -96,6 +96,20 @@ module Sunspot
96
96
  "#<Sunspot::Search::Hit:#{@class_name} #{@primary_key}>"
97
97
  end
98
98
 
99
+ #
100
+ # Returns the instance primary key when the Hit is used to generate urls
101
+ # For example, using a search that stores the :name attribute:
102
+ #
103
+ # hits = Sunspot.search(Object) do ...
104
+ #
105
+ # hits.each do |hit|
106
+ # link_to hit.stored(:name), edit_object_path(hit)
107
+ # end
108
+ #
109
+ def to_param
110
+ self.primary_key
111
+ end
112
+
99
113
  private
100
114
 
101
115
  def setup
@@ -120,15 +134,15 @@ module Sunspot
120
134
 
121
135
  def stored_value(field_name, dynamic_field_name)
122
136
  setup.stored_fields(field_name, dynamic_field_name).each do |field|
123
- if value = @stored_values[field.indexed_name]
124
- case value
125
- when Array
126
- return value.map { |item| field.cast(item) }
127
- else
128
- return field.cast(value)
129
- end
137
+ value = @stored_values[field.indexed_name]
138
+
139
+ if Array === value
140
+ return value.map { |item| field.cast(item) }
141
+ elsif !value.nil?
142
+ return field.cast(value)
130
143
  end
131
144
  end
145
+
132
146
  nil
133
147
  end
134
148
  end