Использование Rails Migration в другой базе данных, чем стандартное "производство" или "разработка",

У меня есть проект rails, который определяет стандартное производство:, разработка и: тестовые DB-соединения в config/database.yml

Кроме того, у меня есть quiz_development: и quiz_production: определение, указывающее на host_database/db/user/password

Теперь моя цель - определить Миграцию, которая использует "quiz_#{RAILS_ENV}`" в качестве своей конфигурации базы данных.

Что я пробовал (и не смог):

  • Настройка ActiveRecord:: Base.connection в файле миграции
  • Изменение задачи db: migrate в rails для установки ActiveRecord:: Base.connection там

Вопрос:

Как я могу сделать rake db: migrate использовать другое определение базы данных?

Спасибо, Франк

Ответ 1

Немного поздно, но сегодня я столкнулся с этой проблемой, и я придумал эту обычную задачу:

namespace :db do
  desc "Apply db tasks in custom databases, for example  rake db:alter[db:migrate,test-es] applies db:migrate on the database defined as test-es in databases.yml"
  task :alter, [:task,:database] => [:environment] do |t, args|
    require 'activerecord'
    puts "Applying #{args.task} on #{args.database}"
    ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[args.database])
    Rake::Task[args.task].invoke
  end
end

Ответ 2

Там гораздо легче ответить. Добавьте это к своей миграции:

def connection
  ActiveRecord::Base.establish_connection("quiz_#{Rails.env}").connection
end

Это для Rails 3.1. Для Rails 2.X или 3.0 вместо него используется функция класса (например, def self.connection)

Ответ 3

Я получил это для работы со следующим кодом.

class AddInProgressToRefHighLevelStatuses < ActiveRecord::Migration
  def connection
    @connection = ActiveRecord::Base.establish_connection("sdmstore_#{Rails.env}").connection
  end

  def change
    add_column :ref_high_level_statuses, :is_in_progress, :boolean, :default => true

    @connection = ActiveRecord::Base.establish_connection("#{Rails.env}").connection
  end
end

Необходимо было установить соединение обратно, чтобы заставить его записать миграцию в таблицу schema_migrations, чтобы грабли не пытались повторно запустить миграцию в следующий раз. Предполагается, что вы хотите, чтобы таблица schema_migrations в конфигурации базы данных по умолчанию отслеживала контролируемые миграции в контроле версий для соответствующего проекта.

Мне не удалось выполнить миграцию вниз.

Ответ 4

Вы должны определить другие базы данных/среды в /config/environment.

После этого вы можете использовать следующую команду для переноса этой конкретной среды.

rake db:migrate RAILS_ENV=customenvironment

Ответ 5

Эй, я копался в этом несколько дней, и я закончил с этим решением, просто хотел поделиться им, это может помочь кому-то.

Вот полный смысл для него. https://gist.github.com/rafaelchiti/5575309 У этого есть детали и объяснение. Но найдите ниже более подробную информацию, если они вам понадобятся.

Подход основан на добавлении пространства имен к уже известным рейк-задачам db: migrate, db: create, db: drop и выполнять эти задачи с другой базой данных. А затем добавление класса активной активной записи (AR) для подключения на основе конфигурации нового файла database.yml. Таким образом, вам не нужно взламывать миграции с помощью соединений, и вы получаете чистую структуру каталогов.

Ваша структура закончится следующим образом

config
  |- database.yml
  \- another_database.yml (using the same nomenclature of 'development', 'test', etc).

db
  |- migrate (default migrate directory)
  |- schema.rb
  |- seed.rb

another_db
  |- migrate (migrations for the second db)
  |- schema.rb (schema that will be auto generated for this db)
  |- seed.rb (seed file for the new db)

Затем в вашем коде вы можете создать базовый класс и прочитать конфигурацию из этого нового файла database.yml и подключиться к нему только на моделях, которые наследуют от этого базового класса AR. (пример в основном).

Best!.

Ответ 6

Для Rails 3.2 это то, что мы сделали, работает с мигрированием вверх и вниз:

class CreateYourTable < ActiveRecord::Migration

  def connection
    @connection ||= ActiveRecord::Base.connection
  end

  def with_proper_connection
    @connection = YourTable.connection
    yield
    @connection = ActiveRecord::Base.connection
  end


  def up
    with_proper_connection do
      create_table :your_table do |t|
      end
    end
  end

  def down
    with_proper_connection do
      drop_table :your_table
    end
  end

end

Ответ 7

Следуя @Bryan Larsen, если вы используете абстрактный класс для присоединения серии моделей к другой базе данных и хотите перенести схемы на них, вы можете сделать это:

