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 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