rom-rb/rom-migrator

View on GitHub
lib/rom/migrator.rb

Summary

Maintainability
A
0 mins
Test Coverage
# encoding: utf-8

require "immutability"
require "logger"
require "rom"

module ROM

  require_relative "migrator/errors"     # gem-specific errors
  require_relative "migrator/logger"     # default logger for migration
  require_relative "migrator/settings"   # adapter-specific settings
  require_relative "migrator/registrar"  # registers migrations
  require_relative "migrator/migration"  # changes the persistence
  require_relative "migrator/migrations" # defines the order of migrations
  require_relative "migrator/source"     # describes sources for migrations
  require_relative "migrator/sources"    # filters sources by their numbers
  require_relative "migrator/generator"  # scaffolds a migration
  require_relative "migrator/class_dsl"  # sets registrar and settings

  # The abstract base class for adapter-specific migrators
  #
  # The migrator stores all adapter-specific settings and provides methods to:
  # - [#apply] migrations
  # - [#reverse] migrations
  # - [#create_file] with migration
  # - declare custom [.migration]
  # - instantiate unnumbered [#migration]
  #
  # @author nepalez <andrew.kozin@gmail.com>
  #
  # @abstract
  #
  class Migrator

    # defines `.settings`, `.registrar` and `.migration`
    # along with helpers customizing settings and registrar.
    extend ClassDSL

    # @private
    attr_reader :gateway, :registrar, :template, :logger, :paths, :counter

    # Initializes the migrator with reference to the gateway.
    #
    # @param [ROM::Gateway] gateway
    # @option options [Array<String>] :paths
    #   Custom paths to migrations
    # @option options [String] :path
    #   Custom path to migrations (alternative to :paths)
    # @option options [::Logger] :logger
    #   The custom logger to be used instead of default (that logs to +$stdout+)
    #
    def initialize(gateway, options = {})
      default    = self.class.settings
      @template  = options.fetch(:template) { default.template }
      @counter   = options.fetch(:counter)  { default.counter }
      @logger    = options.fetch(:logger)   { default.logger }
      @paths     = options[:paths] || [options.fetch(:path) { default.path }]
      @gateway   = gateway
      @registrar = self.class.registrar.new(gateway)
    end

    # Returns a number for the next migration
    #
    # @return [String]
    #
    def next_number
      counter.call(files.last_number).to_s
    end

    # Defines and instantiates unnumbered migration object,
    # using a block to customize the migration's +up+ and +down+ methods.
    #
    # @param [Proc] block The block describing a migration
    # @param [String] number The number for the migration
    #
    # @return [ROM::Migrator::Migration]
    #
    def migration(number = nil, &block)
      klass = self.class.migration(&block)
      klass.new migration_options.merge(number: number || next_number)
    end

    # Applies migrations
    #
    # @param [Hash] options
    # @option options [nil, #to_s] :target The target version
    #
    # @return [undefined]
    #
    def apply(options = {})
      target = options[:target]
      files
        .after_numbers(registrar.registered)
        .upto_number(target)
        .to_migrations(migration_options)
        .apply
    end

    # @!method reverse(target = nil, options = {})
    # Reverses migrations
    #
    # @param [Hash] options
    # @option options [nil, #to_s] :target The target version
    # @option options [Boolean] :allow_missing_files
    #   Whether reversion should continue if some registered files are missed
    #
    # @return [undefined]
    #
    def reverse(options = {})
      target, skip = options.values_at(:target, :allow_missing_files)
      files
        .with_numbers(registrar.registered, !skip)
        .after_numbers(target)
        .to_migrations(migration_options)
        .reverse
    end

    # Generates the migration
    #
    # @param [Hash] options
    # @option (see ROM::Migrator::Generator.call)
    #
    # @return (see ROM::Migrator::Generator.call)
    #
    def create_file(options = {})
      defaults = {
        path:     paths.first,
        logger:   logger,
        template: template,
        number:   next_number
      }
      Generator.call defaults.merge(options)
    end

    private

    def files
      Sources.from_folders(paths)
    end

    def migration_options
      { gateway: gateway, logger: logger, registrar: registrar }
    end

  end # class Migrator

end # module ROM