在公司的一个项目中,无意中发现了 Rails 的这个 bug。
如果 params 属性不包含上传文件,一切嵌套属性表现正常。但是当 params 中包含文件的时候(multipart 请求),嵌套属性将出现问题,请参看以下测试代码
unless File.exist?('Gemfile')
  File.write('Gemfile', <<-GEMFILE)
    source 'https://rubygems.org'
    gem 'rails', path: '~/code/rails'
    gem 'arel', github: 'rails/arel'
    gem 'rack', github: 'rack/rack'
    gem 'i18n', github: 'svenfuchs/i18n'
  GEMFILE
  system 'bundle'
end
require 'bundler'
Bundler.setup(:default)
require 'rails'
require 'action_controller/railtie'
require 'yaml'
class TestApp < Rails::Application
  config.root = File.dirname(__FILE__)
  config.session_store :cookie_store, key: 'cookie_store_key'
  config.secret_token    = 'secret_token'
  config.secret_key_base = 'secret_key_base'
  config.logger = Logger.new($stdout)
  Rails.logger  = config.logger
  routes.draw do
    post '/' => 'test#test'
  end
end
class TestController < ActionController::Base
  include Rails.application.routes.url_helpers
  def test
    render json: my_params
  end
  private
  def my_params
    params.require(:thing).permit(
      # :some_file,
      :some_scalar_attribute,
      :some_array_of_scalars  => [],
      :some_array_of_hashes_containing_scalars => [:scalar_one, :scalar_two]
    )
  end
end
require 'minitest/autorun'
require 'rack/test'
class BugTest < Minitest::Test
  include Rack::Test::Methods
  POST_DATA = {
    "thing" => {
      "some_file" => Rack::Test::UploadedFile.new(File.open(File.join(Rails.root, 'myfile.csv'))),
      "some_scalar_attribute" => "foo",
      "some_array_of_scalars" => ["b", "c"],
      "some_array_of_hashes_containing_scalars" => [
        {"scalar_one" => "d", "scalar_two" => "e"},
        {"scalar_one" => "f", "scalar_two" => "g"}
      ]
    }
  }
  JSON_DATA = '{"some_scalar_attribute":"foo","some_array_of_scalars":["b","c"],"some_array_of_hashes_containing_scalars":[{"scalar_one":"d","scalar_two":"e"},{"scalar_one":"f","scalar_two":"g"}]}'
  def test_returns_success
    post '/', POST_DATA
    assert last_response.ok?
    assert_equal JSON_DATA, last_response.body
  end
  private
    def app
      Rails.application
    end
end
用 Ruby 的 mini test 测试此代码之后,测试将显示不通过。这里,我往 Rails 的 controller POST 了一个"some_array_of_hashes_containing_scalars"属性。它是一个 array of hash(杂凑数组)。当 Rails 接受到此杂凑数组的时候,params["some_array_of_hashes_containing_scalars"] 只接受到了数组中的最后一个杂凑,之前的杂凑都莫名其妙的被 Rails 过滤掉了。而当在 POST_DATA 中删除/注释掉“some_file”属性之后,测试通过。一切正常!