Module: SearchFlip::Index::ClassMethods

Extended by:
Forwardable
Defined in:
lib/search_flip/index.rb

Instance Method Summary collapse

Instance Method Details

#analyze(request, params = {}) ⇒ Hash

Sends an analyze request to Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Examples:

CommentIndex.analyze(analyzer: "standard", text: "this is a test")

Returns:

  • (Hash)

    The raw response



491
492
493
494
495
# File 'lib/search_flip/index.rb', line 491

def analyze(request, params = {})
  response = connection.http_client.headers(accept: "application/json").post("#{index_url}/_analyze", json: request, params: params)

  SearchFlip::JSON.parse(response.to_s)
end

#bulk(options = {}) ⇒ Object

Initiates and yields a bulk object, such that index, import, create, update and delete requests can be appended to the bulk request. Sends a refresh request afterwards if auto_refresh is enabled.

Examples:

CommentIndex.bulk ignore_errors: [409] do |bulk|
  bulk.create comment.id, CommentIndex.serialize(comment),
    version: comment.version, version_type: "external_gte"

  bulk.delete comment.id, routing: comment.user_id

  # ...
end

Parameters:

  • options (Hash) (defaults to: {})

    Specifies options regarding the bulk indexing

Options Hash (options):

  • ignore_errors (Array)

    Specifies an array of http status codes that shouldn’t raise any exceptions, like eg 409 for conflicts, ie when optimistic concurrency control is used.

  • raise (Boolean)

    Prevents any exceptions from being raised. Please note that this only applies to the bulk response, not to the request in general, such that connection errors, etc will still raise.

See Also:



629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/search_flip/index.rb', line 629

def bulk(options = {})
  default_options = {
    http_client: connection.http_client,
    bulk_limit: connection.bulk_limit,
    bulk_max_mb: connection.bulk_max_mb
  }

  url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url

  SearchFlip::Bulk.new("#{url}/_bulk", default_options.merge(options)) do |indexer|
    yield indexer
  end

  refresh if SearchFlip::Config[:auto_refresh]
end

#close_indexBoolean

Closes the index within Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or raises SearchFlip::ResponseError



343
344
345
# File 'lib/search_flip/index.rb', line 343

def close_index
  connection.close_index(index_name_with_prefix)
end

#connectionSearchFlip::Connection

Returns the SearchFlip::Connection for the index. Override to specify a custom connection.

Returns:



668
669
670
671
672
# File 'lib/search_flip/index.rb', line 668

def connection
  ConnectionMutex.synchronize do
    @connection ||= SearchFlip::Connection.new(base_url: SearchFlip::Config[:base_url])
  end
end

#create(scope, options = {}, additional_index_options = {}) ⇒ Object

Indexes the given record set, array of records or individual record using Elasticsearch’s create operation via the Bulk API, such that the request will fail if a record with a particular primary key already exists in Elasticsearch.



560
561
562
563
564
565
566
567
568
# File 'lib/search_flip/index.rb', line 560

def create(scope, options = {}, additional_index_options = {})
  bulk options do |indexer|
    each_record(scope, index_scope: true) do |object|
      indexer.create record_id(object), serialize(object), index_options(object).merge(additional_index_options)
    end
  end

  scope
end

#create_indexBoolean

Creates the index within Elasticsearch and applies index settings, if specified. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or false



334
335
336
# File 'lib/search_flip/index.rb', line 334

def create_index
  connection.create_index(index_name_with_prefix, index_settings)
end

#criteriaSearchFlip::Criteria

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates an SearchFlip::Criteria for the current index, which is used as a base for chaining criteria methods.

Returns:



246
247
248
# File 'lib/search_flip/index.rb', line 246

def criteria
  SearchFlip::Criteria.new(target: self)
end

#delete(scope, options = {}, additional_index_options = {}) ⇒ Object

Deletes the given record set, array of records or individual record from Elasticsearch using the Bulk API.



594
595
596
597
598
599
600
601
602
# File 'lib/search_flip/index.rb', line 594

def delete(scope, options = {}, additional_index_options = {})
  bulk options do |indexer|
    each_record(scope) do |object|
      indexer.delete record_id(object), index_options(object).merge(additional_index_options)
    end
  end

  scope
end

#delete_indexObject

Deletes the index from Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.



387
388
389
# File 'lib/search_flip/index.rb', line 387

def delete_index
  connection.delete_index(index_name_with_prefix)
end

#each_record(scope, index_scope: false) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Used to iterate a record set. Here, a record set may be a) an ActiveRecord::Relation or anything responding to #find_each, b) an Array of records or anything responding to #each or c) a single record.

Parameters:

  • scope

    The record set that gets iterated

  • index_scope (Boolean) (defaults to: false)

    Set to true if you want the the index scope to be applied to the scope



155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/search_flip/index.rb', line 155

