Grape::Entity errors but route carries on...

272 views
Skip to first unread message

Alan

unread,
Aug 24, 2020, 8:10:30 AM8/24/20
to Grape Framework Discussion
Hi,

I just encountered this and would like to sanity check.

When you present a Grape::Entity, rendering of the entity is deferred until some point deep within the framework and so runtime errors within your entity do not impede evaluation of the rest of your route (including after blocks).

For example, the below will error and the consumer of the API is not going to get a useful response, but log_success! and the after block are both still going to run:

module API
  class API < Grape::API
    class User < Struct.new(:name, keyword_init: true); end

    class UserEntity < Grape::Entity
      expose :display_name

      def display_name
        @object.name / 'seventy' # Runtime error.
      end
    end

    helpers do
      def log_success!
        puts 'It works, pay me.'
      end
    end

    after do
      puts 'Pay me more.'
    end

    get '/' do
      user = User.new(name: 'Boop')

      present user, with: UserEntity

      log_success!
    end
  end
end

Started GET "/" for 127.0.0.1 at 2020-08-24 12:30:27 +0100
It works, pay me.
Pay me more.
caught error of type NoMethodError in after callback inside Grape::Middleware::Formatter : undefined method `/' for "Boop":String
NoMethodError - undefined method `/' for "Boop":String:

A workaround seems to be to force the serialisation of the entity within the route, e.g. to use:

get '/' do
  user = User.new(name: 'Boop')

  present UserEntity.represent(user).as_json

  # Now we don't get here if the above errors.
  #
  log_success!
end

Are there any unintended consequences of doing it like this or is there an alternative? Obviously in a real application we'll want to rescue from errors and present the errors in a JSON response of their own but I don't think that's material here.

Thanks.

Daniel D.

unread,
Aug 24, 2020, 9:21:54 AM8/24/20
to Grape Framework Discussion
Conceptually, Grape separates calculating data to return, and formatting that data. When you present an entity, you're returning it. When you call .as_json, you're formatting it. So the obvious unintended consequence is that in order to support multiple formats you will have to reimplement this logic in the API implementation (calling .as_json, to_xml, to_s, etc.). Then you'd have to deal with errors by hand. 

Grape makes this all very easy for you. Don't call as_json here and rely on the built-in formatters and built-in error handling with "rescue_from". You should be able to accomplish everything you're set to accomplish.

cheers
dB.


--
You received this message because you are subscribed to the Google Groups "Grape Framework Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ruby-grape+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ruby-grape/cf4bd638-bdec-4d9a-bf8a-7770a3044905n%40googlegroups.com.


--

Alan

unread,
Aug 25, 2020, 5:33:04 AM8/25/20
to Grape Framework Discussion
Hi Daniel,

Thanks for that. Yes I see what you mean about my workaround breaking the separation of the data from the representation. I can't see any other way to achieve what I'm looking for though, am I missing something?

To be clear, the intention is to run some code (e.g. to log usage for the purpose of audit/billing) only when we're confident that the request has succeeded. Code placed at the end of the route or in after/finally blocks runs before the formatters and so ignores the possibility of errors arising from formatters.

I guess one could argue that the formatters should be so simple that the probability of error is low enough to ignore, but with something like Grape::Entity where we have lots of opportunities to dynamically construct the response with Procs and blocks, that seems less reasonable.

Through the rescue hooks one could try and undo whatever might have happened in the after/finally filters, but it doesn't seem like a very good solution.

Is there any option to force evaluation of the formatters a little earlier than it currently is so that the after filters can be skipped in the event of error, e.g. https://github.com/ruby-grape/grape/blob/192a2a2/lib/grape/endpoint.rb#L269, or is that basically the same thing? Alternatively is there any possibility of adding a callback/filter when the response has actually been successfully constructed?

Thanks.

Daniel D.

unread,
Aug 25, 2020, 8:52:25 AM8/25/20
to Grape Framework Discussion
I don't think you're missing anything. You can just move the rendering into the body of API call. Note that present just ultimately calls "body data", which you can save some effort by doing body UserEntity.represent(user).as_json

A possible improvement could be to make UserEntity.represent(user) do all the eager checking and pre-rendering.

Reply all
Reply to author
Forward
0 new messages