xapian_db 1.3.5.4 → 1.3.7.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -32,9 +32,9 @@ I tried hard but I couldn't find such a thing so I decided to write it, based on
32
32
 
33
33
  == Requirements
34
34
 
35
- * ruby 1.9.2 or newer
35
+ * ruby 2.0.0 or newer
36
36
  * rails 3.0 or newer (if you want to use it with rails)
37
- * xapian-core and xapian-ruby binaries installed
37
+ * xapian-core and xapian-ruby binaries 1.2.x installed
38
38
 
39
39
  == Installing xapian binaries
40
40
 
@@ -47,7 +47,7 @@ Make sure version 1.2.6 or newer is installed.
47
47
  If you want to use xapian_db in a Rails app, you need Rails 3 or newer.
48
48
 
49
49
  For a first look, look at the examples in the examples folder. There's the simple ruby script basic.rb that shows the basic
50
- usage of XapianDB without rails. In the basic_rails folder you'll find a very simple Rails app unsing XapianDb.
50
+ usage of XapianDB without rails. In the basic_rails folder you'll find a very simple Rails app using XapianDb.
51
51
 
52
52
  The following steps assume that you are using xapian_db within a Rails app.
53
53
 
@@ -64,7 +64,7 @@ You can override these defaults by placing a config file named 'xapian_db.yml' i
64
64
  adapter: datamapper # Avaliable adapters: :active_record, :datamapper
65
65
  language: de # Global language; can be overridden for specific blueprints
66
66
  term_min_length: 2 # Ignore single character terms
67
- enable_query_flags: FLAG_PHRASE, FLAG_SPELLING_CORRECTION
67
+ enabled_query_flags: FLAG_PHRASE, FLAG_SPELLING_CORRECTION
68
68
 
69
69
  development:
70
70
  database: db/xapian_db/development
@@ -194,6 +194,32 @@ are ordered by relevance and - within the same relevance - by the natural sort o
194
194
  end
195
195
  end
196
196
 
197
+ You can specify a Ruby method for preprocessing the indexed terms. The method needs to be a class method that takes one argument (the terms to be indexed) and
198
+ returns a string. You can configure it globaly in the config or on a blueprint. For example, this setup will make words with accented e characters searchable
199
+ by their accentless form:
200
+
201
+ class Util
202
+ def self.strip_accents(terms)
203
+ terms.gsub(/[éèêëÉÈÊË]/, "e")
204
+ end
205
+ end
206
+
207
+ XapianDb::Config.setup do |config|
208
+ config.indexer_preprocess_callback Util.method(:strip_accents)
209
+ end
210
+
211
+ You may use attributes from associated objects in a blueprint; if you do that and an associated object is updated, your objects should be reindexed, too. You can tell XapnaDB about those dependencies like so:
212
+
213
+ XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
214
+ blueprint.attribue :address, :as => :json
215
+
216
+ blueprint.dependency :Address, when_changed: %i(street zip city) do |address|
217
+ Person.joins{ address }.where{ adresses.id == my{ address.id } }
218
+ end
219
+ end
220
+
221
+ The block you supply to the dependency declaration must return a collection of objects that should get reindexed, too.
222
+
197
223
  If you want to manage the (re)indexing of your objects on your own, turn off autoindexing in your blueprint:
198
224
 
199
225
  XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
@@ -246,6 +272,10 @@ You can query attributes:
246
272
 
247
273
  results = XapianDb.search "name:Foo"
248
274
 
275
+ You can force the sort order:
276
+
277
+ results = XapianDb.search "name:Foo", order: [:name]
278
+
249
279
  You can query objects of a specific class:
250
280
 
251
281
  results = Person.search "name:Foo"
@@ -146,7 +146,7 @@ module XapianDb
146
146
  # @return [String] the encoded number
147
147
  def self.encode(number)
148
148
  case number.class.name
149
- when "Fixnum", "Float", "Bignum"
149
+ when "Fixnum", "Float", "Bignum", "Integer"
150
150
  Xapian::sortable_serialise number
151
151
  when "BigDecimal"
152
152
  Xapian::sortable_serialise number.to_f
