Question

I'm little new to Ruby, but I have the following coded using Grape API in Ruby. I have @data = YAML.load() called every time it hits GET /api/v1/foo, is there a way in Grape to only load it once and use it? This way is more optimized and not calling YAML.load() at every time. Should I override the initialize method and put a super() for this operation?

Thanks,

require 'grape'
require 'json'
require "yaml"

module MyProject
  CONFIG_FILE = "./config.yml"

  class Api < Grape::API
    rescue_from :all
    prefix 'api'
    version 'v1'
    format :json

    resources :foo do
      get do
        @data = YAML.load(File.open(MyProject::CONFIG_FILE))
      end
    end
  end
end
Was it helpful?

Solution

The short answer is that Grape doesn't work quite how you think, and attribute variables of the MyProject::Api are not the way forward for your new web service. However, it is an interesting question, and worth exploring why this is so.

If you add a puts self.inspect inside the resources :foo block, and run using rackup, when you call the route you should see that self is in fact a Grape::Endpoint object. Also, no matter what you try to do with instance variables, they will always start in the same state for each request. That is because Grape turns your route definitions into prepared Grape::Endpoint objects, with a lot of the definition data and setup put into a quickly-accessible form (so that it is not figured out on each request). Eventually, on each request, the matching Grape::Endpoint object including your block (and other details you defined for the route) is duplicated before being called, meaning that state is not maintained between requests.

This may seem complicated, but most frameworks covering web service requests will do something similar. Generally you don't want request-handling state to persist between requests. Frameworks with larger scope - e.g. Rails - have places to put more persistent data planned out for you. Grape does not have this defined, which has its pros and cons. One obvious plus point is that you are more free to use whatever other data persistence approach that you wish to.

23tux's answer will sort you out immediately for loading config. Although I'm not entirely sure how @@data becomes accessible to the endpoint block (it may even be creating a closure around the variable).

Longer term, you should look to moving config management out of your MyProject::Api class, and including it as a module via Grape's helpers method (I'm happy to provide an example if you are interested).

Edit: Example based on your current code, but moving config management to a separate module:

require 'grape'
require 'json'
require "yaml"

module MyProject

  module Config
    CONFIG_FILE = "./config.yml"
    @@data = nil
    def config
      @@data ||= YAML.load( File.open( CONFIG_FILE ) )
    end
  end

  class Api < Grape::API
    rescue_from :all
    prefix 'api'
    version 'v1'
    format :json

    helpers MyProject::Config

    resources :foo do
      get do
        config
      end
    end
  end
end

This is one step further, structurally, than 23tux's answer, but is still not completely separating concerns of storage (and caching etc) versus api access. As you progress towards a more sophisticated web service, you will want to keep the Grape route definitions simple, with only a small amount of logic that manages or manipulates the data - well, at least as seen directly in the blocks.

One way to link between your Grape definitions, and other gems that might manage config, logging, authentication and other services, is via Grape's helpers method. Grape also has some built-in helper methods for common tasks.

The main exception to using helpers MyModule to add shared functions into Grape is when you want to manage displaying data objects (aka "models") from your core application. For that you have a few choices, but the grape-entity gem and the present method is not a bad place to start.

OTHER TIPS

If the @data is the same for the whole api, and doesn't change at any time, just use a class variable

require 'grape'
require 'json'
require "yaml"

module MyProject
  CONFIG_FILE = "./config.yml"

  class Api < Grape::API
    @@data = YAML.load(File.open(MyProject::CONFIG_FILE))

    rescue_from :all
    prefix 'api'
    version 'v1'
    format :json

    resources :foo do
      get do
        puts @@data
      end
    end
  end
end

Not tested, but with this way, you ensure that the data is only loaded once, when your Api class is loaded

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top