Class: HasGeoLookup::IndexChecker

Inherits:
Object
  • Object
show all
Defined in:
lib/has_geo_lookup/index_checker.rb

Overview

Utility class for checking and recommending database indexes for optimal performance

This class analyzes models that include HasGeoLookup and provides recommendations for database indexes to optimize geographic queries. It can detect missing indexes and optionally create them automatically.

Examples:

Check indexes for all models

HasGeoLookup::IndexChecker.analyze_all_models

Check indexes for a specific model

HasGeoLookup::IndexChecker.check_model(Listing)

Create missing indexes

HasGeoLookup::IndexChecker.create_missing_indexes(Listing)

Constant Summary collapse

REQUIRED_COLUMNS =

Required columns for HasGeoLookup functionality

[
  { name: :latitude, type: :decimal, precision: 10, scale: 6 },
  { name: :longitude, type: :decimal, precision: 10, scale: 6 }
].freeze
{
  coordinate_indexes: [
    { columns: [:latitude, :longitude], name: 'coordinates' },
    { columns: [:latitude], name: 'latitude' },
    { columns: [:longitude], name: 'longitude' }
  ],
  geo_attribute_indexes: [
    { columns: [:country], name: 'country' },
    { columns: [:state_or_province], name: 'state_or_province' },
    { columns: [:city], name: 'city' },
    { columns: [:postal_code], name: 'postal_code' }
  ]
}.freeze

Class Method Summary collapse

Class Method Details

.analyze_all_modelsHash

Analyze all models that include HasGeoLookup

Scans the application for models that include HasGeoLookup and analyzes their index coverage for geographic operations.

Examples:

results = HasGeoLookup::IndexChecker.analyze_all_models
# => {
#   "Listing" => {
#     missing_indexes: 2,
#     recommendations: ["add_index :listings, [:latitude, :longitude]"],
#     table_name: "listings"
#   }
# }

Returns:

  • (Hash)

    Summary of analysis results by model



58
59
60
61
62
63
64
65
66
67
# File 'lib/has_geo_lookup/index_checker.rb', line 58

def analyze_all_models
  results = {}
  
  # Find all models that include HasGeoLookup
  models_with_geo_lookup.each do |model|
    results[model.name] = check_model(model)
  end
  
  results
end

.check_missing_columns(model) ⇒ Array<Hash>

Check for missing required columns in a model

Parameters:

  • model (Class)

    ActiveRecord model class

Returns:

  • (Array<Hash>)

    Array of missing column definitions



132
133
134
135
136
# File 'lib/has_geo_lookup/index_checker.rb', line 132

def check_missing_columns(model)
  REQUIRED_COLUMNS.reject do |col_def|
    model.column_names.include?(col_def[:name].to_s)
  end
end

.check_model(model) ⇒ Hash

Check index coverage for a specific model

Analyzes the database indexes for a model and compares them against the recommended indexes for HasGeoLookup functionality.

Examples:

analysis = HasGeoLookup::IndexChecker.check_model(Listing)
puts analysis[:missing_indexes].length
puts analysis[:recommendations]

Parameters:

  • model (Class)

    ActiveRecord model class

Returns:

  • (Hash)

    Analysis results with missing indexes and recommendations



81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/has_geo_lookup/index_checker.rb', line 81

def check_model(model)
  return { error: "Model does not include HasGeoLookup" } unless model.include?(HasGeoLookup)
  
  table_name = model.table_name
  existing_indexes = get_existing_indexes(table_name)
  missing_columns = check_missing_columns(model)
  missing_indexes = []
  recommendations = []
  
  # Check coordinate indexes (always recommended, but only if columns exist or will be created)
  RECOMMENDED_INDEXES[:coordinate_indexes].each do |index_def|
    # Check if all required columns exist or will be added
    columns_available = index_def[:columns].all? do |col|
      model.column_names.include?(col.to_s) || missing_columns.any? { |mc| mc[:name] == col }
    end
    
    next unless columns_available
    
    unless has_index?(existing_indexes, index_def[:columns])
      missing_indexes << index_def
      recommendations << generate_index_command(table_name, index_def)
    end
  end
  
  # Check geo attribute indexes (only for columns that exist)
  RECOMMENDED_INDEXES[:geo_attribute_indexes].each do |index_def|
    columns = index_def[:columns].select { |col| model.column_names.include?(col.to_s) }
    next if columns.empty?
    
    unless has_index?(existing_indexes, columns)
      index_def_with_existing_cols = index_def.merge(columns: columns)
      missing_indexes << index_def_with_existing_cols
      recommendations << generate_index_command(table_name, index_def_with_existing_cols)
    end
  end
  
  {
    table_name: table_name,
    missing_columns: missing_columns.length,
    missing_column_details: missing_columns,
    missing_indexes: missing_indexes.length,
    missing_index_details: missing_indexes,
    recommendations: recommendations,
    existing_indexes: existing_indexes.map { |idx| idx.columns.sort }
  }
