fixtures の変更

Fixtureも変わっています。大きくは、関連オブジェクトをidではなくhuman readableな名前で記述できるようになった点ですが、このエントリは関係ありません。隠れた変更点?として、fixtureのキャッシュ機能が追加されています。


Rails 2.0 のFixtureクラスでは、Fixtureをファイルから読み込んでインスタンス化するcreate_fixtureメソッドに、

    table_names_to_fetch = table_names.reject { |table_name| fixture_is_cached?(connection, table_name) }

    unless table_names_to_fetch.empty?
    ...
    end

のような形で、キャッシュされている(すでにインスタンス化されている)Fixtureは、ファイルの再読込をしないようなロジックが追加されています。つまり、Rails 1.2.Xでは、test/unit のテストケースがN個ある場合に、test/fixtures はN回読み込まれるわけですが、Rails 2.0.X では、これが1回で済むようになりました、ということです。

このキャッシュ単位は、fixturesのファイル単位、つまりモデルごと、もう少しいえばテーブルごと、ということになります。

で、WebJourneyでは、(1)「初期データとして投入するfixture」と(2)「テストデータとして投入するfixture」を2つのFixtureファイルにしておいて、テスト実行時には、Fixtureクラスのlaod_fixtureメソッドを上書きして、2つのファイルを読み込めるように書き換えていたのですが、このキャッシュ機能の性で、2つめのファイルが読み込めなくなってしまいました。
(1)も(2)も、同じモデルのFixtureなので、(1)のファイルを読み込み終わったときに、「キャッシュしたぜ」フラグが立って、(2)のFixtureを作ろうとしたときに、「キャッシュにあるぜ」といわれて、読み込まれない、というオチ。

実際には、Rails 1.2.X ではこんな感じの記述を test/test_helper.rb に書いていました。db/init には (1)の初期データ用fixtureをいれておいて、test/fixturesには(2)のテスト用fixtureをいれておくようにしています。

  class_inheritable_accessor :initialize_fixtures_path
  self.initialize_fixtures_path = File.join(RAILS_ROOT, "db/init")

  alias :load_fixtures_from_test_directory :load_fixtures
  def load_fixtures
    @initialize_fixtures = {}
    @exist_fixtures = []
    fixture_table_names.each do |table_name|
      @exist_fixtures << table_name if File.exists?(File.join(initialize_fixtures_path, table_name + ".yml"))
    end

    fixtures = Fixtures.create_fixtures(initialize_fixtures_path, @exist_fixtures, fixture_class_names)
    unless fixtures.nil?
      if fixtures.instance_of?(Fixtures)
        @initialize_fixtures[fixtures.table_name] = fixtures
      else
        fixtures.each do |f|
          @initialize_fixtures[f.table_name] = f
        end
      end
    end
    load_fixtures_from_test_directory
    # load fixtures defined in db/init directory
    @initialize_fixtures.each { |t, f|
      f.insert_fixtures
    }
  end

Rails オリジナルのfixture(test/fixtures)をロードした時点(load_fixtures_from_test_directory)で、DBには、test/fixturesで定義されているデータのみが存在することになるので(それまでにあったデータはトランザクションコンテキストの中で一度消える、テストが終わるとRollbackされるのでもどる:use_transactional...=trueの場合)、ここで、db/initにあるデータをinsertしにいきます。

ところが、Rails 2.0 では、この load_fixtures_from_test_directory を実行しても、それ以前にFixture.create_fixtureをしているので、「キャッシュにあるぜ」フラグのせいで、正しくtest/fixtures の内容がDBに登録されません。

というわけで、「キャッシュにあるぜ」フラグを明示的に解消すべく、

    Fixtures.reset_cache
    fixtures = Fixtures.create_fixtures(initialize_fixtures_path, @exist_fixtures, fixture_class_names)

    ...(snip)...

    Fixtures.reset_cache
    load_fixtures_from_test_directory

として回避します。要するに、Fixtureのキャッシュなんかいらないよ、テストなんて時間がかかったっていいじゃないか、というモチベーション。「果報は寝て待て」ということです。

Fixtureは1テーブルにつき1ファイル、というConventionを無視した報い、ですね。。