class CreatePosts < ActiveRecord::Migration
    def connection
      Post.connection
    end
    def up
      ...
    end
end

с моделью создайте что-то вроде:

class Post < ReferenceData
end

и

class ReferenceData < ActiveRecord::Base
  self.abstract_class = true
  establish_connection "reference_data_#{Rails.env}"
end

Ответ 8

Недавно я боролся с той же проблемой. Цель состояла в том, чтобы разделить таблицу историй с другой базой данных, поскольку она была уже такой большой и все еще очень быстро развивалась.

Я начал пытаться разрешить это, выполнив ActiveRecord::Base.establish_connection(:history_database), но не смог получить никаких вариантов этого способа для работы без закрытия соединения. Затем, наконец, я нашел решение ниже.

В модели History после внесения этого изменения:

class History < ActiveRecord::Base

  # Directs queries to a database specifically for History
  establish_connection :history_database

  ...
end

Я смог сделать это в процессе миграции, и он отлично работал:

class CreateHistoriesTableInHistoryDatabase < ActiveRecord::Migration
  def up
    History.connection.create_table :histories do |t|
      ...
    end
  end

  def down
    History.connection.drop_table :histories
  end
end

Это создаст таблицу в другой базе данных, но изменит таблицу schema_migrations в исходной базе данных, чтобы миграция не выполнялась снова.

Ответ 9

module ActiveRecord::ConnectionSwitch
  def on_connection(options)
    raise ArgumentError, "Got nil object instead of db config options :(" if options.nil?
    ActiveRecord::Base.establish_connection(options)
    yield
  ensure
    ActiveRecord::Base.establish_connection ActiveRecord::Base.configurations[Rails.env]
  end
end

ActiveRecord.send :extend, ActiveRecord::ConnectionSwitch

Если вы разместите его внутри config/initializers/, вы сможете сделать что-то вроде этого:

ActiveRecord.on_connection ActiveRecord::Base.configurations['production'] do
  Widget.delete_all
end

Это приведет к удалению всех виджетов на производственной базе данных и последующему восстановлению связи с текущим Rails env db.

Если вы просто хотите, чтобы он был доступен в ваших миграциях, расширьте класс ActiveRecord::Migration.

Ответ 10

В rails 3.2 добавление метода подключения к вашей миграции НЕ работает. Итак, все ответы вроде

def connection
 @connection ||= ActiveRecord::Base.establish_connection
end

просто не работает (не может down, не работает с change, потерянного соединения и т.д.). Причина этого в том, что класс ActiveRecord:: Migration и Migrator имеет соединения жестко закодированные в ActiveRecord:: Base все через place.

К счастью этот пост указал мне на этот билет, который имеет хорошее решение, а именно переопределение фактической рейк-задачи.

В итоге я использовал немного другую задачу rake, чтобы я мог быть конкретным в отношении миграции, которую я запускал в другой базе данных (мы пытались поддерживать несколько версий db):

Здесь моя lib/task/database.rake

# Augment the main migration to migrate your engine, too.
task 'db:migrate', 'nine_four:db:migrate'

namespace :nine_four do
    namespace :db do
        desc 'Migrates the 9.4 database'
        task :migrate => :environment do
            with_engine_connection do
                ActiveRecord::Migrator.migrate("#{File.dirname(__FILE__)}/../../nine_four/migrate", ENV['VERSION'].try(:to_i))
            end
        end
    end
end

# Hack to temporarily connect AR::Base to your engine.
def with_engine_connection
    original = ActiveRecord::Base.remove_connection
    ActiveRecord::Base.establish_connection("#{ Rails.env }_nine_four")
    yield
ensure
    ActiveRecord::Base.establish_connection(original)
end

Это позволяет нам переносить миграции в одну базу данных в свой собственный подкаталог (девять_four/migrations вместо db/migrations). Он также дает общую изоляцию каждой базы данных с точки зрения их схем и версий миграции. Единственный недостаток - запустить две задачи рейка (db: migrate и nine_four: db: migrate).

Ответ 11

В дополнение к выполнению миграции в другой среде, я также хочу, чтобы схемы были в отдельных файлах. Вы можете сделать это из командной строки:

RAILS_ENV=quiz_development SCHEMA=db/schema_quiz_development.rb rake db:migrate

Но мне нравится пользовательский подход к грабежной задаче, поэтому я могу набрать это вместо:

rake db:with[quiz_development, db:migrate]

Здесь задача rake:

