29

In Sinatra, I'm unable to create global variables which are assigned values only once in the application lifetime. Am I missing something? My simplified code looks like this:

require 'rubygems' if RUBY_VERSION < "1.9"
require 'sinatra/base'

class WebApp < Sinatra::Base
  @a = 1

  before do
    @b = 2  
  end

  get '/' do
    puts @a, @b
    "#{@a}, #{@b}"
  end

end

WebApp.run!

This results in

nil
2

in the terminal and ,2 in the browser.

If I try to put @a = 1 in the initialize method, I'm getting an error in the WebApp.run! line.

I feel I'm missing something because if I can't have global variables, then how can I load large data during application instantiation?

before do seems to get called every time there is a request from the client side.

arrac
  • 597
  • 1
  • 5
  • 15
  • 8
    The reason that `@a` doesn't work is that the `get` block runs in the context of an instance of the `WebApp` class, whereas you set it in the context of the class. It's just like any instance variable. If you change it to `@@a` it should work (but there are better ways, see my answer below). – Theo Dec 24 '10 at 11:03
  • 1
    Sorry for the delayed response. So, you mean, every `http get` creates an instance of the WebApp. That explains a lot. Thanks. – arrac Dec 30 '10 at 08:30

5 Answers5

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

Neil Slater
  • 26,512
  • 6
  • 76
  • 94
Theo
  • 131,503
  • 21
  • 160
  • 205
  • 3
    This was very helpful. Just wanted to add, that if you want to change a setting later, you need to use regular assignment, so something like: `def '/change'{ settings.my_config_property = 'goodbye world' }` – Fotios Feb 24 '12 at 18:07
  • 1
    Also - if you are running on something like Heroku, each instance of your app will have its own seperate copy of any global, so they should only be used as a cache, usually. – Tom Andersen Feb 27 '12 at 19:22
  • @Fotios I am kind of looking to change the setting dynamically, but a little unclear on the code that you've written. Could you elaborate? – Ankit Dhingra Mar 11 '14 at 06:09
  • +1 for tip re Shotgun, just could not figure out why my config vars were being overwritten until I came here & penny dropped - this is of course exactly how Shotgun works! – MatzFan Dec 06 '16 at 14:19
  • How to assign global variable values to variables in controllers? After setting global variables I tried to print the values, they worked and show values in the console, but how to assign them to a variable. I tried this: "totalUnits = settings.totalUnits_". "totalUnits_" is my global variable. This throws an error: undefined local variable or method `settings' for..... – Curious Developer Jun 14 '18 at 10:20
6

I ran into a similar issue, I was trying to initialize an instance variable @a using the initialize method but kept receiving an exception every time:

class MyApp < Sinatra::Application

    def initialize
        @a = 1
    end

    get '/' do
        puts @a
        'inside get'
    end
end

I finally decided to look into the Sinatra code for initialize:

# File 'lib/sinatra/base.rb', line 877

def initialize(app = nil)
  super()
  @app = app
  @template_cache = Tilt::Cache.new
  yield self if block_given?
end

Looks like it does some necessary bootstrapping and I needed to call super().

    def initialize
        super()
        @a = 1
    end

This seemed to fix my issue and everything worked as expected.

Mike R
  • 4,448
  • 3
  • 33
  • 40
1

Another option:

helpers do

  def a
   a ||= 1
  end

end
Johnny
  • 7,073
  • 9
  • 46
  • 72
0

Building on Theo's accepted solution, it is also possible to do:

class App < Sinatra::Application

  set :blabla, ''

  namespace '/b' do
    get '/baby' do
      # do something where bouh is assigned a value
      settings.blabla = 'bouh'
    end
  end
 
  namespace '/z'
    get '/human' do
      # settings.blabla is available here with newly assigned value
    end
  end
end
thiebo
  • 1,339
  • 1
  • 17
  • 37
-12

You could use OpenStruct.

require 'rubygems'
require 'sinatra'
require 'ostruct'

configure do
  Struct = OpenStruct.new(
    :foo => 'bar'
  )
end

get '/' do
  "#{Struct.foo}" # => bar
end

You can even use the Struct class in views and other loaded files.

Ethan Turkeltaub
  • 2,931
  • 8
  • 30
  • 45