Integrating Grape Framework with Jets


#1

My apologies if my understanding of the relationship/similarities of Rails’ relationship with Rack and Jets’ relationship with Rack is a bit naive, but I was attempting to integrate the Grape gem/middleware in a brand new Jets application and am having trouble mapping the installation to Jets.

I attempted a variety of ways of getting the Grape middleware to play nicely with Jets, but I settled on making a new lib/api.rb after installing the Grape gem into my Jets project that looks like this:

class Api < Grape::API
  get :hello do
    { hello: 'world' }
  end
end

Then, to add this new Grape class into the middleware stack I first attempted to mount Api after adding the new lib folder to the Jets autoload directories (similar to the Rails instructions at https://github.com/ruby-grape/grape ). This resulted in a NoMethodError mainly because I assume the Jets Router does not have the mounting capabilities implemented as of yet.

My next attempt, also perhaps a bit naive, was to manually require '.lib/api' in the config/application.rb, then follow up by injecting the Grape class into the middleware stack usingconfig.middleware.use(Api)` within the application.configure block in application.rb. This resulted in an ArugmentError stacktrace, most likely because Grape usually requires more configuration/initialization before being passed directly into an existing Middleware chain when you don’t use Rack::Cascade as follows:

ArgumentError: wrong number of arguments (given 1, expected 0)
        /var/lib/gems/2.5.0/gems/grape-1.2.3/lib/grape/api/instance.rb:118:in `initialize'
        /var/lib/gems/2.5.0/gems/grape-1.2.3/lib/grape/api.rb:16:in `new'
        /var/lib/gems/2.5.0/gems/grape-1.2.3/lib/grape/api.rb:16:in `new'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware/layer.rb:31:in `build'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware/stack.rb:62:in `block in build'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware/stack.rb:62:in `each'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware/stack.rb:62:in `inject'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware/stack.rb:62:in `build'
        /var/lib/gems/2.5.0/gems/jets-1.8.11/lib/jets/middleware.rb:11:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/lint.rb:49:in `_call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/lint.rb:37:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/show_exceptions.rb:23:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/common_logger.rb:33:in `call'
        /var/lib/gems/2.5.0/gems/shotgun-0.9.2/lib/shotgun/loader.rb:86:in `proceed_as_child'
        /var/lib/gems/2.5.0/gems/shotgun-0.9.2/lib/shotgun/loader.rb:31:in `call!'
        /var/lib/gems/2.5.0/gems/shotgun-0.9.2/lib/shotgun/loader.rb:18:in `call'
        /var/lib/gems/2.5.0/gems/shotgun-0.9.2/lib/shotgun/favicon.rb:12:in `call'
        /var/lib/gems/2.5.0/gems/shotgun-0.9.2/lib/shotgun/static.rb:14:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:68:in `block in call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `each'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/urlmap.rb:53:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/builder.rb:153:in `call'
        /var/lib/gems/2.5.0/gems/rack-2.0.7/lib/rack/handler/webrick.rb:86:in `service'
        /usr/lib/ruby/2.5.0/webrick/httpserver.rb:140:in `service'
        /usr/lib/ruby/2.5.0/webrick/httpserver.rb:96:in `run'
        /usr/lib/ruby/2.5.0/webrick/server.rb:307:in `block in start_thread'

Before I delve further into the Grape, Jets and Rack source code to figure out the best practice to mounting a Middleware such as Grape, I wanted to see if anyone had any experience with similar middleware issues in Jets and could recommend best practices.

Thanks in advance!


#2

Dug into this. Got Grape working as middleware locally by playing with it like this:

lib/api.rb:

require "grape"

class GrapeApi < Grape::API
  get :hello do
    { hello: 'world' }
  end
end

class Api
  def initialize(app)
    @app = GrapeApi.new       
  end                

  def call(env)      
    @app.call(env)   
  end 
end

It just translated the standard middleware interface to the Grape API interface. Then we use the middleware like this:

config/application.rb:

require "#{Jets.root}/lib/api"

Jets.application.configure do   # ...
  config.middleware.use(Api)

Note, the Grape middleware does prevent the regular Jets application from running, which may have some side effects also. The code above is mainly just some hacks to play with Grape. The code works locally.

However, Grape as middleware won’t work when deployed to AWS as there’s no way to route to the middleware via API Gateway. Remember API gateway is controlled with config/routes.rb.

RE: Jets Router does not have the mounting capabilities implemented as of yet.

Yup, the Jets route does not have mounting capabilities. Think to get something like Grape work with Jets, getting the mount keyword for the Jets route is a good path. Will consider PRs for this. Until then, don’t think there’s a good way to use Grapes. Hope that at least answers the question.


#3

Ah that makes quite a bit of sense. Thanks so much for the deep dive and excellent explanation @tung! If I can, I’ll look into the implementation details of the mount method in side-project time and see if I can get a preliminary implementation put together.