Static Variables in Ruby

Are Ruby class variables similar to the Java static variables?

There's a lot of similarity between Ruby and Java by virtue of them being object-oriented, but their family tree is different. Ruby leans very heavily on Smalltalk while Java inherits from the C++ school of thinking.

The difference here is that Ruby's concept of public/private/protected is a lot weaker, they're more suggestions than rules, and things like static methods or constants are more of a pattern than a construct in the language.

Global variables are frowned on quite heavily, they can cause chaos if used liberally. The Ruby way is to namespace things:

$ugly_global = 0  # Not recommended, could conflict with other code
# Ownership of this variable isn't made clear.

$ugly_global += 1 # Works, but again, it's without context.

module UglyCounter # Defines a module/namespace to live in
def self.current # Defines a clear interface to this value
@counter ||= 0 # Initializes a local instance variable
end

def self.current=(v) # Allow modification of this value
@counter = v.to_i # A chance to perform any casting/cleaning
end
end

UglyCounter.current += 1 # Modifies the state of a variable, but
# the context is made clear.

Even a thin layer like this module gives you the ability to intercept read/write operations from this variable and alter the behaviour. Maybe you want to default to a particular value or convert values into a normalized form. With a bare global you have to repeat this code everywhere. Here you can consolidate it.

Class variables are a whole different thing. They're also best avoided because sharing data between the class and instances of this class can be messy. They're two different contexts and that separation should be respected.

class MessyClass
@@shared = 0

def counter
@@shared
end

def counter=(v)
@@shared = v
end
end

This is a pretty rough take on how to use a shared class-level instance variable. The problem here is each instance is directly modifying it, bypassing the class context, which means the class is helpless. This is fundamentally rude, the instance is over-extending its authority. A better approach is this:

class CleanerClass
def self.counter
@counter ||= 0
end

def self.counter=(v)
@counter = v.to_i
end

# These are reduced to simple bridge methods, nothing more. Because
# they simply forward calls there's no breach of authority.
def counter
self.class.counter
end

def counter=(v)
self.class.counter = v
end
end

In many languages a static class method becomes available in the scope of an instance automatically, but this is not the case in Ruby. You must write bridge/proxy/delegate methods, the terminology here varying depending on what you're used to.

Static variables in ruby, like in C functions

Scope your variable in a method and return lambda

def counter
count = 0
lambda{count = count+1}
end

test = counter
test[]
#=>1
test[]
#=>2

ruby access static variable

You can't do what you want to do :)

@harald is right. attr_reader will define GETTER only for instance variable, for "static" (aka "class variables") you need to define setter and getter by yourself:

class A
@@ololo = 1

# instance level

# getter
def ololo
@@ololo
end
# setter
def ololo=trololo
@@ololo = trololo
end

# and class level
# if you need it

# getter
def self.ololo
@@ololo
end
# setter
def self.ololo=trololo
@@ololo = trololo
end
end

So:

a = A.new
b = A.new
A.ololo
#=> 1
a.ololo
#=> 1
A.ololo = 100
A.ololo
#=> 100
a.ololo
#=> 100
b.ololo
#=> 100
a.ololo = 4
A.ololo
#=> 4

...

Shorter one:

class A
@ololo = 1
class << self
attr_accessor :ololo
end
end

Is it good practice to use static variables in rails controller?

It is a better practice to not use class variables (those that start with @@) in ruby; see here why

This might look like a weird code, but this is the more conventional way:

You set a "class" instance variable, instead of setting a "class variable".

class MyController < ApplicationController
@my_fabricator = nil

class << self
def some_method
@my_fabricator ||= MyFabricatorClass.new
@product = @my_fabricator.create_product
end
end
end

About class << self, see here

The above code is just the same as:

class MyController < ApplicationController
@my_fabricator = nil

def self.some_method
@my_fabricator ||= MyFabricatorClass.new
@product = @my_fabricator.create_product
end
end

Now you can just do:

MyController.some_method

Static variables in Ruby to make one up counter function?

I'd probably do it this way:

class EverreadyBunnyCounter
def initialize
@counter = 0
end

def current
@counter
end

def next
@counter += 1
end
end

foo = EverreadyBunnyCounter.new
bar = EverreadyBunnyCounter.new
foo.next # => 1
bar.next # => 1
foo.next # => 2
bar.current # => 1

