Is a Global Variable Defined Inside a Sinatra Route Shared Between Requests

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

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.

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).

How do I set a global variable in Sinatra depending on the production or development environment?

Request is not available at the top level, only inside request handlers.

Write a method instead of a global setting, e.g.:

def location(request)
production? ? request.location.city : 'Melbourne'
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).

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.

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.

How do I pass variables between routes without using sessions or haml in Sinatra?

You should really be using Sinatra Flash for the kind of mechanism you are after. Flash messages usually expire after one request so you don't need to manage them manually (They do use sessions in the background for storage):

require 'sinatra'
require 'sinatra/flash'

enable :sessions

get '/' do
erb :home
end

get '/send/:user' do
Process.detach(fork{ exec "ruby send.rb #{params[:user]} > output.txt"})
flash[:msg] = "Process for the user #{params[:user]} initiated, it will take a few minutes"
redirect '/'
end

In home.erb:

<span style="margin:auto; text-align: center; padding:10px"><%= flash[:msg] %></span>

Sinatra: before filter ordering

This works for me, on Sinatra 1.3.2.

before do
@filter = [] << 'everything'
end
before '/filter' do
@filter << 'specific'
end
get '/filter' do
@filter.inspect
end

This gives me ["everything", "specific"] which is what I would expect. Is it possible you do not have the catch-all filter before all the rest?

In Sinatra, routes are evaluated in order from the top, not by how well they match. Therefore, if you have the specific filters before the catch-all filter, it will evaluate those first, as seen here:

before '/filter' do
@filter = [] << 'specific'
end
before do
@filter << 'everything'
end
get '/filter' do
@filter.inspect
end # => ["specific", "everything"]

Create object globally in Sinatra

If you want a globally accessible variable, just use the $ notation:

$myobj = MyObject.new
... more code
$myobj.some_method

However, a slightly more elegant solution might be something along these lines:

require 'sinatra/base'

class MyApp < Sinatra::Base
set :sessions, true
set :my_obj, MyObject.new

get '/' do
# call settings.my_obj
end
end


Related Topics



Leave a reply



Submit