概要
前回「パスによってDBのアクセスを変更する」といった記事を書きました
普通に使う分には問題なかったのですが意図せずテストがコケてしまったので調査を兼ねて残しておきます
結論としては「Rspecのhookでコネクションをデフォルトに接続し直す」ことで回避しました
具体的に状態、問題、解決方法について見ていきます
状態
問題が発生したコードは以前の記事で説明した通り以下の要件を満たすものです
- Railsでマルチデータベース構成を行う
- マルチデータベースでは異なるスキーマを取り扱う
- Contentテーブルにnameカラムの有無
- PostテーブルとCardテーブルが片方にしかない
- マルチデータベースの接続切り替えはモデル単位ではなくアクセスするパスを参照
「原則としてはAデータベースを参照するが、特定のパスから始まる場合はBデータベースを参照。同一のテーブルがあってもカラムが異なる」という状態です
問題
次に発生した問題について見てみます
今回は書いたコードを運用し続けていくためにテストを導入、内容は「特定のパスでリクエストが成功するテスト」「モデルが特定の属性を所持しているか確認するテスト」といった内容です
今回はテストにRSpecを利用します
「Aデータベースを参照するが、特定のパスから始まる場合はBデータベースを参照」なので予期している事としては下記の通り
- モデルのテストは特定のパスにアクセスしないので、Aデータベースを見てほしい
- リクエストのテストは特定のパスにアクセスするので、Bデータベースを見てほしい
リクエストはmiddlewareで明示的に接続する処理を記述しているので問題ありませんが、モデルに記載はないため「リクエスト」→「モデル」と実行されると「モデルのデータ取得時にBデータベースを参照する」となり意図しない正しくテストが動かなくなります
これが今回の問題です
rails_helperにフックを書いて確認してみます
config.around(:each) do |example|
puts "Running: #{example.file_path}:#{example.metadata[:line_number]}"
puts "before: #{ActiveRecord::Base.connection_db_config.database}"
example.run
puts "after #{ActiveRecord::Base.connection_db_config.database}"
end
# >>>>>>>モデルから先に実行される場合
Running: ./spec/models/content_spec.rb:6
before: kanban_test
after kanban_test
.Running: ./spec/controllers/contents_controller_spec.rb:5
before: kanban_test
after board_test
.
Finished in 0.65759 seconds (files took 3.16 seconds to load)
2 examples, 0 failures
# >>>>>>>リクエストから先に実行される場合
Randomized with seed 31590
Running: ./spec/controllers/contents_controller_spec.rb:5
before: kanban_test
after board_test
.Running: ./spec/models/content_spec.rb:6
before: board_test
after board_test
F
Failures:
1) Content attributes has the expected attributes
Failure/Error: subject { Content.create(title: 'hoge', name: 'fuga') }
ActiveModel::UnknownAttributeError:
unknown attribute 'name' for Content.
# ./spec/models/content_spec.rb:5:in `block (3 levels) in <main>'
# ./spec/models/content_spec.rb:7:in `block (3 levels) in <main>'
# ./spec/rails_helper.rb:80:in `block (2 levels) in <top (required)>'
Finished in 0.79844 seconds (files took 4.69 seconds to load)
2 examples, 1 failure
モデルのテスト時のbefore,afterのデータベースが切り替わったままになっているのが確認できます
解決方法
request spec実行後にdefaultに接続し直すことで回避します
config.after(:each, type: :request) do
ActiveRecord::Base.establish_connection(:default)
end
当たり前っちゃ当たり前なのですがもうちょっとスマートに解決したいところではあります...
最後に
middlewareで切り替えたりなどを普通は行わないので今回のようなケースはあまりないと思いますが参考になれば幸いです
RSpecのコネクション周りをもうちょっと見ていきたいですね
実行環境やサンプルコードはこちらに載せています