MOE3: 定数定義の罠
OrderStatus の定数
テスト環境でちょっと嵌まった。
OrderStatus
のクラス定義はこんな感じ。
class OrderStatus < ActiveRecord::Base UNEXECUTED = OrderStatus.where(code: 0).first INVALID = OrderStatus.where(code:100).first SUCCESS = OrderStatus.where(code:200).first SATISFIED = OrderStatus.where(code:201).first FAILURE = OrderStatus.where(code:300).first CUT = OrderStatus.where(code:301).first STANDOFF = OrderStatus.where(code:302).first DISLODGED = OrderStatus.where(code:303).first CONFLICT = OrderStatus.where(code:400).first DISBANDED = OrderStatus.where(code:401).first end
OrderStatus::SUCCESS
のように使用する。code
の値そのものには大して意味がないので気にしなくても良い。
重要なのは、OrderStatus
クラスのロード時に db/seeds.rb で設定されたレコードが読み込まれて定数が定義されることだ。
落とし穴
開発環境では問題なく動く。
しかしテスト環境ではこれがうまくいかない。全てのステータス定数が nil
になってしまうのだ。
ActiveRecord の遅延評価か、あるいはキャッシュが悪さしているのかとしばらく悩んで、ようやく原因に思い当たった。
テスト環境と db/seeds.rb
本来 Rails のテスト環境では db/seeds.rb は読み込まれないのだが、今回は OrderStatus
のようにテスト環境でも使いたいマスタデータがあるので、spec/spec_helper.rb の冒頭で require
している。
テスト環境では spec の example
実行の度に DB が初期化され、db/seeds.rb の再読み込みが行われてマスタデータが設定される。
# This file is copied to spec/ when you run 'rails generate rspec:install' ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'rspec/autorun' require Rails.root.join("db", "seeds") # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } #(略)
要するにタイミングの問題である。
db/seeds.rb によってマスタデータが生成される前に OrderStatus
クラスがロードされているので、定数定義のためのクエリ実行時は order_status
テーブルは空っぽなのだ。
定数定義の右辺、OrderStatus
のクエリが常に nil を返すのは当然だった。
対策
本来のタイミングで定数が正しく定義されないのなら、テスト実行タイミングで定義しなおせばよい。
Ruby では定数の上書きは可能なのだが、それをやるといちいち警告が出て目障りなので spec/support/order_status.rb を下記の通り作成した。
このファイルは spec/spec_helper.rb によって自動的に読み込まれる。
# OrderStatus クラス再定義 Object.class_eval { remove_const :OrderStatus } load Rails.root.join("app/models", "order_status.rb")
定数の再定義ではなく、OrderStatus
そのものを再定義してしまうわけだ。
一応補足すると、require
では app/models/order_status.rb
の二度読みができないので load
を使っている。
これでテスト環境でも OrderStatus
が期待通りに機能するようになった。