Module: HasHelpers::Index::ClassMethods

Defined in:
app/lib/has_helpers/index.rb

Constant Summary collapse

EXCLUDED_SEARCH_TYPES =
[:date, :datetime, :uuid]

Instance Method Summary collapse

Instance Method Details

#active_recordObject



533
534
535
536
537
538
539
540
541
# File 'app/lib/has_helpers/index.rb', line 533

def active_record
  @active_record ||= begin
                       ar = Class.new(::ActiveRecord::Base)
                       ar.primary_key = "id"
                       ar.table_name = view_name
                       ar.inheritance_column = nil
                       ar
                     end
end

#all_values_from_attributes(attrs) ⇒ Object



497
498
499
# File 'app/lib/has_helpers/index.rb', line 497

def all_values_from_attributes(attrs)
  normalize_fields(attrs.slice(*columns_to_search).values.compact)
end

#cleanObject

Deletes all the indices that are not currently in use. It does this by checking if they have an alias.



380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'app/lib/has_helpers/index.rb', line 380

def clean
  indices_to_delete = old_indices

  # If clean occurs during a reindex, and it runs in the time between
  # when the new index is created and when it's assigned an alias, then
  # it's possible that it will delete the newly created index which would
  # cause reindexing to fail. We need to make sure that the most recent
  # index is not affected by cleaning.
  keyword_indices = indices.keys.select { |idx_name| idx_name.include?("keyword") }
  non_keyword_indices = indices.keys - keyword_indices
  most_recent_keyword_index_name = keyword_indices.sort.last
  most_recent_index_name = non_keyword_indices.sort.last
  [most_recent_index_name, most_recent_keyword_index_name].each do |most_recent_name|
    if most_recent_name&.match?(/20\d{15}/) # If the most recent index doesn't have a timestamp, it should probably be deleted anyway.
      indices_to_delete = indices_to_delete.reject do |non_aliased_index_name, _|
        non_aliased_index_name == most_recent_name
      end
    end
  end

  indices_to_delete.each { |name, _options| delete(name) }
end

#clean!Object

Raises:



403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
# File 'app/lib/has_helpers/index.rb', line 403

def clean!
  raise Error, "clean! should only be used for testing" unless ::HasUtils::Env.test?

  refresh
  all_deletes = [index_name, keyword_search_index_name].map do |idx_name|
    results = client.search(index: idx_name, body: { query: { match_all: {} } })
    results.fetch("hits", {}).fetch("hits", []).map do |result|
      {
        delete: {
          routing: result["_routing"],
          _index: idx_name,
          _id: result["_id"],
          _type: result["_type"]
        }
      }
    end
  end
  Searchkick.indexer.queue(all_deletes.flatten)
  refresh
end

#columns_to_searchObject



543
544
545
546
547
# File 'app/lib/has_helpers/index.rb', line 543

def columns_to_search
  @columns_to_search ||= active_record.columns.
    reject { |column| EXCLUDED_SEARCH_TYPES.include?(column.type) || column.name =~ /_id[s]?$/ }.
    map { |column| column.name.to_sym }
end

#delete(index) ⇒ Object

Searchkick.client.indices.get_alias(name: "policy_index_production").keys Searchkick.client.indices.delete(index: index)



493
494
495
# File 'app/lib/has_helpers/index.rb', line 493

def delete(index)
  client.indices.delete(index: index)
end

#depends_on(*klasses, **klasses_with_keys) ⇒ Object

Specifies what other models should trigger a reindex, as well as the conditions for triggering one. The to attribute the key to search against the from set. id by default. The from attribute to get the object from. By default model.name.foreign_key e.g. advisor_id or id if dependency_inheritance is present. The dependent_attributes attribute is a list of the attributes that has to be changed to make a reindex The dependency_inheritance attribute is a inheritance list of lists declaring the paths to get to the resource.

Example usage (using ::AdvisorIndex as the example index):

depends_on(::Contract)
This will make it so that any change to a contract that has an advisor_id will cause the advisor to be reindexed.

depends_on("Contract": { dependent_attributes: %w(writing_number advisor_id) })
This will make it so that the advisor is reindexed only when the contract's writing number or advisor id is updated.

Example usage (using ::ContractIndex as the example index):

depends_on("ContractLevel": { dependent_attributes: %w(product_type_id) })
This will make it so that the ContractIndex is reindexed only when the contract level's product_type_id is updated.

depends_on("Carrier": { dependent_attributes: %w(name),
      dependency_inheritance:  [
        <tt>[</tt> { target: "Contract", to: "carrier_id" } <tt>]</tt>
      ]
})
As the Carrier has an indirect reference to a contract we have to set the inheritance to get to it.
This will update the ContractIndex only when the carrier's name is updated through the path defined on the
dependency_inheritance array. In this case this contract will be updated:
Contract.where( carrier_id: "UPDATED_CARRIER_ID" )

