class MultiAR::MultiAR

Base of MultiAR gem.

Must be initialized before most actions works, that relies on MultiAR#app for getting configuration.

Attributes

app[RW]

Instance of MultiAR::MultiAR, automatically assigned by MultiAR::MultiAR#new. Used internally in the gem, to access configuration and other internal parts.

db_config[R]
environment[R]

Public Class Methods

add_database(database_name, migration_path) click to toggle source

Add a database and its migration path. For standard Rails setup, this would be “customdbname”, “db/migrate”.

The directory structure of how MultiAR uses the path is a bit different from traditional way: for each database, there is directory inside the migration dir.

For example, if project uses database named “messy_database” and migration dir is “my/migration/dir”, migrations would be looked from path “my/migration/dir/messy_database”.

@note often you want to add full path to this dir, `__dir__` is useful for this.

# File lib/multi_ar.rb, line 123
def self.add_database database_name, migration_path
  unless migration_path.nil?
    raise "Migration dir #{migration_path} does not exist." unless Dir.exist? migration_path
    begin
      ActiveRecord::Tasks::DatabaseTasks.migrations_paths << migration_path
    rescue NameError
      # multi_ar can be used without migration support, so adding a database to migration paths is only necessary when actually running migrations.
    end
  end
  @@__databases[database_name] = migration_path
end
add_migration_dir(dir) click to toggle source

A helper method to add migrations for multiple databases that reside in same location.

Expects the given directory contain subdirectories that contain the actual migrations. For example, `db/migrate/db_name`, where `db/migrate` is dir given as an argument to this method and `db_name` is name of the database.

# File lib/multi_ar.rb, line 86
def self.add_migration_dir dir
  raise "Directory #{dir} does not exist." unless Dir.exist? dir

  Dir.chdir dir do
    dbs = Dir.glob "*/"
    dbs.each do |database|
      add_database database, "#{dir}/#{database.chop}"
    end
  end
end
databases() click to toggle source

@api private

Returns internal databases array.

# File lib/multi_ar.rb, line 151
def self.databases
  @@__databases
end
migration_dir_for(database_name) click to toggle source

Calculates path to migration dir for given database.

# File lib/multi_ar.rb, line 104
def self.migration_dir_for database_name
  ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
migration_dirs() click to toggle source

Array of paths to directories where migrations resides. @see add_database

# File lib/multi_ar.rb, line 99
def self.migration_dirs
  ActiveRecord::Tasks::DatabaseTasks.migrations_paths
end
new(databases: nil, environment: "development", config: "config/settings.yaml", db_config: "config/database.yaml", verbose: false, migrations_from_gem: nil) click to toggle source

@param databases array of available databases @todo config file is overriding parameters passed here… I think it should be other way around, but need more custom logic for that :/

# File lib/multi_ar.rb, line 32
def initialize databases: nil, environment: "development", config: "config/settings.yaml", db_config: "config/database.yaml", verbose: false, migrations_from_gem: nil

  # first load config
  if not config.nil? and File.exist? config
    require "psych"
    config = Psych.load_file config
    b = binding
    config.each do |key, value|
      # If databases have been passed, we don’t have much reason to override it from config
      if key != "databases" || databases.nil?
        b.local_variable_set key.to_sym, value
      end
    end
  end

  # then check that we have data in format we want it to be
  raise "#{db_config} is not valid path to a file. Try specifying --db-config <path> or configuring it in the configuration file." if db_config.nil? or !File.exist?(db_config)
  #raise "databases is not responding to :each. Try passing passing --databases <database> or configuring it in the configuration file." unless databases.respond_to? :each

  # One can run migrations from a gem, instead of current project.
  load_migration_gem migrations_from_gem, databases if not migrations_from_gem.nil?

  parse_databases_input databases unless databases.nil?

  #@databases = databases
  @db_config = db_config
  @environment = environment
  #@@migration_dirs = migration_dirs unless migration_dirs.empty? # This takes care of that it will only be overridden if there is any given values, making default configs work
  #ActiveRecord::Tasks::DatabaseTasks.migrations_paths = migration_dirs

  Database.initialize db_config: db_config

  #ActiveRecord::Tasks::DatabaseTasks.class_eval { attr_accessor :sub_db_dir }
  #ActiveRecord::Tasks::DatabaseTasks.sub_db_dir = databases.first # TODO: I don’t think this is how it should work

  @rake = ::Rake::Application.new
  ::Rake.application = @rake
  @rake.init
  ::Rake::TaskManager.record_task_metadata = true

  #Rake::Tasks.databases = databases
  Rake::Tasks.environment = environment
  Rake::Tasks.define

  @@verbose = verbose

  MultiAR.app = self
