Ruby / Variables and constants

From WhyNotWiki
Jump to: navigation, search

Contents

Can you reference variables that haven't been initialized?

Yes, if they are global variables or instance variables.

No, if they are local variables or class variables.

def demonstrate
  puts $a
  puts @a
  puts a    # error!
  puts @@a  # error!
end
demonstrate

nil
nil
using_undefined_variables.rb:6:in `demonstrate': undefined local variable or method `a' for main:Object (NameError)
        from using_undefined_variables.rb:9
irb -> a
NameError: undefined local variable or method `a' for main:Object
        from (irb):1

irb -> @@a
NameError: uninitialized class variable @@a in Object
        from (irb):7

irb -> @a
    => nil

irb -> $a
    => nil

How do you check if a variable is defined?

Not like this:

irb -> defined?(:a)
    => "expression"

because that checks if the symbol

defined? is a very special method that doesn't act like other methods. The difference is that you can pass undefined local and class variables to defined? and you won't get an error!

defined? returns nil if the variable (or whatever?) is not defined and returns what kind of "thing" it is if it is defined...

irb -> defined?(a)
    => nil
irb -> defined?(@a)
    => nil
irb -> defined?(@@a)
    => nil
irb -> defined?($a)
    => nil


irb -> a = 'a local variable'
    => "a local variable"
irb -> @a = 'an instance variable'
    => "an instance variable"
irb -> @@a = 'a class  variable'
    => "a class  variable"
irb -> $a = 'a global variable'
    => "a global variable"

irb -> defined?(a)
    => "local-variable"
irb -> defined?(@a)
    => "instance-variable"
irb -> defined?(@@a)
    => "class variable"
irb -> defined?($a)
    => "global-variable"


How do you check if a constant is defined?

At first I thought you might use const_defined? ... but then I saw that you have to ask that of a specific module. What if you just define a constant at the global scope (not in a module)? Then which module would you to check if that constant is defined? I couldn't get that to work at all.

Instead, use the language-built-in (I couldn't find it in the RDoc for Kernel/Module/etc.) check defined? . It will be nil if it's not defined, non-nil if it is defined!

Foo = true
puts Module.const_defined?(:Foo)
# => false
puts defined? Foo
# => constant

What's the difference between a constant and a variable?

Besides that constants begin with a capital letter and variables do not...?

Does it let you change constants? (Yes)

Yes, but it will raise a warning:

irb -> PI
NameError: uninitialized constant PI
        from (irb):1
irb -> PI=3
    => 3

irb -> PI=4
(irb):3: warning: already initialized constant PI
    => 4

irb -> PI
    => 4

However, you may not change a constant from a method apparently:

irb -> def foo; PI=4; end
SyntaxError: compile error
(irb):1: dynamic constant assignment
def foo; PI=4; end
            ^
        from (irb):1

However, this is Ruby, and like most restrictions in Ruby, you can get around this one:

irb -> def foo; self::class::const_set(:PI, 4); end
    => nil

irb -> foo
    => 4

irb -> PI
    => 4

Different scope: To whom do the constants actually belong?

They always belong to a class/module, it appears; you can't just have local constants like in the same way that you can have local variables.

irb -> MODEL_DIR="/path/to/models"
    => "/path/to/models"

irb -> Kernel::MODEL_DIR = "something else entirely"
    => "something else entirely"

irb -> MODEL_DIR
    => "/path/to/models"

irb -> Object::MODEL_DIR
    => "/path/to/models"

irb -> class Foo
irb(main):010:1> MODEL_DIR="Foo's MODEL_DIR"
irb(main):011:1> end
    => "Foo's MODEL_DIR"

irb -> MODEL_DIR
    => "/path/to/models"

irb -> Object::const_get(:MODEL_DIR)
    => "/path/to/models"

irb -> Foo::MODEL_DIR
    => "Foo's MODEL_DIR"

I would have to say that it looks like unless Ruby has some reason to think you want it elsewhere (f.e., you give an explicit scope, Foo::, or you're inside of a class), it will think you want the constant to be defined for the class object "Object".

Checking if a constant is defined

This could be useful, for example, if you want to conditionally mix in a module or something only if a class is available.

include CoolFeature if CoolFeatureModule.const_defined?(:CoolClass)

(not a good example, I know)

Caveat: Did you mean to have 2 variables reference the same object or two copies of the object?

I easily forget that variables and objects are different. A variable just points to an object. So you can have two variables referring to the same object ... even without meaning to! Really! Believe me!

Demonstration:

irb -> original = new = "hi there"; new.capitalize!; original + " => " + new
    => "Hi there => Hi there"

It may not be obvious unless you know Ruby, but original = new = "hi there" causes both original and new (two variables) to both point to the same object in memory. So changing the one variable will affect the other. See, they have the same object_id:

irb -> original == new
    => true
irb -> original.object_id == new.object_id
    => true
irb -> "#{original.object_id} == #{new.object_id}"
    => "-604147648 == -604147648"

The proper way to create independently modifiable copies of an object is with the dup (duplication) method, like so:

irb -> original = "hi there"; new = original.dup; new.capitalize!; original + " => " + new
    => "hi there => Hi there"

It's a little more verbose, but I don't know of a conciser way of doing it!

Observe how the objects have different object IDs:

irb -> original == new
    => false
irb -> original.object_id == new.object_id
    => false
irb -> "#{original.object_id} != #{new.object_id}"
    => "-604271038 != -604271048"

That's because they really are two separate objects!

Variable scope/accessibility

local instance class global
v @v @@v $v
instance instance accessor/getter accessor/setter class class accessor/getter accessor/setter
normally (from within instance/class): @v @v= object.v object.v= @@v Klass.v Klass.v=
cheating (when scope rules would prevent normal access): object.instance_variable_get(:@v) object.instance_variable_set(:@v, new_value) object.send(:v) object.send(:v) klass.class_variable_get(:@@v) klass.class_variable_set(:@@v, new_value) ? klass.send(:v) ?? klass.send(:v)

cattr_accessor|attr_accessor

mattr_accessor (for modules)

Instance (member) variables

From within a method, it's easy enough to access an instance variable. You just refer to it as @foo or whatever it's called.

Outside of the class, it's a little bit harder:

# Doesn't work:
irb -> a.@foo
SyntaxError: compile error
(irb):9: syntax error, unexpected tIVAR
        from (irb):9

# But you can access it this way:
irb -> a.instance_variable_get(:@foo)
    => []

Reminder: You should never access an instance variable like that (I only use it for debugging). Instead, create accessor methods for it, using attr_writer, attr_reader, or attr_accesor.

Class variables

People.send(:class_variable_set, :@@lifespan, 0.minute)

mod.class_variables will only report which class variable have been set (initialized)

irb -> class A; def self.hi; @@a = 1 end; end
    => nil

# You might expect it to list @@a, but it doesn't really treat it as a class_variable until the moment it is initialized
irb -> A.class_variables
    => []

irb -> A.hi
    => 1

irb -> A.class_variables
    => ["@@a"]

For reasons such as that, it's usually a good idea to initialize class variable in the class scope, even if you will later change them from within a method...


irb -> class B; @@b = nil; def self.hi; @@b = 1 end; end
    => nil

irb -> B.class_variables
    => ["@@b"]


Variables  edit   (Category  edit) Category:Variables

Category Variables not found


Constants  edit   (Category  edit) Category:Constants

Category Constants not found
Ads
Personal tools