hirakiucの日記

いろいろ

rspecのfixturesに指定する名前でひっかかった話

rails3のrspecを書いて動かしていたときの話。

rspecのfixturesに指定する名前によっては、例外がログに出る事があるみたい。

fixtureに :xxx_meta という名前を指定した事が引き金だった。

rspecでfixturesを使ってた

とあるrails3なアプリのrspecでfixtureを使ってみたところ、実行する度にログにstack traceが出てくるようになってた。

# spec/models/blog_meta_spec.rb
require 'spec_helper'

describe BlogMeta do
  fixtures :blog_meta
# ... spec...
end

spec/fixtures/blog_meta.yml にはちゃんとfixtureなデータファイルは置いてて、期待する感じで動いてくれる。

動いてくれるのだけど、なぜか実行の度にstack traceがログに出てくる。

気持ち悪い!

rspecの実行の度にstack traceがログに出る

Unable to load blog_metum, underlying cause No such file to load -- blog_metum

(snip)/vendor/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:317:in `rescue in depend_on'
(snip)/vendor/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:312:in `depend_on'
(snip)/vendor/bundle/ruby/1.9.1/gems/activesupport-3.2.13/lib/active_support/dependencies.rb:225:in `require_dependency'
(snip)/vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb:767:in `try_to_load_dependency'
(snip)/vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb:782:in `block in require_fixture_classes'
(snip)/vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb:779:in `each'(snip)/vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb:779:in `require_fixture_classes'
(snip)/vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb:762:in `fixtures'
(snip)/spec/models/blog_meta_spec.rb:4:in `block in <top (required)>'
(snip)/vendor/bundle/ruby/1.9.1/gems/rspec-core-2.13.1/lib/rspec/core/example_group.rb:242:in `module_eval'
(snip)/vendor/bundle/ruby/1.9.1/gems/rspec-core-2.13.1/lib/rspec/core/example_group.rb:242:in `subclass'(snip)/vendor/bundle/ruby/1.9.1/gems/rspec-core-2.13.1/lib/rspec/core/example_group.rb:228:in `describe'
(snip)/vendor/bundle/ruby/1.9.1/gems/rspec-core-2.13.1/lib/rspec/core/dsl.rb:18:in `describe'
(snip)/spec/models/blog_meta_spec.rb:3:in `<top (required)>'

(...snip...)

(一部省略)

原因

結論からいうと、

ActiveSupport(ここでは3.2.13) - String.singularize

文字列の単数形を作ってくれる便利なメソッドの挙動が原因。

$ bundle exec ./script/rails console                                                                                     
Loading development environment (Rails 3.2.13)
1.9.1 :001 > "blog_meta".singularize
 => "blog_metum" 
1.9.1 :002 >

meta の 単数形 が metum になってる。

参考:metaはmetumの複数形じゃないよね

もう少し細かい話

stack traceを追いかけてみると、少しずつ見えてきた。

activerecordでは、fixturesをロードする前に、指定された名前から必要となりそうなファイル名を推測して先にロードしようとがんばる。

そのときに指定した名前を単数形にして、そのファイルをロードしようとする様子。

まさにこのfixtures.rb:781

# vendor/bundle/ruby/1.9.1/gems/activerecord-3.2.13/lib/active_record/fixtures.rb
778       def require_fixture_classes(fixture_names = nil)
779         (fixture_names || fixture_table_names).each do |fixture_name|
780           file_name = fixture_name.to_s
781           file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
782           try_to_load_dependency(file_name)

rspecの "fixtures :blog_meta"という記述の引数にあたる部分が fixture_namesになってわたっていく。

そして、 "blog_meta".singularize => "blog_metum" となり、その"blog_metum"をloadしようとして、そんなファイルないわーと例外を投げる結果になる。

ちなみにここではActiveRecord::Base.pluralize_table_names はdefaultのまま trueでした。

対策

一つの解決方法として、設定で回避する事に。

Railsで単数形をsingularizeすると不可解な変化をする場合の対応

rails3でも同様な対処ができる様子。

# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections do |inflect|
  inflect.singular 'blog_meta', 'blog_meta'
end

こんな風に、「"blog_meta"の複数形は "blog_meta"にしてください」とお願いすると回避できた。

$ bundle exec ./script/rails console                                                                                     
Loading development environment (Rails 3.2.13)
1.9.1 :001 > "blog_meta".singularize
 => "blog_meta"
1.9.1 :002 >

すっきりした。