xapit 0.2.7 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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