namespace :db do
  desc "Run :task against :database"
  task :with, [:database,:task] => [:environment] do |t, args|
    puts "Applying #{args.task} to #{args.database}"
    ENV['SCHEMA'] ||= "#{Rails.root}/db/schema_#{args.database}.rb"
    begin
      oldRailsEnv = Rails.env
      Rails.env = args.database
      ActiveRecord::Base.establish_connection(args.database)
      Rake::Task[args.task].invoke
    ensure
      Rails.env = oldRailsEnv
    end
  end
end

Ответ 12

Я нашел отличный способ сделать это:

class CreateScores < ActiveRecord::Migration

  class ScoresDB < ActiveRecord::Base
    establish_connection("scores_#{Rails.env}")
  end

  def connection
    ScoresDB.connection
  end

  def up
    create_table :scores do |t|
      t.text :account_id
      t.text :offer
    end
  end

  def down
    drop_table :scores
  end
end

Ответ 13

class Article < ActiveRecord::Base

    ActiveRecord::Base.establish_connection(
      :adapter  => "mysql2",
      :host     => "localhost",
      :username => "root",
      :database => "test"
    )
end

и

class Artic < Aritcle
    self.table_name = 'test'

    def self.get_test_name()
        query = "select name from testing"
        tst = connection.select_all(query) #select_all is important!
        tst[0].fetch('name')
    end
end

Вы можете вызвать Artic.get_test_name для выполнения.

Ответ 14

Вы можете использовать эту версию, которая также поддерживает rake db:rollback:

class ChangeQuiz < ActiveRecord::Migration
  def connection
    ActiveRecord::Base.establish_connection("quiz_#{Rails.env}").connection
  end

  def reset_connection
    ActiveRecord::Base.establish_connection(Rails.env)
  end

  def up
    # make changes

    reset_connection
  end

  def self.down
    # reverse changes

    reset_connection
  end
end

Ответ 15

Вы пытались использовать quiz_development как RAILS_ENV (вместо того, чтобы пытаться использовать его "quiz_#{RAILS_ENV}")?

RAILS_ENV=quiz_development rake db:migrate

Ответ 16

Вы также можете перенести все связанные с Quiz_ миграции в отдельную подпапку в каталоге db/, а затем добавить задачи rake, отражающие функции регулярной миграции, но которые ищут миграции в этом подкаталоге. Не супер-элегантный, возможно, но он работает. Вы можете копировать и вставлять задачи рейка уже в рельсы и немного модифицировать их.

Ответ 17

Основываясь на ответе @TheDeadSerious:

module ActiveRecord::ConnectionSwitch  
  def on_connection(connection_spec_name)
    raise ArgumentError, "No connection specification name specified. It should be a valid spec from database.yml" unless connection_spec_name
    ActiveRecord::Base.establish_connection(connection_spec_name)
    yield
  ensure
    ActiveRecord::Base.establish_connection(Rails.env)
  end
end

ActiveRecord.send :extend, ActiveRecord::ConnectionSwitch

Использование:

ActiveRecord.on_connection "sdmstore_#{Rails.env}" do
  Widget.delete_all
end

Ответ 18

если вы хотите отобразить сообщение Wordpress на веб-сайте rails и вы не хотите использовать многомагистральный жемчуг. вы можете использовать приведенный ниже код, чтобы получить данные из блога Wordpress.

 class Article < ActiveRecord::Base

    ActiveRecord::Base.establish_connection(
     :adapter  => "mysql2",
     :host     => "localhost",
     :username => "root",
     :database => "blog"
    )

    self.table_name = 'wp_posts'

    def self.get_post_data()
        query = "select name from testing"
        tst = connection.select_all(query)
        tst[0].fetch('name')
    end
end

Ответ 19

Я получил эту работу, создав отдельные классы соединителей для разных баз данных и используя их в переносах.

class AddExampleToTest < ActiveRecord::Migration
  def connection
    @connection = OtherDatabaseConnector.establish_connection("sdmstore_#{Rails.env}").connection
  end
  def up
    add_column :test, :example, :boolean, :default => true

    @connection = MainDatabaseConnector.establish_connection("#{Rails.env}").connection
  end
  def down
    remove_column :test, :example

    @connection = MainDatabaseConnector.establish_connection("#{Rails.env}").connection
  end
end

Мы можем определить эти классы соединителей в инициализаторах.

class MainDatabaseConnector < ActiveRecord::Base
end
class OtherDatabaseConnector < ActiveRecord::Base
end

ActiveRecord:: Base поддерживает пул соединений, который является хешем, индексированным классом. Подробнее здесь. Поэтому использование отдельных классов для отдельных соединений защищает нас от ошибки закрытого соединения.

Кроме того, использование up и down вместо change позволяет нам откатить миграцию без каких-либо проблем. Все еще не выяснили причину этого.