xapit 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +11 -0
- data/Rakefile +1 -1
- data/features/finding.feature +9 -1
- data/features/step_definitions/xapit_steps.rb +4 -0
- data/lib/xapit/collection.rb +25 -17
- data/lib/xapit/facet.rb +1 -1
- data/lib/xapit/query.rb +7 -6
- data/lib/xapit/query_parsers/abstract_query_parser.rb +27 -9
- data/spec/xapit/collection_spec.rb +27 -4
- data/spec/xapit/query_parsers/abstract_query_parser_spec.rb +9 -0
- data/spec/xapit/query_spec.rb +2 -4
- data/xapit.gemspec +2 -2
- metadata +2 -2
data/CHANGELOG
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
*0.2.7* (July 7th, 2009)
|
2
|
+
|
3
|
+
* support punctuation in wildcard matching
|
4
|
+
|
5
|
+
* fixing nested search calls
|
6
|
+
|
7
|
+
* chain a separate search with "or_search" to find records matching either one
|
8
|
+
|
9
|
+
search("foo").or_search(:conditions => { :priority => 3 })
|
10
|
+
|
11
|
+
|
1
12
|
*0.2.6* (July 6th, 2009)
|
2
13
|
|
3
14
|
* search for field conditions in query string, only supported by ClassicQueryParser
|
data/Rakefile
CHANGED
@@ -2,7 +2,7 @@ require 'rubygems'
|
|
2
2
|
require 'rake'
|
3
3
|
require 'echoe'
|
4
4
|
|
5
|
-
Echoe.new('xapit', '0.2.
|
5
|
+
Echoe.new('xapit', '0.2.7') do |p|
|
6
6
|
p.summary = "Ruby library for interacting with Xapian, a full text search engine."
|
7
7
|
p.description = "Ruby library for interacting with Xapian, a full text search engine."
|
8
8
|
p.url = "http://github.com/ryanb/xapit"
|
data/features/finding.feature
CHANGED
@@ -119,7 +119,6 @@ Scenario: Query for separate OR conditions
|
|
119
119
|
When I query "age" matching "17" or "name" matching "Jack"
|
120
120
|
Then I should find records named "Jane, Jack"
|
121
121
|
|
122
|
-
@focus
|
123
122
|
Scenario: Query for condition in keywords string
|
124
123
|
Given the following indexed records
|
125
124
|
| name | age |
|
@@ -128,3 +127,12 @@ Scenario: Query for condition in keywords string
|
|
128
127
|
| Jack | 17 |
|
129
128
|
When I query for "age:17"
|
130
129
|
Then I should find records named "Jane, Jack"
|
130
|
+
|
131
|
+
Scenario: Query for separate OR conditions and keywords
|
132
|
+
Given the following indexed records
|
133
|
+
| name | age |
|
134
|
+
| John | 23 |
|
135
|
+
| Jane | 17 |
|
136
|
+
| Jack | 18 |
|
137
|
+
When I query for "John" or "age" matching "18" ordered by "name"
|
138
|
+
Then I should find records named "Jack, John"
|
@@ -78,6 +78,10 @@ When /^I query "([^\"]*)" matching "([^\"]*)" or "([^\"]*)" matching "([^\"]*)"$
|
|
78
78
|
@records = XapitMember.search(:conditions => [{ field1.to_sym => value1 }, { field2.to_sym => value2 }])
|
79
79
|
end
|
80
80
|
|
81
|
+
When /^I query for "([^\"]*)" or "([^\"]*)" matching "([^\"]*)" ordered by "([^\"]*)"$/ do |keywords, field, value, order|
|
82
|
+
@records = XapitMember.search(keywords, :order => order).or_search(:conditions => { field.to_sym => value })
|
83
|
+
end
|
84
|
+
|
81
85
|
When /^I query "([^\"]*)" between (\d+) and (\d+)$/ do |field, beginning, ending|
|
82
86
|
@records = XapitMember.search(:conditions => { field.to_sym => beginning.to_i..ending.to_i })
|
83
87
|
end
|
data/lib/xapit/collection.rb
CHANGED
@@ -13,13 +13,14 @@ module Xapit
|
|
13
13
|
[].methods.each do |m|
|
14
14
|
delegate m, :to => :results unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
|
15
15
|
end
|
16
|
+
delegate :query, :base_query, :base_query=, :extra_queries, :extra_queries=, :to => :@query_parser
|
16
17
|
|
17
18
|
def self.search_similar(member, *args)
|
18
19
|
collection = new(member.class, *args)
|
19
20
|
indexer = SimpleIndexer.new(member.class.xapit_index_blueprint)
|
20
21
|
terms = indexer.text_terms(member) + indexer.field_terms(member)
|
21
|
-
collection.base_query.and_query(terms, :or)
|
22
|
-
collection.base_query
|
22
|
+
query = collection.base_query.and_query(terms, :or).not_query("Q#{member.class}-#{member.id}")
|
23
|
+
collection.base_query = query
|
23
24
|
collection
|
24
25
|
end
|
25
26
|
|
@@ -37,7 +38,7 @@ module Xapit
|
|
37
38
|
def size
|
38
39
|
@query_parser.query.count
|
39
40
|
end
|
40
|
-
alias_method :total_entries, :size
|
41
|
+
alias_method :total_entries, :size # alias to total_entries to support will_paginate
|
41
42
|
|
42
43
|
# Returns true if no results are found.
|
43
44
|
def empty?
|
@@ -56,18 +57,29 @@ module Xapit
|
|
56
57
|
|
57
58
|
# Perform another search on this one, inheriting all options already passed.
|
58
59
|
# See Xapit::Membership for search options.
|
59
|
-
|
60
|
-
|
60
|
+
#
|
61
|
+
# Article.search("kite").search("sky") # only performs one query
|
62
|
+
#
|
63
|
+
def search(*args)
|
64
|
+
options = args.extract_options!
|
65
|
+
collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
|
61
66
|
collection.base_query = @query_parser.query
|
62
67
|
collection
|
63
68
|
end
|
64
69
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
70
|
+
# Chain another search returning all records matched by either this search or the previous search
|
71
|
+
# Inherits all options passed in earlier search (such as :page and :order)
|
72
|
+
# See Xapit::Membership for search options.
|
73
|
+
#
|
74
|
+
# Article.search("kite").or_search(:conditions => { :priority => 1 })
|
75
|
+
#
|
76
|
+
def or_search(*args)
|
77
|
+
options = args.extract_options!
|
78
|
+
collection = Collection.new(@query_parser.member_class, args[0].to_s, @query_parser.options.merge(options))
|
79
|
+
collection.base_query = @query_parser.base_query
|
80
|
+
collection.extra_queries = @query_parser.extra_queries
|
81
|
+
collection.extra_queries << @query_parser.primary_query
|
82
|
+
collection
|
71
83
|
end
|
72
84
|
|
73
85
|
# The page number we are currently on.
|
@@ -87,7 +99,7 @@ module Xapit
|
|
87
99
|
|
88
100
|
# Total number of pages with found results.
|
89
101
|
def total_pages
|
90
|
-
(
|
102
|
+
(size / per_page.to_f).ceil
|
91
103
|
end
|
92
104
|
|
93
105
|
# The previous page number. Returns nil if on first page.
|
@@ -151,14 +163,10 @@ module Xapit
|
|
151
163
|
|
152
164
|
private
|
153
165
|
|
154
|
-
def matchset(options = {})
|
155
|
-
@query_parser.query.matchset(options)
|
156
|
-
end
|
157
|
-
|
158
166
|
# TODO this could use some refactoring
|
159
167
|
# See issue #11 for why this is so complex.
|
160
168
|
def fetch_results(options = {})
|
161
|
-
matches = matchset(options).matches
|
169
|
+
matches = @query_parser.matchset(options).matches
|
162
170
|
records_by_class = {}
|
163
171
|
matches.each do |match|
|
164
172
|
class_name, id = match.document.data.split('-')
|
data/lib/xapit/facet.rb
CHANGED
data/lib/xapit/query.rb
CHANGED
@@ -3,11 +3,10 @@ module Xapit
|
|
3
3
|
# this class unless you are trying to query the Xapian database directly.
|
4
4
|
# You may be looking for Xapit::Collection instead.
|
5
5
|
class Query
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :xapian_query
|
7
7
|
|
8
8
|
def initialize(*args)
|
9
9
|
@xapian_query = build_xapian_query(*args)
|
10
|
-
@default_options = { :offset => 0, :sort_descending => false }
|
11
10
|
end
|
12
11
|
|
13
12
|
def and_query(*args)
|
@@ -23,7 +22,7 @@ module Xapit
|
|
23
22
|
end
|
24
23
|
|
25
24
|
def matchset(options = {})
|
26
|
-
options.reverse_merge!
|
25
|
+
options.reverse_merge! :offset => 0, :sort_descending => false
|
27
26
|
enquire = Xapian::Enquire.new(Config.database)
|
28
27
|
if options[:sort_by_values]
|
29
28
|
sorter = Xapian::MultiValueSorter.new
|
@@ -49,9 +48,11 @@ module Xapit
|
|
49
48
|
private
|
50
49
|
|
51
50
|
def merge_query(operator, *args)
|
52
|
-
|
53
|
-
|
54
|
-
|
51
|
+
if args.first.blank?
|
52
|
+
self
|
53
|
+
else
|
54
|
+
Xapit::Query.new([@xapian_query, build_xapian_query(*args)], operator)
|
55
|
+
end
|
55
56
|
end
|
56
57
|
|
57
58
|
def build_xapian_query(query, operator = :and)
|
@@ -1,15 +1,25 @@
|
|
1
1
|
module Xapit
|
2
2
|
class AbstractQueryParser
|
3
|
-
attr_reader :member_class
|
3
|
+
attr_reader :member_class, :options
|
4
4
|
attr_writer :base_query
|
5
|
+
attr_accessor :extra_queries
|
5
6
|
|
6
7
|
def initialize(*args)
|
7
8
|
@options = args.extract_options!
|
8
9
|
@member_class = args[0]
|
9
10
|
@search_text = args[1].to_s
|
11
|
+
@extra_queries = []
|
10
12
|
end
|
11
13
|
|
12
14
|
def query
|
15
|
+
if @extra_queries.blank?
|
16
|
+
primary_query
|
17
|
+
else
|
18
|
+
Query.new([primary_query] + @extra_queries, :or)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def primary_query
|
13
23
|
if (@search_text.split + condition_terms + not_condition_terms + facet_terms).empty?
|
14
24
|
base_query
|
15
25
|
else
|
@@ -47,12 +57,7 @@ module Xapit
|
|
47
57
|
end
|
48
58
|
|
49
59
|
def initial_query
|
50
|
-
|
51
|
-
query.default_options[:offset] = offset
|
52
|
-
query.default_options[:limit] = per_page
|
53
|
-
query.default_options[:sort_by_values] = sort_by_values
|
54
|
-
query.default_options[:sort_descending] = @options[:descending]
|
55
|
-
query
|
60
|
+
Query.new(initial_query_strings, :or)
|
56
61
|
end
|
57
62
|
|
58
63
|
def initial_query_strings
|
@@ -106,6 +111,19 @@ module Xapit
|
|
106
111
|
suggestion.blank? ? nil : suggestion
|
107
112
|
end
|
108
113
|
|
114
|
+
def matchset(options = {})
|
115
|
+
query.matchset(query_options.merge(options))
|
116
|
+
end
|
117
|
+
|
118
|
+
def query_options
|
119
|
+
{
|
120
|
+
:offset => offset,
|
121
|
+
:limit => per_page,
|
122
|
+
:sort_by_values => sort_by_values,
|
123
|
+
:sort_descending => @options[:descending]
|
124
|
+
}
|
125
|
+
end
|
126
|
+
|
109
127
|
private
|
110
128
|
|
111
129
|
def parse_conditions(conditions)
|
@@ -147,10 +165,10 @@ module Xapit
|
|
147
165
|
# Expands the wildcard in the term (just at the end) and returns a query
|
148
166
|
# which will match any term that starts with the given term.
|
149
167
|
def wildcard_query(term, prefix = "")
|
150
|
-
|
168
|
+
full_term = (prefix + term.downcase).sub(/\*$/, '') # remove asterisk at end if it exists
|
151
169
|
parser = Xapian::QueryParser.new
|
152
170
|
parser.database = Xapit::Config.database
|
153
|
-
parser.parse_query(
|
171
|
+
parser.parse_query(full_term[-1..-1], Xapian::QueryParser::FLAG_PARTIAL, full_term[0..-2])
|
154
172
|
end
|
155
173
|
end
|
156
174
|
end
|
@@ -50,10 +50,6 @@ describe Xapit::Collection do
|
|
50
50
|
Xapit::Collection.new(XapitMember, "").last.should == @foo
|
51
51
|
end
|
52
52
|
|
53
|
-
it "should support nested search" do
|
54
|
-
Xapit::Collection.new(XapitMember, "world").search("foo") == [@foo]
|
55
|
-
end
|
56
|
-
|
57
53
|
it "should support page and per_page options" do
|
58
54
|
Xapit::Collection.new(XapitMember, :page => 1, :per_page => 1).should == [@hello]
|
59
55
|
Xapit::Collection.new(XapitMember, :page => 2, :per_page => 1).should == [@foo]
|
@@ -148,6 +144,33 @@ describe Xapit::Collection do
|
|
148
144
|
Xapit::Collection.new(nil, "foo", :classes => [String, Array]).should == []
|
149
145
|
Xapit::Collection.new(nil, "foo", :classes => [String, Array, XapitMember]).should == [@foo]
|
150
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
|
151
174
|
end
|
152
175
|
end
|
153
176
|
end
|
@@ -48,4 +48,13 @@ describe Xapit::AbstractQueryParser do
|
|
48
48
|
parser = Xapit::AbstractQueryParser.new(XapitMember, :conditions => { :foo => [2..5, 10] })
|
49
49
|
parser.condition_terms.first.xapian_query.description.should == expected.description
|
50
50
|
end
|
51
|
+
|
52
|
+
it "should expand punctuated terms properly" do
|
53
|
+
XapitMember.xapit { |i| i.field :name }
|
54
|
+
bar = XapitMember.new(:name => "foo-bar")
|
55
|
+
baz = XapitMember.new(:name => "foo-baz")
|
56
|
+
zap = XapitMember.new(:name => "foo-zap")
|
57
|
+
Xapit.index_all
|
58
|
+
XapitMember.search(:conditions => { :name => "foo-b*"}).should == [bar, baz]
|
59
|
+
end
|
51
60
|
end
|
data/spec/xapit/query_spec.rb
CHANGED
@@ -31,8 +31,7 @@ describe Xapit::Query do
|
|
31
31
|
Xapian::Query.new(Xapian::Query::OP_AND, ["bar"])
|
32
32
|
)
|
33
33
|
query = Xapit::Query.new("foo")
|
34
|
-
query.and_query("bar")
|
35
|
-
query.xapian_query.description.should == expected.description
|
34
|
+
query.and_query("bar").xapian_query.description.should == expected.description
|
36
35
|
end
|
37
36
|
|
38
37
|
it "should OR two queries together" do
|
@@ -41,8 +40,7 @@ describe Xapit::Query do
|
|
41
40
|
Xapian::Query.new(Xapian::Query::OP_AND, ["bar"])
|
42
41
|
)
|
43
42
|
query = Xapit::Query.new("foo")
|
44
|
-
query.or_query("bar")
|
45
|
-
query.xapian_query.description.should == expected.description
|
43
|
+
query.or_query("bar").xapian_query.description.should == expected.description
|
46
44
|
end
|
47
45
|
|
48
46
|
it "should build a query from an array of mixed strings and queries" do
|
data/xapit.gemspec
CHANGED
@@ -2,11 +2,11 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{xapit}
|
5
|
-
s.version = "0.2.
|
5
|
+
s.version = "0.2.7"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["Ryan Bates"]
|
9
|
-
s.date = %q{2009-07-
|
9
|
+
s.date = %q{2009-07-07}
|
10
10
|
s.description = %q{Ruby library for interacting with Xapian, a full text search engine.}
|
11
11
|
s.email = %q{ryan (at) railscasts (dot) com}
|
12
12
|
s.extra_rdoc_files = ["CHANGELOG", "lib/xapit/adapters/abstract_adapter.rb", "lib/xapit/adapters/active_record_adapter.rb", "lib/xapit/adapters/data_mapper_adapter.rb", "lib/xapit/collection.rb", "lib/xapit/config.rb", "lib/xapit/facet.rb", "lib/xapit/facet_blueprint.rb", "lib/xapit/facet_option.rb", "lib/xapit/index_blueprint.rb", "lib/xapit/indexers/abstract_indexer.rb", "lib/xapit/indexers/classic_indexer.rb", "lib/xapit/indexers/simple_indexer.rb", "lib/xapit/membership.rb", "lib/xapit/query.rb", "lib/xapit/query_parsers/abstract_query_parser.rb", "lib/xapit/query_parsers/classic_query_parser.rb", "lib/xapit/query_parsers/simple_query_parser.rb", "lib/xapit/rake_tasks.rb", "lib/xapit.rb", "LICENSE", "README.rdoc", "tasks/spec.rb", "tasks/xapit.rake"]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xapit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Bates
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-07-
|
12
|
+
date: 2009-07-07 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|