diff --git a/CHANGELOG.markdown b/CHANGELOG.markdown index 43a447e33d..96a209d9da 100644 --- a/CHANGELOG.markdown +++ b/CHANGELOG.markdown @@ -2,6 +2,7 @@ ==================== * [#265](https://github.com/intridea/grape/issues/264): Fix: The class ValidationError should be in the module "Grape::Exceptions". Fixes [#264](https://github.com/intridea/grape/issues/264) - [@thepumpkin1979](https://github.com/thepumpkin1979). +* [#269](https://github.com/intridea/grape/pull/269): Fix: LocalJumpError will not be raised when using explict return in API methods - [@simulacre](https://github.com/simulacre) * Your contribution here. 0.2.2 diff --git a/lib/grape/endpoint.rb b/lib/grape/endpoint.rb index 7c7d4cc32c..d2e16f8d2f 100644 --- a/lib/grape/endpoint.rb +++ b/lib/grape/endpoint.rb @@ -11,9 +11,37 @@ class Endpoint attr_accessor :block, :options, :settings attr_reader :env, :request + class << self + # @api private + # + # Create an UnboundMethod that is appropriate for executing an endpoint + # route. + # + # The unbound method allows explicit calls to +return+ without raising a + # +LocalJumpError+. The method will be removed, but a +Proc+ reference to + # it will be returned. The returned +Proc+ expects a single argument: the + # instance of +Endpoint+ to bind to the method during the call. + # + # @param [String, Symbol] method_name + # @return [Proc] + # @raise [NameError] an instance method with the same name already exists + def generate_api_method(method_name, &block) + if instance_methods.include?(method_name.to_sym) || instance_methods.include?(method_name.to_s) + raise NameError.new("method #{method_name.inspect} already exists and cannot be used as an unbound method name") + end + define_method(method_name, &block) + method = instance_method(method_name) + remove_method(method_name) + proc { |endpoint_instance| method.bind(endpoint_instance).call } + end + end + def initialize(settings, options = {}, &block) @settings = settings - @block = block + if block_given? + method_name = "#{options[:method]} #{settings.gather(:namespace).join( "/")} #{Array(options[:path]).join("/")}" + @block = self.class.generate_api_method(method_name, &block) + end @options = options raise ArgumentError, "Must specify :path option." unless options.key?(:path) @@ -135,7 +163,7 @@ def declared(params, options = {}) unless settings[:declared_params] raise ArgumentError, "Tried to filter for declared parameters but none exist." end - + settings[:declared_params].inject({}){|h,k| output_key = options[:stringify] ? k.to_s : k.to_sym if params.key?(output_key) || options[:include_missing] @@ -324,7 +352,7 @@ def run(env) run_filters after_validations - response_text = instance_eval &self.block + response_text = @block.call(self) run_filters afters cookies.write(header) diff --git a/spec/grape/endpoint_spec.rb b/spec/grape/endpoint_spec.rb index 162e3ba36a..4c887062f2 100644 --- a/spec/grape/endpoint_spec.rb +++ b/spec/grape/endpoint_spec.rb @@ -293,7 +293,7 @@ def app; subject end last_response.body.should == '{"dude":"rad"}' end end - + describe "#redirect" do it "should redirect to a url with status 302" do subject.get('/hey') do @@ -354,6 +354,32 @@ def memoized last_response.body.should == 'yo' end + it 'should allow explicit return calls' do + subject.get('/home') do + return "Hello" + end + + get '/home' + last_response.status.should == 200 + last_response.body.should == "Hello" + end + + describe ".generate_api_method" do + it "should raise NameError if the method name is already in use" do + expect { + Grape::Endpoint.generate_api_method("version", &proc{}) + }.to raise_error(NameError) + end + it "should raise ArgumentError if a block is not given" do + expect { + Grape::Endpoint.generate_api_method("GET without a block method") + }.to raise_error(ArgumentError) + end + it "should return a Proc" do + Grape::Endpoint.generate_api_method("GET test for a proc", &proc{}).should be_a Proc + end + end + describe '#present' do it 'should just set the object as the body if no options are provided' do subject.get '/example' do