xapit 0.2.7 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/{CHANGELOG → CHANGELOG.rdoc} +7 -2
  2. data/Gemfile +19 -0
  3. data/LICENSE +4 -4
  4. data/README.rdoc +61 -108
  5. data/Rakefile +11 -10
  6. data/features/facets.feature +93 -82
  7. data/features/finding.feature +196 -138
  8. data/features/indexing.feature +35 -37
  9. data/features/remote_server.feature +10 -0
  10. data/features/step_definitions/xapit_steps.rb +53 -25
  11. data/features/suggestions.feature +20 -14
  12. data/features/support/env.rb +13 -6
  13. data/features/support/xapit_helpers.rb +8 -9
  14. data/lib/generators/xapit/install_generator.rb +14 -0
  15. data/lib/generators/xapit/templates/xapit.ru +6 -0
  16. data/lib/generators/xapit/templates/xapit.yml +11 -0
  17. data/lib/xapit.rb +106 -64
  18. data/lib/xapit/client/collection.rb +150 -0
  19. data/lib/xapit/client/facet.rb +11 -0
  20. data/lib/xapit/client/facet_option.rb +29 -0
  21. data/lib/xapit/client/index_builder.rb +67 -0
  22. data/lib/xapit/client/membership.rb +46 -0
  23. data/lib/xapit/client/model_adapters/abstract_model_adapter.rb +30 -0
  24. data/lib/xapit/client/model_adapters/active_record_adapter.rb +27 -0
  25. data/lib/xapit/client/model_adapters/default_model_adapter.rb +7 -0
  26. data/lib/xapit/client/railtie.rb +18 -0
  27. data/lib/xapit/client/remote_database.rb +21 -0
  28. data/lib/xapit/client/tasks.rb +18 -0
  29. data/lib/xapit/server/app.rb +27 -0
  30. data/lib/xapit/server/database.rb +47 -0
  31. data/lib/xapit/server/indexer.rb +138 -0
  32. data/lib/xapit/server/query.rb +240 -0
  33. data/spec/fixtures/blankdb/flintlock +0 -0
  34. data/spec/fixtures/blankdb/iamchert +1 -0
  35. data/spec/fixtures/blankdb/postlist.DB +0 -0
  36. data/spec/fixtures/blankdb/postlist.baseA +0 -0
  37. data/spec/fixtures/blankdb/record.DB +0 -0
  38. data/spec/fixtures/blankdb/record.baseA +0 -0
  39. data/spec/fixtures/blankdb/termlist.DB +0 -0
  40. data/spec/fixtures/blankdb/termlist.baseA +0 -0
  41. data/spec/fixtures/xapit.ru +13 -0
  42. data/spec/fixtures/xapit.yml +4 -0
  43. data/spec/spec_helper.rb +8 -9
  44. data/spec/support/spec_macros.rb +6 -0
  45. data/spec/{xapit_member.rb → support/xapit_member.rb} +14 -16
  46. data/spec/xapit/client/collection_spec.rb +63 -0
  47. data/spec/xapit/client/facet_option_spec.rb +26 -0
  48. data/spec/xapit/client/facet_spec.rb +13 -0
  49. data/spec/xapit/client/index_builder_spec.rb +66 -0
  50. data/spec/xapit/client/membership_spec.rb +43 -0
  51. data/spec/xapit/client/model_adapters/active_record_adapter_spec.rb +62 -0
  52. data/spec/xapit/client/model_adapters/default_model_adapter_spec.rb +7 -0
  53. data/spec/xapit/client/remote_database_spec.rb +19 -0
  54. data/spec/xapit/server/app_spec.rb +22 -0
  55. data/spec/xapit/server/database_spec.rb +37 -0
  56. data/spec/xapit/server/indexer_spec.rb +82 -0
  57. data/spec/xapit/server/query_spec.rb +43 -0
  58. data/spec/xapit/xapit_spec.rb +28 -0
  59. metadata +124 -93
  60. data/Manifest +0 -60
  61. data/features/sorting.feature +0 -29
  62. data/init.rb +0 -1
  63. data/install.rb +0 -8
  64. data/lib/xapit/adapters/abstract_adapter.rb +0 -47
  65. data/lib/xapit/adapters/active_record_adapter.rb +0 -20
  66. data/lib/xapit/adapters/data_mapper_adapter.rb +0 -10
  67. data/lib/xapit/collection.rb +0 -187
  68. data/lib/xapit/config.rb +0 -84
  69. data/lib/xapit/facet.rb +0 -67
  70. data/lib/xapit/facet_blueprint.rb +0 -59
  71. data/lib/xapit/facet_option.rb +0 -56
  72. data/lib/xapit/index_blueprint.rb +0 -147
  73. data/lib/xapit/indexers/abstract_indexer.rb +0 -116
  74. data/lib/xapit/indexers/classic_indexer.rb +0 -29
  75. data/lib/xapit/indexers/simple_indexer.rb +0 -38
  76. data/lib/xapit/membership.rb +0 -137
  77. data/lib/xapit/query.rb +0 -89
  78. data/lib/xapit/query_parsers/abstract_query_parser.rb +0 -174
  79. data/lib/xapit/query_parsers/classic_query_parser.rb +0 -29
  80. data/lib/xapit/query_parsers/simple_query_parser.rb +0 -75
  81. data/lib/xapit/rake_tasks.rb +0 -13
  82. data/rails_generators/xapit/USAGE +0 -13
  83. data/rails_generators/xapit/templates/setup_xapit.rb +0 -1
  84. data/rails_generators/xapit/templates/xapit.rake +0 -4
  85. data/rails_generators/xapit/xapit_generator.rb +0 -20
  86. data/spec/xapit/adapters/active_record_adapter_spec.rb +0 -31
  87. data/spec/xapit/adapters/data_mapper_adapter_spec.rb +0 -10
  88. data/spec/xapit/collection_spec.rb +0 -176
  89. data/spec/xapit/config_spec.rb +0 -62
  90. data/spec/xapit/facet_blueprint_spec.rb +0 -29
  91. data/spec/xapit/facet_option_spec.rb +0 -80
  92. data/spec/xapit/facet_spec.rb +0 -73
  93. data/spec/xapit/index_blueprint_spec.rb +0 -112
  94. data/spec/xapit/indexers/abstract_indexer_spec.rb +0 -111
  95. data/spec/xapit/indexers/classic_indexer_spec.rb +0 -35
  96. data/spec/xapit/indexers/simple_indexer_spec.rb +0 -69
  97. data/spec/xapit/membership_spec.rb +0 -55
  98. data/spec/xapit/query_parsers/abstract_query_parser_spec.rb +0 -60
  99. data/spec/xapit/query_parsers/classic_query_parser_spec.rb +0 -20
  100. data/spec/xapit/query_parsers/simple_query_parser_spec.rb +0 -86
  101. data/spec/xapit/query_spec.rb +0 -60
  102. data/tasks/spec.rb +0 -9
  103. data/tasks/xapit.rake +0 -1
  104. data/uninstall.rb +0 -5
  105. data/xapit.gemspec +0 -30
