Class: XapianDb::DocumentBlueprint

Inherits:
Object
  • Object
show all
Includes:
Utilities
Defined in:
lib/xapian_db/document_blueprint.rb

Overview

A document blueprint describes the mapping of an object to a Xapian document for a given class.

Examples:

A simple document blueprint configuration for the class Person

XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
  blueprint.attribute       :name, :weight => 10
  blueprint.attribute       :first_name
  blueprint.index           :remarks
end

A document blueprint configuration with a complex attribute for the class Person

XapianDb::DocumentBlueprint.setup(:Person) do |blueprint|
  blueprint.attribute       :complex, :weight => 10 do
    # add some logic here to evaluate the value of 'complex'
  end
end

Author:

  • Gernot Kogler

Defined Under Namespace

Classes: Dependency, IndexOptions

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Utilities

#assert_valid_keys, #camelize, #constantize

Constructor Details

#initializeDocumentBlueprint

Construct the blueprint


272
273
274
275
276
277
278
279
280
# File 'lib/xapian_db/document_blueprint.rb', line 272

def initialize
  @attributes_hash      = {}
  @indexed_methods_hash = {}
  @type_map             = {}
  @dependencies         = []
  @_natural_sort_order  = :id
  @autoindex            = true
  @indexer_preprocess_callback = nil
end

Class Attribute Details

.blueprintsObject (readonly)

Returns the value of attribute blueprints


29
30
31
# File 'lib/xapian_db/document_blueprint.rb', line 29

def blueprints
  @blueprints
end

Instance Attribute Details

#_natural_sort_orderObject (readonly)


Blueprint DSL methods



269
270
271
# File 'lib/xapian_db/document_blueprint.rb', line 269

def _natural_sort_order
  @_natural_sort_order
end

#dependenciesObject (readonly)


Blueprint DSL methods



269
270
271
# File 'lib/xapian_db/document_blueprint.rb', line 269

def dependencies
  @dependencies
end

#lazy_base_queryObject (readonly)


Blueprint DSL methods



269
270
271
# File 'lib/xapian_db/document_blueprint.rb', line 269

def lazy_base_query
  @lazy_base_query
end

#type_mapObject (readonly)


Instance methods



185
186
187
# File 'lib/xapian_db/document_blueprint.rb', line 185

def type_map
  @type_map
end

Class Method Details

.attributesArray<Symbol>

Return an array of all defined attributes


144
145
146
# File 'lib/xapian_db/document_blueprint.rb', line 144

def attributes
  @attributes || []
end

.blueprint_for(klass_or_name) ⇒ DocumentBlueprint

Get the blueprint for a class


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/xapian_db/document_blueprint.rb', line 87

def blueprint_for(klass_or_name)
  if @blueprints
    if klass_or_name.is_a?(Class)
      warn "xapian_db: blueprint_for(Class) is deprecated; use blueprint_for(Symbol) or blueprint_for(String) instead"
      key = klass_or_name.name
    else
      key = klass_or_name.to_s
    end
    while key != "Object" && key != "BasicObject"
      if @blueprints.has_key? key
        return @blueprints[key]
      else
        klass = XapianDb::Utilities.constantize key
        key = klass.superclass.name
      end
    end
  end
  return nil
end

.configured?(name) ⇒ Boolean

is a blueprint configured for the given name?


65
66
67
# File 'lib/xapian_db/document_blueprint.rb', line 65

def configured?(name)
  @blueprints && @blueprints.has_key?(name.to_s)
end

.configured_classesArray<Class>

Get all configured classes


71
72
73
74
75
76
77
# File 'lib/xapian_db/document_blueprint.rb', line 71

def configured_classes
  if @blueprints
    @blueprints.keys.map {|class_name| XapianDb::Utilities.constantize(class_name) }
  else
    []
  end
end

.dependencies_for(klass_name, changed_attrs) ⇒ Object


79
80
81
82
83
# File 'lib/xapian_db/document_blueprint.rb', line 79

