class Dbdoc::Manager

Dbdoc::Manager class manages database schema documentation.

It knows how to generate and update documentation based on the database schema generated by the schema query for user's database.

Public Class Methods

new(local_path: Dir.pwd) click to toggle source
# File lib/dbdoc/manager.rb, line 14
def initialize(local_path: Dir.pwd)
  @local_path = local_path
  @config = Dbdoc::Config.new(local_path: local_path).load
end

Public Instance Methods

apply() click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/dbdoc/manager.rb, line 55
def apply
  puts "--> APPLY"
  puts
  puts

  input_schema = read_input_schema.map { |r| r.first(4) }.map { |r| r.join(":") }
  current_schema = read_documented_schema

  added_columns = input_schema - current_schema
  dropped_columns = current_schema - input_schema

  doc_folder = File.join(@local_path, "doc")

  Dir.mkdir(doc_folder) unless Dir.exist?(doc_folder)

  ## DROP COLUMNS
  dropped_columns.each do |column|
    schema_name, table_name, column_name = column.split(":")

    columns_file = File.join(doc_folder, schema_name, table_name, "columns.yml")
    next unless File.exist?(columns_file)

    columns = YAML.load(File.read(columns_file))
    columns.reject! { |c| c[:name] == column_name }
    columns.each { |c| c[:description].strip! }

    File.open(columns_file, "w") do |f|
      f.puts(columns.to_yaml)
    end
  end

  ## DROP EMPTY TABLES
  Dir.entries(doc_folder).each do |schema_name|
    next if schema_name == "."
    next if schema_name == ".."

    schema_folder = File.join(doc_folder, schema_name)
    next unless File.directory?(File.join(doc_folder, schema_name))

    Dir.entries(schema_folder).each do |table_name|
      next if table_name == "."
      next if table_name == ".."

      table_folder = File.join(schema_folder, table_name)
      next unless File.directory?(table_folder)

      columns_file = File.join(table_folder, "columns.yml")
      next unless File.exist?(columns_file)

      columns = YAML.load(File.read(columns_file))

      if columns.empty?
        puts "--> DELETING #{schema_name}.#{table_name}"
        FileUtils.rm_rf(table_folder)
      end
    end
  end

  ## DROP EMPTY SCHEMAS
  Dir.entries(doc_folder).each do |schema_name|
    next if schema_name == "."
    next if schema_name == ".."

    schema_folder = File.join(doc_folder, schema_name)
    next unless File.directory?(schema_folder)

    FileUtils.rm_rf(schema_folder) if Dir.empty?(schema_folder)
  end

  create_new_columns(added_columns)
end
plan() click to toggle source
# File lib/dbdoc/manager.rb, line 19
def plan
  input_schema = read_input_schema.map { |r| r.first(4).join(":") }
  current_schema = read_documented_schema

  {
    new_columns: input_schema - current_schema,
    columns_to_drop: current_schema - input_schema
  }
end
query() click to toggle source
# File lib/dbdoc/manager.rb, line 47
def query
  db_type = @config["db"]["type"]
  query_file = File.join(File.expand_path(__dir__), "../..", "config", "schema_queries", "#{db_type}.sql")

  File.read(query_file)
end
todo() click to toggle source
# File lib/dbdoc/manager.rb, line 29
def todo
  doc_folder_files = File.join(@local_path, "doc", "**/*")

  Dir[doc_folder_files].each do |file|
    next if file == "."
    next if file == ".."
    next if File.directory?(file)

    File.read(file).split("\n").each_with_index do |line, i|
      next unless line.include?("TODO")

      relative_path = file.gsub(@local_path, "")

      puts "#{relative_path}:#{i + 1}"
    end
  end
end

Private Instance Methods

create_new_columns(added_columns) click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize

