class MigrationBundler::CLI

Attributes

project[R]

Public Class Methods

source_root() click to toggle source

Configures root path for resources (e.g. templates)

# File lib/migration_bundler/cli.rb, line 18
def self.source_root
  File.dirname(__FILE__)
end
start(given_args = ARGV, config = {}) click to toggle source

Hook into the command execution for dynamic task configuration

Calls superclass method
# File lib/migration_bundler/cli.rb, line 315
def self.start(given_args = ARGV, config = {})
  if File.exists?(Dir.pwd + '/.migration_bundler.yml')
    project = MigrationBundler::Project.load
    project.database_target_class.register_with_cli(self)
  end
  super
end

Public Instance Methods

config(key = nil, value = nil) click to toggle source
# File lib/migration_bundler/cli.rb, line 298
def config(key = nil, value = nil)
  if key && value
    project.config[key] = value
    project.save!(Dir.pwd)
  elsif key
    value = project.config[key]
    if value
      say "#{key}=#{value}"
    else
      say "No value for key '#{key}'"
    end
  else
    project.config.each { |key, value| say "#{key}=#{value}" }
  end
end
drop() click to toggle source
# File lib/migration_bundler/cli.rb, line 102
def drop
  @project = MigrationBundler::Project.load
  invoke(project.database_target_class, :drop, [], options)
end
dump() click to toggle source
# File lib/migration_bundler/cli.rb, line 90
def dump
  @project = MigrationBundler::Project.load
  invoke(project.database_target_class, :dump, [], options)
end
generate() click to toggle source
# File lib/migration_bundler/cli.rb, line 231
def generate
  project = MigrationBundler::Project.load
  invoke(project.database_target_class, :generate, [], options)
  target_names = options['targets'] || project.targets
  MigrationBundler::Util.target_classes_named(target_names) do |target_class|
    say "Invoking target '#{target_class.name}'..."
    invoke(target_class, :generate, [], options)
  end
end
init(path) click to toggle source
# File lib/migration_bundler/cli.rb, line 30
def init(path)
  if File.exists?(path)
    raise Error, "Cannot create repository: regular file exists at path '#{path}'" unless File.directory?(path)
    raise Error, "Cannot create repository into non-empty path '#{path}'" if File.directory?(path) && Dir.entries(path) != %w{. ..}
  end
  self.destination_root = File.expand_path(path)
  empty_directory('.')
  inside(destination_root) { git init: '-q' }

  # hydrate the project
  project_name = options['name'] || File.basename(path)
  sanitized_options = options.reject { |k,v| %w{bundler pretend database}.include?(k) }
  sanitized_options[:name] = project_name
  sanitized_options[:database_url] = options[:database] || "sqlite:#{project_name}.sqlite"
  @project = MigrationBundler::Project.set(sanitized_options)

  # generate_gitignore
  template('templates/gitignore.erb', ".gitignore")
  git_add '.gitignore'

  # generate_config
  create_file '.migration_bundler.yml', YAML.dump(sanitized_options)
  git_add '.migration_bundler.yml'

  # generate_gemfile
  if options['bundler']
    template('templates/Gemfile.erb', "Gemfile")
  end

  # init_targets
  project = MigrationBundler::Project.set(sanitized_options)
  target_options = options.merge('name' => project_name)
  MigrationBundler::Util.target_classes_named(options[:targets]) do |target_class|
    say "Initializing target '#{target_class.name}'..."
    invoke(target_class, :init, [], target_options)
  end
  project.config['db.dump_tables'] = %w{schema_migrations}
  project.save!(destination_root) unless options['pretend']
  git_add '.migration_bundler.yml'

  # Run after targets in case they modify the Gemfile
  # run_bundler
  if options['bundler']
    git_add "Gemfile"
    bundle
    git_add "Gemfile.lock"
  end

  # touch_database
  create_file(project.schema_path)
  git_add project.schema_path

  # init_database_adapter
  empty_directory('migrations')
  inside do
    invoke(project.database_target_class, :init, [], target_options)
  end
end
load() click to toggle source
# File lib/migration_bundler/cli.rb, line 96
def load
  @project = MigrationBundler::Project.load
  invoke(project.database_target_class, :load, [], options)
end
migrate(version = nil) click to toggle source
# File lib/migration_bundler/cli.rb, line 152
def migrate(version = nil)
  project = MigrationBundler::Project.load

  if migrations.up_to_date?
    say "Database is up to date."
    return
  end

  target_version = version || migrations.latest_version
  if database.migrations_table?
    say "Migrating from #{database.current_version} to #{target_version}"
  else
    say "Migrating new database to #{target_version}"
  end
  say

  with_padding do
    say "Migrating database..."
    say
    with_padding do
      migrations.pending do |version, path|
        say "applying migration: #{path}", :green
        begin
          database.execute_migration(File.read(path))
          database.insert_version(version)
        rescue project.database_class.exception_class => exception
          fail Error, "Failed loading migration: #{exception}"
        end
      end
    end
    say
  end

  say "Migration to version #{target_version} complete."

  if options['dump']
    say
    invoke :dump, [], options
  end
