Scope question

Consider the following Sinatra app:

require 'sinatra'
vara = "foo"
VARB = "bar"
helpers do
  def geta
    vara
  end
  def getb
    VARB
  end
end
get '/a' do geta end
get '/b' do getb end

When I run that and browse to /b, it displays "bar". When I browse to
/a, however, it produces the error: "undefined local variable or method
`vara'".

Why do these two variables differ in scope?

How can I declare a variable that has global scope but is not a
constant?

Thanks in advance...

···

--
Posted via http://www.ruby-forum.com/.

Method definitions are not closures, so they don't have access to external
local scope. Use $vara to declare a global, if you really think that's a
good idea.

···

On Feb 27, 2010 4:27 PM, "Nick Brown" <nick@nick-brown.com> wrote:

Consider the following Sinatra app:

require 'sinatra'
vara = "foo"
VARB = "bar"
helpers do
def geta
   vara
end
def getb
   VARB
end
end
get '/a' do geta end
get '/b' do getb end

When I run that and browse to /b, it displays "bar". When I browse to
/a, however, it produces the error: "undefined local variable or method
`vara'".

Why do these two variables differ in scope?

How can I declare a variable that has global scope but is not a
constant?

Thanks in advance...
--
Posted via http://www.ruby-forum.com/.

Nick Brown wrote:

Consider the following Sinatra app:

require 'sinatra'
vara = "foo"
VARB = "bar"
helpers do
  def geta
    vara
  end
  def getb
    VARB
  end
end
get '/a' do geta end
get '/b' do getb end

When I run that and browse to /b, it displays "bar". When I browse to
/a, however, it produces the error: "undefined local variable or method
`vara'".

Why do these two variables differ in scope?

How can I declare a variable that has global scope but is not a
constant?

Thanks in advance...

Important disclaimer: I'm kind of new at this, so I'm just taking a stab
at it...

One way you could do that is to declare a class variable for the class
Object. Because every object inherits from Object, everything in ruby
should have access to that same class variable. E.g.,

class Object
  @@var = "Hello"
end

puts @@var

Hello

class Silly
  def Silly.hi
    puts @@var
  end
end

Silly.hi

Hello

@@var = "goodbye"

Silly.hi

goodbye

But it's generally considered good form to not use global variables in
the first place. There's almost always a way to avoid using them. The
danger of using them is that if you use a global variable with your
code, and someone else uses a global variable of the same name in their
code, the two bits of code will both break if they are ever run
together.

···

--
Posted via http://www.ruby-forum.com/\.

Nick Brown wrote:

Consider the following Sinatra app:

require 'sinatra'
vara = "foo"
VARB = "bar"
helpers do
  def geta
    vara
  end
  def getb
    VARB
  end
end
get '/a' do geta end
get '/b' do getb end

When I run that and browse to /b, it displays "bar". When I browse to
/a, however, it produces the error: "undefined local variable or method
`vara'".

Why do these two variables differ in scope?

Which two variables?

vara is a local variable. Its scope extends to the end of the block
where it's defined. However a method definition using 'def ... end' or a
class definition using 'class ... end' block starts a fresh empty scope
inside it.

VARB is not a variable at all, it's a constant. (Constants start with
capital letters in Ruby).

How can I declare a variable that has global scope but is not a
constant?

$varc is a true global variable.

But usually you don't want these. Better is an instance variable of a
class (remembering that a class is just an object)

class Foo
  @vard = "hello"
  def self.vard
    @vard
  end
  def self.vard=(v)
    @vard=v
  end
end

puts Foo.vard
Foo.vard = "world"
puts Foo.vard

Inside an instance method of foo you can use either 'Foo.vard' or
'self.class.vard' to get at this value.

···

--
Posted via http://www.ruby-forum.com/\.

But it's generally considered good form to not use global variables in
the first place. There's almost always a way to avoid using them. The
danger of using them is that if you use a global variable with your
code, and someone else uses a global variable of the same name in their
code, the two bits of code will both break if they are ever run
together.

