xapian_db 1.3.5.4 → 1.3.7.4

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