@@ -160,7 +160,7 @@ module XapianDb
160
160
  # @return [BigDecimal] the decoded number
161
161
  def self.decode(encoded_number)
162
162
  begin
163
- BigDecimal.new(Xapian::sortable_unserialise(encoded_number).to_s)
163
+ BigDecimal(Xapian::sortable_unserialise(encoded_number).to_s)
164
164
  rescue TypeError
165
165
  raise ArgumentError.new "#{encoded_number} cannot be unserialized"
166
166
  end
@@ -175,7 +175,7 @@ module XapianDb
175
175
  def self.encode(number)
176
176
  return nil if number.nil?
177
177
  case number.class.name
178
- when "Fixnum"
178
+ when "Fixnum", "Integer"
179
179
  Xapian::sortable_serialise number
180
180
  else
181
181
  raise ArgumentError.new "#{number} was expected to be an integer"
@@ -105,9 +105,9 @@ module XapianDb
105
105
 
106
106
  # Update an object in the index
107
107
  # @param [Object] obj An instance of a class with a blueprint configuration
108
- def self.index(obj, commit=true)
108
+ def self.index(obj, commit=true, changed_attrs: [])
109
109
  writer = @block_writer || XapianDb::Config.writer
110
- writer.index obj, commit
110
+ writer.index obj, commit, changed_attrs: changed_attrs
111
111
  end
112
112
 
113
113
  # Remove a document from the index
@@ -119,11 +119,11 @@ module XapianDb
119
119
 
120
120
  # Update or delete a xapian document belonging to an object depending on the ignore_if logic(if present)
121
121
  # @param [Object] object An instance of a class with a blueprint configuration
122
- def self.reindex(object, commit=true)
122
+ def self.reindex(object, commit=true, changed_attrs: [])
123
123
  writer = @block_writer || XapianDb::Config.writer
124
124
  blueprint = XapianDb::DocumentBlueprint.blueprint_for object.class.name
125
125
  if blueprint.should_index?(object)
126
- writer.index object, commit
126
+ writer.index object, commit, changed_attrs: changed_attrs
127
127
  else
128
128
  writer.delete_doc_with object.xapian_id, commit
129
129
  end
@@ -191,5 +191,4 @@ module XapianDb
191
191
  @block_writer = nil
192
192
  end
193
193
  end
194
-
195
194
  end
@@ -49,7 +49,12 @@ module XapianDb
49
49
  # add the after commit logic, unless the blueprint has autoindexing turned off
50
50
  if XapianDb::DocumentBlueprint.blueprint_for(klass.name).autoindex?
51
51
  after_commit do
52
- XapianDb.reindex(self) unless self.destroyed?
52
+ unless self.destroyed?
53
+ XapianDb.reindex(self, true, changed_attrs: self.previous_changes.keys)
54
+ XapianDb::DocumentBlueprint.dependencies_for(klass.name, self.previous_changes.keys).each do |dependency|
55
+ dependency.block.call(self).each{ |model| XapianDb.reindex model, true, changed_attrs: self.previous_changes.keys }
56
+ end
57
+ end
53
58
  end
54
59
  end
55
60
 
@@ -64,7 +69,6 @@ module XapianDb
64
69
  XapianDb.reindex_class(klass, options)
65
70
  end
66
71
  end
67
-
68
72
  end
69
73
 
70
74
  # Implement the document helper methods on a module
@@ -76,7 +80,7 @@ module XapianDb
76
80
 
77
81
  # Implement access to the model id
78
82
  define_method :id do
79
- return @id unless @d.nil?
83
+ return @id unless @id.nil?
80
84
  # retrieve the class and id from data
81
85
  klass_name, id = data.split("-")
82
86
  @id = id.to_i
@@ -90,11 +94,8 @@ module XapianDb
90
94
  klass = constantize klass_name
91
95
  @indexed_object = klass.find(id.to_i)
92
96
  end
93
-
94
97
  end
95
-
96
98
  end
97
-
98
99
  end
99
100
  end
100
101
  end
@@ -32,7 +32,7 @@ module XapianDb
32
32
  end
