assert_pedantic_semantic_thing

mly : June 17th, 2006

Say you're getting into the whole RESTful thing and realizing that maybe it isn't such a good idea to allow GET requests to modify something. Being the good citizen you are, you set about requiring all your state-modifying actions to require POST requests. Looking at the examples you might do something like this (taken from a [DHH comment](http://www.loudthinking.com/arc/000529.html)):

verify :method => :post, :only => :delete, 
:redirect_to => { :action => "forbidden_verb" }
Which looks all nice and neat except for the fact that it's stupidly un-semantic and wasteful to boot. After all, isn't this what the [405 response code](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.6) is for? Keeping with the spirit of letting HTTP do its job, we instead create a verify statement like so:

verify :only => [:update], :method => :post, :render => {:status => 405, :text => 'Method Not Allowed'}, :add_headers => {'Allow' => 'POST'}
where `:only` points to the methods we want to protect by being POST only. This is fine and dandy, but you want to make certain that these methods work, and that future updates to rails don't break things, so you're a good developer who eats his Wheaties and writes his tests. However, you also like DRY and want to keep it simple. So I'll cut out the work for you and just tell you to stick this in your `test_helper.rb`:

  def assert_header(header, expected)
    if expected.is_a?(String)
      assert_equal @response.headers[header], expected
    elsif expected.is_a?(Regex)
      assert @response.headers[header].match(expected), "#{@response.headers[header]} does not match #{expected}"
    else
      assert false, expected + " is not a String or Regex"
    end
  end
  
  def assert_method_not_allowed(action, params={})
    parameters, good, methods = params[:params] || {}, params[:good] || :post, params[:bad] || :get
    methods = [methods].flatten
    methods.each do |m|
      send m, action, parameters
      assert_header('Allow', good.to_s.upcase)
    end
  end
You can then do this sort of thing in your functional tests:

  def test_update__with_get
    assert_method_not_allowed(:update, {:good => :post, :bad => :get})
  end
Note that you could leave off the hash that is the second argument, since those are default values. Customize to taste by checking out the first line of the `assert_method_not_allowed` definition.

5 Responses to “assert_pedantic_semantic_thing”

  1. Yaroslav Markin Says:
    Very nice tutorial, thanks!
  2. mly Says:
    I feel I should mention that the new simply_restful stuff (the plugin, that will be in 1.2) pretty much makes this all obsolete, and rightfully so. If you're not comfortable living on the edge, however, this can still be useful.
  3. lukfugl Says:
    Why not DRY out that verify line a little too:
    class ActionController::Base
      def self.allowed_methods( *methods, options={} )
        verify options.merge(
          :method => methods,
          :render => {
            :status => 405,
            :text => 'Method Not Allowed'
          },
          :add_headers => {
            'Allow' => methods.map{ |m|
              m.to_s.upcase
            }.join(', ')
          }
        )
      end
    end
    
    class MyController < ActionController::Base
      allowed_methods :post, :only => :update
    end
  4. John Bledsoe Says:
    To those that come back later: :add_headers is only available in Edge Rails. lukfugl: An optional array argument must follow any regular arguments. So *methods must be switched with options. Which unfortunately hurts semantics.
  5. anluctpmje Says:

    Hello! Good Site! Thanks you! jeylmuheko

Sorry, comments are closed for this article.