end

.generate_index_migration(model = nil) ⇒ String

Generate a Rails migration file for creating missing columns and indexes

Creates a timestamped migration file containing all missing columns and indexes for optimal HasGeoLookup performance. This is the recommended approach for production use as it maintains proper migration history and version control.

Examples:

Generate migration for a specific model

HasGeoLookup::IndexChecker.generate_index_migration(Listing)

Generate migration for all models with missing columns/indexes

HasGeoLookup::IndexChecker.generate_index_migration

Parameters:

  • model (Class) (defaults to: nil)

    ActiveRecord model class, or nil for all models

Returns:

  • (String)

    Path to the generated migration file, or nil if nothing needed



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/has_geo_lookup/index_checker.rb', line 153

def generate_index_migration(model = nil)
  if model
    models_to_check = { model.name => check_model(model) }
  else
    models_to_check = analyze_all_models
  end
  
  models_with_missing = models_to_check.select do |_, analysis|
    (analysis[:missing_columns] || 0) > 0 || (analysis[:missing_indexes] || 0) > 0
  end
  
  if models_with_missing.empty?
    puts "✓ All models have optimal columns and indexes for HasGeoLookup"
    return nil
  end
  
  # Generate migration content
  migration_name = "add_has_geo_lookup_setup"
  migration_name += "_for_#{model.name.underscore}" if model
  
  migration_content = generate_migration_content(models_with_missing)
  migration_path = create_migration_file(migration_name, migration_content)
  
  puts "✓ Generated migration: #{migration_path}"
  puts "Run 'rails db:migrate' to apply the columns and indexes"
  
  migration_path
end

.performance_reportString

Generate a performance report for HasGeoLookup usage

Creates a comprehensive report showing index coverage across all models that use HasGeoLookup functionality.

Returns:

  • (String)

    Formatted report suitable for console output



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/has_geo_lookup/index_checker.rb', line 188

def performance_report
  results = analyze_all_models
  
  if results.empty?
    return "No models found that include HasGeoLookup"
  end
  
  report = []
  report << "=" * 80
  report << "HasGeoLookup Performance Analysis"
  report << "=" * 80
  
  total_missing = 0
  
  results.each do |model_name, analysis|
    report << "\n#{model_name} (table: #{analysis[:table_name]})"
    report << "-" * 40
    
    missing_columns = analysis[:missing_columns] || 0
    missing_indexes = analysis[:missing_indexes] || 0
    
    if missing_columns.zero? && missing_indexes.zero?
      report << "✓ All recommended columns and indexes present"
    else
      if missing_columns > 0
        total_missing += missing_columns
        report << "#{missing_columns} missing column#{'s' if missing_columns > 1}: #{analysis[:missing_column_details].map { |c| c[:name] }.join(', ')}"
      end
      
      if missing_indexes > 0
        total_missing += missing_indexes
        report << "#{missing_indexes} missing index#{'es' if missing_indexes > 1}"
        report << "\nRecommendations:"
        analysis[:recommendations].each do |rec|
          report << "  #{rec}"
        end
      end
    end
    
    report << "\nExisting indexes: #{analysis[:existing_indexes].join(', ')}" if analysis[:existing_indexes].any?
  end
  
  report << "\n" + "=" * 80
  report << "Summary: #{total_missing} missing columns/indexes across #{results.length} model#{'s' if results.length != 1}"
  
  if total_missing > 0
    report << "\nTo create missing columns and indexes, run:"
    report << "  rake has_geo_lookup:create_setup"
  end
  
  report << "=" * 80
  
  report.join("\n")
end