Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
Reconfiguring Rails Conventions – Highlighting Missing Strong Parameters

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.

Comments
Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *