Universal settings in Sinatra

2019-05-11 18:27发布

问题:

I have a class in Sinatra where I set some settings (from JSON, as it happens):

class Pavo < Sinatra::Base
  configure :development do
    set :config, JSON.parse(File.open(File.dirname(__FILE__) + "/pavo.configuration.development.json", "rb").read)
    set :config_mtime, File.mtime(File.dirname(__FILE__) + "/pavo.configuration.development.json")
  end

  [...]

  get '/' do
    puts "whatever"
  end
end

And that class has a model, that is required to read those settings.

class Resolver < Sinatra::Base
  def get_data(workpid)
    url_str = settings.config['public']['BOOKS_DATA_SERVICE_URL'].gsub('${WORKPID}', workpid)
    return Resolver.get_json(url_str)
  end
  [...]
end

However, the Resolver class can't do it: undefined method `config' for Resolver:Class.

Maybe I have the wrong scope, or I should be using Sinatra::Application?

回答1:

When you get a class to inherit from Sinatra::Base you are making that a Sinatra application. Each application gets its own settings object. If you want to share settings across applications you have some options:

  1. Merge the applications.
  2. Make the settings more globally accessible.
  3. Inheritance (see edit below)

Merging them is easy (unless there's some particular reason we're not aware of), you basically put them in the same class.

To make the settings more globally accessible, I'd do the following:

a) Wrap the entire application in a module to namespace it.
b) Put the settings you want to use in a class instance variable accessible via a "getter" method.

e.g.

module MyNamespace

  def self.global_settings
    @gs ||= # load your settings
  end

  class App < Sinatra::Base
    configure do
      set :something_from_the_global, MyNamespace.global_settings.something
    end
  end

  class SecondaryApp < Sinatra::Base
    helpers do
      def another_method
        MyNamespace.global_settings.something_else # available anywhere
      end
    end
    configure do # they're also available here, since you set them up before the app
      set :something_from_the_global, MyNamespace.global_settings.something
    end
  end

end

That's fine if you've got some very small apps, but if you're using multiple apps then you'll want to separate things out a bit. The way I tend to organise an app is remove everything from the rackup file (usually config.ru) that does anything, aside from requires and run. I put the middleware and app setup in another file, usually app/config.rb so I know it's the stuff from the config.ru. Then each application gets its own file (e.g. app/app.rb, app/secondary.rb)

# app/config.rb

require "app"
require "secondary"

module MyNamespace
  # set up your getters… e.g.

  def self.global_settings
    @gs ||= # load your settings
  end

  def self.app
    Rack::Builder.app do

      # …and middleware here
      use SecondaryApp
      run App
    end
  end
end

# config.ru

require 'rubygems'
require 'bundler'
Bundler.require

root = File.expand_path File.dirname(__FILE__)
require File.join( root , "./app/config.rb" )

map "/" do
  run MyNamespace.app
end

There are a lot of benefits to this kind of set up - it's easier to test; it's easier to organise; you can move apps around easier. But YMMV as always.


I should also add, as it's very remiss of me not to, that it's also possible to use inheritance, for example:

require 'sinatra/base'    

module MyNamespace
  class Controller < Sinatra::Base
    configure :development do
      set :config, "some JSON"
      set :mtime, Time.now.to_s
    end
  end

  class App1 < Controller

    get "/app1" do
      "in App1 config: #{settings.config} mtime: #{settings.mtime}"
    end
  end

  class App2 < Controller

    get "/app2" do
      "in App2 with config: #{settings. config} mtime: #{settings.mtime}"
    end
  end
end

Settings, routes, helpers, filters are all inherited, so if you configure something in the ancestor app it will be available in the inheritors. Sometimes it will be better to do it this way, probably when settings are just "global" to the Sinatra apps or when you want to create reusable apps and controllers. Other times, you'll need settings that can be used in models, libraries etc, and then the more global solution I've given first would be best.



标签: ruby sinatra