kuahyeow-sunspot 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) 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/adapters.rb +265 -0
  11. data/lib/sunspot/composite_setup.rb +186 -0
  12. data/lib/sunspot/configuration.rb +38 -0
  13. data/lib/sunspot/data_extractor.rb +47 -0
  14. data/lib/sunspot/date_facet.rb +36 -0
  15. data/lib/sunspot/date_facet_row.rb +17 -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/dsl.rb +3 -0
  24. data/lib/sunspot/facet.rb +51 -0
  25. data/lib/sunspot/facet_row.rb +34 -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 +38 -0
  30. data/lib/sunspot/instantiated_facet_row.rb +12 -0
  31. data/lib/sunspot/query/base_query.rb +94 -0
  32. data/lib/sunspot/query/connective.rb +126 -0
  33. data/lib/sunspot/query/dynamic_query.rb +69 -0
  34. data/lib/sunspot/query/field_facet.rb +149 -0
  35. data/lib/sunspot/query/field_query.rb +57 -0
  36. data/lib/sunspot/query/pagination.rb +39 -0
  37. data/lib/sunspot/query/query_facet.rb +72 -0
  38. data/lib/sunspot/query/query_facet_row.rb +19 -0
  39. data/lib/sunspot/query/restriction.rb +233 -0
  40. data/lib/sunspot/query/scope.rb +165 -0
  41. data/lib/sunspot/query/sort.rb +36 -0
  42. data/lib/sunspot/query/sort_composite.rb +33 -0
  43. data/lib/sunspot/query.rb +190 -0
  44. data/lib/sunspot/query_facet.rb +33 -0
  45. data/lib/sunspot/query_facet_row.rb +21 -0
  46. data/lib/sunspot/schema.rb +165 -0
  47. data/lib/sunspot/search/hit.rb +66 -0
  48. data/lib/sunspot/search.rb +220 -0
  49. data/lib/sunspot/session.rb +201 -0
  50. data/lib/sunspot/setup.rb +271 -0
  51. data/lib/sunspot/type.rb +200 -0
  52. data/lib/sunspot/util.rb +164 -0
  53. data/lib/sunspot.rb +470 -0
  54. data/solr/etc/jetty.xml +212 -0
  55. data/solr/etc/webdefault.xml +379 -0
  56. data/solr/lib/jetty-6.1.3.jar +0 -0
  57. data/solr/lib/jetty-util-6.1.3.jar +0 -0
  58. data/solr/lib/jsp-2.1/ant-1.6.5.jar +0 -0
  59. data/solr/lib/jsp-2.1/core-3.1.1.jar +0 -0
  60. data/solr/lib/jsp-2.1/jsp-2.1.jar +0 -0
  61. data/solr/lib/jsp-2.1/jsp-api-2.1.jar +0 -0
  62. data/solr/lib/servlet-api-2.5-6.1.3.jar +0 -0
  63. data/solr/solr/conf/elevate.xml +36 -0
  64. data/solr/solr/conf/protwords.txt +21 -0
  65. data/solr/solr/conf/schema.xml +50 -0
  66. data/solr/solr/conf/solrconfig.xml +696 -0
  67. data/solr/solr/conf/stopwords.txt +57 -0
  68. data/solr/solr/conf/synonyms.txt +31 -0
  69. data/solr/start.jar +0 -0
  70. data/solr/webapps/solr.war +0 -0
  71. data/spec/api/adapters_spec.rb +33 -0
  72. data/spec/api/build_search_spec.rb +1018 -0
  73. data/spec/api/indexer_spec.rb +311 -0
  74. data/spec/api/query_spec.rb +153 -0
  75. data/spec/api/search_retrieval_spec.rb +335 -0
  76. data/spec/api/session_spec.rb +157 -0
  77. data/spec/api/spec_helper.rb +1 -0
  78. data/spec/api/sunspot_spec.rb +18 -0
  79. data/spec/integration/dynamic_fields_spec.rb +55 -0
  80. data/spec/integration/faceting_spec.rb +169 -0
  81. data/spec/integration/keyword_search_spec.rb +83 -0
  82. data/spec/integration/scoped_search_spec.rb +289 -0
  83. data/spec/integration/spec_helper.rb +1 -0
  84. data/spec/integration/stored_fields_spec.rb +10 -0
  85. data/spec/integration/test_pagination.rb +32 -0
  86. data/spec/mocks/adapters.rb +32 -0
  87. data/spec/mocks/blog.rb +3 -0
  88. data/spec/mocks/comment.rb +19 -0
  89. data/spec/mocks/connection.rb +84 -0
  90. data/spec/mocks/mock_adapter.rb +30 -0
  91. data/spec/mocks/mock_record.rb +48 -0
  92. data/spec/mocks/photo.rb +8 -0
  93. data/spec/mocks/post.rb +73 -0
  94. data/spec/mocks/user.rb +8 -0
  95. data/spec/spec_helper.rb +47 -0
  96. data/tasks/gemspec.rake +24 -0
  97. data/tasks/rcov.rake +28 -0
  98. data/tasks/rdoc.rake +22 -0
  99. data/tasks/schema.rake +19 -0
  100. data/tasks/spec.rake +24 -0
  101. data/tasks/todo.rake +4 -0
  102. data/templates/schema.xml.haml +24 -0
  103. metadata +247 -0
