これは Ruby not on Rails の記事です。
non-RailsでActive Recordを使う方法は検索するといくつか出てきますが、fixtureについては出てこないみたいなので書きます。
Active Recordを単体で使う方法は参考文献に挙げたのでそちらを参照してください。ディレクトリ構成やファイル配置などはいろいろやり方があると思いますので適宜読み替えてください。
fixtureはテストで使うのでテストフレームワークが問題になりますが、わたしはRSpecを使っています。とはいえ必要なことはどのフレームワークでもあまり変わらないはずです。
Active Recordのバージョンは6.0.2.1でやっています。
テスト
- テストのクラスで
ActiveRecord::TestFixtures
をinclude
する。- minitestなら
MiniTest::Test
のサブクラス、RSpecならexample groupのコンテキスト(describe
やcontext
の中でit
の外)でinclude
するということです。
- minitestなら
- テストのクラスで
self.fixture_path=
を設定する。-
use_transactional_tests
やuse_instantiated_fixtures
などの設定が必要な場合は同様に設定できます。
-
- テストのクラスで
fixtures :users
のようにして使うfixtureを指定する。-
fixtures :all
ですべてのfixtureを指定したのと同じになります。
-
- (minitest以外では)lifecycle hookでテストの実行前に
setup_fixtures
が、実行後にteardown_fixtures
が実行されるようにする。- minitestでは
ActiveRecord::TestFixtures
が面倒を見てくれる(before_setup
とafter_teardown
に追加してくれる)ので大丈夫。 - RSpecではそれぞれ
before
とafter
で呼ぶ。(before(:context)
でfixtureを使うには工夫が必要そうです)
- minitestでは
- (Rails < 6.1)
ActiveSupport::TestCase
で定義されているmethod_name
メソッドが呼ばれるので用意しておく必要がある。- これはminitestで定義されている
name
のaliasなので、minitestを使っているなら同じようにaliasとして定義すればよい。 - Rails >= 6.1では
name
を使うように修正済み -
ActiveSupport::TestCase
はMiniTest::Test
の薄いラッパなのでこちらを使ってしまうのもありかもしれません。どうせActive RecordはActive Supportに依存しているわけですし。
- これはminitestで定義されている
- minitest以外では、minitestで定義されている
name
(Rails >= 6.1) あるいはActiveSupport::TestCase
で定義されているmethod_name
(Rails < 6.1) が呼ばれるので何かしら定義しておく必要がある。- これはminitestでは
test_example
のようなテストメソッドのメソッド名を返すので、これに準じてテスト固有の名前を取れるならそれを返すのが望ましい。 - RSpecでは
RSpec.current_example.description
で取れる。
- これはminitestでは
- RSpecを使っている場合は
rspec-rails
のRSpec::Rails::FixtureSupport
をinclude
するとこのあたりの面倒を見てくれそうな気がしますが、Rails
がいない場合はそのままでは動かなさそうです。
まとめると次のような感じになります。
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
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タスクたちを読み込んでやります。
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_to
associationを認識できずに落ちます(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
のトップレベルでもいいと思います。