def dependencies_for(klass_name, changed_attrs)
  @blueprints.values.map(&:dependencies)
                    .flatten
                    .select{ |dependency| dependency.dependent_on == klass_name && dependency.interested_in?(changed_attrs) }
end

.resetObject

reset the blueprint setup


59
60
61
# File 'lib/xapian_db/document_blueprint.rb', line 59

def reset
  @blueprints = {}
end

.searchable_prefixesArray<String>

Return an array of all configured text methods in any blueprint


138
139
140
# File 'lib/xapian_db/document_blueprint.rb', line 138

def searchable_prefixes
  @searchable_prefixes || []
end

.setup(klass_or_name) {|blueprint| ... } ⇒ Object

Configure the blueprint for a class. Available options:

Yields:

  • (blueprint)

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/xapian_db/document_blueprint.rb', line 36

def setup(klass_or_name, &block)
  name = class_name_from klass_or_name

  @blueprints ||= {}
  blueprint = DocumentBlueprint.new
  yield blueprint if block_given? # configure the blueprint through the block
  validate_type_consistency_on blueprint

  # Remove a previously loaded blueprint for this class to avoid stale blueprint definitions
  @blueprints.delete_if { |indexed_class, blueprint| indexed_class == name }
  @blueprints[name] = blueprint

  lazy_load_adapter_for blueprint, name

  @searchable_prefixes = @blueprints.values.map { |blueprint| blueprint.searchable_prefixes }.flatten.compact.uniq || []

  # We can always do a field search on the name of the indexed class
  @searchable_prefixes << "indexed_class"
  @attributes = @blueprints.values.map { |blueprint| blueprint.attribute_names}.flatten.compact.uniq.sort || []
  blueprint
end

.type_info_for(attribute) ⇒ Symbol

Get the type info of an attribute


128
129
130
131
132
133
134
# File 'lib/xapian_db/document_blueprint.rb', line 128

def type_info_for(attribute)
  return nil if @blueprints.nil?
  @blueprints.values.each do |blueprint|
    return blueprint.type_map[attribute] if blueprint.type_map.has_key?(attribute)
  end
  nil
end

.value_number_for(attribute) ⇒ Integer

Get the value number for an attribute. Please note that this is not the index in the values array of a xapian document but the valueno. Therefore, document.values returns the wrong data, use document.value(value_number) instead.


112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/xapian_db/document_blueprint.rb', line 112

def value_number_for(attribute)
  return 0 if attribute.to_sym == :indexed_class
  return 1 if attribute.to_sym == :natural_sort_order
  raise ArgumentError.new "attribute #{attribute} is not configured in any blueprint" if @attributes.nil?
  position = @attributes.index attribute.to_sym
  if position
    # We add 2 because slot 0 and 1 are reserved for indexed_class and natural_sort_order
    return position + 2
  else
    raise ArgumentError.new "attribute #{attribute} is not configured in any blueprint"
  end
end

Instance Method Details

#_adapterObject

return the adpater to use for this blueprint


293
294
295
# File 'lib/xapian_db/document_blueprint.rb', line 293

def _adapter
  @_adapter || XapianDb::Config.adapter || XapianDb::Adapters::GenericAdapter
end

#accessors_moduleModule

Lazily build and return a module that implements accessors for each field


228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/xapian_db/document_blueprint.rb', line 228

def accessors_module
  return @accessors_module unless @accessors_module.nil?
  @accessors_module = Module.new

  # Add the accessors for the indexed class and the score
  @accessors_module.instance_eval do

    define_method :indexed_class do
      self.values[0].value
    end

    define_method :score do
      @score
    end

    define_method :attributes do
      blueprint = XapianDb::DocumentBlueprint.blueprint_for indexed_class
      blueprint.attribute_names.inject({}) { |hash, attr| hash.tap { |hash| hash[attr.to_s] = self.send attr } }
    end
  end

  # Add an accessor for each attribute
  attribute_names.each do |attribute|
    index = DocumentBlueprint.value_number_for(attribute)
    codec = XapianDb::TypeCodec.codec_for @type_map[attribute]
    @accessors_module.instance_eval do
      define_method attribute do
        codec.decode self.value(index)
      end
    end
  end

  # Let the adapter add its document helper methods (if any)
  _adapter.add_doc_helper_methods_to(@accessors_module)
  @accessors_module
