phobos/phobos_db_checkpoint

View on GitHub
lib/phobos_db_checkpoint/cli.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'thor'
require 'fileutils'

module PhobosDBCheckpoint
  module CLI
    # rubocop:disable Metrics/ClassLength, Metrics/LineLength
    class Commands < Thor
      include Thor::Actions

      map '-v' => :version
      map '--version' => :version

      desc 'version', 'Outputs the version number. Can be used with: phobos_db_checkpoint -v or phobos_db_checkpoint --version'
      def version
        puts PhobosDBCheckpoint::VERSION
      end

      desc 'init', 'Initialize your project with PhobosDBCheckpoint'
      # rubocop:disable Metrics/AbcSize
      def init
        create_file('Rakefile') unless File.exist?(File.join(destination_root, 'Rakefile'))
        prepend_to_file 'Rakefile', "require 'phobos_db_checkpoint'\nPhobosDBCheckpoint.load_tasks\n"
        copy_file 'templates/database.yml.example', 'config/database.yml'

        cmd = self.class.new
        cmd.destination_root = destination_root
        cmd.invoke(:copy_migrations)

        create_file('phobos_boot.rb') unless File.exist?(File.join(destination_root, 'phobos_boot.rb'))
        append_to_file 'phobos_boot.rb', File.read(phobos_boot_template)
      end
      # rubocop:enable Metrics/AbcSize

      desc 'copy-migrations', 'Copy required migrations to the project'
      option :destination,
             aliases: ['-d'],
             default: 'db/migrate',
             banner: 'Destination folder relative to your project'
      option :config,
             aliases: ['-c'],
             default: 'config/database.yml',
             banner: 'Database configuration relative to your project'
      # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
      def copy_migrations
        ENV['DB_CONFIG'] = options[:config] if options[:config]

        PhobosDBCheckpoint.configure unless active_connection?

        destination_fullpath = File.join(destination_root, options[:destination])
        generated_migrations = list_migrations(destination_fullpath)
        FileUtils.mkdir_p(destination_fullpath)
        file_path = nil
        template_migrations_metadata.each do |metadata|
          if migration_exists?(generated_migrations, metadata[:name])
            say_status('exists', metadata[:name])
          else
            file_path = File.join(options[:destination], "#{metadata[:number]}_#{metadata[:name]}")
            template_path = File.join('templates/migrate', metadata[:path])
            template(template_path, file_path)
          end
        end
      rescue StandardError
        FileUtils.rm_f(file_path.to_s)
        raise
      end
      # rubocop:enable Metrics/MethodLength, Metrics/AbcSize

      desc 'migration NAME', 'Generates a new migration with the given name. Use underlines (_) as a separator, ex: add_new_column'
      option :destination,
             aliases: ['-d'],
             default: 'db/migrate',
             banner: 'Destination folder relative to your project'
      def migration(name)
        migration_name = name.gsub(/[^\w]*/, '')
        @new_migration_class_name = migration_name.split('_').map(&:capitalize).join('')
        file_name = "#{migration_number}_#{migration_name}.rb"
        destination_fullpath = File.join(destination_root, options[:destination], file_name)
        template(new_migration_template, destination_fullpath)
      end

      desc 'init-events-api', 'Initialize your project with events API'
      def init_events_api
        copy_file 'templates/config.ru', 'config.ru'
        say '   Start the API with: `rackup config.ru`'
      end

      def self.source_root
        File.expand_path(File.join(File.dirname(__FILE__), '../..'))
      end

      private

      def migration_exists?(list, name)
        list.find { |filename| filename =~ /#{name}/ }
      end

      def migration_number(index = 0)
        [Time.now.utc.strftime('%Y%m%d%H%M%S%6N'), format('%.21d', index)].max
      end

      def template_migrations_metadata
        @template_migrations_metadata ||= begin
          index = 0
          template_migrations.map do |path|
            index += 1
            { path: path, name: path.gsub(/\.erb$/, ''), number: migration_number(index) }
          end
        end
      end

      def template_migrations
        @template_migrations ||= list_migrations(migrations_template_dir)
      end

      def list_migrations(dir)
        return [] unless Dir.exist?(dir)

        Dir.entries(dir).select { |f| f =~ /\.rb(\.erb)?$/ }.sort
      end

      def migrations_template_dir
        File.join(self.class.source_root, 'templates/migrate')
      end

      def phobos_boot_template
        File.join(self.class.source_root, 'templates/phobos_boot.rb')
      end

      def new_migration_template
        File.join(self.class.source_root, 'templates/new_migration.rb.erb')
      end

      def active_connection?
        ActiveRecord::Base
          .connection_pool
          .with_connection(&:active?)
      rescue StandardError
        false
      end
    end
    # rubocop:enable Metrics/ClassLength, Metrics/LineLength
  end
end