depends_on("HasContactInfo::Demographic": { dependent_attributes: %w(nickname ssn),
     dependency_inheritance:  [
       [ { target: "Advisor", to: "demographic_id" },
         { target: "Contract", to: "advisor_id" }
       ]
    ]
})

This will make it so that the Contract is reindex only when (following the inheritance) the Contract's Advisor's nicknamme or ssn are updated

depends_on("Firm": { from: "id", dependent_attributes: %w(name taxid), dependency_inheritance: [ [{ target: "Contract", to: "corporate_id" }] , [{ target: "Contract", to: "broker_dealer_id" }], [{ target: "Contract", to: "firm_id" }] ] }) In this case we have 3 lists inside the dependency_inheritance list. Each will be independet of each other. This will make it so that the Contract is reindexed ony when: 1. The Contract's corporate name or taxid are updated 2. The Contract's broker_dealer name or taxid are updated 3. The Contract's firm name or taxid gets updated



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'app/lib/has_helpers/index.rb', line 362

def depends_on(*klasses, **klasses_with_keys)
  @depends_on ||= [].tap do |dependency_array|
    klasses.each do |dependency_klass|
      klass = dependency_klass.to_s.safe_constantize
      dependency_array << add_dependency(klass: klass)
    end
    klasses_with_keys.each do |klass_name, keys|
      dependency_array << add_dependency(
        klass: klass_name.to_s.safe_constantize,
        from_key: keys[:from], to_key: keys[:to],
        dependent_attributes: keys[:dependent_attributes],
        dependency_inheritance: keys[:dependency_inheritance])
    end
  end
end

#fieldsObject



549
550
551
# File 'app/lib/has_helpers/index.rb', line 549

def fields
  @fields ||= active_record.columns.map { |column| column.name.to_sym }
end

#filtersObject



553
554
555
# File 'app/lib/has_helpers/index.rb', line 553

def filters
  @filters
end

#filters=(filters) ⇒ Object



557
558
559
# File 'app/lib/has_helpers/index.rb', line 557

def filters=(filters)
  @filters = filters.map(&:to_sym)
end

#get_refresh_interval(settings, index_name) ⇒ Object



466
467
468
# File 'app/lib/has_helpers/index.rb', line 466

def get_refresh_interval(settings, index_name)
  settings.fetch(index_name, {}).fetch("settings", {}).fetch("index", {}).fetch("refresh_interval", nil)
end

#get_settings(index) ⇒ Object



455
456
457
458
459
460
# File 'app/lib/has_helpers/index.rb', line 455

def get_settings(index)
  client.indices.get_settings index: index
rescue Elasticsearch::Transport::Transport::Errors::NotFound
  # not found
  {}
end

#global_index!Object



501
502
503
# File 'app/lib/has_helpers/index.rb', line 501

def global_index!
  @is_global = true
end

#heavy_indicesObject



462
463
464
# File 'app/lib/has_helpers/index.rb', line 462

def heavy_indices
  ::HasHelpers::Index.indices.to_a.select { |index| index.is_heavy_index }
end

#index_nameObject



521
522
523
# File 'app/lib/has_helpers/index.rb', line 521

def index_name
  @index_name = [type.tr("/", "_"), ENV["ENV_NAME"] || ::HasUtils::Env.current_environment].join("_")
end

#index_refresh_disabled?(refresh_interval) ⇒ Boolean

Returns:

  • (Boolean)


470
471
472
# File 'app/lib/has_helpers/index.rb', line 470

def index_refresh_disabled?(refresh_interval)
  refresh_interval == "-1"
end

#indexing_allowed?Boolean

Returns:

  • (Boolean)


474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
# File 'app/lib/has_helpers/index.rb', line 474

def indexing_allowed?
  heavy_indices.each do |h_index_name|
    next if h_index_name == name

    indices_with_aliases = indices.select { |_, index| !index["aliases"].empty? }
    next if indices_with_aliases.empty?

    indices_with_aliases.each do |index_name, _|
      settings = get_settings(index_name)
      refresh_interval = get_refresh_interval(settings, index_name)
      return false if index_refresh_disabled?(refresh_interval)
    end
  end

  true
end

#indicesObject



424
425
426
427
428
429
430
# File 'app/lib/has_helpers/index.rb', line 424

def indices
  regex = /\A#{Regexp.escape(index_name)}/
  keyword_regex = /\A#{Regexp.escape("keyword_" + index_name)}/
  client.indices.get_alias.select { |name, _options| name.match?(regex) || name.match?(keyword_regex) }
rescue Elasticsearch::Transport::Transport::Errors::NotFound
  {}
end

#is_global?Boolean

Returns:

  • (Boolean)


505
506
507
# File 'app/lib/has_helpers/index.rb', line 505

def is_global?
  @is_global
end

#is_heavy_index(value = false) ⇒ Object



268
269
270
# File 'app/lib/has_helpers/index.rb', line 268

def is_heavy_index(value = false)
  @is_heavy_index ||= value
end

#keyword_reindex_queueObject



302
303
304
# File 'app/lib/has_helpers/index.rb', line 302