end

#adapter(type) ⇒ Object

Set the adapter


287
288
289
290
# File 'lib/xapian_db/document_blueprint.rb', line 287

def adapter(type)
  # We try to guess the adapter name
  @_adapter = XapianDb::Adapters.const_get("#{camelize(type.to_s)}Adapter")
end

#attribute(name, options = {}, &block) ⇒ Object

Add an attribute to the blueprint. Attributes will be stored in the xapian documents an can be accessed from a search result.

Examples:

For complex attribute configurations you may pass a block:

XapianDb::DocumentBlueprint.setup(:IndexedObject) do |blueprint|
  blueprint.attribute :complex do
    if @id == 1
      "One"
    else
      "Not one"
    end
  end
end

Options Hash (options):

  • :weight (Integer) — default: 1

    The weight for this attribute.

  • :index (Boolean) — default: true

    Should the attribute be indexed?

  • :as (Symbol)

    should add type info for range queries (:date, :numeric)

Raises:

  • (ArgumentError)

326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/xapian_db/document_blueprint.rb', line 326

def attribute(name, options={}, &block)
  raise ArgumentError.new("You cannot use #{name} as an attribute name since it is a reserved method name of Xapian::Document") if reserved_method_name?(name)
  do_not_index    = options.delete(:index) == false
  @type_map[name] = (options.delete(:as) || :string)

  if block_given?
    @attributes_hash[name] = {:block => block}.merge(options)
  else
    @attributes_hash[name] = options
  end
  self.index(name, options, &block) unless do_not_index
end

#attribute_namesArray<Symbol>

Get the names of all configured attributes sorted alphabetically


189
190
191
# File 'lib/xapian_db/document_blueprint.rb', line 189

def attribute_names
  @attributes_hash.keys.sort
end

#attributes(*attributes) ⇒ Object

Add a list of attributes to the blueprint. Attributes will be stored in the xapian documents ans can be accessed from a search result.


342
343
344
345
346
347
348
349
# File 'lib/xapian_db/document_blueprint.rb', line 342

def attributes(*attributes)
  attributes.each do |attr|
    raise ArgumentError.new("You cannot use #{attr} as an attribute name since it is a reserved method name of Xapian::Document") if reserved_method_name?(attr)
    @attributes_hash[attr] = {}
    @type_map[attr] = :string
    self.index attr
  end
end

#autoindex(boolean) ⇒ Object

Should objects for this blueprint be automatically reindexed?


299
300
301
# File 'lib/xapian_db/document_blueprint.rb', line 299

def autoindex(boolean)
  @autoindex = boolean
end

#autoindex?Boolean

Get the autoindex value


305
306
307
# File 'lib/xapian_db/document_blueprint.rb', line 305

def autoindex?
  @autoindex
end

#base_query(expression = nil, &block) ⇒ Object

Define a base query to select one or all objects of the indexed class. The reason for a base query is to optimize the query avoiding th 1+n problematic. The base query should only include joins(…) and includes(…) calls.

Examples:

Include the adresses

blueprint.base_query Person.includes(:addresses)

396
397
398
399
400
401
402
# File 'lib/xapian_db/document_blueprint.rb', line 396

def base_query(expression = nil, &block)
  if expression
    warn "xapian_db: directly passing a base query in a blueprint configuration is deprecated, wrap them in a block"
    block = lambda { expression }
  end
  @lazy_base_query = block
end

#block_for_attribute(attribute) ⇒ Block

Get the block associated with an attribute


196
197
198
# File 'lib/xapian_db/document_blueprint.rb', line 196

def block_for_attribute(attribute)
  @attributes_hash[attribute][:block]
end