@@ -1,29 +0,0 @@
1
- module Xapit
2
- class ClassicQueryParser < AbstractQueryParser
3
- def xapian_query_from_text(text)
4
- xapian_parser.parse_query(cleanup_text(text), Xapian::QueryParser::FLAG_WILDCARD | Xapian::QueryParser::FLAG_PHRASE | Xapian::QueryParser::FLAG_BOOLEAN | Xapian::QueryParser::FLAG_LOVEHATE)
5
- end
6
-
7
- def xapian_parser
8
- @xapian_parser ||= build_xapian_parser
9
- end
10
-
11
- def cleanup_text(text)
12
- text.gsub(/\b([a-z])\*/i) { $1 }
13
- end
14
-
15
- def build_xapian_parser
16
- parser = Xapian::QueryParser.new
17
- parser.database = Config.database
18
- parser.stemmer = Xapian::Stem.new(Config.stemming)
19
- parser.stemming_strategy = Xapian::QueryParser::STEM_SOME
20
- parser.default_op = Xapian::Query::OP_AND
21
- if @member_class
22
- @member_class.xapit_index_blueprint.field_attributes.each do |field|
23
- parser.add_prefix(field.to_s, "X#{field}-")
24
- end
25
- end
26
- parser
27
- end
28
- end
29
- end
@@ -1,75 +0,0 @@
1
- module Xapit
2
- class SimpleQueryParser < AbstractQueryParser
3
- # REFACTORME this is a bit complex for one method...
4
- def xapian_query(instructions = nil)
5
- instructions ||= parsed
6
- instructions = [:add, instructions] if instructions.kind_of? String
7
- operator = (instructions.first == :or ? Xapian::Query::OP_OR : Xapian::Query::OP_AND)
8
- words = instructions[1..-1].select { |i| i.kind_of? String }
9
- query = Xapian::Query.new(operator, words) unless words.empty?
10
- instructions[1..-1].select { |i| i.kind_of? Array }.each do |sub_instructions|
11
- if sub_instructions.first == :not
12
- sub_operator = Xapian::Query::OP_AND_NOT
13
- else
14
- sub_operator = operator
15
- end
16
- if query
17
- query = Xapian::Query.new(sub_operator, query, xapian_query(sub_instructions))
18
- else
19
- query = xapian_query(sub_instructions)
20
- end
21
- end
22
- query
23
- end
24
-
25
- def parsed
26
- parse(@search_text.downcase)
27
- end
28
-
29
- def xapian_query_from_text(text)
30
- xapian_query(parse(text.downcase))
31
- end
32
-
33
- private
34
-
35
-
36
- def parse(text)
37
- if text.kind_of? Array
38
- [:and, *text]
39
- else
40
- text = text.strip
41
- if text =~ /\sor\s/ui
42
- [:or, *text.split(/\s+or\s+/ui).map { |t| parse(t) }]
43
- elsif text =~ /\s+/u
44
- words = text.scan(/(?:\bnot\s+)?[^\s]+/ui)
45
- words.map! do |word|
46
- if Config.stemming
47
- if word =~ /^not\s/ui
48
- [:not, "Z" + stemmer.call(word.sub(/^not\s+/ui, ''))]
49
- else
50
- "Z" + stemmer.call(word)
51
- end
52
- else
53
- if word =~ /^not\s/ui
54
- [:not, word.sub(/^not\s+/ui, '')]
55
- else
56
- word
57
- end
58
- end
59
- end
60
- [:and, *words]
61
- else
62
- if Config.stemming && !text.blank?
63
- "Z" + stemmer.call(text)
64
- else
65
- text
66
- end
67
- end
68
- end
69
- end
70
-
71
- def stemmer
72
- @stemmer ||= Xapian::Stem.new(Config.stemming)
73
- end
74
- end
75
- end
@@ -1,13 +0,0 @@
1
- # TODO investigate why this is needed to ensure it doesn't load twice
2
- unless @xapit_rake_loaded
3
- @xapit_rake_loaded = true
4
- namespace :xapit do
5
- desc "Index all xapit models."
6
- task :index => :environment do
7
- Xapit.remove_database
8
- Xapit.index_all do |member_class|
9
- puts "Indexing #{member_class.name}"
10
- end
11
- end
12
- end
13
- end
@@ -1,13 +0,0 @@
1
- Description:
2
- Generates files necessary to setup Xapit. This includes an initializer
3
- to specify the configuration (setup_xapit.rb) and a rake file to load
4
- the rake tasks (xapit.rake).
5
-
6
- IMPORTANT: Only use this generator if you are using the gem version of
7
- Xapit, it is not needed if you are installing via Rails plugin.
8
-
9
- Examples:
10
- script/generate xapit
11
-
12
- Initializer: config/initializers/setup_xapit.rb
13
- Rake Tasks: lib/tasks/xapit.rake
@@ -1 +0,0 @@
1
- Xapit.setup(:database_path => "#{Rails.root}/db/xapiandb")
@@ -1,4 +0,0 @@
1
- begin
2
- require 'xapit/rake_tasks'
3
- rescue MissingSourceFile
4
- end
@@ -1,20 +0,0 @@
1
- class XapitGenerator < Rails::Generator::Base
2
- def manifest
3
- record do |m|
4
- m.directory "config/initializers"
5
- m.file "setup_xapit.rb", "config/initializers/setup_xapit.rb"
6
-
7
- m.directory "lib/tasks"
8
- m.file "xapit.rake", "lib/tasks/xapit.rake"
9
- end
10
- end
11
-
12
- protected
13
- def banner
14
- <<-EOS
15
- Generates files necessary to setup Xapit.
16
-
17
- USAGE: #{$0} #{spec.name}
18
- EOS
19
- end
20
- end
@@ -1,31 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- describe Xapit::ActiveRecordAdapter do
4
- it "should be used for ActiveRecord::Base subclasses" do
5
- Xapit::ActiveRecordAdapter.should_not be_for_class(Object)
6
- klass = Object.new
7
- stub(klass).ancestors { ["ActiveRecord::Base"] }
8
- Xapit::ActiveRecordAdapter.should be_for_class(klass)
9
- end
10
-
11
- it "should pass find_single to find method to target" do
12
- target = Object.new
13
- mock(target).find_by_id(1, :conditions => "foo") { :record }
14
- adapter = Xapit::ActiveRecordAdapter.new(target)
15
- adapter.find_single(1, :conditions => "foo").should == :record
16
- end
17
-
18
- it "should pass find_multiple to find method to target" do
19
- target = Object.new
20
- mock(target).find([1, 2]) { :record }
21
- adapter = Xapit::ActiveRecordAdapter.new(target)
22
- adapter.find_multiple([1, 2]).should == :record
23
- end
24
-
25
- it "should pass find_each to target" do
26
- target = Object.new
27
- mock(target).find_each(:args) { 5 }
28
- adapter = Xapit::ActiveRecordAdapter.new(target)
29
- adapter.find_each(:args).should == 5
30
- end
31
- end
@@ -1,10 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
2
-
3
- describe Xapit::DataMapperAdapter do
4
- it "should be used for DataMapper::Resource model" do
5
- Xapit::DataMapperAdapter.should_not be_for_class(Object)
6
- klass = Object.new
7
- stub(klass).ancestors { ["DataMapper::Resource"] }
8
- Xapit::DataMapperAdapter.should be_for_class(klass)
9
- end
10
- end
@@ -1,176 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- describe Xapit::Collection do
4
- describe "with database" do
5
- before(:each) do
6
- XapitMember.xapit do |index|
7
- index.text :name
8
- index.field :name
9
- index.facet :name
10
- index.sortable :name
11
- end
12
- end
13
-
14
- describe "indexed" do
15
- before(:each) do
16
- @hello = XapitMember.new(:name => "hello world")
17
- @foo = XapitMember.new(:name => "foo bar")
18
- Xapit.index_all
19
- end
20
-
21
- it "should find all xapit members in database given nil" do
22
- Xapit::Collection.new(XapitMember, nil).should == [@hello, @foo]
23
- end
24
-
25
- it "should matching xapit member given a word" do
26
- Xapit::Collection.new(XapitMember, "foo").should == [@foo]
27
- end
28
-
29
- it "should not be case sensitive on query matching" do
30
- Xapit::Collection.new(XapitMember, "BAR Foo").should == [@foo]
31
- end
32
-
33
- it "should have 2 records for empty string" do
34
- Xapit::Collection.new(XapitMember, "").size.should == 2
35
- end
36
-
37
- it "should not be empty for blank query" do
38
- Xapit::Collection.new(XapitMember, "").empty?.should be_false
39
- end
40
-
41
- it "should filter by conditions, case insensitive" do
42
- Xapit::Collection.new(XapitMember, "", :conditions => { :name => "HELLO world"}).should == [@hello]
43
- end
44
-
45
- it "should know first entry" do
46
- Xapit::Collection.new(XapitMember, "").first.should == @hello
47
- end
48
-
49
- it "should know last entry" do
50
- Xapit::Collection.new(XapitMember, "").last.should == @foo
51
- end
52
-
53
- it "should support page and per_page options" do
54
- Xapit::Collection.new(XapitMember, :page => 1, :per_page => 1).should == [@hello]
55
- Xapit::Collection.new(XapitMember, :page => 2, :per_page => 1).should == [@foo]
56
- end
57
-
58
- it "should have offset" do
59
- Xapit::Collection.new(XapitMember, :page => 2, :per_page => 1).offset.should == 1
60
- end
61
-
62
- it "should have total_entries, total_pages, current_page, per_page, previous_page, next_page" do
63
- collection = Xapit::Collection.new(XapitMember, "", :per_page => 1, :page => 2)
64
- collection.total_entries.should == 2
65
- collection.total_pages.should == 2
66
- collection.previous_page.should == 1
67
- collection.next_page.should be_nil
68
- end
69
-
70
- it "should set xapit_relevance in results" do
71
- results = Xapit::Collection.new(XapitMember, "")
72
- results.each do |record|
73
- record.xapit_relevance.class.should == Fixnum
74
- end
75
- end
76
-
77
- it "should find nothing when searching unknown facet" do
78
- Xapit::Collection.new(XapitMember, :facets => ["unknownfacet"]).should be_empty
79
- end
80
-
81
- it "should find matching facet" do
82
- ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
83
- Xapit::Collection.new(XapitMember, :facets => ids*2).should == [@hello]
84
- end
85
-
86
- it "should split facets string on dash" do
87
- ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
88
- Xapit::Collection.new(XapitMember, :facets => (ids*2).join("-")).should == [@hello]
89
- end
90
-
91
- it "should have one facet with two options with blank keywords" do
92
- facets = Xapit::Collection.new(XapitMember, "").facets
93
- facets.size.should == 1
94
- facets.first.options.size.should == 2
95
- end
96
-
97
- it "should have no applied facets when there are no given facets" do
98
- Xapit::Collection.new(XapitMember, "").applied_facet_options.should be_empty
99
- end
100
-
101
- it "should list applied facets" do
102
- ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
103
- results = Xapit::Collection.new(XapitMember, "", :facets => (ids*2).join("-"))
104
- results.applied_facet_options.map(&:name).should == ["hello world", "hello world"]
105
- end
106
-
107
- it "should pass existing facet identifiers to applied options" do
108
- ids = Xapit::FacetBlueprint.new(XapitMember, 0, :name).identifiers_for(@hello)
109
- results = Xapit::Collection.new(XapitMember, "", :facets => (ids*2).join("-"))
110
- results.applied_facet_options.first.existing_facet_identifiers.should == (ids*2)
111
- end
112
-
113
- it "should sort records in specified order" do
114
- Xapit::Collection.new(XapitMember, :order => :name).should == [@foo, @hello]
115
- end
116
-
117
- it "should have no spelling suggestions for empty query" do
118
- Xapit::Collection.new(XapitMember).spelling_suggestion.should == nil
119
- end
120
-
121
- it "should have no spelling suggestion for very different query" do
122
- Xapit::Collection.new(XapitMember, "match nothing").spelling_suggestion.should == nil
123
- end
124
-
125
- it "should have spelling suggestion for single-word query" do
126
- Xapit::Collection.new(XapitMember, "wrld").spelling_suggestion.should == "world"
127
- end
128
-
129
- it "should have spelling suggestion for multi-word query" do
130
- Xapit::Collection.new(XapitMember, "helo bat wrld").spelling_suggestion.should == "hello bar world"
131
- end
132
-
133
- it "should raise error when fetching spelling suggestion if spelling is disabled" do
134
- Xapit::Config.options[:spelling] = false
135
- lambda { Xapit::Collection.new(XapitMember, "foo").spelling_suggestion }.should raise_error
136
- end
137
-
138
- it "should find similar records" do
139
- member = XapitMember.new(:name => "foo bar world")
140
- Xapit::Collection.search_similar(member).should == [@foo, @hello]
141
- end
142
-
143
- it "should be able to specify classes" do
144
- Xapit::Collection.new(nil, "foo", :classes => [String, Array]).should == []
145
- Xapit::Collection.new(nil, "foo", :classes => [String, Array, XapitMember]).should == [@foo]
146
- end
147
-
148
- it "should support nested or_search" do
149
- Xapit::Collection.new(XapitMember, "world", :order => :name).or_search("foo").should == [@foo, @hello]
150
- end
151
-
152
- it "should override options in nested or_search" do
153
- Xapit::Collection.new(XapitMember, "world", :order => :name, :per_page => 2).or_search("foo", :per_page => 1).should == [@foo]
154
- end
155
-
156
- it "should combine multiple or_search" do
157
- @buz = XapitMember.new(:name => "buz")
158
- @zot = XapitMember.new(:name => "zot")
159
- Xapit.remove_database
160
- Xapit.index_all
161
- Xapit::Collection.new(XapitMember, "world", :order => :name).or_search("foo").or_search("buz").should == [@buz, @foo, @hello]
162
- end
163
-
164
- it "should support nested search" do
165
- @zap = XapitMember.new(:name => "zap world")
166
- Xapit.remove_database
167
- Xapit.index_all
168
- Xapit::Collection.new(XapitMember, "world").search("zap") == [@zap]
169
- end
170
-
171
- it "should inherit options in nested collection search" do
172
- Xapit::Collection.new(XapitMember, "world", :per_page => 3).search("zap").per_page.should == 3
173
- end
174
- end
175
- end
176
- end
@@ -1,62 +0,0 @@
1
- require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
-
3
- describe Xapit::Config do
4
- it "should be able to set database path and fetch writable or readable" do
5
- Xapit::Config.writable_database.should be_kind_of(Xapian::WritableDatabase)
6
- Xapit::Config.database.should be_kind_of(Xapian::Database)
7
- end
8
-
9
- it "should default query parser to SimpleQueryParser" do
10
- Xapit::Config.setup
11
- Xapit::Config.query_parser.should == Xapit::ClassicQueryParser
12
- end
13
-
14
- it "should be able to set query parser on setup" do
15
- Xapit::Config.setup(:query_parser => Xapit::SimpleQueryParser)
16
- Xapit::Config.query_parser.should == Xapit::SimpleQueryParser
17
- end
18
-
19
- it "should default indexer to SimpleIndexer" do
20
- Xapit::Config.setup
21
- Xapit::Config.indexer.should == Xapit::SimpleIndexer
22
- end
23
-
24
- it "should be able to set indexer on setup" do
25
- Xapit::Config.setup(:indexer => Xapit::ClassicIndexer)
26
- Xapit::Config.indexer.should == Xapit::ClassicIndexer
27
- end
28
-
29
- it "should have spelling enabled by default" do
30
- Xapit::Config.setup
31
- Xapit::Config.spelling?.should be_true
32
- end
33
-
34
- it "should be able to specify spelling setting at setup" do
35
- Xapit::Config.setup(:spelling => false)
36
- Xapit::Config.spelling?.should be_false
37
- end
38
-
39
- it "should default stemming to english" do
40
- Xapit::Config.setup
41
- Xapit::Config.stemming == "english"
42
- end
43
-
44
- it "should be able to specify stemming setting at setup" do
45
- Xapit::Config.setup(:stemming => "german")
46
- Xapit::Config.stemming == "german"
47
- end
48
-
49
- it "should remove the database if it is a true xapian database" do
50
- Xapit::Config.writable_database # load the database
51
- Xapit::Config.remove_database
52
- File.exist?(Xapit::Config.path).should be_false
53
- end
54
-
55
- it "should NOT remove the database if it is not a xapian database" do
56
- path = Xapit::Config.path + "/testing"
57
- FileUtils.mkdir_p(path)
58
- Xapit::Config.remove_database
59
- File.exist?(Xapit::Config.path).should be_true
60
- FileUtils.rm_rf(Xapit::Config.path)
61
- end
62
- end