xapian_db 0.3.1 → 0.3.2
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.md +33 -0
- data/LICENSE +20 -0
- data/README.rdoc +168 -0
- data/lib/xapian_db/adapters/active_record_adapter.rb +30 -30
- data/lib/xapian_db/adapters/datamapper_adapter.rb +29 -18
- data/lib/xapian_db/adapters/generic_adapter.rb +21 -12
- data/lib/xapian_db/config.rb +63 -30
- data/lib/xapian_db/database.rb +61 -28
- data/lib/xapian_db/document_blueprint.rb +90 -38
- data/lib/xapian_db/index_writers/direct_writer.rb +26 -14
- data/lib/xapian_db/indexer.rb +51 -20
- data/lib/xapian_db/query_parser.rb +17 -14
- data/lib/xapian_db/railtie.rb +8 -7
- data/lib/xapian_db/resultset.rb +33 -22
- data/lib/xapian_db.rb +35 -16
- metadata +38 -21
- data/CHANGELOG +0 -15
- data/examples/basic.rb +0 -59
data/lib/xapian_db/database.rb
CHANGED
@@ -1,50 +1,69 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# Singleton class representing a Xapian database.
|
4
3
|
# @author Gernot Kogler
|
5
4
|
|
6
5
|
module XapianDb
|
7
6
|
|
8
|
-
# Base class for a Xapian database
|
7
|
+
# Base class for a Xapian database
|
9
8
|
class Database
|
10
|
-
|
11
|
-
|
9
|
+
|
10
|
+
# A readable xapian database (see http://xapian.org/docs/apidoc/html/classXapian_1_1Database.html)
|
11
|
+
attr_reader :reader
|
12
|
+
|
12
13
|
# Size of the database (number of docs)
|
14
|
+
# @return [Integer] The number of docs in the database
|
13
15
|
def size
|
14
16
|
reader.doccount
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
# Store a Xapian document
|
20
|
+
# @param [Xapian::Document] doc A Xapian document (see http://xapian.org/docs/sourcedoc/html/classXapian_1_1Document.html).
|
21
|
+
# While you can pass any valid xapian document, you might want to use the {XapianDb::Indexer} to build a xapian doc
|
18
22
|
def store_doc(doc)
|
19
23
|
# We always replace; Xapian adds the document automatically if
|
20
24
|
# it is not found
|
21
25
|
writer.replace_document("Q#{doc.data}", doc)
|
22
26
|
end
|
23
27
|
|
24
|
-
# Delete a document by a unique term; this method is used by the
|
28
|
+
# Delete a document identified by a unique term; this method is used by the
|
25
29
|
# orm adapters
|
30
|
+
# @param [String] term A term that uniquely identifies a document
|
26
31
|
def delete_doc_with_unique_term(term)
|
27
32
|
writer.delete_document("Q#{term}")
|
28
33
|
true
|
29
34
|
end
|
30
35
|
|
31
|
-
# Delete all docs of a specific class
|
36
|
+
# Delete all docs of a specific class
|
37
|
+
# @param [Class] klass A class that has a {XapianDb::DocumentBlueprint} configuration
|
32
38
|
def delete_docs_of_class(klass)
|
33
39
|
writer.delete_document("C#{klass}")
|
34
40
|
true
|
35
41
|
end
|
36
|
-
|
42
|
+
|
37
43
|
# Perform a search
|
38
|
-
|
44
|
+
# @param [String] expression A valid search expression.
|
45
|
+
# @param [Hash] options
|
46
|
+
# @option options [Integer] :per_page (10) How many docs per page?
|
47
|
+
# @example Simple Query
|
48
|
+
# resultset = db.search("foo")
|
49
|
+
# @example Wildcard Query
|
50
|
+
# resultset = db.search("fo*")
|
51
|
+
# @example Boolean Query
|
52
|
+
# resultset = db.search("foo or baz")
|
53
|
+
# @example Field Query
|
54
|
+
# resultset = db.search("name:foo")
|
55
|
+
# @return [XapianDb::Resultset] The resultset
|
56
|
+
def search(expression, options={})
|
57
|
+
opts = {:per_page => 10}.merge(options)
|
39
58
|
@query_parser ||= QueryParser.new(self)
|
40
|
-
query
|
41
|
-
enquiry
|
59
|
+
query = @query_parser.parse(expression)
|
60
|
+
enquiry = Xapian::Enquire.new(reader)
|
42
61
|
enquiry.query = query
|
43
|
-
Resultset.new(enquiry)
|
62
|
+
Resultset.new(enquiry, opts)
|
44
63
|
end
|
45
|
-
|
64
|
+
|
46
65
|
end
|
47
|
-
|
66
|
+
|
48
67
|
# In Memory database
|
49
68
|
class InMemoryDatabase < Database
|
50
69
|
|
@@ -52,51 +71,65 @@ module XapianDb
|
|
52
71
|
@writer ||= Xapian::inmemory_open
|
53
72
|
@reader = @writer
|
54
73
|
end
|
55
|
-
|
74
|
+
|
75
|
+
# Get the writer to write to the database
|
76
|
+
# @return [Xapian::WritableDatabase] A xapian database that is writable (see http://xapian.org/docs/apidoc/html/classXapian_1_1WritableDatabase.html)
|
56
77
|
def writer
|
57
78
|
@writer
|
58
79
|
end
|
59
80
|
|
60
|
-
# Commit all pending changes
|
81
|
+
# Commit all pending changes
|
61
82
|
def commit
|
62
83
|
# Nothing to do for an in memory database
|
63
84
|
end
|
64
|
-
|
85
|
+
|
65
86
|
end
|
66
87
|
|
67
88
|
# Persistent database on disk
|
68
89
|
class PersistentDatabase < Database
|
69
|
-
|
90
|
+
|
91
|
+
# Constructor
|
92
|
+
# @param [Hash] options Options for the persistent database
|
93
|
+
# @option options [String] :path A path to the file system
|
94
|
+
# @option options [Boolean] :create Should the database be created? <b>Will overwrite an existing database if true!</b>
|
95
|
+
# @example Force the creation of a database. Will overwrite an existing database
|
96
|
+
# db = XapianDb::PersistentDatabase.new(:path => "/tmp/mydb", :create => true)
|
97
|
+
# @example Open an existing database. The database must exist
|
98
|
+
# db = XapianDb::PersistentDatabase.new(:path => "/tmp/mydb", :create => false)
|
70
99
|
def initialize(options)
|
71
100
|
@path = options[:path]
|
72
101
|
@db_flag = options[:create] ? Xapian::DB_CREATE_OR_OVERWRITE : Xapian::DB_OPEN
|
73
102
|
if options[:create]
|
74
|
-
# make sure the path exists; Xapian will not create the necessary directories
|
103
|
+
# make sure the path exists; Xapian will not create the necessary directories
|
75
104
|
FileUtils.makedirs @path
|
76
105
|
@writer = Xapian::WritableDatabase.new(@path, @db_flag)
|
77
106
|
end
|
78
107
|
@reader = Xapian::Database.new(@path)
|
79
108
|
end
|
80
|
-
|
81
|
-
# Get the readable instance of the database
|
109
|
+
|
110
|
+
# Get the readable instance of the database. On each access this method reopens the readable database
|
111
|
+
# to make sure you get the latest changes to the index
|
112
|
+
# @return [Xapian::Database] A readable xapian database (see http://xapian.org/docs/apidoc/html/classXapian_1_1Database.html)
|
82
113
|
def reader
|
83
114
|
# Always reopen the readable database so we get live index data
|
84
115
|
# TODO: make this configurable
|
85
116
|
@reader.reopen
|
86
117
|
@reader
|
87
118
|
end
|
88
|
-
|
89
|
-
# The writer is instantiated layzily to avoid a permanent write lock on the database
|
119
|
+
|
120
|
+
# The writer is instantiated layzily to avoid a permanent write lock on the database. Please note that
|
121
|
+
# you will get locking exceptions if you open the same database multiple times and access the writer
|
122
|
+
# in more than one instance!
|
123
|
+
# @return [Xapian::WritableDatabase] A xapian database that is writable (see http://xapian.org/docs/apidoc/html/classXapian_1_1WritableDatabase.html)
|
90
124
|
def writer
|
91
125
|
@writer ||= Xapian::WritableDatabase.new(@path, @db_flag)
|
92
126
|
end
|
93
|
-
|
94
|
-
# Commit all pending changes
|
127
|
+
|
128
|
+
# Commit all pending changes
|
95
129
|
def commit
|
96
130
|
writer.commit
|
97
|
-
reader.reopen
|
98
131
|
end
|
99
|
-
|
132
|
+
|
100
133
|
end
|
101
|
-
|
134
|
+
|
102
135
|
end
|
@@ -1,19 +1,32 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# A document blueprint describes the mapping of an object to a Xapian document
|
4
|
-
# for a given class.
|
5
|
-
# @author Gernot Kogler
|
6
|
-
|
7
3
|
module XapianDb
|
8
|
-
|
4
|
+
|
5
|
+
# A document blueprint describes the mapping of an object to a Xapian document
|
6
|
+
# for a given class.
|
7
|
+
# @example A simple document blueprint configuration for the class Person
|
8
|
+
# XapianDb::DocumentBlueprint.setup(Person) do |blueprint|
|
9
|
+
# # Our Person class has a method lang_cd. We use this method to
|
10
|
+
# # index each person with its language
|
11
|
+
# blueprint.language_method :lang_cd
|
12
|
+
# blueprint.attribute :name, :weight => 10
|
13
|
+
# blueprint.attribute :first_name
|
14
|
+
# blueprint.index :remarks
|
15
|
+
# end
|
16
|
+
# @author Gernot Kogler
|
9
17
|
class DocumentBlueprint
|
10
18
|
|
11
|
-
# ---------------------------------------------------------------------------------
|
19
|
+
# ---------------------------------------------------------------------------------
|
12
20
|
# Singleton methods
|
13
|
-
# ---------------------------------------------------------------------------------
|
21
|
+
# ---------------------------------------------------------------------------------
|
14
22
|
class << self
|
15
23
|
|
16
|
-
# Configure the blueprint for a class
|
24
|
+
# Configure the blueprint for a class.
|
25
|
+
# Available options:
|
26
|
+
# - language_method (see {#language_method} for details)
|
27
|
+
# - adapter (see {#adapter} for details)
|
28
|
+
# - attribute (see {#attribute} for details)
|
29
|
+
# - index (see {#index} for details)
|
17
30
|
def setup(klass, &block)
|
18
31
|
@blueprints ||= {}
|
19
32
|
blueprint = DocumentBlueprint.new
|
@@ -24,46 +37,54 @@ module XapianDb
|
|
24
37
|
@adapter.add_class_helper_methods_to klass
|
25
38
|
@searchable_prefixes = nil # force rebuild of the searchable prefixes
|
26
39
|
end
|
27
|
-
|
40
|
+
|
28
41
|
# Get the blueprint for a class
|
42
|
+
# @return [DocumentBlueprint]
|
29
43
|
def blueprint_for(klass)
|
30
44
|
@blueprints[klass] if @blueprints
|
31
45
|
end
|
32
46
|
|
33
47
|
# Return an array of all configured text methods in any blueprint
|
48
|
+
# @return [Array<String>] All searchable prefixes
|
34
49
|
def searchable_prefixes
|
35
50
|
return [] unless @blueprints
|
36
51
|
return @searchable_prefixes unless @searchable_prefixes.nil?
|
37
52
|
prefixes = []
|
38
|
-
@blueprints.each do |
|
53
|
+
@blueprints.values.each do |blueprint|
|
39
54
|
prefixes << blueprint.searchable_prefixes
|
40
55
|
end
|
41
56
|
@searchable_prefixes = prefixes.flatten.compact.uniq
|
42
57
|
end
|
43
|
-
|
58
|
+
|
44
59
|
end
|
45
60
|
|
46
|
-
# ---------------------------------------------------------------------------------
|
61
|
+
# ---------------------------------------------------------------------------------
|
47
62
|
# Instance methods
|
48
|
-
# ---------------------------------------------------------------------------------
|
63
|
+
# ---------------------------------------------------------------------------------
|
64
|
+
|
65
|
+
# Set / get the indexer
|
66
|
+
# @return [XapianDb::Indexer]
|
49
67
|
attr_accessor :indexer
|
50
|
-
|
68
|
+
|
51
69
|
# Return an array of all configured text methods in this blueprint
|
70
|
+
# @return [Array<String>] All searchable prefixes
|
52
71
|
def searchable_prefixes
|
53
|
-
@prefixes ||= indexed_methods.
|
72
|
+
@prefixes ||= indexed_methods.keys
|
54
73
|
end
|
55
|
-
|
74
|
+
|
56
75
|
# Lazily build and return a module that implements accessors for each field
|
76
|
+
# @return [Module] A module containing all accessor methods
|
57
77
|
def accessors_module
|
58
78
|
return @accessors_module unless @accessors_module.nil?
|
59
79
|
@accessors_module = Module.new
|
60
|
-
|
80
|
+
|
81
|
+
# Add the accessor for the indexed class
|
61
82
|
@accessors_module.instance_eval do
|
62
83
|
define_method :domain_class do
|
63
84
|
self.values[0].value
|
64
85
|
end
|
65
86
|
end
|
66
|
-
|
87
|
+
|
67
88
|
@attributes.each_with_index do |field, index|
|
68
89
|
@accessors_module.instance_eval do
|
69
90
|
define_method field do
|
@@ -72,50 +93,81 @@ module XapianDb
|
|
72
93
|
end
|
73
94
|
end
|
74
95
|
# Let the adapter add its document helper methods (if any)
|
75
|
-
adapter = XapianDb::Config.adapter || XapianDb::Adapters::GenericAdapter
|
96
|
+
adapter = @adapter || XapianDb::Config.adapter || XapianDb::Adapters::GenericAdapter
|
76
97
|
adapter.add_doc_helper_methods_to(@accessors_module)
|
77
98
|
@accessors_module
|
78
99
|
end
|
79
|
-
|
80
|
-
# ---------------------------------------------------------------------------------
|
100
|
+
|
101
|
+
# ---------------------------------------------------------------------------------
|
81
102
|
# Blueprint DSL methods
|
82
|
-
# ---------------------------------------------------------------------------------
|
83
|
-
|
84
|
-
|
103
|
+
# ---------------------------------------------------------------------------------
|
104
|
+
|
105
|
+
# The name of the method that returns a Xapian compliant language code. The
|
106
|
+
# configured class must implement this method.
|
107
|
+
attr_reader :lang_method
|
108
|
+
|
109
|
+
# Collection of the configured attribute methods
|
110
|
+
# @return [Array<Symbol>] The names of the configured attribute methods
|
111
|
+
attr_reader :attributes
|
112
|
+
|
113
|
+
# Collection of the configured index methods
|
114
|
+
# @return [Hash<Symbol, IndexOptions>] A hashtable containing all index methods as
|
115
|
+
# keys and IndexOptions as values
|
116
|
+
attr_reader :indexed_methods
|
117
|
+
|
118
|
+
# Set / read a custom adapter.
|
119
|
+
# Use this configuration option if you need a specific adapter for an indexed class.
|
120
|
+
# If set, it overrides the globally configured adapter (see also {Config#adapter})
|
121
|
+
attr_accessor :adapter
|
122
|
+
|
85
123
|
# Construct the blueprint
|
86
124
|
def initialize
|
87
125
|
@attributes = []
|
88
126
|
@indexed_methods = {}
|
89
127
|
end
|
90
|
-
|
91
|
-
# Set
|
92
|
-
|
93
|
-
|
128
|
+
|
129
|
+
# Set the name of the method to get the language for an indexed object
|
130
|
+
# @param [Symbol] lang The method name. The method must return a language supported
|
131
|
+
# by Xapian (see http://xapian.org/docs/apidoc/html/classXapian_1_1Stem.html for supported languages)
|
132
|
+
def language_method(lang)
|
133
|
+
@lang_method = lang
|
94
134
|
end
|
95
|
-
|
96
|
-
# Add an attribute to the
|
97
|
-
#
|
98
|
-
#
|
135
|
+
|
136
|
+
# Add an attribute to the blueprint. Attributes will be stored in the xapian documents an can be
|
137
|
+
# accessed from a search result.
|
138
|
+
# @param [String] name The name of the method that delivers the value for the attribute
|
139
|
+
# @param [Hash] options
|
140
|
+
# @option options [Integer] :weight (1) The weight for this attribute.
|
141
|
+
# @option options [Boolean] :index (true) Should the attribute be indexed?
|
142
|
+
# @todo Make sure the name does not collide with a method name of Xapian::Document since
|
99
143
|
def attribute(name, options={})
|
100
144
|
opts = {:index => true}.merge(options)
|
101
145
|
@attributes << name
|
102
146
|
self.index(name, opts) if opts[:index]
|
103
147
|
end
|
104
148
|
|
105
|
-
# Add an indexed value to the
|
149
|
+
# Add an indexed value to the blueprint. Indexed values are not accessible from a search result.
|
150
|
+
# @param [String] name The name of the method that delivers the value for the index
|
151
|
+
# @param [Hash] options
|
152
|
+
# @option options [Integer] :weight (1) The weight for this indexed value
|
106
153
|
def index(name, options={})
|
107
154
|
@indexed_methods[name] = IndexOptions.new(options)
|
108
155
|
end
|
109
156
|
|
110
|
-
# Options for an indexed
|
111
|
-
class IndexOptions
|
157
|
+
# Options for an indexed method
|
158
|
+
class IndexOptions
|
159
|
+
|
160
|
+
# The weight for the indexed value
|
112
161
|
attr_accessor :weight
|
113
|
-
|
162
|
+
|
163
|
+
# Constructor
|
164
|
+
# @param [Hash] options
|
165
|
+
# @option options [Integer] :weight (1) The weight for the indexed value
|
114
166
|
def initialize(options)
|
115
167
|
@weight = options[:weight] || 1
|
116
168
|
end
|
117
169
|
end
|
118
|
-
|
170
|
+
|
119
171
|
end
|
120
|
-
|
172
|
+
|
121
173
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# This writer writes changes directly to the open database.
|
3
|
+
# This writer writes changes directly to the open database.
|
4
4
|
# Use the direct writer only for single process environments
|
5
5
|
# (one single rails app server, e.g. one mongrel).
|
6
6
|
# For multi process environemnts you should use a writer that
|
@@ -9,12 +9,13 @@
|
|
9
9
|
|
10
10
|
module XapianDb
|
11
11
|
module IndexWriters
|
12
|
-
|
12
|
+
|
13
13
|
class DirectWriter
|
14
|
-
|
14
|
+
|
15
15
|
class << self
|
16
|
-
|
16
|
+
|
17
17
|
# Update an object in the index
|
18
|
+
# @param [Object] obj An instance of a class with a blueprint configuration
|
18
19
|
def index(obj)
|
19
20
|
blueprint = XapianDb::DocumentBlueprint.blueprint_for(obj.class)
|
20
21
|
doc = blueprint.indexer.build_document_for(obj)
|
@@ -23,30 +24,41 @@ module XapianDb
|
|
23
24
|
end
|
24
25
|
|
25
26
|
# Remove an object from the index
|
27
|
+
# @param [Object] obj An instance of a class with a blueprint configuration
|
26
28
|
def unindex(obj)
|
27
29
|
XapianDb.database.delete_doc_with_unique_term(obj.xapian_id)
|
28
30
|
XapianDb.database.commit
|
29
31
|
end
|
30
32
|
|
31
33
|
# Reindex all objects of a given class
|
32
|
-
|
34
|
+
# @param [Class] klass The class to reindex
|
35
|
+
# @param [Hash] options Options for reindexing
|
36
|
+
# @option options [Boolean] :verbose (false) Should the reindexing give status informations?
|
37
|
+
def reindex_class(klass, options={})
|
38
|
+
opts = {:verbose => false}.merge(options)
|
33
39
|
# First, delete all docs of this class
|
34
40
|
XapianDb.database.delete_docs_of_class(klass)
|
35
41
|
blueprint = XapianDb::DocumentBlueprint.blueprint_for(klass)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
42
|
+
show_progressbar = false
|
43
|
+
if opts[:verbose]
|
44
|
+
if defined?(ProgressBar)
|
45
|
+
show_progressbar = true
|
46
|
+
end
|
47
|
+
obj_count = klass.count
|
48
|
+
puts "Reindexing #{obj_count} objects..."
|
49
|
+
pbar = ProgressBar.new("Status", obj_count) if show_progressbar
|
50
|
+
end
|
51
|
+
klass.all.each do |obj|
|
40
52
|
doc = blueprint.indexer.build_document_for(obj)
|
41
53
|
XapianDb.database.store_doc(doc)
|
42
|
-
pbar.inc
|
54
|
+
pbar.inc if show_progressbar
|
43
55
|
end
|
44
56
|
XapianDb.database.commit
|
45
57
|
end
|
46
|
-
|
58
|
+
|
47
59
|
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
60
|
+
|
61
|
+
end
|
62
|
+
|
51
63
|
end
|
52
64
|
end
|
data/lib/xapian_db/indexer.rb
CHANGED
@@ -1,18 +1,39 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# The indexer creates a Xapian::Document from a configured object
|
4
|
-
# @author Gernot Kogler
|
5
|
-
|
6
3
|
module XapianDb
|
7
|
-
|
4
|
+
|
5
|
+
# The indexer creates a Xapian::Document from an object. They object must be an instance
|
6
|
+
# of a class that has a blueprint configuration.
|
7
|
+
# @author Gernot Kogler
|
8
8
|
class Indexer
|
9
|
-
|
9
|
+
|
10
|
+
# Supported languages and mapping to the stemmer to use
|
11
|
+
LANGUAGE_MAP = {:da => :danish,
|
12
|
+
:nl => :dutch,
|
13
|
+
:en => :english,
|
14
|
+
:fi => :finnish,
|
15
|
+
:fr => :french,
|
16
|
+
:de => :german2, # Normalises umlauts and ß
|
17
|
+
:hu => :hungarian,
|
18
|
+
:it => :italian,
|
19
|
+
:nb => :norwegian,
|
20
|
+
:nn => :norwegian,
|
21
|
+
:no => :norwegian,
|
22
|
+
:pt => :portuguese,
|
23
|
+
:ro => :romanian,
|
24
|
+
:ru => :russian,
|
25
|
+
:es => :spanish,
|
26
|
+
:sv => :swedish,
|
27
|
+
:tr => :turkish}
|
28
|
+
# Constructor
|
29
|
+
# @param [XapianDb::DocumentBlueprint] document_blueprint The blueprint to use
|
10
30
|
def initialize(document_blueprint)
|
11
31
|
@document_blueprint = document_blueprint
|
12
32
|
end
|
13
|
-
|
14
|
-
# Build the
|
33
|
+
|
34
|
+
# Build the document for an object. The object must respond to 'xapian_id'.
|
15
35
|
# The configured adapter should implement this method.
|
36
|
+
# @return [Xapian::Document] The xapian document (see http://xapian.org/docs/sourcedoc/html/classXapian_1_1Document.html)
|
16
37
|
def build_document_for(obj)
|
17
38
|
@obj = obj
|
18
39
|
@blueprint = DocumentBlueprint.blueprint_for(@obj.class)
|
@@ -22,15 +43,15 @@ module XapianDb
|
|
22
43
|
index_text
|
23
44
|
@xapian_doc
|
24
45
|
end
|
25
|
-
|
46
|
+
|
26
47
|
private
|
27
|
-
|
48
|
+
|
28
49
|
# Store all configured fields
|
29
50
|
def store_fields
|
30
51
|
|
31
52
|
# We store the class name of the object at position 0
|
32
53
|
@xapian_doc.add_value(0, @obj.class.name)
|
33
|
-
|
54
|
+
|
34
55
|
pos = 1
|
35
56
|
@blueprint.attributes.each do |attribute, options|
|
36
57
|
value = @obj.send(attribute)
|
@@ -38,24 +59,20 @@ module XapianDb
|
|
38
59
|
pos += 1
|
39
60
|
end
|
40
61
|
end
|
41
|
-
|
62
|
+
|
42
63
|
# Index all configured text methods
|
43
64
|
def index_text
|
44
|
-
term_generator = Xapian::TermGenerator.new
|
65
|
+
term_generator = Xapian::TermGenerator.new
|
45
66
|
term_generator.document = @xapian_doc
|
46
|
-
|
47
|
-
# (retrieve the language from the object, if configured)
|
48
|
-
stemmer = Xapian::Stem.new("english")
|
49
|
-
term_generator.stemmer = stemmer
|
67
|
+
term_generator.stemmer = get_stemmer
|
50
68
|
# TODO: Configure and enable these features
|
51
69
|
# tg.stopper = stopper if stopper
|
52
|
-
# tg.stemmer = stemmer
|
53
70
|
# tg.set_flags Xapian::TermGenerator::FLAG_SPELLING if db.spelling
|
54
71
|
|
55
72
|
# Always index the class and the primary key
|
56
73
|
@xapian_doc.add_term("C#{@obj.class}")
|
57
74
|
@xapian_doc.add_term("Q#{@obj.xapian_id}")
|
58
|
-
|
75
|
+
|
59
76
|
@blueprint.indexed_methods.each do |method, options|
|
60
77
|
value = @obj.send(method)
|
61
78
|
unless value.nil?
|
@@ -69,7 +86,21 @@ module XapianDb
|
|
69
86
|
end
|
70
87
|
end
|
71
88
|
end
|
72
|
-
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# Configure the stemmer to use
|
93
|
+
def get_stemmer
|
94
|
+
# Do we have a language config on the blueprint?
|
95
|
+
if @blueprint.lang_method
|
96
|
+
lang = @obj.send(@blueprint.lang_method)
|
97
|
+
return Xapian::Stem.new(LANGUAGE_MAP[lang.to_sym].to_s) if lang && LANGUAGE_MAP.has_key?(lang.to_sym)
|
98
|
+
end
|
99
|
+
# Do we have a global stemmer?
|
100
|
+
return XapianDb::Config.stemmer if XapianDb::Config.stemmer
|
101
|
+
return Xapian::Stem.new("none") # No language config
|
102
|
+
end
|
103
|
+
|
73
104
|
end
|
74
|
-
|
105
|
+
|
75
106
|
end
|
@@ -1,34 +1,37 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# Parse a query expression and convert it to Xapian Query arguments
|
4
|
-
# @author Gernot Kogler
|
5
|
-
|
6
3
|
module XapianDb
|
7
|
-
|
4
|
+
|
5
|
+
# Parse a query expression and create a xapian query object
|
6
|
+
# @author Gernot Kogler
|
8
7
|
class QueryParser
|
9
|
-
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
# @param [XapianDb::Database] database The database to query
|
10
11
|
def initialize(database)
|
11
12
|
@db = database
|
12
|
-
|
13
|
+
|
13
14
|
# Set the parser options
|
14
15
|
@query_flags = 0
|
15
|
-
@query_flags |= Xapian::QueryParser::FLAG_WILDCARD
|
16
|
-
@query_flags |= Xapian::QueryParser::FLAG_BOOLEAN
|
17
|
-
@query_flags |= Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE
|
16
|
+
@query_flags |= Xapian::QueryParser::FLAG_WILDCARD # enable wildcards
|
17
|
+
@query_flags |= Xapian::QueryParser::FLAG_BOOLEAN # enable boolean operators
|
18
|
+
@query_flags |= Xapian::QueryParser::FLAG_BOOLEAN_ANY_CASE # enable case insensitive boolean operators
|
18
19
|
end
|
19
|
-
|
20
|
+
|
21
|
+
# Parse an expression
|
22
|
+
# @return [Xapian::Query] The query object (see http://xapian.org/docs/apidoc/html/classXapian_1_1Query.html)
|
20
23
|
def parse(expression)
|
21
24
|
parser = Xapian::QueryParser.new
|
22
25
|
parser.database = @db.reader
|
23
26
|
parser.default_op = Xapian::Query::OP_AND # Could be made configurable
|
24
27
|
# TODO: Setup stopper, stemmer, defaults and fields
|
25
|
-
|
26
|
-
# Add the searchable prefixes to allow searches by field
|
28
|
+
|
29
|
+
# Add the searchable prefixes to allow searches by field
|
27
30
|
# (like "name:Kogler")
|
28
31
|
XapianDb::DocumentBlueprint.searchable_prefixes.each{|prefix| parser.add_prefix(prefix.to_s.downcase, "X#{prefix.to_s.upcase}") }
|
29
32
|
parser.parse_query(expression, @query_flags)
|
30
33
|
end
|
31
|
-
|
34
|
+
|
32
35
|
end
|
33
|
-
|
36
|
+
|
34
37
|
end
|
data/lib/xapian_db/railtie.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
# Configuration for a rails app
|
4
|
-
# @author Gernot Kogler
|
5
|
-
|
6
3
|
require 'xapian_db'
|
7
4
|
require 'rails'
|
8
5
|
|
9
6
|
module XapianDb
|
7
|
+
|
8
|
+
# Configuration for a rails app
|
9
|
+
# @author Gernot Kogler
|
10
10
|
class Railtie < ::Rails::Railtie
|
11
11
|
|
12
12
|
config.before_configuration do
|
@@ -25,7 +25,7 @@ module XapianDb
|
|
25
25
|
adapter = :active_record
|
26
26
|
writer = :direct
|
27
27
|
end
|
28
|
-
|
28
|
+
|
29
29
|
# Do the configuration
|
30
30
|
XapianDb::Config.setup do |config|
|
31
31
|
if database_path == ":memory:"
|
@@ -33,11 +33,12 @@ module XapianDb
|
|
33
33
|
else
|
34
34
|
config.database database_path
|
35
35
|
end
|
36
|
-
config.adapter adapter.to_sym
|
36
|
+
config.adapter adapter.to_sym
|
37
37
|
config.writer writer.to_sym
|
38
|
+
config.language(env_config["language"]) if env_config["language"]
|
38
39
|
end
|
39
|
-
|
40
|
+
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
end
|
43
44
|
end
|