def each_record(scope, index_scope: false)
  return enum_for(:each_record, scope, index_scope: index_scope) unless block_given?

  if scope.respond_to?(:find_each)
    (index_scope ? self.index_scope(scope) : scope).find_each do |record|
      yield record
    end
  else
    (scope.respond_to?(:each) && !scope.is_a?(SearchFlip::Result) ? scope : Array(scope)).each do |record|
      yield record
    end
  end
end

#fetch_records(ids) ⇒ Object

Returns a record set, usually an ActiveRecord::Relation, for the specified ids, ie primary keys. Override this method for custom primary keys and/or ORMs.

Parameters:

  • ids (Array)

    The array of ids to fetch the records for

Returns:

  • The record set or an array of records



199
200
201
# File 'lib/search_flip/index.rb', line 199

def fetch_records(ids)
  model.where(id: ids)
end

#freeze_indexBoolean

Freezes the index within Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or raises SearchFlip::ResponseError



361
362
363
# File 'lib/search_flip/index.rb', line 361

def freeze_index
  connection.freeze_index(index_name_with_prefix)
end

#get(id, params = {}) ⇒ Hash

Retrieves the document specified by id from Elasticsearch. Raises SearchFlip::ResponseError specific exceptions in case any errors occur.

Examples:

UserIndex.get(1)
UserIndex.get(1, routing: "key")

Parameters:

  • id (String, Fixnum)

    The id to get

  • params (Hash) (defaults to: {})

    Optional params for the request

Returns:

  • (Hash)

    The specified document



457
458
459
460
461
462
# File 'lib/search_flip/index.rb', line 457

def get(id, params = {})
  url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : "#{index_url}/_doc"
  response = connection.http_client.headers(accept: "application/json").get("#{url}/#{id}", params: params)

  SearchFlip::JSON.parse(response.to_s)
end

#get_index_settingsHash

Fetches the index settings from Elasticsearch. Sends a GET request to index_url/_settings. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Hash)

    The index settings



324
325
326
# File 'lib/search_flip/index.rb', line 324

def get_index_settings
  connection.get_index_settings(index_name_with_prefix)
end

#get_mappingHash

Retrieves the current type mapping from Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Hash)

    The current type mapping



426
427
428
429
430
431
432
# File 'lib/search_flip/index.rb', line 426

def get_mapping
  if include_type_name?
    connection.get_mapping(index_name_with_prefix, type_name: type_name)
  else
    connection.get_mapping(index_name_with_prefix)
  end
end

#include_type_name?Boolean

Returns whether or not to include a type name. As types are deprecated in Elasticsearch 7, this method controls whether or not to include the type name. By default, the method returns true for Elasticsearch versions before 7 or if the specified type name for the index is not equal to _doc.

Returns:

  • (Boolean)


440
441
442
# File 'lib/search_flip/index.rb', line 440

def include_type_name?
  type_name != "_doc" || (connection.distribution.nil? && connection.version.to_i < 7)
end

#index(scope, options = {}, additional_index_options = {}) ⇒ Object

Indexes the given record set, array of records or individual record. A record set usually is an ActiveRecord::Relation, but can be any other ORM as well. Uses the Elasticsearch bulk API no matter what is provided. Refreshes the index if auto_refresh is enabled. Raises SearchFlip::ResponseError in case any errors occur.

Examples:

CommentIndex.import Comment.all
CommentIndex.import [comment1, comment2]
CommentIndex.import Comment.first
CommentIndex.import Comment.all, ignore_errors: [409]
CommentIndex.import Comment.all, raise: false

Parameters:

  • scope

    A record set, array of records or individual record to index

  • options (Hash) (defaults to: {})

    Specifies options regarding the bulk indexing

  • additional_index_options (Hash) (defaults to: {})

    Provides custom index options for eg routing, versioning, etc

Options Hash (options):

  • ignore_errors (Array)

    Specifies an array of http status codes that shouldn’t raise any exceptions, like eg 409 for conflicts, ie when optimistic concurrency control is used.

  • raise (Boolean)

    Prevents any exceptions from being raised. Please note that this only applies to the bulk response, not to the request in general, such that connection errors, etc will still raise.

See Also:



542
543
544
545
546
547
548
549
550
# File 'lib/search_flip/index.rb', line 542

def index(scope, options = {}, additional_index_options = {})
  bulk options do |indexer|
    each_record(scope, index_scope: true) do |object|
      indexer.index record_id(object), serialize(object), index_options(object).merge(additional_index_options)
    end
  end

  scope
end

#index_exists?Boolean

Returns whether or not the associated Elasticsearch index already exists.

Returns:

  • (Boolean)

    Whether or not the index exists



314
315
316
# File 'lib/search_flip/index.rb', line 314

def index_exists?
  connection.index_exists?(index_name_with_prefix)
