module ModelSchema::Dumper

Public Class Methods

dump_model_schema(db, path, opts) click to toggle source

Dumps a valid model_schema into the given file path. Accepts options as per the OptionParser above.

# File lib/model_schema/dumper.rb, line 69
def self.dump_model_schema(db, path, opts)
  model = parse_model_file(path)
  abort "In #{path}, couldn't find class that extends Sequel::Model" if !model

  klass = Class.new(Sequel::Model(model[:table_name]))
  klass.db = db
  klass.plugin(ModelSchema::Plugin)

  # dump table generator given by model_schema
  generator = klass.send(:table_generator)
  commands = [generator.dump_columns, generator.dump_constraints,
              generator.dump_indexes].reject{|s| s == ''}.join("\n\n")

  # account for indentation
  tab = opts[:tabbing] == 0 ? "\t" : ' ' * opts[:tabbing]
  schema_indentation = model[:indentation] + tab
  command_indentation = schema_indentation + tab

  commands = commands.lines.map {|l| l == "\n" ? l : command_indentation + l}.join
  commands = commands.gsub('=>', ' => ')

  dump_lines = ["#{schema_indentation}model_schema do\n",
                "#{commands}\n",
                "#{schema_indentation}end\n"]

  lines = model[:lines_before] + dump_lines + model[:lines_after]
  File.write(path, lines.join)
end
parse_model_file(path) click to toggle source

Parses the model file at the given path, returning a hash of the form:

:table_name => the model table name :lines_before => an array of lines before the expected model schema dump :lines_after => an array of lines after the expected model schema dump :indentation => the indentation (leading whitespace) of the model class

Returns nil if the file couldn’t be parsed.

# File lib/model_schema/dumper.rb, line 106
def self.parse_model_file(path)
  lines = File.read(path).lines

  lines.each_with_index do |line, index|
    match = SEQUEL_MODEL_REGEX.match(line)

    if match
      # extract table name as symbol
      table_name = match[1]
      if table_name[0] == ':'
        table_name = table_name[1..-1].to_sym
      else
        abort "In #{path}, can't find a symbol table name in line: #{line}"
      end

      # indentation for model_schema block
      indentation = LEADING_WHITESPACE_REGEX.match(line)[0]

      return {
        :table_name => table_name.to_sym,
        :lines_before => lines[0..index],
        :lines_after => lines[(index + 1)..-1],
        :indentation => indentation,
      }
    end
  end

  nil
end
run(args) click to toggle source

Parses options and then dumps the model schema.

# File lib/model_schema/dumper.rb, line 9
def self.run(args)
  opts = {}
  opts[:tabbing] = 2

  parser = OptionParser.new do |p|
    p.banner = 'Usage: dump_model_schema [options] model_file [model_file ...]'
    p.separator "\nDumps a valid model_schema block in each given model_file.\n\n"

    p.on('-c', '--connection CONNECTION',
         'Connection string for database') do |connection|
      opts[:connection] = connection
    end

    p.on('-t', '--tabbing TABBING', Integer,
              'Number of spaces for tabbing, or 0 for hard tabs') do |tabbing|
      opts[:tabbing] = tabbing
    end

    p.on('-v', '--version', 'Print version') do
      puts ModelSchema::VERSION
      exit
    end

    p.on('-h', '--help', 'Print help') do
      puts parser
      exit
    end
  end

  model_files = parser.parse(args)

  # model and connection are required
  abort 'Must provide at least one model file.' if model_files.empty?
  abort 'Must provide a connection string with -c or --connection.' if !opts[:connection]

  db = Sequel.connect(opts[:connection])
  db.extension(:schema_dumper)

  if db.is_a?(Sequel::Postgres::Database)
    # include all Postgres type extensions so schema dumps are accurate
    db.extension(:pg_array, :pg_enum, :pg_hstore, :pg_inet, :pg_json,
                 :pg_range, :pg_row)
  end

  had_error = false
  model_files.each do |path|
    begin
      dump_model_schema(db, path, opts)
    rescue StandardError, SystemExit => error
      # SystemExit error messages are already printed by abort()
      $stderr.puts error.message if error.is_a?(StandardError)
      had_error = true
    end
  end

  exit 1 if had_error
end