33
33
 
34
34
  # Install delegates for the config instance variables
35
- [:database, :adapter, :writer, :stemmer, :stopper].each do |attr|
35
+ [:database, :adapter, :writer, :stemmer, :stopper, :preprocess_terms].each do |attr|
36
36
  define_method attr do
37
37
  @config.nil? ? nil : @config.instance_variable_get("@_#{attr}")
38
38
  end
@@ -183,5 +183,21 @@ module XapianDb
183
183
  @_enabled_query_flags.delete flag
184
184
  end
185
185
 
186
+ # Set the indexer preprocess callback.
187
+ # @param [Method] method a class method; needs to take one parameter and return a string.
188
+ # @example
189
+ # class Util
190
+ # def self.strip_accents(terms)
191
+ # terms.gsub(/[éèêëÉÈÊË]/, "e")
192
+ # end
193
+ # end
194
+ #
195
+ # XapianDb::Config.setup do |config|
196
+ # config.indexer_preprocess_callback Util.method(:strip_accents)
197
+ # end
198
+ def indexer_preprocess_callback(method)
199
+ @_preprocess_terms = method
200
+ end
201
+
186
202
  end
187
203
  end
@@ -35,10 +35,19 @@ module XapianDb
35
35
  true
36
36
  end
37
37
 
38
- # Delete all docs of a specific class
38
+ # Delete all docs of a specific class.
39
+ #
40
+ # If `klass` tracks its descendants, then docs of any subclasses will be deleted, too.
41
+ # (ActiveRecord does this by default; the gem 'descendants_tracker' offers an alternative.)
42
+ #
39
43
  # @param [Class] klass A class that has a {XapianDb::DocumentBlueprint} configuration
40
44
  def delete_docs_of_class(klass)
41
45
  writer.delete_document("C#{klass}")
46
+ if klass.respond_to? :descendants
47
+ klass.descendants.each do |subclass|
48
+ writer.delete_document("C#{subclass}")
49
+ end
50
+ end
42
51
  true
43
52
  end
44
53
 
@@ -73,6 +82,12 @@ module XapianDb
73
82
  enquiry.query = query
74
83
  sort_indices = opts.delete :sort_indices
75
84
  sort_decending = opts.delete :sort_decending
85
+ order = opts.delete :order
86
+ raise ArgumentError.new "you can't use sort_indices and order, only one of them" if sort_indices && order
87
+
88
+ if order
89
+ sort_indices = order.map{ |attr_name| XapianDb::DocumentBlueprint.value_number_for attr_name.to_sym }
90
+ end
76
91
 
77
92
  sorter = Xapian::MultiValueKeyMaker.new
78
93
  if sort_indices
@@ -80,7 +95,7 @@ module XapianDb
80
95
  enquiry.set_sort_by_key_then_relevance(sorter, sort_decending)
81
96
  else
82
97
  sorter.add_value DocumentBlueprint.value_number_for(:natural_sort_order)
83
- enquiry.set_sort_by_relevance_then_key sorter, true
98
+ enquiry.set_sort_by_relevance_then_key sorter, false
84
99
  end
85
100
 
86
101
  opts[:spelling_suggestion] = @query_parser.spelling_suggestion
@@ -26,6 +26,8 @@ module XapianDb
26
26
  # ---------------------------------------------------------------------------------
27
27
  class << self
28
28
 
29
+ attr_reader :blueprints
30
+
29
31
  # Configure the blueprint for a class.
30
32
  # Available options:
31
33
  # - adapter (see {#adapter} for details)
@@ -50,6 +52,7 @@ module XapianDb
50
52
  # We can always do a field search on the name of the indexed class
51
53
  @searchable_prefixes << "indexed_class"
52
54
  @attributes = @blueprints.values.map { |blueprint| blueprint.attribute_names}.flatten.compact.uniq.sort || []
55
+ blueprint
53
56
  end
54
57
 
55
58
  # reset the blueprint setup
@@ -66,11 +69,17 @@ module XapianDb
66
69
  # Get all configured classes
67
70
  # @return [Array<Class>]
68
71
  def configured_classes
