Class: Railbow::MigrationParser
- Inherits:
-
Object
- Object
- Railbow::MigrationParser
- 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 change_column_default change_column_null 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 =
Dual-table methods are included here too: their first argument is always a table, even when the second is passed as
to_table:instead of positionally. /\b(?:#{(SINGLE_TABLE_METHODS + DUAL_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
- KEYWORD_TABLE_PATTERN =
Helper/DSL calls that name their target table via a keyword argument, e.g.
convert_to_monthly_partitions(table: :apples)orremove_foreign_key :accounts, to_table: :owners. Catches project-level migration helpers regardless of the method name. Barefrom:/to:are deliberately not matched —change_column_default :users, :status, from: nil, to: "active"would yield phantom tables. /\b(?:table|to_table|from_table):\s*[:"']?(\w+)/- COMMENT_PATTERN =
Ruby
#comments up to end of line, but not#{}interpolation. /#(?!\{).*/- 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,includescollide 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
67 68 69 70 71 72 |
# File 'lib/railbow/migration_parser.rb', line 67 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
74 75 76 77 78 79 80 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 |
# File 'lib/railbow/migration_parser.rb', line 74 def self.extract_tables_from_content(content) return [] if content.nil? || content.empty? # Comments would otherwise produce phantom tables — an explanatory # "# ALTER TABLE foo ..." or a commented-out create_table reads exactly # like the real thing to the patterns below. content = content.gsub(COMMENT_PATTERN, "") 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 content.scan(KEYWORD_TABLE_PATTERN) { |match| tables << match[0] } 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 |