diff --git a/lib/zero/request.rb b/lib/zero/request.rb index 36ef705..e8f4513 100644 --- a/lib/zero/request.rb +++ b/lib/zero/request.rb @@ -68,7 +68,8 @@ module Zero # get the method of the request # @return [Symbol] the symbol representation of the method def method - @method ||= @env[CONST_REQUEST_METHOD].downcase.to_sym + return @method if @method + @method = extract_method end # is the method 'GET'? # @return [Boolean] true if this is a get request @@ -103,5 +104,32 @@ module Zero # constant for the request method key # @api private CONST_REQUEST_METHOD = 'REQUEST_METHOD' + # regex to match for valid http methods + CONST_VALID_METHODS = /\A(get|post|put|delete|head|link|unlink|patch)\Z/ + # constant for post + CONST_POST = 'post' + # constant for the method keyword + CONST_METHOD = '_method' + + # this function tries to figure out what method the request used + # + # The problem with extracting the request method is, that every client out + # there can speak all methods (get, post, put, whatever) but not browsers. + # When sending a form, they can only send get or post forms, but not put + # or delete, which makes them a pain to use. So instead an override was + # introduced in rails and other frameworks to support `_method` in the post + # payload. + # So this function does essentially the same, so please do not remove it + # until browsers learned how to do it. + # @return [Symbol] the extracted method + def extract_method + method = @env[CONST_REQUEST_METHOD].downcase + return method.to_sym unless method == CONST_POST + if params.payload.has_key?(CONST_METHOD) + method = params.payload[CONST_METHOD].downcase + return method.to_sym if CONST_VALID_METHODS.match(method) + end + CONST_POST.to_sym + end end end diff --git a/spec/unit/zero/request/method_spec.rb b/spec/unit/zero/request/method_spec.rb index 42ea56a..b9352e1 100644 --- a/spec/unit/zero/request/method_spec.rb +++ b/spec/unit/zero/request/method_spec.rb @@ -5,4 +5,47 @@ describe Zero::Request, '#method' do let(:env) { EnvGenerator.get('/foo') } its(:method) { should == :get } + + context 'with post requests' do + context 'and _method defined' do + let(:env) do + EnvGenerator.post('/foo', { + :input => '_method=put', + 'CONTENT_TYPE' => 'multipart/form-data' + }) + end + it 'uses _method from the payload to change the method' do + expect(subject.method).to be(:put) + end + end + + context 'and _method not defined' do + let(:env) do + EnvGenerator.post('/foo', { + :input => 'foo=bar', + 'CONTENT_TYPE' => 'multipart/form-data' + }) + end + its(:method) { should == :post } + end + + context 'and _method has wrong content' do + let(:env) do + EnvGenerator.post('/foo', { + :input => '_method=foobar', + 'CONTENT_TYPE' => 'multipart/form-data' + }) + end + its(:method) { should == :post } + end + + context 'and no payload' do + let(:env) do + EnvGenerator.post('/foo', { + 'CONTENT_TYPE' => 'multipart/form-data' + }) + end + its(:method) { should == :post } + end + end end