# File lib/dbdoc/manager.rb, line 213
def create_new_columns(added_columns)
  doc_folder = File.join(@local_path, "doc")

  added_columns.map! { |r| r.split(":") }
  new_columns = read_input_schema.select do |row|
    added_columns.any? { |r| r == row.first(4) }
  end

  schemas = new_columns.group_by(&:first)

  schemas_and_tables = schemas.each_with_object({}) do |(schema_name, tables), o|
    tables.map(&:shift)

    o[schema_name] = tables.group_by(&:first)
  end

  schemas_and_tables.each do |schema_name, tables|
    schema_folder = File.join(doc_folder, schema_name)

    Dir.mkdir(schema_folder) unless Dir.exist?(schema_folder)

    tables.each do |table_name, columns|
      # 1. create table folder
      table_folder = File.join(schema_folder, table_name)

      Dir.mkdir(table_folder) unless Dir.exist?(table_folder)

      # 2. create examples folder with test example
      table_example_folder = File.join(table_folder, "examples")

      Dir.mkdir(table_example_folder) unless Dir.exist?(table_example_folder)

      # 2a. create example file
      example_file = File.join(table_example_folder, "1_example.md")
      example_table_example_file = File.join(DBDOC_HOME, "doc_files", "table_example.md")

      FileUtils.cp(example_table_example_file, example_file)

      # 3. create table description.md
      table_description_file = File.join(table_folder, "description.md")

      example_table_description_file = File.join(DBDOC_HOME, "doc_files", "table_description.md")
      FileUtils.cp(example_table_description_file, table_description_file)

      # 4. create table columns.yml
      columns_yaml = File.join(table_folder, "columns.yml")

      next if File.exist?(columns_yaml)

      columns_erb_tamplate_file = File.join(DBDOC_HOME, "doc_files", "columns.yml.erb")
      columns_yaml_template = ERB.new(File.read(columns_erb_tamplate_file), nil, "-")
      File.open(columns_yaml, "w") do |f|
        f.puts columns_yaml_template.result_with_hash({
          columns: columns
        })
      end
    end
  end
end
input_schema() click to toggle source

rubocop:enable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/dbdoc/manager.rb, line 130
def input_schema
  File.read(File.join(@local_path, "schema", "schema.csv"))
end
read_documented_schema() click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/dbdoc/manager.rb, line 172
def read_documented_schema
  doc_folder = File.join(@local_path, "doc")

  return [] unless Dir.exist?(doc_folder)
  return [] if Dir.empty?(doc_folder)

  keys = []
  Dir.entries(doc_folder).each do |schema_name|
    next if schema_name == "."
    next if schema_name == ".."

    schema_folder = File.join(doc_folder, schema_name)
    next unless File.directory?(schema_folder)

    Dir.entries(schema_folder).each do |table_name|
      next if table_name == "."
      next if table_name == ".."

      table_folder = File.join(schema_folder, table_name)
      next unless File.directory?(table_folder)

      columns_file = File.join(table_folder, "columns.yml")
      next unless File.exist?(columns_file)

      columns = YAML.load(File.read(columns_file), [Symbol])
      columns.each do |column|
        keys.push([
          schema_name,
          table_name,
          column[:name],
          column[:type]
        ].join(":"))
      end
    end
  end

  keys
end
read_input_schema() click to toggle source

rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity

# File lib/dbdoc/manager.rb, line 135
def read_input_schema
  rows = input_schema.split("\n")
  with_header = rows[0].include?("table_schema")

  rows.shift if with_header

  rows.map! do |r|
    r.split(",").map(&:strip).map { |c| c.gsub('"', "") }.first(5)
  end

  @config["ignorelist"]&.map { |r| r.split(/[\.\#]/) }&.each do |b|
    schema_pattern, table_pattern, column_pattern = b

    rows.reject! do |row|
      schema_name, table_name, column_name, = row

      if column_pattern
        next unless column_name =~ Regexp.new(column_pattern.gsub("*", ".*"))
      end

      if table_pattern
        next unless table_name =~ Regexp.new(table_pattern.gsub("*", ".*"))
      end

      if schema_pattern
        next unless schema_name =~ Regexp.new(schema_pattern.gsub("*", ".*"))
      end

      true
    end
  end

  rows
end