在标准“生产”或“开发”之外的不同数据库上使用Rails迁移

时间:2009-09-10 11:01:02

标签: ruby-on-rails ruby database migration

我有一个运行的rails项目,用于定义标准生产:,:开发和:在config / database.yml中测试数据库连接

另外我有一个quiz_development:和quiz_production:定义指向不同的主机/ db / user / password

我现在的目标是定义一个使用“quiz_#{RAILS_ENV}`”作为其数据库配置的迁移。

我尝试过(并且失败了):

  • 在迁移文件中设置ActiveRecord :: Base.connection
  • 在rails中更改db:migrate任务以设置ActiveRecord :: Base.connection

问题:

如何让rake db:migrate使用其他数据库定义?

谢谢, 弗兰克

20 个答案:

答案 0 :(得分:35)

有一个更简单的答案。将其添加到您的迁移中:

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

那是Rails 3.1。对于Rails 2.X或3.0,它是一个类函数(例如def self.connection

答案 1 :(得分:18)

我使用以下代码进行此操作。

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表,因此rake不会尝试在下次重新运行迁移。这假设您希望默认数据库配置中的schema_migrations表跟踪已检入相应项目的版本控制的迁移。

我无法让向下迁移工作。

答案 2 :(得分:13)

您应该在/ config / environments中定义其他数据库/环境。

之后,您可以使用以下命令迁移该特定环境。

rake db:migrate RAILS_ENV=customenvironment

答案 3 :(得分:11)

有点晚了,但我今天正在解决这个问题,我想出了这个自定义rake任务:

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

答案 4 :(得分:8)

我最近遇到了同样的问题。目标是将历史表分割到不同的数据库,因为它已经很大并且仍在快速增长。

我开始尝试通过执行ActiveRecord::Base.establish_connection(:history_database)来解决它,但在没有关闭连接的情况下无法获得该方式的任何变化。最后我发现了下面的解决方案。

在进行此更改后的历史记录模型中:

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表,以便不再运行迁移。

答案 5 :(得分:8)

从@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

答案 6 :(得分:7)

嘿,我已经深入研究了几天,我最终得到了这个解决方案,只是想分享它,它可能对某人有帮助。

这里有完整的要点。 https://gist.github.com/rafaelchiti/5575309 它有详细说明和解释。但如果您需要,请在下面找到更多详细信息。

该方法基于向已知的rake任务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基类继承的模型上连接它。 (在要点中的例子)。

最佳!

答案 7 :(得分:7)

对于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

答案 8 :(得分:5)

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环境的数据库的连接。

如果您只想在迁移中使其可用,则会扩展ActiveRecord::Migration类。

答案 9 :(得分:4)

在rails 3.2中,为迁移添加连接方法不起作用。所以所有答案都是

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

根本不起作用(不能down,不能用change,连接丢失等等。)原因是ActiveRecord :: Migration和Migrator类有连接硬编码为ActiveRecord :: Base all over the place

幸运的是this post向我指出this ticket有一个很好的解决方案,即覆盖实际的rake task

我最终使用了一个略有不同的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

这允许我们将特定于一个数据库的迁移放在它们自己的子目录中(nine_four / migrations而不是db / migrations)。 它还为每个数据库提供了模式和迁移版本的完全隔离。唯一的缺点是要运行两个rake任务(db:migrate和nine_four:db:migrate)。

答案 10 :(得分:2)

我找到了一个很好的方法来做到这一点:

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

答案 11 :(得分:2)

除了在不同的环境中运行迁移之外,我还希望将模式放在单独的文件中。您可以从命令行执行此操作:

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

但我喜欢自定义rake任务方法,所以我可以输入:

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 :(得分:1)

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来执行。

答案 13 :(得分:1)

您可以使用此版本,该版本也支持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

答案 14 :(得分:0)

我通过为不同的数据库创建单独的连接器类并在迁移中使用它们来实现这一目的。

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保留一个连接池,该连接池是由该类索引的哈希。 Read more here。因此,使用单独的类进行单独连接可以保护我们免受封闭连接错误的影响。

此外,使用updown代替change,我们可以毫无问题地回滚迁移。仍然没有弄清楚原因。

答案 15 :(得分:0)

您是否尝试过将quiz_development用作RAILS_ENV(而不是尝试让它使用"quiz_#{RAILS_ENV}")?

RAILS_ENV=quiz_development rake db:migrate

答案 16 :(得分:0)

例如,我有一个study_history模型:

rails g model study_history lesson:references user:references history_type:references
  1. 在database.yml中定义mysql部分
player_records:
  adapter: mysql2
  encoding: utf8
  host: 1.2.3.4
  username: root
  password: 
  timeout: 5000
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 20 } %>
  database: player_records
  1. 修改StudyHistory模型,添加prepare_connect,它将连接上面的mysql数据库player_records(我首先在mysql服务器中添加了该数据库):
class StudyHistory < ApplicationRecord
  establish_connection :player_records
  
  belongs_to :lesson
  belongs_to :user
  belongs_to :history_type
end
  1. 使用迁移文件中的连接来创建表:
class CreateStudyHistories < ActiveRecord::Migration[6.0]
  def change
    StudyHistory.connection.create_table :study_histories do |t|
      t.references :lesson, null: false
      t.references :user, null: false
      t.references :history_type, null: false

      t.timestamps
    end
  end
end

现在,您可以运行

rails db:migrate

就是这样,我在Rails 6上进行了测试,它的工作原理就像一种魅力,您可以从不同的数据库(本地sqlite3和远程mysql)中获取数据。

irb(main):029:0> StudyHistory.first.lesson
   (42.5ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_Z
ERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  StudyHistory Load (30.0ms)  SELECT `study_histories`.* FROM `study_histories` ORDER BY `study_histories`.`id` ASC LIMIT 1
   (0.0ms)  
 SELECT sqlite_version(*)
  Lesson Load (0.1ms)  SELECT "lessons".* FROM "lessons" WHERE "lessons"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<Lesson id: 1, title: "people", cn: nil, description: nil, version: nil, course_id: 1, created_at: "2020-03-01 23:57
:02", updated_at: "2020-05-08 09:57:40", level: "aa", ready: false, pictureurl: "/pictures/kiss^boy and girl^boy^girl.jp
g">

答案 17 :(得分:0)

如果你想在你的rails网站上显示wordpress帖子和 你不想使用多魔法连接宝石。您可以使用以下代码从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

答案 18 :(得分:0)

基于@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

答案 19 :(得分:0)

您还可以将所有与quiz_相关的迁移移动到db /目录中的单独子文件夹中,然后添加镜像常规迁移功能但在该子目录中查找迁移的rake任务。可能不是超级优雅但它有效。您可以复制并粘贴已经在rails中的rake任务,然后稍微修改一下。