gems-uff/sapos

View on GitHub
lib/generators/migrate_bigint_generator.rb

Summary

Maintainability
C
1 day
Test Coverage
F
0%
# Copyright (c) Universidade Federal Fluminense (UFF).
# This file is part of SAPOS. Please, consult the license terms in the LICENSE file.

# frozen_string_literal: true

require "active_support/inflector"


def columns_to_index(columns)
  if columns.count == 1
    return ":" + columns[0]
  end
  result = columns.collect { |x| ":" + x }.join(", ")
  "[#{result}]"
end

def create_code(result, wrap, foreign_keys, references_by_table, newtype, useoldname)
  wrap.each do |table, schema_tup|
    result << "    remove_index :#{table}, name: \"#{schema_tup[2]}\" if index_exists?(:#{table}, name: \"#{schema_tup[2]}\")"
  end
  foreign_keys.each do |fk, col1, col2, extra|
    sing1 = col1.singularize.capitalize
    sing2 = col2.singularize
    result << "    add_column :#{col1}, :temp_#{sing2}_id, #{newtype}"
    result << "    #{sing1}.update_all(\"temp_#{sing2}_id = #{sing2}_id\")"
    result << "    remove_reference :#{col1}, :#{sing2}"
  end
  result << ""

  references_by_table.each do |table, references|
    result << "    # #{table}"
    references.each do |reftable, fk_attr, columns, oldname|
      if useoldname
        result << "    remove_index :#{reftable}, name: \"#{oldname}\" if index_exists?(:#{reftable}, name: \"#{oldname}\")"
      else
        result << "    remove_index :#{reftable}, name: \"index_#{reftable}_on_#{columns.join("_and_")}\" if index_exists?(:#{reftable}, name: \"index_#{reftable}_on_#{columns.join("_and_")}\")"
      end
    end
    result << "    change_column :#{table}, :id, #{newtype}, unique: true, null: false, auto_increment: true"
    references.each do |reftable, fk_attr, columns, oldname|
      result << "    change_column :#{reftable}, :#{fk_attr}, #{newtype}"
      if useoldname
        result << "    add_index :#{reftable}, #{columns_to_index(columns)} unless index_exists?(:#{reftable}, #{columns_to_index(columns)})"
      else
        result << "    add_index :#{reftable}, #{columns_to_index(columns)}, name: \"#{oldname}\" unless index_exists?(:#{reftable}, #{columns_to_index(columns)})"
      end
    end
    result << ""
  end

  result << ""
  wrap.each do |table, schema_tup|
    result << "    change_column :#{table}, :#{schema_tup[0]}, #{newtype}"
    result << "    add_index :#{table}, #{columns_to_index(schema_tup[1])} unless index_exists?(:#{table}, #{columns_to_index(schema_tup[1])})"
  end
  foreign_keys.each do |fk, col1, col2, extra|
    extra = ", foreign_key: {#{extra.delete_prefix(",")} }" if extra
    sing1 = col1.singularize.capitalize
    sing2 = col2.singularize
    result << "    add_reference :#{col1}, :#{sing2}#{extra}"
    result << "    #{sing1}.update_all(\"#{sing2}_id = temp_#{sing2}_id\")"
    result << "    remove_column :#{col1}, :temp_#{sing2}_id"
  end
end


def create_migration(wrap, references_by_table, add_extra, foreign_keys)
  result = []
  result << "class MigrateIdsToBigint < ActiveRecord::Migration[6.0]"
  result << "  def up"
  create_code(result, wrap, foreign_keys, references_by_table, ":integer, limit: 8", true)
  result << ""
  result << "    # new indexes"
  add_extra.each do |table, fk_attr|
    result << "    add_index :#{table}, #{columns_to_index([fk_attr])} unless index_exists?(:#{table}, #{columns_to_index([fk_attr])})"
  end
  result << "  end"
  result << ""
  result << "  def down"
  result << "    # new indexes"
  add_extra.each do |table, fk_attr|
    result << "    remove_index :#{table}, name: \"index_#{table}_on_#{fk_attr}\" if index_exists?(:#{table}, name: \"index_#{table}_on_#{fk_attr}\")"
  end
  result << ""
  create_code(result, wrap, foreign_keys, references_by_table, ":integer", false)
  result << "  end"
  result << "end"
  result.join($/)
end

namespace :generate do
  task :create_bigint_migration do
    create_migration(wrap, references_by_table, add_extra, foreign_keys)
  end
end

class MigrateBigintGenerator < Rails::Generators::Base
  def create_bigint_migration
    models = {}

    Dir["app/models/*"].each do |name|
      model_name = name.match(/app\/models\/(.*?)\.rb/)[1].pluralize
      indexes = []
      data = File.read(name)
      data.scan(/belongs_to.*?:([\w_]*)(,.*)?/).collect do |match|
        name = match[0]
        table_name = name.pluralize
        fk_attr = name + "_id"
        if match[1]
          classname = match[1].match(/class_name: "(.*?)"/)
          table_name = classname[1].downcase.pluralize if classname
          fk = match[1].match(/foreign_key: "(.*?)"/)
          fk_attr = fk[1] if fk
        end
        indexes << [fk_attr, table_name, name]
      end
      models[model_name] = indexes
    end

    schema = File.read("db/schema.rb")
    tables = {}
    schema.scan(/  create_table "(.*?)".*? do \|t\|(.*?)end\n/m).collect do |match|
      indexes = []
      match[1].scan(/t.index \["(.*?)"\], name: "(.*?)"/).collect do |indexmatch|
        fk_attr = indexmatch[0]
        if indexmatch[0].count(",") > 0
          fk_attr = fk_attr.match(/([\w_]*?id)/)[0]
        end
        columns = indexmatch[0].split(/", "/)
        if fk_attr.end_with? "_id"
          indexes << [fk_attr, columns, indexmatch[1]]
        end
      end
      tables[match[0]] = indexes
    end

    foreign_keys = schema.scan(/(add_foreign_key "(.*?)", "(.*?)"(.*))/).collect(&:to_a)

    wrap = []
    add_extra = []

    references_by_table = Hash[tables.collect { |t, v| [ t, [] ] }]

    tables.each do |table, schema_indexes|
      if model_indexes = models[table]
        # Find relationships that existed in rails, but that did not exist in the database
        model_indexes.each do |fk_attr, table_name, belongs_to|
          unless schema_indexes.any? { |x| x[0] == fk_attr }
            add_extra << [table, fk_attr]
          end
        end
        schema_indexes.each do |schema_tup|
          found = false
          model_indexes.each do |model_tup|
            if model_tup[0] == schema_tup[0]
              found = true
              # build references using fk that exists in both
              references_by_table[model_tup[1]] << [table] + schema_tup
            end
          end
          unless found
            # fk that does not exist in rails
            wrap << [table, schema_tup]
          end
        end
      else
        # puts(key) # carrier_wave_files
      end
    end

    result = create_migration(wrap, references_by_table, add_extra, foreign_keys)
    create_file "db/migrate/#{Time.now.strftime("%Y%m%d%H%M%S")}_migrate_ids_to_bigint.rb", result
  end
end