xapit 0.2.6 → 0.2.7
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.
- 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
|
|