【问题标题】:Rails 3 + rspec + postgres: How to run test suite against a database with foreign key constraints?Rails 3 + rspec + postgres:如何针对具有外键约束的数据库运行测试套件?
【发布时间】:2014-08-18 16:58:49
【问题描述】:

我有一个使用 PostgreSQL 9.1 作为数据库的 Rails 3 应用程序。我们使用 RSpec 和 FactoryGirl 进行测试。

数据模型有很多一对多或多对多关系的模型,这些约束在 Rails 模型中使用 has_many、belongs_to、has_many :through 等进行编码。我们的代码看起来像:

class User < ActiveRecord::Base
   attr_accessible :name
   has_many :phones
end

class Phone < ActiveRecord::Base
   attr_accessible :number, user_id
   belongs_to :user
end

PostgreSQL 中的模式看起来像这样

CREATE TABLE users (id integer, name VARCHAR(20));
CREATE TABLE phones (id integer, number VARCHAR(20), user_id integer);

但是,我更喜欢使用外键约束而不是仅在模型中对数据库中的数据约束进行编码。为此,我创建了一个 Rails 迁移并添加了外键约束,如下所示:

sql = "ALTER TABLE phones ADD CONSTRAINT phones_user_id FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT;"
ActiveRecord::Base.connection.execute(sql)

当我添加外键约束并在开发模式下启动应用程序时,数据库强制执行约束,如果用户有任何依赖电话,我无法删除它。无论我是在“psql”PostgreSQL 控制台还是 IRB Rails 控制台中,这都是正确的。

但是,当我尝试编写 RSpec 测试以查看外键是否强制删除限制时,测试失败并且我能够删除具有相关电话的用户。显然,“rake db:test:prepare”不会使用外键准备数据库。

有没有一种方法可以针对强制外键约束的数据库运行我的 RSpec 测试套件?

【问题讨论】:

    标签: ruby-on-rails postgresql rspec foreign-keys rspec-rails


    【解决方案1】:

    在 Rails 4 中,这很简单:

    在 config/application.rb 中,设置

    module YourApp
      class Application < Rails::Application
        config.active_record.schema_format = :sql
      end
    end
    

    rake RAILS_ENV=test db:migrate 然后将您在 SQL 中的模式转储到db/structure.sqlrake RAILS_ENV=test test:load 将加载它。这些任务将 pg_dump 用于 Postgres,因此它们将保留架构的各个方面。

    Rails 3.2 的测试数据库相关的 Rake 任务不尊重config.active_record.schema_format,所以我什至没有设置它。相反,我覆盖了使用db:structure:load 而不是db:schema:load 的任务。在 lib/tasks/db.rb 中:

    namespace :db do
      namespace :migrate do
        # We override the original of this task for consistency. Like the original, it doesn't seed.
        Rake::Task['db:migrate:reset'].clear
        task reset: [ 'db:drop', 'db:create', 'db:structure:load', 'db:migrate' ]
      end
    
      # This overrides the original, which does db:schema:load. The original doesn't migrate; this version does,
      # since, unlike schema.rb, *_structure.sql does not necessarily include all migrations.
      Rake::Task['db:setup'].clear
      task setup: [ 'test:ensure_environment_is_test', 'db:create', 'db:structure:load', 'db:migrate', 'db:seed' ]
    
      Rake::Task['db:reset'].clear
      task reset: [ 'test:ensure_environment_is_test', 'db:drop', 'db:setup' ]
    
      namespace :test do
        desc "rspec tasks depend on this task, so we override it to set up the database in the way that we want."
        Rake::Task['db:test:prepare'].clear
        task prepare: [ 'db:reset' ]
      end
    
    end
    
    namespace :test do
      task :ensure_environment_is_test do
        raise "Don't know how to db:setup RAILS_ENV=#{Rails.env}" unless Rails.env.test?
      end
    end
    

    手动运行rake db:structure:dump 以创建db/structure.sql,并偶尔再次执行以汇总您的迁移。您可以在每次迁移时修改上述内容以进行转储,就像 Rails 4 的任务一样,但您可能会也可能不会发现转储中无关紧要的更改会让人讨厌。

    在早期版本的 Rails 中需要更多的黑客攻击;希望我们不必去那里。

    无论 Rails 版本如何,都值得一读 activerecord gem 的lib/active_record/railties/database.rb

    【讨论】:

      猜你喜欢
      • 2020-03-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-02-19
      相关资源
      最近更新 更多