rubyrestdesign-patternssinatrafeaturetoggle

Sinatra API feature toggle


The gist

Is it possible to bake feature-toggle-like functionality into a Sinatra application?

A bit about feature toggles, just in-case ;)

Back story

I've set up a modular Sinatra project, and I tend to implement a GET/POST/PUT/DELETE endpoint for all my resources; it makes it easier to test the app and manipulate the data while in development.

Problem

When I go into production I don't want the unneeded endpoints to exist (e.g DELETE '/users').

Question

Can I annotate the methods with some kind of a :development flag, or maybe intercept the request in a before block? Would you do this using a helper? I'm not sure if I'm heading down the right path here, I'm probably over complicating it(?)

How would one go about this?

If you've done something like this it would be great if you can share your findings with the nation.


Solution

  • You can use the current environment to decide whether you define an action. For example:

    class MyApp < Sinatra::Application
      if settings.development?
        get '/admin' do
          'VIPs only'
        end
      end
    end
    

    If you have a lot to toggle, you might want to isolate them in one file that you can decide to require or not:

    # routes/init.rb
    require_relative 'main'
    require_relative 'debug' if settings.development?
    
    # routes/main.rb
    class MyApp < Sinatra::Application
      get '/' do
        'Hello!'
      end
    end
    
    # routes/debug.rb
    class MyApp < Sinatra::Application
      get '/admin' do
        'VIPs only'
      end
    end
    

    Or if you want to list your development-only paths in one place, here's a filter version:

    class MyApp < Sinatra::Application
      DEVELOPMENT_PATHS = %w[
        /admin
      ]
    
      before do
        unless settings.development? || !DEVELOPMENT_PATHS.include?(request.path)
          halt 404 
        end
      end
    end
    

    Then you could also build some decorator-like methods that add to the list:

    class MyApp < Sinatra::Application
      def self.development_only(path)
        DEVELOPMENT_PATHS << path
      end
    
      get '/admin' do
        'VIPs only'
      end
      development_only '/admin
    end
    

    In general, I'd recommend caution when introducing significant differences between the code that runs in development vs. production. Inevitably, the dev code is either left untested or becomes cumbersome to maintain properly. In this case, there's the danger that you miss a route you intended to hide and it becomes available to everyone in production. I'd tend towards not having these routes at all and manipulating my dev environment from a console, or going all the way to the other end and building fully-tested and production-ready user permissions with something like sinatra-authentication.