【问题标题】:Using Rails Migration on different database than standard "production" or "development"在不同于标准“生产”或“开发”的数据库上使用 Rails 迁移
【发布时间】:2010-11-27 03:19:30
【问题描述】:

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

此外,我还有一个 quiz_development: 和 quiz_production: 定义指向不同的主机/数据库/用户/密码

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

我已经尝试过(但失败了):

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

问题:

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

谢谢, 弗兰克

【问题讨论】:

    标签: ruby-on-rails ruby database migration


    【解决方案1】:

    例如,我有一个 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模型,添加建立连接,它会连接你上面的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">
    

    【讨论】:

      【解决方案2】:

      我通过为不同的数据库创建单独的连接器类并在迁移中使用它们来完成这项工作。

      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 可以让我们毫无问题地回滚迁移。还没弄明白是什么原因。

      【讨论】:

        【解决方案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 表,这样 rake 下次不会尝试重新运行迁移。这假设您希望默认数据库配置中的 schema_migrations 表跟踪检查到相应项目的版本控制中的迁移。

        我无法让向下迁移工作。

        【讨论】:

        • 是的,这对于让 Bryan Larsen 的解决方案发挥作用是绝对必要的。谢谢
        • 嗨,我仍然有这个问题。它工作正常,正在迁移并将迁移版本添加到主数据库中的迁移表中,但随后返回错误:connection is closed... 奇怪的是,除了这个错误之外,一切正常。
        • @Tallmaris 我也有同样的问题。你找到解决办法了吗?
        • @RajaVarma,不抱歉。最后我决定忍受这个错误,一切都很好。我也停止了那个项目,所以我不知道其他人是否找到了另一个解决方案:/
        • @Tallmaris 实际上,我进行了两次带有单独 establish_connection 连接的迁移,并且我的模型中已经有一个 establish_connection 用于它使用不同的数据库。现在我从迁移中删除了established_connection,并使用了模型中的那个,就像这样Model.connection,它工作得很好。我不确定是否是解决问题的那个,但进行冗余连接可能是失败的原因。
        【解决方案4】:

        我最近遇到了同样的问题。目标是将历史记录表拆分到另一个数据库,因为它已经很大并且还在快速增长。

        我开始尝试通过执行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】:

          我找到了一个非常干净的方法来做到这一点:

          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
          

          【讨论】:

            【解决方案6】:

            你可以使用这个版本,它也支持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
            

            【讨论】:

              【解决方案7】:

              在 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 任务,这样我就可以具体了解我在不同数据库上运行的迁移(我们试图支持多个数据库版本):

              这是我的 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)。

              【讨论】:

              • 分离schema.rb/structure.sql 和迁移至关重要,所有其他响应似乎都错过了这一点。 This SO post 更进一步,并添加了其他有用的 rake 任务。
              【解决方案8】:

              如果您想在您的 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
              

              【讨论】:

                【解决方案9】:
                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 来执行。

                【讨论】:

                  【解决方案10】:

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

                  【讨论】:

                    【解决方案11】:

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

                    【讨论】:

                    • 我们做了类似的事情,但是使用了 ConnectionMigration 超类和 #with_connection(connection) 方法,因此我们可以在不同的迁移中使用该功能,并使用不同的连接而不会重复。此外,不需要覆盖#connection(请参阅api.rubyonrails.org/classes/ActiveRecord/…)。
                    • 注意:这不适用于change 方法;你必须使用updown
                    • 如果您想单独迁移,请参阅stackoverflow.com/a/16542724/670433
                    【解决方案12】:

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

                    【讨论】:

                      【解决方案13】:

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

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

                      该方法基于将命名空间添加到已知的 rake 任务 db:migrate、db:create、db:drop 并使用不同的数据库执行这些任务。然后根据新的 database.yml 文件的配置添加一个用于连接的基本活动记录 (AR) 类。这样您就不需要使用连接的东西来破解迁移,并且您可以获得一个干净的目录结构。

                      你的结构最终会是这样的

                      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 基类继承的模型上连接到它。 (要点中的示例)。

                      最好的!。

                      【讨论】:

                      • 当我们在 application.rb 中有 config.active_record.schema_format = :sql 时,运行 rake store:db:migrate 会覆盖当前的 db/structure.sql 而不是更新 db_store/structure.sql。任何想法为什么?
                      • 关于上面的评论,将 ENV['SCHEMA'] 更改为 ENV['DB_STRUCTURE'] 让它工作。更多细节在这里 - github.com/rails/docrails/blob/master/activerecord/lib/…
                      • @rafael 您的 Gist 不再可用。您能否重新发布或扩展您的答案,详细说明您是如何做到的?
                      • Gist 不再可用,取决于外部资源会降低答案的质量,特别是随着时间的推移,这些会消失
                      【解决方案14】:

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

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

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

                      【讨论】:

                      • 这似乎接近真正的答案,但失败并出现错误:关系“schema_migrations”不存在。
                      • 我使用了这个,使用 Rails 3.1.4,它确实运行了迁移。但是,它不会进行向下迁移,并且(即使没有尝试向下)运行 rake db:migrate 会继续运行相同的迁移,因为表已经添加了该列。
                      • 得到 rake 以不重新运行迁移。请看下面我的回答。 (不幸的是 #{$!} stackoverflow 不支持 cmets 中的代码示例。)
                      • 这仍然适用于 rails 4.2,但是我收到了一个弃用警告:“不推荐将字符串传递给 ActiveRecord::Base.establish_connection 以进行配置查找...”以避免此问题强制转换字符串到一个符号:ActiveRecord::Base.establish_connection("quiz_#{Rails.env}".to_sym).connection
                      • 如果您在迁移中使用#change 方法,回滚将不起作用。我正在使用 rails-4.1.14.1。最初在我的例子中,#connection 方法被描述为@connection || ActiveRecord::Base.connection。当你回滚迁移时,@connection 被 ActiveRecord::Migration::CommandRecorder 的实例替换,#connection 方法将返回它。但是在您的情况下,将返回默认连接,因此回滚将不起作用,因为它将尝试执行“向上”迁移。
                      【解决方案15】:
                      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 数据库的连接。

                      如果您只是想让它在您的迁移中可用,请扩展 ActiveRecord::Migration 类。

                      【讨论】:

                        【解决方案16】:

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

                        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
                        

                        【讨论】:

                          【解决方案17】:

                          有点晚了,但我今天正在处理这个问题,我想出了这个自定义 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
                          

                          【讨论】:

                          • 谢谢,稍微更正一下:我将 require 'activerecord' 更改为 require 'active_record'
                          【解决方案18】:

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

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

                          rake db:migrate RAILS_ENV=customenvironment
                          

                          【讨论】:

                            【解决方案19】:

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

                            【讨论】:

                              【解决方案20】:

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

                              RAILS_ENV=quiz_development rake db:migrate
                              

                              【讨论】:

                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 1970-01-01
                                • 2023-04-04
                                • 2017-03-05
                                • 2013-12-28
                                相关资源
                                最近更新 更多