end
solve_migration_path(database_name) click to toggle source

Returns calculated migration path or if path have not been given, fallback to default db/migrate/DB_NAME if it exists.

# File lib/multi_ar.rb, line 156
def self.solve_migration_path database_name
  path = self.databases[database_name]
  if path.nil?
    path = "db/migrate/#{database_name}"
    raise "Trying to use migrations for non-existing database. Please specify database if you have migrations in custom location. Given database: #{database_name}" unless File.exist? path
  end

  path
end
verb(str) click to toggle source

Outputs contents if verbose flag has been passed.

# File lib/multi_ar.rb, line 109
def self.verb str
  return unless @@verbose
  puts str
end

Public Instance Methods

list_tasks(all_rake_tasks: false) click to toggle source

@todo this shows rake in start of the command, we want to show multi_ar instead.

# File lib/multi_ar.rb, line 136
def list_tasks all_rake_tasks: false
  @rake.options.show_all_tasks = true if all_rake_tasks
  @rake.options.show_tasks = :tasks
  @rake.options.show_task_pattern = // # all tasks; we don’t have support for string-matching tasks
  @rake.display_tasks_and_comments
end
rake_task(task_name) click to toggle source

Invokes Rake task from `task_name`

# File lib/multi_ar.rb, line 144
def rake_task task_name
  @rake.invoke_task task_name
end

Private Instance Methods

load_migration_gem(migrations_from_gem, databases) click to toggle source

def self.resolve_databases_paths end

# File lib/multi_ar.rb, line 177
def load_migration_gem migrations_from_gem, databases
  # If the gem is not installed, we just want to fail.
  gem migrations_from_gem

  spec = Gem.loaded_specs[migrations_from_gem]
  gem_dir = nil
  if spec.respond_to? :full_gem_path
    gem_dir = spec.full_gem_path
  else
    require_paths = spec["require_paths"]
    gem_dir = require_paths[0]
  end

  # This file contains gem specific details which can be used to bootstrap MultiAR.
  # There could be default values, but for security, as loading arbitrary gems accidentally could cause hard-to-recognize security bugs, since migrations are plain Ruby code.
  # TODO: document contents of that file somewhere.
  info_file = "#{gem_dir}/config/multi_ar_gem.yaml"

  raise "File #{info_file} does not exist. Please check that your gem contains config/multi_ar_gem.yaml for automatic operation." unless File.exist? info_file
  yaml = YAML.load_file info_file

  migration_dir = yaml["migration_dir"]

  # Put in gem’s migration path, so stuff should just magically work

  databases.each do |database, migration_path|
    databases[database] = "#{gem_dir}/#{migration_dir}"
  end
end
parse_databases_input(dbs) click to toggle source

Supports three different input formats:

  1. Array with strings of database names

  2. Array with hashes of { “database” => “db_name”, “migration_path” => “/path/to/migrations” }

  3. Hash with key as database name and value as migration path

# File lib/multi_ar.rb, line 212
def parse_databases_input dbs
  if dbs.kind_of? Array
    dbs.each do |database|
      if database.kind_of? Hash
        ::MultiAR::MultiAR::add_database database["database"], database["migration_path"]
      else
        ::MultiAR::MultiAR::add_database database, "#{default_migration_path}/#{database}"
      end
    end
    return
  end

  raise "input databases needs to be either Hash or Array" unless dbs.kind_of? Hash

  dbs.each do |database, migration_path|
    ::MultiAR::MultiAR::add_database database, migration_path
  end
end