問題
以下のようなコードは、恐らくバグになります。
json = users.each do |user| id = user.id full_name = user.full_name { id: id, full_name: full_name, } end render json: json
これはid
とfull_name
をもったオブジェクトの配列をjsonでrenderするように見えます。
ですが、実際にはuser
に含まれる全ての(id
やfull_name
以外も)情報がJSONとして返ってしまうでしょう。例えば、password
などが入っていたら目もあてられません。
また、id
やfull_name
は(恐らく)JSONとしてrenderされるため、開発をしていても問題に気が付かない可能性があります。
原因
この問題は、Array#each
をArray#map
と間違って使ってしまったことに起因しています。
そしてこの2つの問題の違いは、ブロックの戻り値が使われるか、というところにあります。
each
はブロックの戻り値を使わず、self
を返します。
そのため先程の例ではusers
とjson
は全く同じオブジェクトが入ります。
対策
これを防ぐため、RuboCop でeach
のブロックの最後の値がリテラルや変数など、評価しても何も起きないものである場合に警告を出すようにしました。
例えば先程の例では、each
のブロックの最後の値がリテラルであり、評価しても意味がないため、警告を出します。
これは既存のLint/Void
cop の拡張として実装されています。
なお、この Cop はeach
がブロックの戻り値を使うようなメソッドとして定義されている場合に偽陽性が存在します。
ですが、each
と言う名前をそのような用途に使うことは殆どないと考えられるため、問題にはならないでしょう。
この機能は次回の RuboCop のリリース後に有効になります。