Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

2013-12-30

Testing strong parameters

Pivot Robbie Clutton describes strong parameters as follows:
Strong parameters are a way of white listing HTTP query parameters and moves the burden of whitelisting from the ActiveModel/ActiveRecord classes and into the controllers.
Source
My view is that this testing strategy is far too complex, and here I outline a simpler approach.

First, let's understand the API. It was a bit tricky for me at first, partly because the documentation is vague.

We can whitelist attributes via the permit method, and we can require them via the require method.

Example

params.fetch(:project).require(:name)
params.fetch(:project).permit(:description)

Regarding the handling of missing or unpermitted keys, here's what the docs say:
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
Additionally, this behaviour can be changed by changing the config.action_controller.action_on_unpermitted_parameters property in your environment files. If set to :log the unpermitted attributes will be logged, if set to :raise an exception will be raised.
The unpermitted key behavior happens instantly. That is, if you require a parameter that's missing, you get an ActionController::ParameterMissing exception. If you permit some parameters, any unpermitted parameters will instantly be indicated via an ActionController::UnpermittedParameters exception or a log line, depending on your action_on_unpermitted_parameters setting.

Note that this means you don't want to have a permit before a require if you're using the :raise option (which I strongly suggest you do, because that's confident coding), because the permit call would then raise an exception.

But what's this default behavior where keys that are not permitted are "filtered out and ignored"? This just means that the call to permit returns a hash in which the unpermitted keys are removed. The original params object is unaltered.

In any case, this brings us back to..

Testing

First off, recognize that it's easy to test that all the right parameters are permitted. We simply write a controller test using an update hash including all of the permitted parameters.

Now, to assert that any other params aren't permitted, we simply make a context in which we have a single unpermitted parameter, and assert that the unpermitted attribute isn't updated.

But now what about require? That's considerably more work to test, since you'd have to make nested contexts for every single required parameter. Therefore I advise simply not to use require at all. I'm not even sure why it was added. Feel free to tell me if you can think of a good reason.

This strategy is just radically simpler than the approach Robbie Clutton took.

2012-04-15

Bi-directional use of class_name in ActiveRecord associations

I recently wanted to use ActiveRecord's association syntax on two models, called Proposal and Period. To add clarity, I wanted the Proposals to be called struck_proposals, from the perspective of the Period. And I wanted the Period to be known as struck_on_period from the perspective of the Proposal. While the Rails guide explains how to do this in one direction or the other, it took considerable searching to figure out how to set up a "bi-directional" aliasing like this. Finally, it turned out that there are two ways to do it, seen here.
class Period < ActiveRecord::Base
  has_many :struck_proposals, class_name: 'Proposal'
end

class Proposal < ActiveRecord::Base
  belongs_to :struck_on_period, class_name: 'Period',
    foreign_key: :period_id
end
Or this way:
class Period < ActiveRecord::Base
  has_many :struck_proposals, class_name: 'Proposal',
    foreign_key: :struck_on_period_id
end

class Proposal < ActiveRecord::Base
  belongs_to :struck_on_period, class_name: 'Period'
end
Since either way works, the choice unfortunately seems arbitrary. But I later noticed that it's actually not. Just imagine that you wanted to add an additional type of Proposal, e.g. an executed Proposal. Then a Proposal could belong to a Period in two different ways. But it obviously couldn't have two different columns named period_id. It would have to have a struck_on_period_id and an executed_on_period_id. So the latter implementation is the correct one.

2012-01-25

Fast specs without loading Rails, with Ruby 1.9

My goal is to create a fast test suite, which does not load Rails. You can see this concept discussed by Gary Bernhardt in one of his DestroyAllSoftware screencasts here.

The gist of it is, I have a spec_fast directory, and a test like RAILS_ROOT/spec_fast/lib/foo_spec.rb.

I also have a RAILS_ROOT/spec_fast/spec_helper that e.g. defines constants that would exist if Rails were loaded, that my code might need.

The problem is that foo_spec can't require from the spec_fast directory, because it's not in $LOAD_PATH. Ruby 1.9 apparently doesn't add "." to $LOAD_PATH.

I tried to hack Gary's fast test script to prepend it to $LOAD_PATH by adding a few things.
if [ $need_rails ]; then
    command="ruby -S bundle exec $command"
else
    this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    rails_dir=$(dirname $this_dir)
    spec_fast_dir="$rails_dir/spec_fast"
    command="RUBYLIB=\"$spec_fast_dir\" $command"
fi
But then I get a complaint: script/spec_fast: line 39: RUBYLIB="/Users/USER/workspace/RAILS_ROOT/spec_fast": No such file or directory Clearly I'm lacking some BaSH expertise, although maybe there's a better way to solve this problem anyway?

UPDATE

This seems to work.
command="rspec $filename"

if [ $need_rails ]; then
    RAILS_ENV=test ruby -S bundle exec $rspec_exec
else
    this_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    rails_dir=$(dirname $this_dir)
    spec_fast_dir="$rails_dir/spec_fast"
    RUBYLIB="$spec_fast_dir" $command $filename
fi
And then I just use "require 'fast_spec_helper'" in my fast spec.

2010-03-09

testing cookies with rails 2.3

At first I was doing this, in order to make a test pass.

@request.cookies[:foo] = 'bar'

It wouldn't work. After just spending a weekend hitting my head against a wall with this, the solution turned out to be simple.

@request.cookies['foo'] = 'bar'

'foo' not :foo -- argh!

2009-03-03

scaling rails

this is all kinds of awesome. http://railslab.newrelic.com/scaling-rails