xapit 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/Manifest +178 -0
- data/README.rdoc +183 -0
- data/Rakefile +15 -0
- data/TODO +23 -0
- data/features/facets.feature +51 -0
- data/features/finding.feature +119 -0
- data/features/indexing.feature +41 -0
- data/features/step_definitions/common_steps.rb +7 -0
- data/features/step_definitions/xapit_steps.rb +117 -0
- data/features/support/env.rb +7 -0
- data/features/support/xapit_helpers.rb +27 -0
- data/init.rb +3 -0
- data/install.rb +9 -0
- data/lib/xapit.rb +39 -0
- data/lib/xapit/collection.rb +165 -0
- data/lib/xapit/config.rb +83 -0
- data/lib/xapit/facet.rb +59 -0
- data/lib/xapit/facet_blueprint.rb +59 -0
- data/lib/xapit/facet_option.rb +56 -0
- data/lib/xapit/index_blueprint.rb +117 -0
- data/lib/xapit/indexers/abstract_indexer.rb +101 -0
- data/lib/xapit/indexers/classic_indexer.rb +27 -0
- data/lib/xapit/indexers/simple_indexer.rb +31 -0
- data/lib/xapit/membership.rb +103 -0
- data/lib/xapit/query.rb +62 -0
- data/lib/xapit/query_parsers/abstract_query_parser.rb +115 -0
- data/lib/xapit/query_parsers/classic_query_parser.rb +19 -0
- data/lib/xapit/query_parsers/simple_query_parser.rb +75 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/tmp/xapdb/flintlock +0 -0
- data/spec/tmp/xapdb/iamflint +0 -0
- data/spec/tmp/xapdb/postlist.DB +0 -0
- data/spec/tmp/xapdb/postlist.baseA +0 -0
- data/spec/tmp/xapdb/postlist.baseB +0 -0
- data/spec/tmp/xapdb/record.DB +0 -0
- data/spec/tmp/xapdb/record.baseA +0 -0
- data/spec/tmp/xapdb/record.baseB +0 -0
- data/spec/tmp/xapdb/spelling.DB +0 -0
- data/spec/tmp/xapdb/spelling.baseA +0 -0
- data/spec/tmp/xapdb/spelling.baseB +0 -0
- data/spec/tmp/xapdb/termlist.DB +0 -0
- data/spec/tmp/xapdb/termlist.baseA +0 -0
- data/spec/tmp/xapdb/termlist.baseB +0 -0
- data/spec/tmp/xapian_db/flintlock +0 -0
- data/spec/tmp/xapian_db/iamflint +0 -0
- data/spec/tmp/xapian_db/postlist.DB +0 -0
- data/spec/tmp/xapian_db/postlist.baseA +0 -0
- data/spec/tmp/xapian_db/record.DB +0 -0
- data/spec/tmp/xapian_db/record.baseA +0 -0
- data/spec/tmp/xapian_db/termlist.DB +0 -0
- data/spec/tmp/xapian_db/termlist.baseA +0 -0
- data/spec/tmp/xapiandab/flintlock +0 -0
- data/spec/tmp/xapiandab/iamflint +0 -0
- data/spec/tmp/xapiandab/postlist.DB +0 -0
- data/spec/tmp/xapiandab/postlist.baseA +0 -0
- data/spec/tmp/xapiandab/postlist.baseB +0 -0
- data/spec/tmp/xapiandab/record.DB +0 -0
- data/spec/tmp/xapiandab/record.baseA +0 -0
- data/spec/tmp/xapiandab/record.baseB +0 -0
- data/spec/tmp/xapiandab/spelling.DB +0 -0
- data/spec/tmp/xapiandab/spelling.baseA +0 -0
- data/spec/tmp/xapiandab/spelling.baseB +0 -0
- data/spec/tmp/xapiandab/termlist.DB +0 -0
- data/spec/tmp/xapiandab/termlist.baseA +0 -0
- data/spec/tmp/xapiandab/termlist.baseB +0 -0
- data/spec/tmp/xapiandatab/flintlock +0 -0
- data/spec/tmp/xapiandatab/iamflint +0 -0
- data/spec/tmp/xapiandatab/postlist.DB +0 -0
- data/spec/tmp/xapiandatab/postlist.baseA +0 -0
- data/spec/tmp/xapiandatab/postlist.baseB +0 -0
- data/spec/tmp/xapiandatab/record.DB +0 -0
- data/spec/tmp/xapiandatab/record.baseA +0 -0
- data/spec/tmp/xapiandatab/record.baseB +0 -0
- data/spec/tmp/xapiandatab/spelling.DB +0 -0
- data/spec/tmp/xapiandatab/spelling.baseA +0 -0
- data/spec/tmp/xapiandatab/spelling.baseB +0 -0
- data/spec/tmp/xapiandatab/termlist.DB +0 -0
- data/spec/tmp/xapiandatab/termlist.baseA +0 -0
- data/spec/tmp/xapiandatab/termlist.baseB +0 -0
- data/spec/tmp/xapiandataba/flintlock +0 -0
- data/spec/tmp/xapiandataba/iamflint +0 -0
- data/spec/tmp/xapiandataba/postlist.DB +0 -0
- data/spec/tmp/xapiandataba/postlist.baseA +0 -0
- data/spec/tmp/xapiandataba/postlist.baseB +0 -0
- data/spec/tmp/xapiandataba/record.DB +0 -0
- data/spec/tmp/xapiandataba/record.baseA +0 -0
- data/spec/tmp/xapiandataba/record.baseB +0 -0
- data/spec/tmp/xapiandataba/spelling.DB +0 -0
- data/spec/tmp/xapiandataba/spelling.baseA +0 -0
- data/spec/tmp/xapiandataba/spelling.baseB +0 -0
- data/spec/tmp/xapiandataba/termlist.DB +0 -0
- data/spec/tmp/xapiandataba/termlist.baseA +0 -0
- data/spec/tmp/xapiandataba/termlist.baseB +0 -0
- data/spec/tmp/xapiandatabas/flintlock +0 -0
- data/spec/tmp/xapiandatabas/iamflint +0 -0
- data/spec/tmp/xapiandatabas/postlist.DB +0 -0
- data/spec/tmp/xapiandatabas/postlist.baseA +0 -0
- data/spec/tmp/xapiandatabas/record.DB +0 -0
- data/spec/tmp/xapiandatabas/record.baseA +0 -0
- data/spec/tmp/xapiandatabas/termlist.DB +0 -0
- data/spec/tmp/xapiandatabas/termlist.baseA +0 -0
- data/spec/tmp/xapiandatb/flintlock +0 -0
- data/spec/tmp/xapiandatb/iamflint +0 -0
- data/spec/tmp/xapiandatb/postlist.DB +0 -0
- data/spec/tmp/xapiandatb/postlist.baseA +0 -0
- data/spec/tmp/xapiandatb/postlist.baseB +0 -0
- data/spec/tmp/xapiandatb/record.DB +0 -0
- data/spec/tmp/xapiandatb/record.baseA +0 -0
- data/spec/tmp/xapiandatb/record.baseB +0 -0
- data/spec/tmp/xapiandatb/spelling.DB +0 -0
- data/spec/tmp/xapiandatb/spelling.baseA +0 -0
- data/spec/tmp/xapiandatb/spelling.baseB +0 -0
- data/spec/tmp/xapiandatb/termlist.DB +0 -0
- data/spec/tmp/xapiandatb/termlist.baseA +0 -0
- data/spec/tmp/xapiandatb/termlist.baseB +0 -0
- data/spec/tmp/xapiandbase/flintlock +0 -0
- data/spec/tmp/xapiandbase/iamflint +0 -0
- data/spec/tmp/xapiandbase/postlist.DB +0 -0
- data/spec/tmp/xapiandbase/postlist.baseA +0 -0
- data/spec/tmp/xapiandbase/postlist.baseB +0 -0
- data/spec/tmp/xapiandbase/record.DB +0 -0
- data/spec/tmp/xapiandbase/record.baseA +0 -0
- data/spec/tmp/xapiandbase/record.baseB +0 -0
- data/spec/tmp/xapiandbase/spelling.DB +0 -0
- data/spec/tmp/xapiandbase/spelling.baseA +0 -0
- data/spec/tmp/xapiandbase/spelling.baseB +0 -0
- data/spec/tmp/xapiandbase/termlist.DB +0 -0
- data/spec/tmp/xapiandbase/termlist.baseA +0 -0
- data/spec/tmp/xapiandbase/termlist.baseB +0 -0
- data/spec/xapit/collection_spec.rb +153 -0
- data/spec/xapit/config_spec.rb +48 -0
- data/spec/xapit/facet_blueprint_spec.rb +29 -0
- data/spec/xapit/facet_option_spec.rb +80 -0
- data/spec/xapit/facet_spec.rb +73 -0
- data/spec/xapit/index_blueprint_spec.rb +60 -0
- data/spec/xapit/indexers/abstract_indexer_spec.rb +74 -0
- data/spec/xapit/indexers/classic_indexer_spec.rb +26 -0
- data/spec/xapit/indexers/simple_indexer_spec.rb +53 -0
- data/spec/xapit/membership_spec.rb +39 -0
- data/spec/xapit/query_parsers/abstract_query_parser_spec.rb +23 -0
- data/spec/xapit/query_parsers/classic_query_parser_spec.rb +15 -0
- data/spec/xapit/query_parsers/simple_query_parser_spec.rb +86 -0
- data/spec/xapit/query_spec.rb +41 -0
- data/spec/xapit_member.rb +32 -0
- data/tasks/spec.rb +9 -0
- data/tasks/xapit.rake +9 -0
- data/tmp/xapiandatabase/flintlock +0 -0
- data/tmp/xapiandatabase/iamflint +0 -0
- data/tmp/xapiandatabase/postlist.DB +0 -0
- data/tmp/xapiandatabase/postlist.baseA +0 -0
- data/tmp/xapiandatabase/postlist.baseB +0 -0
- data/tmp/xapiandatabase/record.DB +0 -0
- data/tmp/xapiandatabase/record.baseA +0 -0
- data/tmp/xapiandatabase/record.baseB +0 -0
- data/tmp/xapiandatabase/spelling.DB +0 -0
- data/tmp/xapiandatabase/spelling.baseA +0 -0
- data/tmp/xapiandatabase/spelling.baseB +0 -0
- data/tmp/xapiandatabase/termlist.DB +0 -0
- data/tmp/xapiandatabase/termlist.baseA +0 -0
- data/tmp/xapiandatabase/termlist.baseB +0 -0
- data/tmp/xapiandatabase/value.baseB +0 -0
- data/tmp/xapiandb/flintlock +0 -0
- data/tmp/xapiandb/iamflint +0 -0
- data/tmp/xapiandb/postlist.DB +0 -0
- data/tmp/xapiandb/postlist.baseA +0 -0
- data/tmp/xapiandb/postlist.baseB +0 -0
- data/tmp/xapiandb/record.DB +0 -0
- data/tmp/xapiandb/record.baseA +0 -0
- data/tmp/xapiandb/record.baseB +0 -0
- data/tmp/xapiandb/spelling.DB +0 -0
- data/tmp/xapiandb/spelling.baseA +0 -0
- data/tmp/xapiandb/spelling.baseB +0 -0
- data/tmp/xapiandb/termlist.DB +0 -0
- data/tmp/xapiandb/termlist.baseA +0 -0
- data/tmp/xapiandb/termlist.baseB +0 -0
- data/tmp/xapiandb/value.baseB +0 -0
- data/uninstall.rb +5 -0
- data/xapit.gemspec +30 -0
- metadata +257 -0
@@ -0,0 +1,119 @@
|
|
1
|
+
Background:
|
2
|
+
Given an empty database at "tmp/xapiandatabase"
|
3
|
+
|
4
|
+
Scenario: Query Matching No Records
|
5
|
+
Given indexed records named "John, Jane"
|
6
|
+
When I query for "Sam"
|
7
|
+
Then I should find 0 records
|
8
|
+
|
9
|
+
Scenario: Query Matching One Record
|
10
|
+
Given indexed records named "John, Jane"
|
11
|
+
When I query for "John"
|
12
|
+
Then I should find record named "John"
|
13
|
+
|
14
|
+
Scenario: Query Matching Two Records
|
15
|
+
Given indexed records named "John Smith, Jane Smith, John Smithsonian"
|
16
|
+
When I query for "Smith"
|
17
|
+
Then I should find records named "John Smith, Jane Smith"
|
18
|
+
|
19
|
+
Scenario: Query Field Matching
|
20
|
+
Given the following indexed records
|
21
|
+
| name | age |
|
22
|
+
| John | 23 |
|
23
|
+
| Jane | 17 |
|
24
|
+
| Jack | 17 |
|
25
|
+
When I query "age" matching "17"
|
26
|
+
Then I should find records named "Jane, Jack"
|
27
|
+
|
28
|
+
Scenario: Query for Page 1
|
29
|
+
Given 3 indexed records
|
30
|
+
When I query page 1 at 2 per page
|
31
|
+
Then I should find 2 records
|
32
|
+
|
33
|
+
Scenario: Query for Page 2
|
34
|
+
Given 3 indexed records
|
35
|
+
When I query page 2 at 2 per page
|
36
|
+
Then I should find 1 record
|
37
|
+
And I should have 3 records total
|
38
|
+
|
39
|
+
Scenario: Query for One Facet
|
40
|
+
Given the following indexed records
|
41
|
+
| name | age |
|
42
|
+
| John | 23 |
|
43
|
+
| Jane | 17 |
|
44
|
+
| Jack | 17 |
|
45
|
+
When I query facets "0c93ee1"
|
46
|
+
Then I should find records named "Jane, Jack"
|
47
|
+
|
48
|
+
|
49
|
+
Scenario: Query for Two Facets
|
50
|
+
Given the following indexed records
|
51
|
+
| name | age |
|
52
|
+
| John | 23 |
|
53
|
+
| Jane | 17 |
|
54
|
+
| Jack | 17 |
|
55
|
+
When I query facets "0c93ee1-078661c"
|
56
|
+
Then I should find records named "Jane"
|
57
|
+
|
58
|
+
Scenario: Query for All Records Class Agnostic
|
59
|
+
Given indexed records named "John, Jane"
|
60
|
+
When I query for "John" on Xapit
|
61
|
+
Then I should find 1 record
|
62
|
+
|
63
|
+
Scenario: Query Matching Or Query
|
64
|
+
Given indexed records named "John, Jane, Jacob"
|
65
|
+
When I query for "Jane OR John"
|
66
|
+
Then I should find records named "John, Jane"
|
67
|
+
|
68
|
+
Scenario: Query Matching Not Query
|
69
|
+
Given indexed records named "John Smith, John Johnson"
|
70
|
+
When I query for "John NOT Smith"
|
71
|
+
Then I should find records named "John Johnson"
|
72
|
+
|
73
|
+
Scenario: Query for Facets with Keywords
|
74
|
+
Given the following indexed records
|
75
|
+
| name | age |
|
76
|
+
| John | 23 |
|
77
|
+
| Jane | 17 |
|
78
|
+
| Jack | 17 |
|
79
|
+
When I query "Jane" with facets "0c93ee1"
|
80
|
+
Then I should find record named "Jane"
|
81
|
+
|
82
|
+
Scenario: Query for All Records Sorted by Name
|
83
|
+
Given indexed records named "Zebra, Apple, Banana"
|
84
|
+
When I query "" sorted by name
|
85
|
+
Then I should find records named "Apple, Banana, Zebra"
|
86
|
+
|
87
|
+
Scenario: Query for All Records Sorted by Age then Name
|
88
|
+
Given the following indexed records
|
89
|
+
| name | age |
|
90
|
+
| Banana | 23 |
|
91
|
+
| Zebra | 17 |
|
92
|
+
| Apple | 17 |
|
93
|
+
When I query "" sorted by age, name
|
94
|
+
Then I should find records named "Apple, Zebra, Banana"
|
95
|
+
|
96
|
+
Scenario: Query for All Records Sorted by Name Descending
|
97
|
+
Given indexed records named "Zebra, Apple, Banana"
|
98
|
+
When I query "" sorted by name descending
|
99
|
+
Then I should find records named "Zebra, Banana, Apple"
|
100
|
+
|
101
|
+
Scenario: Spelling suggestion
|
102
|
+
Given indexed records named "Zebra, Apple, Bike"
|
103
|
+
When I query for "zerba bike aple"
|
104
|
+
Then I should have "zebra bike apple" as a spelling suggestion
|
105
|
+
|
106
|
+
Scenario: Match similar words with stemming
|
107
|
+
Given indexed records named "flies, fly, glider"
|
108
|
+
When I query for "flying"
|
109
|
+
Then I should find records named "flies, fly"
|
110
|
+
|
111
|
+
Scenario: Find similar records
|
112
|
+
Given indexed records named "Jason John Smith, John Doe, Jason Smith, Jacob Johnson"
|
113
|
+
When I query for similar records for "Jason John Smith"
|
114
|
+
Then I should find records named "Jason Smith, John Doe"
|
115
|
+
|
116
|
+
Scenario: Unicode characters in search
|
117
|
+
Given indexed records named "über cool, uber hot"
|
118
|
+
When I query for "über"
|
119
|
+
Then I should find records named "über cool"
|
@@ -0,0 +1,41 @@
|
|
1
|
+
Scenario: Save xapian database on index
|
2
|
+
Given no file exists at "tmp/xapiandb"
|
3
|
+
And I configured the database to be saved at "tmp/xapiandb"
|
4
|
+
And 3 records
|
5
|
+
When I index the database
|
6
|
+
Then I should find a directory at "tmp/xapiandb"
|
7
|
+
|
8
|
+
Scenario: Fetch all records which are indexed
|
9
|
+
Given an empty database at "tmp/xapiandb"
|
10
|
+
And records named "John, Jane, Joe"
|
11
|
+
When I index the database
|
12
|
+
And I query for ""
|
13
|
+
Then I should find records named "John, Jane, Joe"
|
14
|
+
|
15
|
+
Scenario: Split indexed text fields differently
|
16
|
+
Given an empty database at "tmp/xapiandb"
|
17
|
+
And records named "JohnXSmith, JaneXSmith, JoeXBlack"
|
18
|
+
When I index the database splitting name by "X"
|
19
|
+
And I query for "Smith"
|
20
|
+
Then I should find records named "JohnXSmith, JaneXSmith"
|
21
|
+
|
22
|
+
Scenario: Index Multiple Field Values Separately
|
23
|
+
Given an empty database at "tmp/xapiandatabase"
|
24
|
+
And the following indexed records
|
25
|
+
| name | age |
|
26
|
+
| John | 17, 16 |
|
27
|
+
| Jack | 17 |
|
28
|
+
| Jane | 16 |
|
29
|
+
When I query "age" matching "16"
|
30
|
+
Then I should find records named "Jane, John"
|
31
|
+
|
32
|
+
Scenario: Index Weighted Attributes
|
33
|
+
Given an empty database at "tmp/xapiandatabase"
|
34
|
+
And the following indexed records with "name" weighted by "10"
|
35
|
+
| name | description |
|
36
|
+
| foo | bar |
|
37
|
+
| bar | foo |
|
38
|
+
When I query for "bar"
|
39
|
+
Then I should find records named "bar, foo"
|
40
|
+
When I query for "foo"
|
41
|
+
Then I should find records named "foo, bar"
|
@@ -0,0 +1,7 @@
|
|
1
|
+
Given /^no file exists at "([^\"]*)"$/ do |path|
|
2
|
+
FileUtils.rm_rf(File.dirname(__FILE__) + "/../../#{path}") if File.exist? path
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /^I should find a directory at "([^\"]*)"$/ do |path|
|
6
|
+
File.exist?(File.dirname(__FILE__) + "/../../#{path}").should be_true
|
7
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
Given /^I configured the database to be saved at "([^\"]*)"$/ do |path|
|
2
|
+
Xapit::Config.setup(:database_path => File.dirname(__FILE__) + "/../../#{path}")
|
3
|
+
end
|
4
|
+
|
5
|
+
Given /^an empty database at "([^\"]*)"$/ do |path|
|
6
|
+
Xapit::Config.setup(:database_path => File.dirname(__FILE__) + "/../../#{path}")
|
7
|
+
Xapit::Config.remove_database
|
8
|
+
XapitMember.delete_all
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /^(indexed )?records? named "([^\"]*)"$/ do |indexed, joined_names|
|
12
|
+
records = joined_names.split(', ').map { |name| {:name => name} }
|
13
|
+
create_records(records, indexed)
|
14
|
+
end
|
15
|
+
|
16
|
+
Given /^([0-9]+) (indexed )?records?$/ do |num, indexed|
|
17
|
+
create_records([:name => "foo"]*num.to_i, indexed)
|
18
|
+
end
|
19
|
+
|
20
|
+
Given /^the following indexed records$/ do |records_table|
|
21
|
+
create_records(records_table.hashes)
|
22
|
+
end
|
23
|
+
|
24
|
+
Given /^the following indexed records with "([^\"]*)" weighted by "([^\"]*)"$/ do |weight_name, weight_value, records_table|
|
25
|
+
create_records(records_table.hashes) do |index, attribute|
|
26
|
+
if attribute.to_s == weight_name
|
27
|
+
index.text attribute, :weight => weight_value.to_i
|
28
|
+
else
|
29
|
+
index.text attribute
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
When /^I index the database$/ do
|
35
|
+
Xapit.index_all
|
36
|
+
end
|
37
|
+
|
38
|
+
When /^I index the database splitting name by "([^\"]*)"$/ do |divider|
|
39
|
+
XapitMember.xapit do |index|
|
40
|
+
index.text(:name) { |name| name.split(divider) }
|
41
|
+
end
|
42
|
+
Xapit.index_all
|
43
|
+
end
|
44
|
+
|
45
|
+
When /^I query for "([^\"]*)"$/ do |query|
|
46
|
+
@records = XapitMember.search(query)
|
47
|
+
end
|
48
|
+
|
49
|
+
When /^I query for "([^\"]*)" on Xapit$/ do |query|
|
50
|
+
@records = Xapit.search(query)
|
51
|
+
end
|
52
|
+
|
53
|
+
When /^I query "([^\"]*)" with facets "([^\"]*)"$/ do |keywords, facets|
|
54
|
+
@records = XapitMember.search(keywords, :facets => facets)
|
55
|
+
end
|
56
|
+
|
57
|
+
Then /^I should find records? named "([^\"]*)"$/ do |joined_names|
|
58
|
+
@records.map(&:name).join(", ").should == joined_names
|
59
|
+
end
|
60
|
+
|
61
|
+
Then /^I should find ([0-9]+) records?$/ do |num|
|
62
|
+
@records.should have(num.to_i).records
|
63
|
+
end
|
64
|
+
|
65
|
+
Then /^I should have ([0-9]+) records? total$/ do |num|
|
66
|
+
@records.total_entries.should == num.to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
When /^I query "([^\"]*)" matching "([^\"]*)"$/ do |field, value|
|
70
|
+
@records = XapitMember.search("", :conditions => { field.to_sym => value })
|
71
|
+
end
|
72
|
+
|
73
|
+
When /^I query page ([0-9]+) at ([0-9]+) per page$/ do |page, per_page|
|
74
|
+
@records = XapitMember.search("", :page => page, :per_page => per_page.to_i)
|
75
|
+
end
|
76
|
+
|
77
|
+
When /^I query facets "([^\"]*)"$/ do |facets|
|
78
|
+
@records = XapitMember.search("", :facets => facets)
|
79
|
+
end
|
80
|
+
|
81
|
+
When /^I query "([^\"]*)" sorted by (.*?)( descending)?$/ do |keywords, sort, descending|
|
82
|
+
@records = XapitMember.search("", :order => sort.split(', '), :descending => descending)
|
83
|
+
end
|
84
|
+
|
85
|
+
When /^I query for similar records for "([^\"]*)"$/ do |keywords|
|
86
|
+
@records = XapitMember.search(keywords).first.search_similar
|
87
|
+
end
|
88
|
+
|
89
|
+
Then /^I should have the following facets$/ do |facets_table|
|
90
|
+
result = []
|
91
|
+
@records.facets.each do |facet|
|
92
|
+
facet.options.each do |option|
|
93
|
+
result << {
|
94
|
+
"facet" => facet.name,
|
95
|
+
"option" => option.name,
|
96
|
+
"count" => option.count.to_s
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
result.should == facets_table.hashes # this is somewhat fragile because it depends on order of hash result
|
101
|
+
end
|
102
|
+
|
103
|
+
Then /^I should have the following applied facets$/ do |facets_table|
|
104
|
+
result = []
|
105
|
+
@records.applied_facet_options.each do |option|
|
106
|
+
result << {
|
107
|
+
"facet" => option.facet.name,
|
108
|
+
"option" => option.name
|
109
|
+
}
|
110
|
+
end
|
111
|
+
result.should == facets_table.hashes # this is somewhat fragile because it depends on order of hash result
|
112
|
+
end
|
113
|
+
|
114
|
+
Then /^I should have "([^\"]*)" as a spelling suggestion$/ do |term|
|
115
|
+
@records.spelling_suggestion.should == term
|
116
|
+
end
|
117
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module XapitHelpers
|
2
|
+
def create_records(records, perform_index = true)
|
3
|
+
Xapit::Config.remove_database
|
4
|
+
XapitMember.delete_all
|
5
|
+
XapitMember.xapit do |index|
|
6
|
+
records.first.keys.each do |attribute|
|
7
|
+
if block_given?
|
8
|
+
yield(index, attribute)
|
9
|
+
else
|
10
|
+
index.text attribute
|
11
|
+
index.field attribute
|
12
|
+
index.facet attribute
|
13
|
+
index.sortable attribute
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
records.each do |attributes|
|
18
|
+
attributes.each do |key, value|
|
19
|
+
attributes[key] = value.split(', ') if value.include? ', '
|
20
|
+
end
|
21
|
+
XapitMember.new(attributes.symbolize_keys)
|
22
|
+
end
|
23
|
+
Xapit.index_all if perform_index
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
World(XapitHelpers)
|
data/init.rb
ADDED
data/install.rb
ADDED
data/lib/xapit.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
require 'rubygems'
|
3
|
+
require 'xapian'
|
4
|
+
|
5
|
+
# Looking for more documentation? A good place to start is Xapit::Membership
|
6
|
+
module Xapit
|
7
|
+
|
8
|
+
# Index all membership classes with xapit defined. Delegates to Xapit::IndexBlueprint.
|
9
|
+
# You will likely want to call Xapit::Config.remove_database before this.
|
10
|
+
def self.index_all(*args, &block)
|
11
|
+
IndexBlueprint.index_all(*args, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Used to perform a search on all indexed models. The returned collection can
|
15
|
+
# contain instances of different classes which were indexed.
|
16
|
+
#
|
17
|
+
# # perform a simple full text search
|
18
|
+
# @records = Xapit.search("phone")
|
19
|
+
#
|
20
|
+
# See Xapit::Membership for details on search options.
|
21
|
+
def self.search(*args)
|
22
|
+
Collection.new(nil, *args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require File.dirname(__FILE__) + '/xapit/membership'
|
27
|
+
require File.dirname(__FILE__) + '/xapit/index_blueprint'
|
28
|
+
require File.dirname(__FILE__) + '/xapit/collection'
|
29
|
+
require File.dirname(__FILE__) + '/xapit/config'
|
30
|
+
require File.dirname(__FILE__) + '/xapit/facet_blueprint'
|
31
|
+
require File.dirname(__FILE__) + '/xapit/facet'
|
32
|
+
require File.dirname(__FILE__) + '/xapit/facet_option'
|
33
|
+
require File.dirname(__FILE__) + '/xapit/query'
|
34
|
+
require File.dirname(__FILE__) + '/xapit/query_parsers/abstract_query_parser'
|
35
|
+
require File.dirname(__FILE__) + '/xapit/query_parsers/simple_query_parser'
|
36
|
+
require File.dirname(__FILE__) + '/xapit/query_parsers/classic_query_parser'
|
37
|
+
require File.dirname(__FILE__) + '/xapit/indexers/abstract_indexer'
|
38
|
+
require File.dirname(__FILE__) + '/xapit/indexers/simple_indexer'
|
39
|
+
require File.dirname(__FILE__) + '/xapit/indexers/classic_indexer'
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Xapit
|
2
|
+
# This is the object which is returned when performing a search. It behaves like an array, so you do not need
|
3
|
+
# to worry about fetching the results separately. Just loop through this collection.
|
4
|
+
#
|
5
|
+
# The results are lazy loading, meaning it does not perform the query on the database until it has to.
|
6
|
+
# This allows you to string queries onto one another.
|
7
|
+
#
|
8
|
+
# Article.search("kite").search("sky") # only performs one query
|
9
|
+
#
|
10
|
+
# This class is compatible with will_paginate; you can pass it to the will_paginate helper in the view.
|
11
|
+
class Collection
|
12
|
+
NON_DELEGATE_METHODS = %w(nil? send object_id class extend size paginate first last empty? respond_to?).to_set
|
13
|
+
[].methods.each do |m|
|
14
|
+
delegate m, :to => :results unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.search_similar(member, *args)
|
18
|
+
collection = new(member.class, *args)
|
19
|
+
indexer = SimpleIndexer.new(member.class.xapit_index_blueprint)
|
20
|
+
terms = indexer.text_terms(member) + indexer.field_terms(member)
|
21
|
+
collection.base_query.and_query(Xapian::Query.new(Xapian::Query::OP_OR, terms))
|
22
|
+
collection.base_query.not_query("Q#{member.class}-#{member.id}")
|
23
|
+
collection
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(*args)
|
27
|
+
@query_parser = Config.query_parser.new(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns an array of results. You should not need to call this directly because most methods are
|
31
|
+
# automatically delegated to this array.
|
32
|
+
def results
|
33
|
+
@results ||= fetch_results
|
34
|
+
end
|
35
|
+
|
36
|
+
# The number of total records found despite any pagination settings.
|
37
|
+
def size
|
38
|
+
@query_parser.query.count
|
39
|
+
end
|
40
|
+
alias_method :total_entries, :size
|
41
|
+
|
42
|
+
# Returns true if no results are found.
|
43
|
+
def empty?
|
44
|
+
@results ? @results.empty? : size.zero?
|
45
|
+
end
|
46
|
+
|
47
|
+
# The first record in the result set.
|
48
|
+
def first
|
49
|
+
fetch_results(:offset => 0, :limit => 1).first
|
50
|
+
end
|
51
|
+
|
52
|
+
# The last record in the result set.
|
53
|
+
def last
|
54
|
+
fetch_results(:offset => size-1, :limit => 1).last
|
55
|
+
end
|
56
|
+
|
57
|
+
# Perform another search on this one, inheriting all options already passed.
|
58
|
+
# See Xapit::Membership for search options.
|
59
|
+
def search(keywords, options = {})
|
60
|
+
collection = Collection.new(@query_parser.member_class, keywords, options)
|
61
|
+
collection.base_query = @query_parser.query
|
62
|
+
collection
|
63
|
+
end
|
64
|
+
|
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
|
71
|
+
end
|
72
|
+
|
73
|
+
# The page number we are currently on.
|
74
|
+
def current_page
|
75
|
+
@query_parser.current_page
|
76
|
+
end
|
77
|
+
|
78
|
+
# How many records to display on each page, defaults to 20. Sets with :per_page option when performing search.
|
79
|
+
def per_page
|
80
|
+
@query_parser.per_page
|
81
|
+
end
|
82
|
+
|
83
|
+
# The offset for the current page
|
84
|
+
def offset
|
85
|
+
@query_parser.offset
|
86
|
+
end
|
87
|
+
|
88
|
+
# Total number of pages with found results.
|
89
|
+
def total_pages
|
90
|
+
(total_entries / per_page.to_f).ceil
|
91
|
+
end
|
92
|
+
|
93
|
+
# The previous page number. Returns nil if on first page.
|
94
|
+
def previous_page
|
95
|
+
current_page > 1 ? (current_page - 1) : nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# The next page number. Returns nil if on last page.
|
99
|
+
def next_page
|
100
|
+
current_page < total_pages ? (current_page + 1): nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# Xapit::Facet objects matching this search query. See class for details.
|
104
|
+
def facets
|
105
|
+
all_facets.select do |facet|
|
106
|
+
facet.options.size > 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Xapit::FacetOption objects which are currently applied to search (through :facets option). Use this to
|
111
|
+
# display the facets which are currently applied.
|
112
|
+
#
|
113
|
+
# <% for option in @articles.applied_facet_options %>
|
114
|
+
# <%=h option.name %>
|
115
|
+
# <%= link_to "remove", :overwrite_params => { :facets => option } %>
|
116
|
+
# <% end %>
|
117
|
+
#
|
118
|
+
# If you set :breadcrumb_facets option to true in Config#setup the link will drop leftover facets
|
119
|
+
# instead of removing the current one. This makes it easy to add a breadcrumb style interface.
|
120
|
+
#
|
121
|
+
# Config.setup(:breadcrumb_facets => true)
|
122
|
+
# <% for option in @articles.applied_facet_options %>
|
123
|
+
# <%= link_to h(option.name), :overwrite_params => { :facets => option } %> >
|
124
|
+
# <% end %>
|
125
|
+
#
|
126
|
+
def applied_facet_options
|
127
|
+
@query_parser.facet_identifiers.map do |identifier|
|
128
|
+
option = FacetOption.find(identifier)
|
129
|
+
option.existing_facet_identifiers = @query_parser.facet_identifiers
|
130
|
+
option
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Includes a suggested variation of a term which will get many more results. Returns nil if no suggestion.
|
135
|
+
#
|
136
|
+
# <% if @articles.spelling_suggestion %>
|
137
|
+
# Did you mean <%= link_to h(@articles.spelling_suggestion), :overwrite_params => { :keywords => @articles.spelling_suggestion } %>?
|
138
|
+
# <% end %>
|
139
|
+
#
|
140
|
+
def spelling_suggestion
|
141
|
+
@query_parser.spelling_suggestion
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def all_facets
|
147
|
+
@query_parser.member_class.xapit_index_blueprint.facets.map do |facet_blueprint|
|
148
|
+
Facet.new(facet_blueprint, @query_parser.query, @query_parser.facet_identifiers)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def matchset(options = {})
|
153
|
+
@query_parser.query.matchset(options)
|
154
|
+
end
|
155
|
+
|
156
|
+
def fetch_results(options = {})
|
157
|
+
matchset(options).matches.map do |match|
|
158
|
+
class_name, id = match.document.data.split('-')
|
159
|
+
member = class_name.constantize.find(id)
|
160
|
+
member.xapit_relevance = match.percent
|
161
|
+
member
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|