1
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Active Record 単体で fixture を使う

これは Ruby not on Rails の記事です。

non-RailsでActive Recordを使う方法は検索するといくつか出てきますが、fixtureについては出てこないみたいなので書きます。

Active Recordを単体で使う方法は参考文献に挙げたのでそちらを参照してください。ディレクトリ構成やファイル配置などはいろいろやり方があると思いますので適宜読み替えてください。

fixtureはテストで使うのでテストフレームワークが問題になりますが、わたしはRSpecを使っています。とはいえ必要なことはどのフレームワークでもあまり変わらないはずです。

Active Recordのバージョンは6.0.2.1でやっています。

テスト

  • テストのクラスでActiveRecord::TestFixturesincludeする。
    • minitestならMiniTest::Testのサブクラス、RSpecならexample groupのコンテキスト(describecontextの中でitの外)でincludeするということです。
  • テストのクラスでself.fixture_path=を設定する。
    • use_transactional_testsuse_instantiated_fixturesなどの設定が必要な場合は同様に設定できます。
  • テストのクラスでfixtures :usersのようにして使うfixtureを指定する。
    • fixtures :allですべてのfixtureを指定したのと同じになります。
  • (minitest以外では)lifecycle hookでテストの実行前にsetup_fixturesが、実行後にteardown_fixturesが実行されるようにする。
    • minitestではActiveRecord::TestFixturesが面倒を見てくれる(before_setupafter_teardownに追加してくれる)ので大丈夫。
    • RSpecではそれぞれbeforeafterで呼ぶ。(before(:context)でfixtureを使うには工夫が必要そうです)
  • (Rails < 6.1) ActiveSupport::TestCaseで定義されているmethod_nameメソッドが呼ばれるので用意しておく必要がある。
    • これはminitestで定義されているnameのaliasなので、minitestを使っているなら同じようにaliasとして定義すればよい。
    • Rails >= 6.1ではnameを使うように修正済み
    • ActiveSupport::TestCaseMiniTest::Testの薄いラッパなのでこちらを使ってしまうのもありかもしれません。どうせActive RecordはActive Supportに依存しているわけですし。
  • minitest以外では、minitestで定義されているname (Rails >= 6.1) あるいはActiveSupport::TestCaseで定義されているmethod_name (Rails < 6.1) が呼ばれるので何かしら定義しておく必要がある。
    • これはminitestではtest_exampleのようなテストメソッドのメソッド名を返すので、これに準じてテスト固有の名前を取れるならそれを返すのが望ましい。
    • RSpecではRSpec.current_example.descriptionで取れる。
  • RSpecを使っている場合はrspec-railsRSpec::Rails::FixtureSupportincludeするとこのあたりの面倒を見てくれそうな気がしますが、Railsがいない場合はそのままでは動かなさそうです。

まとめると次のような感じになります。

user_test.rb
require 'minitest/autorun'
require 'active_record'
require './app/models/user'

class UserTest < MiniTest::Test

  include AcitveRecord::TestFixtures

  self.fixture_path = './test/fixtures'

  fixtures :users

  alias method_name name

  def test_example
    assert_equal 'Yukihiro Matsumoto', users(:matz).name
  end
end
user_spec.rb
require 'active_record'
require './app/models/user'

RSpec.describe User do

  include ActiveRecord::TestFixtures

  self.fixture_path = './spec/fixtures'

  fixtures :users

  # Rails >= 6.1
  def name
    RSpec.current_example.description
  end

  # Rails < 6.1
  def method_name
    RSpec.current_example.description
  end

  before { setup_fixtures }
  after { teardown_fixtures }

  it { expect(users(:matz).name).to eq 'Yukihiro Matsumoto' }

end

必要な記述をひとまとめにしたヘルパーmoduleを作ったりしてもよいでしょう。お好みでどうぞ。

Rake

Rakeタスクdb:fixtures:loadも使えます。

Rakefileでいい感じに設定をしてActive RecordのRakeタスクたちを読み込んでやります。

Rakefile
require 'active_record'

ActiveRecord::Tasks::DatabaseTasks.env = ENV['APP_ENV'] || 'default'
ActiveRecord::Tasks::DatabaseTasks.database_configuration = YAML.load_file('./config/database.yml')
ActiveRecord::Tasks::DatabaseTasks.db_dir = './db'
ActiveRecord::Tasks::DatabaseTasks.fixtures_path = './spec/fixtures'
ActiveRecord::Tasks::DatabaseTasks.root = Dir.pwd

task :environment do
  Dir.glob('./app/models/*.rb') {|file| require file }
  ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration
  ActiveRecord::Base.establish_connection(ActiveRecord::Tasks::DatabaseTasks.env.to_sym)
end

load 'active_record/railties/databases.rake'

db:fixtures:loadを使うにはActiveRecord::Tasks::DatabaseTasks.fixtures_path=が肝心です。他は適宜設定してください。

また、モデルをrequireしておく必要があります。モデルが読み込まれていなくてもタスクを読み込むことはできますが、実行時に下のようにbelongs_toassociationを認識できずに落ちます(fixtureをauthor_idではなくauthorにラベルで指定する形で書いている場合)。

$ rake db:fixtures:load
rake aborted!
ActiveRecord::Fixture::FixtureError: table "books" has no columns named "author".

なお、上ではモデルをenvironmentタスクでrequireしていますが(environmentタスクはActive Recordのタスクが実行される前に呼ばれます)、Rakefileのトップレベルでもいいと思います。

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
1
Help us understand the problem. What are the problem?