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 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.6') do |p|
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"
@@ -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
@@ -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.not_query("Q#{member.class}-#{member.id}")
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
- def search(keywords, options = {})
60
- collection = Collection.new(@query_parser.member_class, keywords, options)
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
- def base_query=(base_query)
66
- @query_parser.base_query = base_query
67
- end
68
-
69
- def base_query
70
- @query_parser.base_query
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
- (total_entries / per_page.to_f).ceil
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('-')
@@ -16,7 +16,7 @@ module Xapit
16
16
 
17
17
  def initialize(blueprint, query, existing_facet_identifiers)
18
18
  @blueprint = blueprint
19
- @query = query.dup
19
+ @query = query
20
20
  @existing_facet_identifiers = existing_facet_identifiers
21
21
  end
22
22
 
@@ -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 :default_options, :xapian_query
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!(default_options)
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
- @count = nil
53
- @xapian_query = Xapian::Query.new(xapian_operator(operator), @xapian_query, build_xapian_query(*args)) unless args.first.blank?
54
- self
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
- query = Query.new(initial_query_strings, :or)
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
- partial_term = term.sub(/\*$/, '') # remove asterisk at end if it exists
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(partial_term, Xapian::QueryParser::FLAG_PARTIAL, prefix)
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
@@ -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
@@ -2,11 +2,11 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{xapit}
5
- s.version = "0.2.6"
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-06}
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.6
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-06 00:00:00 -07:00
12
+ date: 2009-07-07 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15