def keyword_reindex_queue
  ::HasHelpers::ReindexQueue.new(keyword_search_index_name)
end

#keyword_search_field_regexObject



565
566
567
568
569
# File 'app/lib/has_helpers/index.rb', line 565

def keyword_search_field_regex
  # Sorting by longest length to avoid fields whose names are part of another field from taking priority.
  # For example, if name was before code_name in the regex, then code_name:test would match on name:test
  @keyword_search_field_regex ||= columns_to_search.sort_by(&:length).reverse.join(":|") + ":"
end

#keyword_search_index_nameObject



517
518
519
# File 'app/lib/has_helpers/index.rb', line 517

def keyword_search_index_name
  @keyword_search_index_name = ["keyword", type.tr("/", "_"), ENV["ENV_NAME"] || ::HasUtils::Env.current_environment].join("_")
end

#modelObject



306
307
308
# File 'app/lib/has_helpers/index.rb', line 306

def model
  @model ||= name.remove("Index").constantize
end

#old_indicesObject

Returns all indices without an alias. PolicyIndex.old_indices



434
435
436
# File 'app/lib/has_helpers/index.rb', line 434

def old_indices
  indices.select { |_name, options| options["aliases"].blank? }
end

#promote(index) ⇒ Object

Promotes an index for searching.



439
440
441
442
443
444
445
446
447
448
449
# File 'app/lib/has_helpers/index.rb', line 439

def promote(index)
  index_alias = index.include?("keyword") ? keyword_search_index_name : index_name
  old_indices =
    begin
      client.indices.get_alias(name: index_alias).keys
    rescue Elasticsearch::Transport::Transport::Errors::NotFound
      {}
    end
  actions = old_indices.map { |old_name| { remove: { index: old_name, alias: index_alias } } } + [{ add: { index: index, alias: index_alias } }]
  client.indices.update_aliases body: { actions: actions }
end

#refreshObject



276
277
278
279
# File 'app/lib/has_helpers/index.rb', line 276

def refresh
  client.indices.refresh index: index_name
  client.indices.refresh index: keyword_search_index_name
end

#reindexObject



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
# File 'app/lib/has_helpers/index.rb', line 281

def reindex
  ::ActiveRecord::Base.connection.execute <<-SQL
    DROP VIEW IF EXISTS #{view_name};
    CREATE VIEW #{view_name} AS #{self.sql};
  SQL
  clean
  new_name = "#{index_name}_#{Time.zone.now.strftime('%Y%m%d%H%M%S%L')}"
  new_keyword_index_name = "#{keyword_search_index_name}_#{Time.zone.now.strftime('%Y%m%d%H%M%S%L')}"
  # Create the new index, with refresh turned off for faster reindexing.
  create(new_name, settings.deep_merge!(settings: { index: { refresh_interval: -1 } }))
  create(new_keyword_index_name, keyword_index_settings.deep_merge!(settings: { index: { refresh_interval: -1 } }))
  client.indices.put_settings(body: { index: { blocks: { read_only_allow_delete: false } } })
  ElasticsearchReindex.perform_async(self.name, new_name)
  ElasticsearchReindex.perform_async(self.name, new_keyword_index_name)
  "#{new_name} #{new_keyword_index_name}"
end

#reindex_queueObject



298
299
300
# File 'app/lib/has_helpers/index.rb', line 298

def reindex_queue
  ::HasHelpers::ReindexQueue.new(index_name)
end

#search(query, **options) ⇒ Object



272
273
274
# File 'app/lib/has_helpers/index.rb', line 272

def search(query, **options)
  HasHelpers::Index.search(query, indexes: [self], **options)
end

#show_end_dated_records!Object



509
510
511
# File 'app/lib/has_helpers/index.rb', line 509

def show_end_dated_records!
  @show_end_dated_records = true
end

#show_end_dated_records?Boolean

Returns:

  • (Boolean)


513
514
515
# File 'app/lib/has_helpers/index.rb', line 513

def show_end_dated_records?
  @show_end_dated_records || false
end

#sql(sql = nil, &block) ⇒ Object



529
530
531
# File 'app/lib/has_helpers/index.rb', line 529

def sql(sql = nil, &block)
  @sql ||= sql || (block && block.call)
end

#typeObject



525
526
527
# File 'app/lib/has_helpers/index.rb', line 525

def type
  name.underscore
end

#update_settings(index, settings) ⇒ Object



451
452
453
# File 'app/lib/has_helpers/index.rb', line 451

def update_settings(index, settings)
  client.indices.put_settings index: index, body: settings
end

#view_nameObject



561
562
563
# File 'app/lib/has_helpers/index.rb', line 561

def view_name
  "#{name.underscore.tr("/", "_")}_view"
end

#weighted_attr(attr_name = :updated_at) ⇒ Object



264
265
266
# File 'app/lib/has_helpers/index.rb', line 264

def weighted_attr(attr_name = :updated_at)
  @weighted_attr ||= attr_name
end