Class: Railbow::MigrationParser

Inherits:
Object
  • Object
show all
Defined in:
lib/railbow/migration_parser.rb

Constant Summary collapse

SINGLE_TABLE_METHODS =

Methods that take a single table name as the first argument

%w[
  create_table drop_table change_table
  add_column remove_column rename_column change_column
  add_index remove_index
  add_reference remove_reference
  add_belongs_to remove_belongs_to
  add_timestamps remove_timestamps
].freeze
DUAL_TABLE_METHODS =

Methods that take two table names as arguments

%w[
  add_foreign_key remove_foreign_key
].freeze
SINGLE_TABLE_PATTERN =
/\b(?:#{SINGLE_TABLE_METHODS.join("|")})\s+[:"](\w+)/
DUAL_TABLE_PATTERN =
/\b(?:#{DUAL_TABLE_METHODS.join("|")})\s+[:"](\w+)["\s,]+[:"](\w+)/
SQL_TABLE_PATTERNS =

SQL keywords followed by a table name

[
  /\bUPDATE\s+["']?(\w+)["']?/i,
  /\bINSERT\s+INTO\s+["']?(\w+)["']?/i,
  /\bDELETE\s+FROM\s+["']?(\w+)["']?/i,
  /\bALTER\s+TABLE\s+["']?(\w+)["']?/i,
  /\bTRUNCATE\s+(?:TABLE\s+)?["']?(\w+)["']?/i,
  /\bDROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?["']?(\w+)["']?/i,
  /\bCREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?/i
].freeze
MODEL_AR_METHODS =

AR class-level query/mutation methods that strongly signal a constant is a model. Kept narrow on purpose — methods like find, all, first, count, create, new, transaction, order, includes collide with stdlib/non-model constants (e.g. Time.new, File.find, Array#first) and would produce false positives.

%w[
  find_each find_in_batches
  find_by find_by!
  find_or_create_by find_or_create_by!
  find_or_initialize_by
  where update_all delete_all destroy_all
  upsert_all insert_all
  pluck pick
  in_batches
].freeze
MODEL_REFERENCE_PATTERN =
/\b([A-Z][A-Za-z0-9]*(?:::[A-Z][A-Za-z0-9]*)*)\.(?:#{MODEL_AR_METHODS.join("|")})\b/

Class Method Summary collapse

Class Method Details

.extract_tables(filepath) ⇒ Object



53
54
55
56
57
58
# File 'lib/railbow/migration_parser.rb', line 53

def self.extract_tables(filepath)
  return [] if filepath.nil? || filepath.empty?
  return [] unless File.exist?(filepath)

  extract_tables_from_content(File.read(filepath))
end

.extract_tables_from_content(content) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/railbow/migration_parser.rb', line 60

def self.extract_tables_from_content(content)
  return [] if content.nil? || content.empty?

  tables = []

  content.scan(SINGLE_TABLE_PATTERN) { |match| tables << match[0] }
  content.scan(DUAL_TABLE_PATTERN) { |match| tables.concat(match) }
  SQL_TABLE_PATTERNS.each do |pattern|
    content.scan(pattern) { |match| tables << match[0] }
  end
  tables.uniq!

  # Model-based detection is heuristic (a constant that happens to respond
  # to one of these methods isn't necessarily an AR model). Only use it to
  # supplement sparse DDL/SQL findings — skip once we already know of ≥2
  # tables, and cap the final total at 2.
  if tables.size < 2
    content.scan(MODEL_REFERENCE_PATTERN) do |match|
      break if tables.size >= 2
      table = ActiveSupport::Inflector.tableize(match[0].split("::").last)
      tables << table unless tables.include?(table)
    end
  end

  tables
end