sunspot 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/History.txt +83 -0
  2. data/LICENSE +18 -0
  3. data/README.rdoc +154 -0
  4. data/Rakefile +9 -0
  5. data/TODO +9 -0
  6. data/VERSION.yml +4 -0
  7. data/bin/sunspot-configure-solr +46 -0
  8. data/bin/sunspot-solr +62 -0
  9. data/lib/light_config.rb +40 -0
  10. data/lib/sunspot.rb +469 -0
  11. data/lib/sunspot/adapters.rb +265 -0
  12. data/lib/sunspot/composite_setup.rb +186 -0
  13. data/lib/sunspot/configuration.rb +38 -0
  14. data/lib/sunspot/data_extractor.rb +47 -0
  15. data/lib/sunspot/dsl.rb +3 -0
  16. data/lib/sunspot/dsl/field_query.rb +72 -0
  17. data/lib/sunspot/dsl/fields.rb +86 -0
  18. data/lib/sunspot/dsl/query.rb +59 -0
  19. data/lib/sunspot/dsl/query_facet.rb +31 -0
  20. data/lib/sunspot/dsl/restriction.rb +25 -0
  21. data/lib/sunspot/dsl/scope.rb +193 -0
  22. data/lib/sunspot/dsl/search.rb +30 -0
  23. data/lib/sunspot/facet.rb +16 -0
  24. data/lib/sunspot/facet_data.rb +120 -0
  25. data/lib/sunspot/facet_row.rb +10 -0
  26. data/lib/sunspot/field.rb +157 -0
  27. data/lib/sunspot/field_factory.rb +126 -0
  28. data/lib/sunspot/indexer.rb +123 -0
  29. data/lib/sunspot/instantiated_facet.rb +42 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +22 -0
  31. data/lib/sunspot/query.rb +191 -0
  32. data/lib/sunspot/query/base_query.rb +90 -0
  33. data/lib/sunspot/query/connective.rb +126 -0
  34. data/lib/sunspot/query/dynamic_query.rb +69 -0
  35. data/lib/sunspot/query/field_facet.rb +151 -0
  36. data/lib/sunspot/query/field_query.rb +63 -0
  37. data/lib/sunspot/query/pagination.rb +39 -0
  38. data/lib/sunspot/query/query_facet.rb +73 -0
  39. data/lib/sunspot/query/query_facet_row.rb +19 -0
  40. data/lib/sunspot/query/query_field_facet.rb +13 -0
  41. data/lib/sunspot/query/restriction.rb +233 -0
  42. data/lib/sunspot/query/scope.rb +165 -0
  43. data/lib/sunspot/query/sort.rb +36 -0
  44. data/lib/sunspot/query/sort_composite.rb +33 -0
  45. data/lib/sunspot/schema.rb +165 -0
  46. data/lib/sunspot/search.rb +219 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/session.rb +201 -0
  49. data/lib/sunspot/setup.rb +271 -0
  50. data/lib/sunspot/type.rb +200 -0
  51. data/lib/sunspot/util.rb +164 -0
  52. data/solr/etc/jetty.xml +212 -0
  53. data/solr/etc/webdefault.xml +379 -0
  54. data/solr/lib/jetty-6.1.3.jar +0 -0
  55. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  56. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  57. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  58. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  59. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  60. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  61. data/solr/solr/conf/elevate.xml +36 -0
  62. data/solr/solr/conf/protwords.txt +21 -0
  63. data/solr/solr/conf/schema.xml +50 -0
  64. data/solr/solr/conf/solrconfig.xml +696 -0
  65. data/solr/solr/conf/stopwords.txt +57 -0
  66. data/solr/solr/conf/synonyms.txt +31 -0
  67. data/solr/start.jar +0 -0
  68. data/solr/webapps/solr.war +0 -0
  69. data/spec/api/adapters_spec.rb +33 -0
  70. data/spec/api/build_search_spec.rb +1039 -0
  71. data/spec/api/indexer_spec.rb +311 -0
  72. data/spec/api/query_spec.rb +153 -0
  73. data/spec/api/search_retrieval_spec.rb +362 -0
  74. data/spec/api/session_spec.rb +157 -0
  75. data/spec/api/spec_helper.rb +1 -0
  76. data/spec/api/sunspot_spec.rb +18 -0
  77. data/spec/integration/dynamic_fields_spec.rb +55 -0
  78. data/spec/integration/faceting_spec.rb +169 -0
  79. data/spec/integration/keyword_search_spec.rb +83 -0
  80. data/spec/integration/scoped_search_spec.rb +289 -0
  81. data/spec/integration/spec_helper.rb +1 -0
  82. data/spec/integration/stored_fields_spec.rb +10 -0
  83. data/spec/integration/test_pagination.rb +32 -0
  84. data/spec/mocks/adapters.rb +32 -0
  85. data/spec/mocks/blog.rb +3 -0
  86. data/spec/mocks/comment.rb +19 -0
  87. data/spec/mocks/connection.rb +84 -0
  88. data/spec/mocks/mock_adapter.rb +30 -0
  89. data/spec/mocks/mock_record.rb +48 -0
  90. data/spec/mocks/photo.rb +8 -0
  91. data/spec/mocks/post.rb +73 -0
  92. data/spec/mocks/user.rb +8 -0
  93. data/spec/spec_helper.rb +47 -0
  94. data/tasks/gemspec.rake +25 -0
  95. data/tasks/rcov.rake +28 -0
  96. data/tasks/rdoc.rake +22 -0
  97. data/tasks/schema.rake +19 -0
  98. data/tasks/spec.rake +24 -0
  99. data/tasks/todo.rake +4 -0
  100. data/templates/schema.xml.haml +24 -0
  101. metadata +246 -0
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper')
@@ -0,0 +1,10 @@
1
+ describe 'stored fields' do
2
+ before :all do
3
+ Sunspot.remove_all
4
+ Sunspot.index!(Post.new(:title => 'A Title'))
5
+ end
6
+
7
+ it 'should return stored fields' do
8
+ Sunspot.search(Post).hits.first.stored(:title).should == 'A Title'
9
+ end
10
+ end
@@ -0,0 +1,32 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe 'pagination' do
4
+ before :all do
5
+ Sunspot.remove_all
6
+ @posts = (0..19).map do |i|
7
+ Post.new(:blog_id => i)
8
+ end
9
+ Sunspot.index(*@posts)
10
+ end
11
+
12
+ it 'should return all by default' do
13
+ results = Sunspot.search(Post) { order_by :blog_id }.results
14
+ results.should == @posts
15
+ end
16
+
17
+ it 'should return first page of 10' do
18
+ results = Sunspot.search(Post) do
19
+ order_by :blog_id
20
+ paginate :page => 1, :per_page => 10
21
+ end.results
22
+ results.should == @posts[0,10]
23
+ end
24
+
25
+ it 'should return second page of 10' do
26
+ results = Sunspot.search(Post) do
27
+ order_by :blog_id
28
+ paginate :page => 2, :per_page => 10
29
+ end.results
30
+ results.should == @posts[10,10]
31
+ end
32
+ end
@@ -0,0 +1,32 @@
1
+ class AbstractModel
2
+ end
3
+
4
+ class Model < AbstractModel
5
+ end
6
+
7
+ class AbstractModelInstanceAdapter < Sunspot::Adapters::InstanceAdapter
8
+ end
9
+
10
+ class AbstractModelDataAccessor < Sunspot::Adapters::DataAccessor
11
+ end
12
+
13
+ Sunspot::Adapters::InstanceAdapter.register(AbstractModelInstanceAdapter, AbstractModel)
14
+ Sunspot::Adapters::DataAccessor.register(AbstractModelDataAccessor, AbstractModel)
15
+
16
+
17
+ module MixInModel
18
+ end
19
+
20
+ class MixModel
21
+ include MixInModel
22
+ end
23
+
24
+ class MixInModelInstanceAdapter < Sunspot::Adapters::InstanceAdapter
25
+ end
26
+
27
+ class MixInModelDataAccessor < Sunspot::Adapters::DataAccessor
28
+ end
29
+
30
+ Sunspot::Adapters::InstanceAdapter.register(MixInModelInstanceAdapter, MixInModel)
31
+ Sunspot::Adapters::DataAccessor.register(MixInModelDataAccessor, MixInModel)
32
+
@@ -0,0 +1,3 @@
1
+ class Blog < MockRecord
2
+ attr_accessor :name
3
+ end
@@ -0,0 +1,19 @@
1
+ module Namespaced
2
+ class Comment < MockRecord
3
+ attr_reader :id
4
+ attr_accessor :author_name, :published_at, :body, :average_rating, :boost
5
+
6
+ def custom_string
7
+ @custom_string ||= {}
8
+ end
9
+ end
10
+ end
11
+
12
+ Sunspot.setup(Namespaced::Comment) do
13
+ text :body, :author_name
14
+ string :author_name
15
+ time :published_at
16
+ integer :average_rating
17
+ dynamic_string :custom_string
18
+ boost :boost
19
+ end
@@ -0,0 +1,84 @@
1
+ module Mock
2
+ class ConnectionFactory
3
+ def new(adapter = nil, opts = nil)
4
+ if @instance
5
+ raise('Factory can only create an instance once!')
6
+ else
7
+ @instance = Connection.new(adapter, opts)
8
+ end
9
+ end
10
+
11
+ def instance
12
+ @instance ||= Connection.new
13
+ end
14
+ end
15
+
16
+ class Connection
17
+ attr_reader :adds, :commits, :searches
18
+ attr_accessor :adapter, :opts
19
+
20
+ def initialize(adapter = nil, opts = nil)
21
+ @adapter, @opts = adapter, opts
22
+ @adds, @deletes, @deletes_by_query, @commits, @searches = Array.new(5) { [] }
23
+ end
24
+
25
+ def add(documents)
26
+ @adds << Array(documents)
27
+ end
28
+
29
+ def delete_by_id(*ids)
30
+ @deletes << ids
31
+ end
32
+
33
+ def delete_by_query(query)
34
+ @deletes_by_query << query
35
+ end
36
+
37
+ def commit
38
+ @commits << Time.now
39
+ end
40
+
41
+ def select(params)
42
+ @searches << @last_search = params
43
+ end
44
+
45
+ def has_add_with?(*documents)
46
+ @adds.any? do |add|
47
+ documents.all? do |document|
48
+ add.any? do |added|
49
+ if document.is_a?(Hash)
50
+ document.all? do |field, value|
51
+ added.fields_by_name(field).map do |field|
52
+ field.value
53
+ end == Array(value)
54
+ end
55
+ else
56
+ !added.fields_by_name(document).empty?
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def has_delete?(*ids)
64
+ @deletes.any? do |delete|
65
+ delete & ids == ids
66
+ end
67
+ end
68
+
69
+ def has_delete_by_query?(query)
70
+ @deletes_by_query.include?(query)
71
+ end
72
+
73
+ def has_last_search_with?(params)
74
+ return unless @last_search
75
+ if params.respond_to?(:all?)
76
+ params.all? do |key, value|
77
+ @last_search.has_key?(key) && @last_search[key] == value
78
+ end
79
+ else
80
+ @last_search.has_key?(params)
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,30 @@
1
+ require File.join(File.dirname(__FILE__), 'post')
2
+
3
+ module MockAdapter
4
+ class InstanceAdapter < Sunspot::Adapters::InstanceAdapter
5
+ def id
6
+ @instance.id
7
+ end
8
+ end
9
+
10
+ class DataAccessor < Sunspot::Adapters::DataAccessor
11
+ def load(id)
12
+ @clazz.get(id.to_i)
13
+ end
14
+
15
+ def load_all(ids)
16
+ all = @clazz.get_all(ids.map { |id| id.to_i })
17
+ if @custom_title
18
+ all.each { |item| item.title = @custom_title }
19
+ end
20
+ all
21
+ end
22
+
23
+ def custom_title=(custom_title)
24
+ @custom_title = custom_title
25
+ end
26
+ end
27
+ end
28
+
29
+ Sunspot::Adapters::DataAccessor.register(MockAdapter::DataAccessor, MockRecord)
30
+ Sunspot::Adapters::InstanceAdapter.register(MockAdapter::InstanceAdapter, MockRecord)
@@ -0,0 +1,48 @@
1
+ class MockRecord
2
+ IDS = Hash.new { |h, k| h[k] = 0 }
3
+ QUERY_COUNTS = Hash.new { |h, k| h[k] = 0 }
4
+ INSTANCES = Hash.new { |h, k| h[k] = {} }
5
+
6
+ attr_reader :id
7
+
8
+ class <<self
9
+ def reset!
10
+ IDS[name.to_sym] = 0
11
+ INSTANCES[name.to_sym] = {}
12
+ end
13
+ end
14
+
15
+ def initialize(attrs = {})
16
+ attrs.each_pair do |name, value|
17
+ send(:"#{name}=", value)
18
+ end
19
+ @id = IDS[self.class.name.to_sym] += 1
20
+ INSTANCES[self.class.name.to_sym][@id] = self
21
+ end
22
+
23
+ def self.inherited(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ module ClassMethods
28
+ def get(id)
29
+ QUERY_COUNTS[self.name.to_sym] += 1
30
+ get_instance(id)
31
+ end
32
+
33
+ def get_all(ids)
34
+ QUERY_COUNTS[self.name.to_sym] += 1
35
+ ids.map { |id| get_instance(id) }.sort_by { |instance| instance.id }
36
+ end
37
+
38
+ def query_count
39
+ QUERY_COUNTS[self.name.to_sym]
40
+ end
41
+
42
+ private
43
+
44
+ def get_instance(id)
45
+ INSTANCES[self.name.to_sym][id]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,8 @@
1
+ class Photo < MockRecord
2
+ attr_accessor :caption
3
+ end
4
+
5
+ Sunspot.setup(Photo) do
6
+ text :caption
7
+ boost 0.75
8
+ end
@@ -0,0 +1,73 @@
1
+ require File.join(File.dirname(__FILE__), 'blog')
2
+
3
+ class Post < MockRecord
4
+ attr_accessor :title, :body, :blog_id, :published_at, :ratings_average,
5
+ :author_name, :featured, :expire_date
6
+ alias_method :featured?, :featured
7
+
8
+ def category_ids
9
+ @category_ids ||= []
10
+ end
11
+
12
+ def custom_string
13
+ @custom_string ||= {}
14
+ end
15
+
16
+ def custom_fl
17
+ @custom_fl ||= {}
18
+ end
19
+
20
+ def custom_time
21
+ @custom_time ||= {}
22
+ end
23
+
24
+ def custom_boolean
25
+ @custom_boolean ||= {}
26
+ end
27
+
28
+ private
29
+ attr_writer :category_ids, :custom_string, :custom_fl, :custom_time, :custom_boolean
30
+ end
31
+
32
+ Sunspot.setup(Post) do
33
+ text :title, :boost => 2
34
+ text :body
35
+ text :backwards_title do
36
+ title.reverse if title
37
+ end
38
+ string :title, :stored => true
39
+ integer :blog_id, :references => Blog
40
+ integer :category_ids, :multiple => true
41
+ float :average_rating, :using => :ratings_average
42
+ time :published_at
43
+ date :expire_date
44
+ boolean :featured, :using => :featured?
45
+ string :sort_title do
46
+ title.downcase.sub(/^(a|an|the)\W+/, '') if title
47
+ end
48
+ integer :primary_category_id do |post|
49
+ post.category_ids.first
50
+ end
51
+ time :last_indexed_at, :stored => true do
52
+ Time.now
53
+ end
54
+
55
+ dynamic_string :custom_string
56
+ dynamic_float :custom_float, :multiple => true, :using => :custom_fl
57
+ dynamic_integer :custom_integer do
58
+ category_ids.inject({}) do |hash, category_id|
59
+ hash.merge(category_id => 1)
60
+ end
61
+ end
62
+ dynamic_time :custom_time
63
+ dynamic_boolean :custom_boolean
64
+
65
+ boost do
66
+ if ratings_average
67
+ 1 + (ratings_average - 3.0) / 4.0
68
+ end
69
+ end
70
+ end
71
+
72
+ class PhotoPost < Post
73
+ end
@@ -0,0 +1,8 @@
1
+ class User
2
+ attr_accessor :name
3
+ end
4
+
5
+ Sunspot.setup(User) do
6
+ text :name
7
+ string :name
8
+ end
@@ -0,0 +1,47 @@
1
+ using_gems = false
2
+ begin
3
+ require 'spec'
4
+ begin
5
+ require 'ruby-debug'
6
+ rescue LoadError => e
7
+ if using_gems
8
+ module Kernel
9
+ def debugger
10
+ STDERR.puts('Debugger is not available')
11
+ end
12
+ end
13
+ else
14
+ raise(e)
15
+ end
16
+ end
17
+ if ENV['USE_WILL_PAGINATE']
18
+ require 'will_paginate'
19
+ require 'will_paginate/collection'
20
+ end
21
+ rescue LoadError => e
22
+ require 'rubygems'
23
+ if using_gems
24
+ raise(e)
25
+ else
26
+ using_gems = true
27
+ retry
28
+ end
29
+ end
30
+
31
+ unless gem_name = ENV['SUNSPOT_TEST_GEM']
32
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
33
+ else
34
+ gem gem_name
35
+ end
36
+ require 'sunspot'
37
+
38
+ require File.join(File.dirname(__FILE__), 'mocks', 'mock_record.rb')
39
+ Dir.glob(File.join(File.dirname(__FILE__), 'mocks', '**', '*.rb')).each do |file|
40
+ require file unless File.basename(file) == 'mock_record.rb'
41
+ end
42
+
43
+ def without_class(clazz)
44
+ Object.class_eval { remove_const(clazz.name.to_sym) }
45
+ yield
46
+ Object.class_eval { const_set(clazz.name.to_sym, clazz) }
47
+ end
@@ -0,0 +1,25 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |s|
4
+ s.name = 'sunspot'
5
+ s.executables = ['sunspot-solr', 'sunspot-configure-solr']
6
+ s.summary = 'Library for expressive, powerful interaction with the Solr search engine'
7
+ s.email = 'mat@patch.com'
8
+ s.homepage = 'http://github.com/outoftime/sunspot'
9
+ s.description = 'Library for expressive, powerful interaction with the Solr search engine'
10
+ s.authors = ['Mat Brown', 'Peer Allan', 'Dmitriy Dzema', 'Benjamin Krause']
11
+ s.rubyforge_project = 'sunspot'
12
+ s.files = FileList['[A-Z]*', '{bin,lib,spec,tasks,templates}/**/*', 'solr/{etc,lib,webapps}/**/*', 'solr/solr/conf/*', 'solr/start.jar']
13
+ s.add_dependency 'mwmitchell-rsolr', '>= 0.8.9'
14
+ s.add_dependency 'daemons', '~> 1.0'
15
+ s.add_dependency 'optiflag', '~> 0.6.5'
16
+ s.add_dependency 'haml', '~> 2.2'
17
+ s.add_development_dependency 'rspec', '~> 1.1'
18
+ s.add_development_dependency 'ruby-debug', '~> 0.10'
19
+ s.extra_rdoc_files = ['README.rdoc']
20
+ s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' <<
21
+ '--title' << 'Sunspot - Solr-powered search for Ruby objects - API Documentation' <<
22
+ '--main' << 'README.rdoc'
23
+
24
+ end
25
+ end
data/tasks/rcov.rake ADDED
@@ -0,0 +1,28 @@
1
+ require 'rake'
2
+ require 'spec/rake/spectask'
3
+
4
+ desc 'run specs with rcov'
5
+ Spec::Rake::SpecTask.new('rcov') do |t|
6
+ t.spec_files = FileList['spec/**/*_spec.rb']
7
+ t.rcov = true
8
+ t.rcov_dir = File.join('coverage', 'all')
9
+ t.rcov_opts.concat(['--exclude', 'spec', '--sort', 'coverage', '--only-uncovered'])
10
+ end
11
+
12
+ namespace :rcov do
13
+ desc 'run api specs with rcov'
14
+ Spec::Rake::SpecTask.new('api') do |t|
15
+ t.spec_files = FileList['spec/api/*_spec.rb']
16
+ t.rcov = true
17
+ t.rcov_dir = File.join('coverage', 'api')
18
+ t.rcov_opts.concat(['--exclude', 'spec', '--sort', 'coverage', '--only-uncovered'])
19
+ end
20
+
21
+ desc 'run integration specs with rcov'
22
+ Spec::Rake::SpecTask.new('integration') do |t|
23
+ t.spec_files = FileList['spec/integration/*_spec.rb']
24
+ t.rcov = true
25
+ t.rcov_dir = File.join('coverage', 'integration')
26
+ t.rcov_opts.concat(['--exclude', 'spec', '--sort', 'coverage', '--only-uncovered'])
27
+ end
28
+ end