69
- if @blueprints
70
- @blueprints.keys.map {|class_name| XapianDb::Utilities.constantize(class_name) }
71
- else
72
- []
73
- end
72
+ if @blueprints
73
+ @blueprints.keys.map {|class_name| XapianDb::Utilities.constantize(class_name) }
74
+ else
75
+ []
76
+ end
77
+ end
78
+
79
+ def dependencies_for(klass_name, changed_attrs)
80
+ @blueprints.values.map(&:dependencies)
81
+ .flatten
82
+ .select{ |dependency| dependency.dependent_on == klass_name && dependency.interested_in?(changed_attrs) }
74
83
  end
75
84
 
76
85
  # Get the blueprint for a class
@@ -167,7 +176,6 @@ module XapianDb
167
176
  end
168
177
  end
169
178
  end
170
-
171
179
  end
172
180
 
173
181
  # ---------------------------------------------------------------------------------
@@ -258,15 +266,17 @@ module XapianDb
258
266
  # Blueprint DSL methods
259
267
  # ---------------------------------------------------------------------------------
260
268
 
261
- attr_reader :lazy_base_query, :_natural_sort_order
269
+ attr_reader :lazy_base_query, :_natural_sort_order, :dependencies
262
270
 
263
271
  # Construct the blueprint
264
272
  def initialize
265
273
  @attributes_hash = {}
266
274
  @indexed_methods_hash = {}
267
275
  @type_map = {}
276
+ @dependencies = []
268
277
  @_natural_sort_order = :id
269
278
  @autoindex = true
279
+ @indexer_preprocess_callback = nil
270
280
  end
271
281
 
272
282
  # Set the adapter
@@ -377,7 +387,7 @@ module XapianDb
377
387
  @ignore_expression = block
378
388
  end
379
389
 
380
- # Define a base query to select one or all objects of the indexed class. The reason for a
390
+ # Define a base query to select one or all objects of the indexed class. The reason for a
381
391
  # base query is to optimize the query avoiding th 1+n problematic. The base query should only
382
392
  # include joins(...) and includes(...) calls.
383
393
  # @param [expression] a base query expression
@@ -400,6 +410,33 @@ module XapianDb
400
410
  @_natural_sort_order = name || block
401
411
  end
402
412
 
413
+ def dependency(klass_name, when_changed: [], &block)
414
+ @dependencies << Dependency.new(klass_name.to_s, when_changed, block)
415
+ end
416
+
417
+ # Set the indexer preprocess callback.
418
+ # @param [Method] method a class method; needs to take one parameter and return a string.
419
+ # @example
420
+ # class Util
421
+ # def self.strip_accents(terms)
422
+ # terms.gsub(/[éèêëÉÈÊË]/, "e")
423
+ # end
424
+ # end
425
+ #
426
+ # XapianDb::DocumentBlueprint.setup(:IndexedObject) do |blueprint|
427
+ # blueprint.attribute :name
428
+ # blueprint.indexer_preprocess_callback Util.method(:strip_accents)
429
+ # end
430
+ def indexer_preprocess_callback(method)
431
+ @indexer_preprocess_callback = method
432
+ end
433
+
434
+ # Reader for indexer_preprocess_callback.
435
+ # Returns the terms preprocessing method for this blueprint, the global method from config or nil.
436
+ def preprocess_terms
437
+ @indexer_preprocess_callback || XapianDb::Config.preprocess_terms
438
+ end
439
+
403
440
  # Options for an indexed method
404
441
  class IndexOptions
405
442
 
@@ -414,7 +451,24 @@ module XapianDb
414
451
  @no_split = options[:no_split]
415
452
  @block = options[:block]
416
453
  end
454
+ end
455
+
456
+ class Dependency
457
+
458
+ attr_reader :dependent_on, :trigger_attributes, :block
417
459
 