#dependency(klass_name, when_changed: [], &block) ⇒ Object


413
414
415
# File 'lib/xapian_db/document_blueprint.rb', line 413

def dependency(klass_name, when_changed: [], &block)
  @dependencies << Dependency.new(klass_name.to_s, when_changed, block)
end

#ignore_if(&block) ⇒ Object

Add a block of code that evaluates if a model should not be indexed


386
387
388
# File 'lib/xapian_db/document_blueprint.rb', line 386

def ignore_if &block
  @ignore_expression = block
end

#index(*args, &block) ⇒ Object

Add an indexed value to the blueprint. Indexed values are not accessible from a search result. Avaliable options:

  • :weight (default: 1) The weight for this indexed value

Examples:

Simple index declaration

blueprint.index :name

Index declaration with options

blueprint.index :name, :weight => 10

Mass index declaration

blueprint.index :name, :first_name, :profession

Index declaration with a block

blueprint.index :complex, :weight => 10 do
  # add some logic here to calculate the value for 'complex'
end

367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/xapian_db/document_blueprint.rb', line 367

def index(*args, &block)
  case args.size
    when 1
      @indexed_methods_hash[args.first] = IndexOptions.new(:weight => 1, :block => block)
    when 2
      # Is it a method name with options?
      if args.last.is_a? Hash
        options = args.last
        assert_valid_keys options, :weight, :prefixed, :no_split
        @indexed_methods_hash[args.first] = IndexOptions.new(options.merge(:block => block))
      else
        add_indexes_from args
      end
    else # multiple arguments
      add_indexes_from args
  end
end

#indexed_method_namesArray<Symbol>

Get the names of all configured index methods sorted alphabetically


202
203
204
# File 'lib/xapian_db/document_blueprint.rb', line 202

def indexed_method_names
  @indexed_methods_hash.keys.sort
end

#indexer_preprocess_callback(method) ⇒ Object

Set the indexer preprocess callback.

Examples:

class Util
  def self.strip_accents(terms)
    terms.gsub(/[éèêëÉÈÊË]/, "e")
  end
end

XapianDb::DocumentBlueprint.setup(:IndexedObject) do |blueprint|
  blueprint.attribute :name
  blueprint.indexer_preprocess_callback Util.method(:strip_accents)
end

430
431
432
# File 'lib/xapian_db/document_blueprint.rb', line 430

def indexer_preprocess_callback(method)
  @indexer_preprocess_callback = method
end

#natural_sort_order(name = nil, &block) ⇒ Object

Define the natural sort order. Pass a method name or a block, but not both

Raises:

  • (ArgumentError)

408
409
410
411
# File 'lib/xapian_db/document_blueprint.rb', line 408

def natural_sort_order(name=nil, &block)
  raise ArgumentError.new("natural_sort_order accepts a method name or a block, but not both") if name && block
  @_natural_sort_order = name || block
end

#options_for_indexed_method(method) ⇒ IndexOptions

Get the options for an indexed method


209
210
211
# File 'lib/xapian_db/document_blueprint.rb', line 209

def options_for_indexed_method(method)
  @indexed_methods_hash[method]
end

#preprocess_termsObject

Reader for indexer_preprocess_callback. Returns the terms preprocessing method for this blueprint, the global method from config or nil.


436
437
438
# File 'lib/xapian_db/document_blueprint.rb', line 436

def preprocess_terms
  @indexer_preprocess_callback || XapianDb::Config.preprocess_terms
end

#searchable_prefixesArray<String>

Return an array of all configured text methods in this blueprint


215
216
217
# File 'lib/xapian_db/document_blueprint.rb', line 215

def searchable_prefixes
  @searchable_prefixes ||= indexed_method_names
end

#should_index?(obj) ⇒ Boolean

Should the object go into the index? Evaluates an ignore expression, if defined


221
222
223
224
# File 'lib/xapian_db/document_blueprint.rb', line 221

def should_index? obj
  return obj.instance_eval(&@ignore_expression) == false if @ignore_expression
  true
end