The current method isn't necessary but it's sometimes convenient to be able to peek at the current value without forcing it to increment.

Alternately, this might do it:

MAX_INT = (2**(0.size * 8 -2) -1)
counter = (1..MAX_INT).to_enum # => #<Enumerator: 1..4611686018427387903:each>
foo = counter.dup
bar = counter.dup
foo.next # => 1
foo.next # => 2
bar.next # => 1
bar.next # => 2
foo.next # => 3

Defining MAX_INT this way comes from "Ruby max integer". The downside is that you'll eventually run out of values because of the range being used to create the Enumerator, where the previous version using the EverreadyBunnyCounter class will keep on going.

Changing MAX_INT to Float::INFINITY would be a way to fix that:

counter = (1..Float::INFINITY).to_enum # => #<Enumerator: 1..Infinity:each>
foo = counter.dup
bar = counter.dup
foo.next # => 1
foo.next # => 2
bar.next # => 1
bar.next # => 2
foo.next # => 3

The Enumerator documentation has more information.

Dynamically create static variables in a class

Constants are defined like this (they have to start with an uppercase letter):

class A
A = 1
B = 2
C = 3
end

A::A #=> 1
A::B #=> 2
A::C #=> 3

To define them dynamically, use const_set:

class A
%w(A B C).each.with_index(1) { |c, i| const_set(c, i) }
end

Accessing a static variable and method in a module

I assume you're trying this out in Rails development mode. In this mode all constants (classes, modules and constants) are loaded by demand.

When ruby meets undefined constant, it throws an error, which autoloader intercepts and tries to load the constant by converting its name to a path.

In your case, autoloader tries to find
MyNamespace::MY_SET in app/presenters/my_namespace.rb (which fails) and has no idea that you actually defined it in app/presenters/my_namespace/base_presenter.rb. But after you have loaded your MyNamespace::BasePresenter (which, btw, lies on the correct path), MyNamespace, MyNamespace::MY_SET, and MyNamespace.my_method got initialized and become available.

What you need to do is to

a) define MyNamespace correctly and move methods and constants to its definition:

app/presenters/my_namespace.rb

module MyNamespace
MY_SET = Set.new(['a', 'b', 'c'])

def self.my_method
true
end
end

app/presenters/my_namespace/base_presenter.rb

# note that I don't open module here, 
# but use a constant to enable autoloading of MyNamespace module
class MyNamespace::BasePresenter
end

or

b) just move all methods/constants to your BasePresenter class. Since it lies on a correct path, constant MyNamespace::BasePresenter::MY_SET will just work.

module MyNamespace
class BasePresenter

MY_SET = Set.new(['a', 'b', 'c'])

def self.my_method
true
end
end
end

Bonus part

The difference between

module MyNamespace
class BasePresenter
end
end

and

class MyNamespace::BasePresenter
end

is when MyNamespace is undefined in first case it will be defined (module MyNamespace either opens existing module or defines new one), but in second case the mechanism described above will try to load MyNamespace somewhere, and if it fails - you'll get uninitialized constant error MyNamespace.

What it also means to you is if you define all your namespaced classes as a first case (inside module MyNamespace)

app/presenters/my_namespace/base_presenter.rb

module MyNamespace
class BasePresenter
end
end

and also have MyNamespace in its proper place with some code inside

app/presenters/my_namespace.rb

module MyNamespace
MY_SET = Set.new(['a', 'b', 'c'])
end

And if your MyNamespace::BasePresenter will gets loaded first, it'll actually define MyNamespace and your app/presenters/my_namespace.rb will not be loaded (since autoloading loads only missing constants), and you'll have to require it yourself.

presenter = MyNamespace::BasePresenter.new
MyNamespace::MY_SET # boom, uninitialized constant error

The solution here is to define modules in 1 proper place (that autoloading knows how to find) and use class MyNamespace::BasePresenter format for defining namespaced classes in their proper locations.

Static variables versus eigenclass class variables, what's the difference in Ruby?

They are both class variables but in an instance you can access only @@store variable.

Mind that class variables are not thread safe in Ruby, so use Mutex if you plan to use varaible as a Hash.

Using class instance variable for mutex in Ruby



Related Topics



Leave a reply



Submit