Raul: I believe it would also be bad form to hit the database
unnecessarily with every pageview. If It's bad form to cache data in a
variable that is visible to every page, then I don't want to be good :wink:

Is there a "better" way to do this than @@? Why do constants have
different scopes than regular variables?

···

--
Posted via http://www.ruby-forum.com/\.

But it's generally considered good form to not use global variables in
the first place. There's almost always a way to avoid using them. The
danger of using them is that if you use a global variable with your
code, and someone else uses a global variable of the same name in their
code, the two bits of code will both break if they are ever run
together.

Raul: I believe it would also be bad form to hit the database
unnecessarily with every pageview. If It's bad form to cache data in a
variable that is visible to every page, then I don't want to be good :wink:

But in that case you're writing a real Sinatra application not a toy :slight_smile:

Is there a "better" way to do this than @@? Why do constants have
different scopes than regular variables?

You can write your example as:

module Play
   class Application < Sinatra::Base

     vara = "foo"
     VARB = "bar"

     helpers do
       def geta
         vara
       end
       def getb
         VARB
       end
     end

     get '/a' do geta end
     get '/b' do getb end

   end
end

This should make thing a lot clearer as to what's going on, especially regarding scopes since it now looks more like actual Ruby code.

The constant VARB is really Play::Application::VARB in this case. If you want VARB global then you refer to it as ::VARB (everywhere)

In normal Ruby code what and where is vara in this case?

If you use @@vara it'll work, since vara will become a class variable of Play::Application

The trouble with that is that there's only one @@vara and it'll be used for all requests by all users. I'm also thinking that you probably don't really want that. If you don't then you'll have to keep vara someplace else, and look for it in each request.

Cheers,
Bob

···

On 27-Feb-10, at 10:25 PM, Nick Brown wrote:

--
Posted via http://www.ruby-forum.com/\.

----
Bob Hutchison
Recursive Design Inc.
http://www.recursive.ca/
weblog: http://xampl.com/so

Bob Hutchison wrote:

If you use @@vara it'll work, since vara will become a class variable
of Play::Application

The trouble with that is that there's only one @@vara and it'll be
used for all requests by all users. I'm also thinking that you
probably don't really want that. If you don't then you'll have to keep
vara someplace else, and look for it in each request.

OK, I'm with you so far. But here's what I want to do: there is one
value (such as: number of users currently online) that is shown on every
page. Every page view reads this number and displays it. This value is
updated whenever someone logs in or out.

What is the best way to handle that?

···

--
Posted via http://www.ruby-forum.com/\.

Bob Hutchison wrote:

If you use @@vara it'll work, since vara will become a class variable
of Play::Application

The trouble with that is that there's only one @@vara and it'll be
used for all requests by all users. I'm also thinking that you
probably don't really want that. If you don't then you'll have to keep
vara someplace else, and look for it in each request.

OK, I'm with you so far. But here's what I want to do: there is one
value (such as: number of users currently online) that is shown on every
page. Every page view reads this number and displays it. This value is
updated whenever someone logs in or out.

What is the best way to handle that?

That's quite a question.

Well, you could do it as a class variable of Play::Application but that's probably not the best idea. Play::Application instances are created by Sinatra to handle requests, and that's probably all they are should be used for. If you did take this approach then you would need to write accessor methods to increment/decrement the counter as people login/logout -- that is, your authentication system would have to call these accessors. This doesn't sound very good to me (doesn't smell so good eather.)

You'll probably be better off looking for some object in your system that would know about these things, something where this kind of responsibility is in line with its responsibilities. There is likely something in your authentication system that would be the right place for this. If so, then where you have @@vara in Play::Application you ask the authentication system to tell you how many are currently logged in. Maybe something in your user management system?

Cheers,
Bob

···

On 28-Feb-10, at 1:46 PM, Nick Brown wrote:

--
Posted via http://www.ruby-forum.com/\.

----
Bob Hutchison
Recursive Design Inc.
http://www.recursive.ca/
weblog: http://xampl.com/so