end

#index_nameString

Returns the base name of the index within Elasticsearch, ie the index name without prefix.

Returns:

  • (String)

    The base name of the index, ie without prefix

Raises:



275
276
277
# File 'lib/search_flip/index.rb', line 275

def index_name
  raise SearchFlip::MethodNotImplemented, "You must implement #{name}::index_name"
end

#index_name_with_prefixString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns the full name of the index within Elasticsearch, ie with prefix specified via SearchFlip::Config.

Returns:

  • (String)

    The full index name



286
287
288
# File 'lib/search_flip/index.rb', line 286

def index_name_with_prefix
  "#{SearchFlip::Config[:index_prefix]}#{index_name}"
end

#index_options(record) ⇒ Hash

Override this method to automatically pass index options for a record at index-time, like routing or versioning.

Examples:

def self.index_options(comment)
  {
    routing: comment.user_id,
    version: comment.version,
    version_type: "external_gte"
  }
end

Parameters:

  • record

    The record that gets indexed

Returns:

  • (Hash)

    The index options



68
69
70
# File 'lib/search_flip/index.rb', line 68

def index_options(record)
  {}
end

#index_scope(scope) ⇒ Object

Override this method to specify an index scope, which will automatically be applied to scopes, eg. ActiveRecord::Relation objects, passed to #import or #index. This can be used to preload associations that are used when serializing records or to restrict the records you want to index.

Examples:

Preloading an association

class CommentIndex
  # ...

  def self.index_scope(scope)
    scope.preload(:user)
  end
end

CommentIndex.import(Comment.all) # => CommentIndex.import(Comment.preload(:user))

Restricting records

class CommentIndex
  # ...

  def self.index_scope(scope)
    scope.where(public: true)
  end
end

CommentIndex.import(Comment.all) # => CommentIndex.import(Comment.where(public: true))

Parameters:

  • scope

    The supplied scope to extend

Returns:

  • The extended scope



235
236
237
# File 'lib/search_flip/index.rb', line 235

def index_scope(scope)
  scope
end

#index_settingsHash

Override to specify index settings like number of shards, analyzers, refresh interval, etc.

Examples:

def self.index_settings
  {
    settings: {
      number_of_shards: 10,
      number_of_replicas: 2
    }
  }
end

Returns:

  • (Hash)

    The index settings



305
306
307
# File 'lib/search_flip/index.rb', line 305

def index_settings
  {}
end

#index_urlString

Returns the Elasticsearch index URL, ie base URL and index name with prefix.

Returns:

  • (String)

    The Elasticsearch index URL



659
660
661
# File 'lib/search_flip/index.rb', line 659

def index_url
  connection.index_url(index_name_with_prefix)
end

#mappingObject

Specifies a type mapping. Override to specify a custom mapping. Please note that you don’t have to include the type name, even for Elasticsearch versions before 7, as SearchFlip automatically adds the type name if neccessary.

Examples:

def self.mapping
  {
    properties: {
      email: { type: "string", analyzer: "custom_analyzer" }
    }
  }
end


405
406
407
# File 'lib/search_flip/index.rb', line 405

def mapping
  {}
end

#mget(request, params = {}) ⇒ Hash

Retrieves the documents specified by ids from elasticsearch.

Examples:

UserIndex.mget(ids: [1, 2, 3])
UserIndex.mget(docs: [{ _id: 1, routing: "key1" }, { _id: 2, routing: "key2" }])
UserIndex.mget({ ids: [1, 2, 3] }, stored_fields: "username,full_name")

Parameters:

  • request (Hash)

    The raw request

  • params (Hash) (defaults to: {})

    Optional params for the request

Returns:

  • (Hash)

    The raw response



476
477
478
479
480
481
# File 'lib/search_flip/index.rb', line 476

def mget(request, params = {})
  url = connection.distribution.nil? && connection.version.to_i < 8 ? type_url : index_url
  response = connection.http_client.headers(accept: "application/json").post("#{url}/_mget", json: request, params: params)

  SearchFlip::JSON.parse(response.to_s)
end

#open_indexBoolean

Opens the index within Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or raises SearchFlip::ResponseError



352
353
354
# File 'lib/search_flip/index.rb', line 352

def open_index
  connection.open_index(index_name_with_prefix)
end

#record_id(record) ⇒ String, Fixnum

Returns the record’s id, ie the unique identifier or primary key of a record. Override this method for custom primary keys, but return a String or Fixnum.

Examples:

Default implementation

def self.record_id(record)
  record.id
end

Custom primary key

def self.record_id(user)
  user.username
end

Parameters:

  • record

    The record to get the primary key for

Returns:

  • (String, Fixnum)

    The record’s primary key



187
188
189
# File 'lib/search_flip/index.rb', line 187

def record_id(record)
  record.id
