<この記事は「Money Forward Advent Calendar 2015」の12日目の記事です>
Railsでアプリを書いたり、RubyでRailsを書いたり(主にBug Fix)しています。金子です。
前書き
RailsのCIではRailsのmasterとRubyのtrunkでテストが回っています。普段、趣味でこのテストを眺めていて、テストが壊れたりすると原因を調べてFixしたりしています。
RubyにもRailsにも毎日新しい機能が入っていますので、それらが衝突して思わぬバグが生まれることもあります。今年見ていて面白かったバグを3つほど紹介したいと思います。
第3位 突然の大量Error
例えばテスト結果はこちら。
1) Error:
BelongsToAssociationsTest#test_with_select:
ArgumentError: wrong number of arguments (given 0, expected 1)
/home/travis/build/rails/rails/activerecord/lib/active_record/relation/spawn_methods.rb:41:in `instance_exec'
/home/travis/build/rails/rails/activerecord/lib/active_record/relation/spawn_methods.rb:41:in `merge!'
/home/travis/build/rails/rails/activerecord/test/cases/associations/belongs_to_associations_test.rb:349:in `test_with_select'
突然引数の数が足らないと怒られるようになってしまいました。
このエラーはRubyの2.3で新しくHash#to_proc
が導入されたことによります。
Hash#to_proc
は以下のようにHashを1引数の関数(引数をkeyとして、それに対応するvalueを返す関数)に変換するためのメソッドです。
def test_to_proc
h = {
1 => 10,
2 => 20,
3 => 30,
}
assert_equal([10, 20, 30], [1, 2, 3].map(&h))
end
Railsのmerge!
メソッドでは以下のように引数がto_proc
を実装しているときに、instance_exec
に渡していたので、Ruby側の変更でテストが盛大に壊れていました。
def merge!(other) # :nodoc:
if !other.is_a?(Relation) && other.respond_to?(:to_proc)
instance_exec(&other)
else
klass = other.is_a?(Hash) ? Relation::HashMerger : Relation::Merger
klass.new(self, other).merge
end
end
このケースではRails側を修正して対応しました。
新しいメソッドが追加されたことで、gemのテストが通らなくなる瞬間に立ち会えたのが印象的でした。
第2位 突然のSyntaxError
例えばテスト結果はこちら。
/home/travis/build/rails/rails/activerecord/test/models/developer.rb:40: warning: mismatched indentations at 'end' with 'class' at 9
/home/travis/build/rails/rails/activesupport/lib/active_support/dependencies.rb:297:in `require': /home/travis/build/rails/rails/activerecord/test/models/developer.rb:36: syntax error, unexpected keyword_do, expecting keyword_end (SyntaxError)
/home/travis/build/rails/rails/activerecord/test/models/developer.rb:76: syntax error, unexpected keyword_end, expecting end-of-input
突然SyntaxError
が発生しました。
このエラーはRuby側で他のBugを修正したことによります。
RubyのこのBugを修正したことで、以下のようなSyntaxが通らなくなってしまいました。
# RubyのBugチケットで報告された再現ケース
def foo(pr, options, &blk)
p pr.call
end
foo -> { :hello }, a: 1 do end
# Railsの壊れたケース
has_and_belongs_to_many :projects_extended_by_name_and_block,
-> { extending(DeveloperProjectsAssociationExtension) },
:class_name => "Project",
:join_table => "developers_projects",
:association_foreign_key => "project_id" do
def find_least_recent
order("id ASC").first
end
end
RubyでFixが入りました。
第1位 思わぬところからのPatch
例えばテスト結果はこちら。
1) Error:
ActiveModel::Type::IntegerTest#test_casting_nan_and_infinity:
FloatDomainError: Infinity
/home/travis/build/rails/rails/activemodel/lib/active_model/type/integer.rb:39:in `cast_value'
/home/travis/build/rails/rails/activemodel/lib/active_model/type/value.rb:36:in `cast'
/home/travis/build/rails/rails/activemodel/lib/active_model/type/helpers/numeric.rb:12:in `cast'
/home/travis/build/rails/rails/activemodel/test/cases/type/integer_test.rb:35:in `block in <class:IntegerTest>'
Rubyのcaseの最適化で、Float::INFINITY
のときも整数に変換していたため、このようなエラーが発生していました。
日本語で起票されたバグチケットにもかかわらず、"日本語はわからないけど、Rubyならわかる!"というコメントとともにFixされていったのが印象的でした。
まとめ
trunkやmasterを眺めているのは、とてもエキサイティングなので、RubyやRailsを触っている皆様におかれましては、是非trunkやmasterを覗いてみてください。