end
new(name) click to toggle source
# File lib/migration_bundler/cli.rb, line 108
def new(name)
  @project = MigrationBundler::Project.load
  empty_directory('migrations')
  invoke(project.database_target_class, :new, [name], options)
end
package() click to toggle source
# File lib/migration_bundler/cli.rb, line 244
def package
  validate
  say
  generate
  say

  git_add '.'
  git :status unless options['quiet']

  show_diff = options['diff'] != false && (options['diff'] || ask("Review package diff?", limited_to: %w{y n}) == 'y')
  git diff: '--cached' if show_diff

  commit = options['commit'] != false && (options['commit'] || ask("Commit package artifacts?", limited_to: %w{y n}) == 'y')
  if commit
    tag = unique_tag_for_version(migrations.latest_version)
    git commit: "#{options['quiet'] && '-q '}-m 'Packaging release #{tag}' ."
    git tag: "#{tag}"
  else
    say "Package artifacts were built but not committed. Re-run `mb package` when ready to complete build."
  end
end
push() click to toggle source
# File lib/migration_bundler/cli.rb, line 267
def push
  # Verify that the tag exists
  git tag: "-l #{migrations.latest_version}"
  unless $?.exitstatus.zero?
    fail Error, "Could not find tag #{migrations.latest_version}. Did you forget to run `mb package`?"
  end
  push_options = []
  push_options << '--force' if options['force']
  branch_name = project.git_current_branch
  run "git config branch.`git symbolic-ref --short HEAD`.merge", verbose: false
  unless $?.exitstatus.zero?
    say_status :git, "no merge branch detected: setting upstream during push", :yellow
    push_options << "--set-upstream origin #{branch_name}"
  end
  push_options << "origin #{branch_name}"
  push_options << "--tags"

  git push: push_options.join(' ')
  unless $?.exitstatus.zero?
    fail Error, "git push failed."
  end

  # Give the targets a chance to push
  target_names = options['targets'] || project.targets
  MigrationBundler::Util.target_classes_named(target_names) do |target_class|
    say "Invoking target '#{target_class.name}'..."
    invoke(target_class, :push, [], options)
  end
end
status() click to toggle source
# File lib/migration_bundler/cli.rb, line 116
def status
  project = MigrationBundler::Project.load
  migrations = MigrationBundler::Migrations.new(project.migrations_path, database)

  if database.migrations_table?
    say "Current version: #{migrations.current_version}"
    pending_count = migrations.pending.size
    version = (pending_count == 1) ? "version" : "versions"
    say "The database at '#{database}' is #{pending_count} #{version} behind #{migrations.latest_version}" unless migrations.up_to_date?
  else
    say "New database"
    say "The database at '#{database}' does not have a 'schema_migrations' table."
  end

  if migrations.up_to_date?
    say "Database is up to date."
    return
  end

  say
  say "Migrations to be applied"
  with_padding do
    say %q{(use "mb migrate" to apply)}
    say
    with_padding do
      migrations.pending do |version, path|
        say "pending migration: #{path}", :green
      end
    end
    say
  end
end
validate() click to toggle source
# File lib/migration_bundler/cli.rb, line 194
def validate
  project = MigrationBundler::Project.load

  say "Validating project configuration..."
  say_status :git, "configuration", (project.git_url.empty? ? :red : :green)
  if project.git_url.empty?
    fail Error, "Invalid configuration: git does not have a remote named 'origin'."
  end
  say

  invoke(project.database_target_class, :validate, [], options)

  say "Validating schema loads..."
  truncate_database
  load
  say

  say "Validating migrations apply..."
  truncate_database
  migrate
  say

  say "Validating targets..."
  target_names = options['targets'] || project.targets
  MigrationBundler::Util.target_classes_named(target_names) do |target_class|
    with_padding do
      say_status :validate, target_class.name
      invoke_with_padding(target_class, :validate, [], options)
    end
  end
  say

  say "Validation successful."
end

Private Instance Methods

bundle() click to toggle source
# File lib/migration_bundler/cli.rb, line 339
def bundle
  inside(destination_root) { run "bundle" }
end
database() click to toggle source
# File lib/migration_bundler/cli.rb, line 347
def database
  @database ||= project.database_class.new((options[:database] && URI(options[:database])) || project.database_url)
end
migrations() click to toggle source
# File lib/migration_bundler/cli.rb, line 351
def migrations
  @migrations ||= MigrationBundler::Migrations.new(project.migrations_path, database)
end
unique_tag_for_version(version) click to toggle source
# File lib/migration_bundler/cli.rb, line 324
def unique_tag_for_version(version)
  return version if options['pretend']

  revision = nil
  tag = nil
  begin
    tag = [version, revision].compact.join('.')
    existing_tag = run "git tag -l #{tag}", capture: true
    break if existing_tag == ""
    revision = revision.to_i + 1
  end while true
  tag
end