Sinatra with a Persistent Variable

In Sinatra(Ruby), how should I create global variables which are assigned values only once in the application lifetime?

class WebApp < Sinatra::Base
configure do
set :my_config_property, 'hello world'
end

get '/' do
"#{settings.my_config_property}"
end
end

Beware that if you use Shotgun, or some other Rack runner tool that reloads the code on each request the value will be recreated each time and it will look as if it's not assigned only once. Run in production mode to disable reloading and you will see that it's only assigned on the first request (you can do this with for example rackup --env production config.ru).

Sinatra with a persistent variable

You could try:

configure do
@@nokogiri_object = parse_xml
end

Then @@nokogiri_object will be available in your request methods. It's a class variable rather than an instance variable, but should do what you want.

Can I create global variables that work accross routes in Sinatra (Ruby)?

Try something like this maybe?

require 'sinatra'
require './models/questionaire_manager'

set :bind, '0.0.0.0'
set :port, ENV['PORT']
enable :sessions
set :session_secret, 'SecretString#!$%'

helpers do
def quiz_manager
@questionaire = session[:quiz_manager] ||= Questionaire_Manager.new 0
end
end

get '/' do
# Uncomment the line below if you intend to create a new quiz each time
# session[:quiz_manager] = nil
quiz_manager # Initializes the session variable
erb :index
end

post '/' do
quiz_manager.number = params[:number]
redirect '/quiz'
end

get '/quiz' do
quiz_manager.genQuestionaire
erb :quiz
end

post '/quiz' do
redirect '/results'
end

get '/results' do
@number = quiz_manager.number
erb :results
end

Edit:

To clarify what this is doing -- I've created a helper method called quiz_manager that initializes session[:quiz_manager] if it hasn't already been set - This will persist between routes. I'm also setting the class variable @questionnaire so that you can access it within your views.

arrays as global variables in sinatra

Configuration is meant for static data, which is not changed in the life of the application, and not part of its state.

Using configuration to cache data between requests is not a good idea for several reasons:

  • Global data is not insulated - when a user adds an 'item' in your app all other users will see it, since it is stored globally...
  • In-memory data is not scalable - this solution might work as long as you run a single sinatra instance on a single machine. At the moment your application needs to scale, either by adding machines, or even by using multiple processes (in a passenger or unicorn containers) - data changes in one process/machine is not populated to other processes/machines - and it won't be consistent.
  • Not thread safe - even if you scale by making you application multi-threaded, you'll just be entering the world of pain of race-conditions and synchronization blocks
  • It is not persistent - if your process fails for some reason - all of its state is lost.

So, what should you do? There are a couple of options, depending on your requirements - should the data for shared by all users, or insulated between users? Should it be persistent over time?

  • If the data is in the scope of the user, and should be kept only for the current session, you can use cookies to save your data, using Sinatra's sessions
  • If the data is global, should be kept for availability, but if data is lost it is not a catastrophe - you can use memcached
  • If the data should be kept for a long time, you should seriously consider persisting it. It doesn't have to be mapped by ActiveRecord or DataMapper, depending on the complexity of your data. It doesn't even have to be persisted to a relational database like mysql, some noSql options like redis, couchbase, etc. can be easily added to your ruby project.

Is a global variable defined inside a Sinatra route shared between requests?

This part of the Sinatra README about scope is always helpful to read but if you only need the variable to persist for the request then I think there are 3 main ways I'd suggest going about this, and really the key is a filter

A before block

before do
@my_log = []
end

get "/" do
@my_log << "hello"
@my_log << "world"
@my_log.inspect
end

get "/something-else" do
@my_log << "is visible here too"
end

# => output is ["hello", "world"]

@my_log will go out of scope at the end of the request and be re-initialised at the beginning of the next one. It will be accessible by any route, so if for example you used pass to pass it on to another route that would be the only time the other blocks could see what had been set by the prior route block.

Using the settings helper

set :mylog, []

Then same as above, just replace @my_log with settings.my_log. Without the before block reinitialising it then the contents of @my_log would be persisted across requests.

Using the settings helper with something like Redis

# I always do this within a config block as then it's only initialised once
config do
uri = URI.parse(ENV["URL_TO_REDIS"])
set :redis, Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
end

Now the redis instance is available via settings.redis. No need to worry about variable scope (I'd use locals with it), just push straight to Redis. You get the best of both worlds then, but if you want you could do:

before do
@my_log = []
end

get "/" do
@my_log << "hello"
@my_log << "world"
"Hello, World"
end

after do
settings.redis.set "some_key", @my_log
settings.redis.expire "some_key", 600 # or whatever
end

Sinatra global application variable

Sinatra itself doesn’t restart on each request in development mode (it used to), but shotgun has that effect:

Each time a request is received, it forks, loads the application in
the child process, processes the request, and exits the child process.

Simply use ruby web.rb, and everything should work (modulo threading issues that from you comment it looks like you’re aware of).



Related Topics



Leave a reply



Submit