@@ -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,24 @@
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.files = FileList['[A-Z]*', '{bin,lib,spec,tasks,templates}/**/*', 'solr/{etc,lib,webapps}/**/*', 'solr/solr/conf/*', 'solr/start.jar']
12
+ s.add_dependency 'mwmitchell-rsolr', '>= 0.8.9'
13
+ s.add_dependency 'daemons', '~> 1.0'
14
+ s.add_dependency 'optiflag', '~> 0.6.5'
15
+ s.add_dependency 'haml', '~> 2.2'
16
+ s.add_development_dependency 'rspec', '~> 1.1'
17
+ s.add_development_dependency 'ruby-debug', '~> 0.10'
18
+ s.extra_rdoc_files = ['README.rdoc']
19
+ s.rdoc_options << '--webcvs=http://github.com/outoftime/sunspot/tree/master/%s' <<
20
+ '--title' << 'Sunspot - Solr-powered search for Ruby objects - API Documentation' <<
21
+ '--main' << 'README.rdoc'
22
+
23
+ end
24
+ 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
data/tasks/rdoc.rake ADDED
@@ -0,0 +1,22 @@
1
+ gem 'mislav-hanna'
2
+ require 'hanna/rdoctask'
3
+ require 'jeweler'
4
+
5
+ Rake::RDocTask.new(:doc) do |rdoc|
6
+ version = Jeweler::VersionHelper.new(File.join(File.dirname(__FILE__), '..')).to_s
7
+ rdoc.title = "Sunspot #{version} - Solr-powered search for Ruby objects - API Documentation"
8
+ rdoc.main = 'README.rdoc'
9
+ rdoc.rdoc_files.include('README.rdoc', 'lib/sunspot.rb', 'lib/sunspot/**/*.rb')
10
+ rdoc.rdoc_dir = 'doc'
11
+ rdoc.options << "--webcvs=http://github.com/outoftime/sunspot/tree/v#{version}/%s" << '--title' << 'Sunspot - Solr-powered search for Ruby objects - API Documentation'
12
+ end
13
+
14
+ namespace :doc do
15
+ desc 'Generate rdoc and move into pages directory'
16
+ task :publish => :redoc do
17
+ doc_dir = File.join(File.dirname(__FILE__), '..', 'doc')
18
+ publish_dir = File.join(File.dirname(__FILE__), '..', 'pages', 'docs')
19
+ FileUtils.rm_rf(publish_dir) if File.exist?(publish_dir)
20
+ FileUtils.cp_r(doc_dir, publish_dir)
21
+ end
22
+ end
data/tasks/schema.rake ADDED
@@ -0,0 +1,19 @@
1
+ namespace :schema do
2
+ desc 'Compile schema from Haml template'
3
+ task :compile do
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'sunspot', 'schema')
5
+ File.open(
6
+ File.join(
7
+ File.dirname(__FILE__),
8
+ '..',
9
+ 'solr',
10
+ 'solr',
11
+ 'conf',
12
+ 'schema.xml'
13
+ ),
14
+ 'w'
15
+ ) do |file|
16
+ file << Sunspot::Schema.new.to_xml
17
+ end
18
+ end
19
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,24 @@
1
+ gem 'rspec', '~>1.1'
2
+
3
+ require 'spec'
4
+ require 'spec/rake/spectask'
5
+
6
+ desc 'run specs'
7
+ Spec::Rake::SpecTask.new('spec') do |t|
8
+ t.spec_files = FileList[File.join(File.dirname(__FILE__), '..', 'spec', '**', '*_spec.rb')]
9
+ t.spec_opts = ['--color']
10
+ end
11
+
12
+ namespace :spec do
13
+ desc 'run api specs (mock out Solr dependency)'
14
+ Spec::Rake::SpecTask.new('api') do |t|
15
+ t.spec_files = FileList[File.join(File.dirname(__FILE__), '..', 'spec', 'api', '*_spec.rb')]
16
+ t.spec_opts = ['--color']
17
+ end
18
+
19
+ desc 'run integration specs (be sure to run `bin/sunspot-solr start`)'
20
+ Spec::Rake::SpecTask.new('integration') do |t|
21
+ t.spec_files = FileList[File.join(File.dirname(__FILE__), '..', 'spec', 'integration', '*_spec.rb')]
22
+ t.spec_opts = ['--color']
23
+ end
24
+ end
data/tasks/todo.rake ADDED
@@ -0,0 +1,4 @@
1
+ desc 'Show all TODO and related tags'
2
+ task :todo do
3
+ FileList['lib/**/*.rb'].egrep(/#.*(TODO|FIXME|XXX)/)
4
+ end
@@ -0,0 +1,24 @@
1
+ !!! XML
2
+ %schema{ :name => 'sunspot', :version => 0.9 }
3
+ %types
4
+ %fieldtype{ :name => 'text', :class => 'solr.TextField', :positionIncrementGap => 100 }
5
+ %analyzer
6
+ %tokenizer{ :class => schema.tokenizer }/
7
+ - for filter in schema.filters
8
+ %filter{ :class => filter }/
9
+ %fieldtype{ :name => 'rand', :class => 'solr.RandomSortField' }
10
+ - schema.types.each do |type|
11
+ %fieldtype{ :name => type.name, :class => "solr.#{type.class_name}Field", :omitNorms => 'true' }/
12
+ %fields
13
+ %field{ :name => 'id', :type => 'string', :indexed => 'true', :stored => 'true', :multiValued => 'false' }/
14
+ %field{ :name => 'type', :type => 'string', :indexed => 'true', :stored => 'false', :multiValued => 'true' }/
15
+ %field{ :name => 'class_name', :type => 'string', :indexed => 'true', :stored => 'false', :multiValued => 'false' }/
16
+ %field{ :name => 'text', :type => 'text', :indexed => 'true', :stored => 'false', :multiValued => 'true' }/
17
+ %dynamicField{ :name => '*_text', :type => 'text', :indexed => 'true', :stored => 'false', :multiValued => 'true' }/
18
+ %dynamicField{ :name => 'random_*', :type => 'rand', :indexed => 'true', :stored => 'false' }/
19
+ - for dynamic_field in schema.dynamic_fields
20
+ %dynamicField{ :name => dynamic_field.name, :type => dynamic_field.type, :indexed => 'true', :stored => dynamic_field.stored?, :multiValued => dynamic_field.multiValued? }/
21
+ %uniqueKey id
22
+ %defaultSearchField text
23
+ %solrQueryParser{ :defaultOperator => 'AND' }/
24
+ %copyField{ :source => '*_text', :dest => 'text' }/