end

#refreshObject

Sends a index refresh request to Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.



500
501
502
# File 'lib/search_flip/index.rb', line 500

def refresh
  connection.refresh(index_name_with_prefix)
end

#scope(name, &block) ⇒ Object

Adds a named scope to the index.

Examples:

scope(:active) { where(active: true) }

UserIndex.active
scope(:active) { |value| where(active: value) }

UserIndex.active(true)
UserIndex.active(false)

Parameters:

  • name (Symbol)

    The name of the scope

  • block

    The scope definition. Add filters, etc.



141
142
143
# File 'lib/search_flip/index.rb', line 141

def scope(name, &block)
  define_singleton_method(name, &block)
end

#serialize(record) ⇒ Hash

This method is abstract.

Override this method to generate a hash representation of a record, used to generate the JSON representation of it.

Examples:

def self.serialize(comment)
  {
    id: comment.id,
    user_id: comment.user_id,
    message: comment.message,
    created_at: comment.created_at,
    updated_at: comment.updated_at
  }
end

Parameters:

  • record

    The record that gets serialized

Returns:

  • (Hash)

    The hash-representation of the record

Raises:



121
122
123
# File 'lib/search_flip/index.rb', line 121

def serialize(record)
  raise SearchFlip::MethodNotImplemented, "You must implement #{name}::serialize(record)"
end

#type_nameString

Override to specify the type name used within Elasticsearch. Recap, this gem uses an individual index for each index class, because Elasticsearch requires to have the same mapping for the same field name, even if the field is living in different types of the same index.

Returns:

  • (String)

    The name used for the type within the index



266
267
268
# File 'lib/search_flip/index.rb', line 266

def type_name
  "_doc"
end

#type_urlString

Returns the full Elasticsearch type URL, ie base URL, index name with prefix and type name.

Returns:

  • (String)

    The Elasticsearch type URL



650
651
652
# File 'lib/search_flip/index.rb', line 650

def type_url
  connection.type_url(index_name_with_prefix, type_name)
end

#unfreeze_indexBoolean

Unfreezes the index within Elasticsearch. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or raises SearchFlip::ResponseError



370
371
372
# File 'lib/search_flip/index.rb', line 370

def unfreeze_index
  connection.unfreeze_index(index_name_with_prefix)
end

#update(scope, options = {}, additional_index_options = {}) ⇒ Object

Indexes the given record set, array of records or individual record using Elasticsearch’s update operation via the Bulk API, such that the request will fail if a record you want to update does not already exist in Elasticsearch.



578
579
580
581
582
583
584
585
586
# File 'lib/search_flip/index.rb', line 578

def update(scope, options = {}, additional_index_options = {})
  bulk options do |indexer|
    each_record(scope, index_scope: true) do |object|
      indexer.update record_id(object), { doc: serialize(object) }, index_options(object).merge(additional_index_options)
    end
  end

  scope
end

#update_index_settingsBoolean

Updates the index settings within Elasticsearch according to the index settings specified. Raises SearchFlip::ResponseError in case any errors occur.

Returns:

  • (Boolean)

    Returns true or false



380
381
382
# File 'lib/search_flip/index.rb', line 380

def update_index_settings
  connection.update_index_settings(index_name_with_prefix, index_settings)
end

#update_mappingObject

Updates the type mapping within Elasticsearch according to the mapping currently specified. Raises SearchFlip::ResponseError in case any errors occur.



413
414
415
416
417
418
419
# File 'lib/search_flip/index.rb', line 413

def update_mapping
  if include_type_name?
    connection.update_mapping(index_name_with_prefix, { type_name => mapping }, type_name: type_name)
  else
    connection.update_mapping(index_name_with_prefix, mapping)
  end
end

#with_settings(index_name: nil, connection: nil) ⇒ Class

Creates an anonymous class inheriting from the current index with a custom index name and/or connection. This is e.g. useful when working with aliases or proxies.

Examples:

Basic usage

UserIndex.with_settings(index_name: 'new_user_index')
# => #<Class:0x...>

Working with aliases

new_index = UserIndex.with_settings(index_name: 'new_user_index')
new_index.create_index
new_index.import User.all
new_index.connection.update_aliases("...")

Working with proxies

query = UserIndex.with_settings(connection: ProxyConnection).where("...")

Parameters:

  • index_name (String) (defaults to: nil)

    A custom index_name

  • connection (SearchFlip::Connection) (defaults to: nil)

    A custom connection

Returns:

  • (Class)

    An anonymous class



94
95
96
97
98
99
# File 'lib/search_flip/index.rb', line 94

def with_settings(index_name: nil, connection: nil)
  Class.new(self).tap do |klass|
    klass.define_singleton_method(:index_name) { index_name } if index_name
    klass.define_singleton_method(:connection) { connection } if connection
  end
end