460
+ # Constructor
461
+ # @param [String] klass_name Name of the dependent class
462
+ # @param [Array] trigger_attributes List of attributes to watch for changes (if empty, triggers on any change)
463
+ # @option [Block] block Block that is called when changes are detected; the block must return an array of indexeable objects
464
+ def initialize(klass_name, trigger_attributes, block)
465
+ @dependent_on, @trigger_attributes, @block = klass_name, trigger_attributes.map(&:to_s), block
466
+ end
467
+
468
+ def interested_in?(changed_attrs)
469
+ return true if @trigger_attributes.empty?
470
+ (@trigger_attributes & changed_attrs).any?
471
+ end
418
472
  end
419
473
 
420
474
  private
@@ -431,7 +485,5 @@ module XapianDb
431
485
  @reserved_method_names ||= Xapian::Document.instance_methods
432
486
  @reserved_method_names.include?(attr_name.to_sym)
433
487
  end
434
-
435
488
  end
436
-
437
489
  end
@@ -13,7 +13,7 @@ module XapianDb
13
13
  def index_task(options)
14
14
  klass = constantize options[:class]
15
15
  obj = klass.respond_to?(:get) ? klass.get(options[:id]) : klass.find(options[:id])
16
- DirectWriter.index obj
16
+ DirectWriter.index obj, true, changed_attrs: options[:changed_attrs]
17
17
  end
18
18
 
19
19
  def delete_doc_task(options)
@@ -24,7 +24,6 @@ module XapianDb
24
24
  klass = constantize options[:class]
25
25
  DirectWriter.reindex_class klass, :verbose => false
26
26
  end
27
-
28
27
  end
29
28
  end
30
- end
29
+ end
@@ -18,8 +18,8 @@ module XapianDb
18
18
 
19
19
  # Update an object in the index
20
20
  # @param [Object] obj An instance of a class with a blueprint configuration
21
- def index(obj, commit=true)
22
- beanstalk.put( {:task => "index_task", :class => obj.class.name, :id => obj.id }.to_json )
21
+ def index(obj, commit=true, changed_attrs: [])
22
+ beanstalk.put( { :task => "index_task", :class => obj.class.name, :id => obj.id, :changed_attrs => changed_attrs }.to_json )
23
23
  end
24
24
 
25
25
  # Remove an object from the index
@@ -17,11 +17,11 @@ module XapianDb
17
17
  class << self
18
18
 
19
19
  # Update an object in the index
20
- # @param [Object] obj An instance of a class with a blueprint configuration
21
- def index(obj, commit=true)
22
- blueprint = XapianDb::DocumentBlueprint.blueprint_for(obj.class.name)
20
+ # @param [Object] object An instance of a class with a blueprint configuration
21
+ def index(object, commit=true, changed_attrs: [])
22
+ blueprint = XapianDb::DocumentBlueprint.blueprint_for(object.class.name)
23
23
  indexer = XapianDb::Indexer.new(XapianDb.database, blueprint)
24
- doc = indexer.build_document_for(obj)
24
+ doc = indexer.build_document_for(object)
25
25
  XapianDb.database.store_doc(doc)
26
26
  XapianDb.database.commit if commit
27
27
  end
@@ -35,10 +35,10 @@ module XapianDb
35
35
 
36
36
  # Update or delete a xapian document belonging to an object depending on the ignore_if logic(if present)
37
37
  # @param [Object] object An instance of a class with a blueprint configuration
38
- def reindex(object, commit=true)
38
+ def reindex(object, commit=true, changed_attrs: [])
39
39
  blueprint = XapianDb::DocumentBlueprint.blueprint_for object.class.name
40
40
  if blueprint.should_index?(object)
41
- index object, commit
41
+ index object, commit, changed_attrs: changed_attrs
42
42
  else
43
43
  delete_doc_with object.xapian_id, commit
44
44
  end
@@ -12,7 +12,7 @@ module XapianDb
12
12
 
13
13
  # Update an object in the index
14
14
  # @param [Object] obj An instance of a class with a blueprint configuration
15
- def index(obj, commit=true); end
15
+ def index(obj, commit=true, changed_attrs: []); end
16
16
 
17
17
  # Remove an object from the index
18
18
  # @param [String] xapian_id The document id
@@ -28,4 +28,4 @@ module XapianDb
28
28
  end
29
29
  end
30
30
  end
31
- end
31
+ end