Wednesday, 31 March 2010

Variables

In Ruby (as in most OO languages) there are four sort of variables; global, class, instance and local. In languages like Java and C++, you must declare a variable before use, and where you do that determines what sort it is. In Ruby, variables are not declared, so we need another way to indicate this; a prefix.

Variables can contain any object in Ruby; you can readily assign a string to a variable that previously held an integer. Variable names must begin with a lower-case letter or underscore (after the prefix). By convention, they are all lower-case, with words separated by underscores (eg my_variable).

Local Variables

Local variables have no prefix. They exist only within a block of code, such as a method. Once the block completes, the variable disappears.
def test2
local1 = 2
local1.times do
local2 = 3
p "l1=#{local1} l2=#{local2}"
end
p "l1=#{local1}"
#p "l2=#{local2}" # Out of scope
end


Instance Variables

An instance variable belongs to that one instance of the class. It is denoted by a single at symbol (eg @var), and can be accessed from any instance of a class from within a method. They have private access... however, you can access them anyway through these methods:
instance_variables    # Array of strings, listing instance variables
instance_variable_defined?
instance_variable_set
instance_variable_get
remove_instance_variable # Private method


You can also use attr_accessor, attr_reader and attr_writer to allow others to access your instance variables. Note that while the above methods do require the at sign, these three methods do not.

You can also access instance variables using self. These two statements are equivalent.
self.var = 19
@var = 19


Class Variables

A class variable belongs to the whole class (and indeed subclasses), and is accessible from anywhere inside the class, or from any instance of the class. If you change the value in one instance, it will be changed in every instance. It must start with two at symbols (eg @@var).

In Rails, you can access a class variable like a class method (eg MyClass.var) for a descendant of ActiveRecord::Base (analogous to how you access column names). Outside Rails class variables have private access.

Class variables have some perhaps surprising behavior when you look at inheritance, as a value set in a subclass can affect other classes. Let us see that in action. SubClass1 has a parent, SuperClass, a sibling, SubClass2, and a child SubSubClass1.
class SuperClass
@@var1 = 11
@@var2 = 21
def self.out1; @@var1; end
def self.out2; @@var2; end
end

class SubClass1 < var1 =" 12" var3 =" 31" var1 =" 13" var3 =" 32" var2 =" 22"> 13
p SubClass1.out1
# => 13
p SubClass2.out1
# => 13

# @var2 is in the top class and its grandchild class. Despite "skipping
# a generation", the value set in the sub-sub-class still affects the
# super-class
p SuperClass.out2
# => 22
p SubSubClass1.out2
# => 22

# @var3 is in the sibling classes only, not in a common super-class. In this
# case setting the value in one has no effect on the other.
p SubClass1.out3
# => 31
p SubClass2.out3
# => 32

There are a set of methods available to use class variables, analogous to those for instance variables.
class_variables    # Array of strings, listing instance variables
class_variable_defined?
class_variable_set
class_variable_get
remove_class_variable # Private method

In Rails, you can define setters and getters just as you can for instance variables; cattr_accessor, cattr_reader and cattr_writer.

Global Variables

A global variable must start with a dollar (eg $var). It accessible from any class at any time. They are generally regarded as something to avoid; better to keep sections of code isolated from each other as far as possible.

Ruby has a number of predefined global variables. I do not like to use them myself; they make code that much harder to understand, and where there is a more explicit alternative, I would always use that. However, there is a list here.

There is an interesting trace feature for global variables. The trace_var method takes either a string or a symbol representing the global variable (with the dollar sign), together with a proc object (the documentation claims it will take a string or block as well; I do not believe that that is true).
trace_var "$global_var", proc do |x|
puts "$global_var is now #{x}"
end

You can set more than one trace on a global variable, and you can remove them (all) with untrace_var.

The global_variables method will return an array of strings, the names of all the global variables (complete with dollar sign).

Special Variables

Ruby includes a handful of special variables:
self     # "this" in Java, etc.
true
false
nil # "null" in Java, etc., also counts as false
__FILE__ # the current file
__LINE__ # the current line number.



All Together Now

Let us them all in action. Here is some sample code.
$global_var = 32

class TestClass
@@class_var = 14

def initialize
@instance_var = 20
end

def test
local_var = 7
$global_var += 1
@@class_var += 1
@instance_var += 1
local_var += 1
puts "$global_var=#{$global_var}"
puts "@@class_var=#{@@class_var}"
puts "@instance_var=#{@instance_var}"
puts "local_var=#{local_var}"
end
end

Both $global_var and @@class_var are set when the file is loaded. While $global_var is available to everything, @@class_var can only be accessed from within TestClass. Looking at @instance_var, this will be set to 20 for a specific instance of TestClass when the instance is created. It will be incremented whenever test is invoked on that instance, in contrast to $global_var and @@class_var, both of which will get incremented every time test is invoked on any instance. Finally, local_var is created when it is set in the test method, and destroyed when the method terminates.


Struggling with Ruby: Contents Page

1 comment:

leepupu said...

Thanks you for clarify my concept to ruby!