Class: Idxfence::SchemaParser
- Inherits:
-
Object
- Object
- Idxfence::SchemaParser
- Defined in:
- lib/idxfence/schema_parser.rb
Overview
Parses db/schema.rb (Rails' generated, declarative snapshot of the
database schema) with Ripper looking for each create_table block's
boundary structurally, then a narrower regex pass over that block's
own text extracting every t.index [...], unique: true line -- the
same hybrid technique Idxfence::ModelParser uses on model files. See
DETAILS.md's "two passes" section for why this has to be a second,
separate pass over a different file rather than something
ModelParser could infer on its own.
Returns a Hash of table_name => Array<Arraycreate_table block, each an
array of the index's own column names in the order t.index declared
them (order doesn't matter for matching -- Checker compares as sets).
A non-unique t.index (no unique: true) is not collected at all --
it can't enforce uniqueness at the database level regardless of what
columns it covers.
Defined Under Namespace
Classes: MissingSchemaError
Constant Summary collapse
- CREATE_TABLE_IDENT =
"create_table".freeze
- TABLE_NAME_LINE =
/create_table\s+["']([^"']+)["']/.freeze
- INDEX_LINE =
/t\.index\s+\[([^\]]*)\](?:[^\n]*?unique:\s*true)?/.freeze
Class Method Summary collapse
Instance Method Summary collapse
- #call ⇒ Object
-
#initialize(path:) ⇒ SchemaParser
constructor
A new instance of SchemaParser.
Constructor Details
#initialize(path:) ⇒ SchemaParser
Returns a new instance of SchemaParser.
33 34 35 |
# File 'lib/idxfence/schema_parser.rb', line 33 def initialize(path:) @path = path end |
Class Method Details
.parse(path) ⇒ Object
27 28 29 30 31 |
# File 'lib/idxfence/schema_parser.rb', line 27 def self.parse(path) raise MissingSchemaError, "#{path}: no such file" unless File.exist?(path) new(path: path).call end |
Instance Method Details
#call ⇒ Object
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/idxfence/schema_parser.rb', line 37 def call source = File.read(path) sexp = Ripper.sexp(source) raise MissingSchemaError, "#{path}: could not parse (invalid Ruby syntax)" unless sexp lines = source.lines tables = Hash.new { |h, k| h[k] = [] } each_create_table_block(sexp) do |block_node| start_line = min_line(block_node) end_line = max_line(block_node) next unless start_line && end_line block_source = lines[(start_line - 1)...end_line].join table_name = (m = block_source.match(TABLE_NAME_LINE)) && m[1] next unless table_name block_source.each_line do |line| next unless line =~ /t\.index\b/ idx_match = line.match(INDEX_LINE) next unless idx_match next unless line.match?(/unique:\s*true/) columns = idx_match[1].scan(/["']([^"']+)["']/).flatten tables[table_name] << columns unless columns.empty? end end tables end |