Reconfiguring Rails Conventions – Highlighting Missing Strong Parameters

August 23, 2014 Brian Butz

Let’s say we were creating an API that allows us to create ‘foo’s. Foos, as far as we know, will have a first name and a title. We can write a request spec to help us drive out our endpoint’s behavior:


require 'rails_helper'

describe 'the foo resource' do
  it 'allows foos to be created' do
    foo = {
      first_name: 'Jeff',
      title: 'Assistant'
    }

    post '/foos',
      foo.to_json,
      {'CONTENT_TYPE' => 'application/json', 'HTTP_ACCEPT' => 'application/json'}


    get '/foos',
      {},
      {'HTTP_ACCEPT' => 'application/json'}


    foos = JSON.parse(response.body)
    expect(foos.last['first_name'].to eq('Jeff'))
    expect(foos.last['title']).to eq('Assistant')
  end
end

We are in the midst of driving out this feature, and find ourselves with a pretty standard Rails controller:


class FoosController < ApplicationController
  respond_to :json

  def index
    respond_with Foo.all.entries
  end

  def create
    foo = Foo.new(foo_params)
    foo.save
    respond_with foo, location: nil
  end

  private

  def foo_params
    params.require(:foo).permit(:first_name)
  end
end

We run our request spec again, and see that it is failing for the following reason:


expected: "Assistant"
     got: nil

(compared using ==)

./spec/requests/foos_spec.rb:22:in `block (2 levels) in '
-e:1:in `load'
-e:1:in `'

1 example, 1 failure, 0 passed

Right off the bat, it’s not entirely obvious what we’ve missed. Did we forget to do something in the database? In the model? In the controller? While finding the issue is not “Where’s Waldo”-tier, our test is providing us with some pretty weak feedback. Given that this is an integration test, the problem is that we’ve most likely wired something up incorrectly. If we check our test.log, hidden in between the request and our database logs is the culprit:


Started POST "/foos" for 127.0.0.1 at 2014-08-22 16:37:45 -0700
Processing by FoosController#create as JSON
  Parameters: {"first_name"=>"Jeff", "title"=>"Assistant", "foo"=>{"first_name"=>"Jeff", "title"=>"Assistant"}}

Unpermitted parameters: title       

As we are oft to do, we forgot to whitelist the :title parameter, and so the parameter is being completely ignored:


  def foo_params
    params.require(:foo).permit(:first_name)   

Ideally our test would tell us that we’ve made a mistake wiring up our code, rather than the ambiguous error we’re getting. We can make our tests give us better feedback by reconfiguring Rails to throw an error when given unauthorized attributes. In config/environments/test.rb we can add the line:


Rails.application.configure do
  ... # some other config

  config.action_controller.action_on_unpermitted_parameters = :raise

  ... # some other config
end

Now, when we run our request spec, we get this error:


ActionController::UnpermittedParameters: found unpermitted parameters: title
./app/controllers/foos_controller.rb:17:in `foo_params'
./app/controllers/foos_controller.rb:9:in `create'
./spec/requests/foos_spec.rb:11:in `block (2 levels) in '
-e:1:in `load'
-e:1:in `'

1 example, 1 failure, 0 passed

Our test failure reveals the exact line in our code where we’ve misconfigured our app. In the future, it will be obvious when we inevitably make this mistake again. Further, this change will highlight places in our (tested) code where we’ve been sending parameters that haven’t been whitelisted.

About the Author

Biography

Previous
Backing up VCSA 5.5 DBs to S3
Backing up VCSA 5.5 DBs to S3

The Cloud Foundry Development Teams use a heavily-customized VMware vCenter Server Appliance (VCSA) 5.5. We...

Next
Docker Service Broker for Cloud Foundry
Docker Service Broker for Cloud Foundry

Pivotal's Cloud Foundry team is a fan of Docker. In this post, learn how the Docker service broker for Clou...