Constants or Class Variables in Ruby

Constants or class variables in ruby?

The main thing is that by using the CONSTANT notation, you're making it clear to the reader. the lower case, frozen string gives the impression is might be settable, forcing someone to go back and read the RDoc.

When to use constants instead of instance variables in Ruby?

There's a few things to consider here:

  • Will the value change within the life-cycle of an object?
  • Will you need to override the value in sub-classes?
  • Do you need to configure the value at run-time?

The best kind of constants are those that don't really change short of updating the software:

class ExampleClass
STATES = %i[
off
on
broken
].freeze
end

Generally you use these constants internally in the class and avoid sharing them. When you share them you're limited in how they're used. For example, if another class referenced ExampleClass::STATES then you can't change that structure without changing other code.

You can make this more abstract by providing an interface:

class ExampleClass
def self.states
STATES
end
end

If you change the structure of that constant in the future you can always preserve the old behaviour:

class ExampleClass
STATES = {
on: 'On',
off: 'Off',
broken: 'Broken'
}.freeze

def self.states
STATES.keys
end
end

When you're talking about instance variables you mean things you can configure:

class ConfigurableClass
INITIAL_STATE_DEFAULT = :off

def self.initial_state
@initial_state || INITIAL_STATE_DEFAULT
end

def self.initial_state=(value)
@initial_state = value ? value.to_sym
end
end

Constants are great in that they're defined once and used for the duration of the process, so technically they're faster. Instance variables are still pretty quick, and are often a necessity as illustrated above.

Constants vs instance variable

Consider this class:

class Dog
NUMBER_OF_LEGS = 4

@dog_counter = 0

attr_reader :name

def initialize(name)
@name = name
end

def legs
NUMBER_OF_LEGS
end
end

Here, NUMBER_OF_LEGS is a constant, @name is an instance variable (with a getter method), and @dog_counter is what's called a class instance variable.

In Ruby everything is an object, even classes, and as such they can have their own instance variables.

Look at how we could use this class:

dog = Dog.new('Chewbacca')
dog.name
# => 'Chewbacca'

dog.legs
# => 4
Dog::NUMBER_OF_LEGS
# => 4

That's fine, but we do not have a direct interface to access @dog_counter. The only way to do something with it is to use introspection methods:

dog.class.instance_variable_get(:@dog_counter)
# => 0
dog.class.instance_variable_set(:@dog_counter, 1)

dog.class.instance_variable_get(:@dog_counter)
# => 1

dog.class.instance_eval { @dog_counter = 10 }
dog.class.instance_variable_get(:@dog_counter)
# => 10

We can do better than that. Look at this other implementation:

class Dog
@dog_counter = 0

attr_reader :name

class << self
attr_accessor :dog_counter
end

def initialize(name)
@name = name
self.class.dog_counter += 1
end

end

Now we have defined a class accessor (setter and getter), and we're also incrementing it with each new instance. The interface is simple:

Dog.dog_counter
# => 0

dog_1 = Dog.new('Han')
dog_2 = Dog.new('Luke')

Dog.dog_counter
# => 2
dog_2.class.dog_counter
# => 2

As to proper class variables, they are scoped on the class and can be accessed by instances.

The big problem, however, is that they are shared between all classes in the same hierarchy. Each class that sets a new value will update it for all its ancestors and descendants.

For this reason they are generally avoided, and class instance variables are preferred (they are class specific).

class Scientist
@@greet = "Hello, I'm a Scientist!"

def greet
@@greet
end
end

class Biologist < Scientist
@@greet = "Hello, I'm a Biologist!"
end

class Physicist < Scientist
@@greet = "Hello, I'm a Physicist!"
end

class ParticlePhysicist < Physicist
@@greet = "Hello, I'm a ParticlePhysicist!"
end

biologist = Biologist.new
biologist.greet
# => "Hello, I'm a ParticlePhysicist!"

Use Constant or Class Variable?

It's definitely not a constant unless it's constant!

Instead of a class variable, I'd suggest a class-instance variable. You get all the benefits of a class variable, with even better scoping (other members of the class won't clobber the value, and easier access too.

class Foo

class << self
def number_of_links
@number_of_links ||= 10 # or some sensible default, you might make this a constant to highlight the number when you're reading the code.
end

def number_of_links=( n )
@number_of_links = n
end
end
end

puts Foo.number_of_links
# => 10
Foo.number_of_links = 20

puts Foo.number_of_links
# => 20

see http://blog.codegram.com/2011/4/understanding-class-instance-variables-in-ruby for more.

Set a Ruby variable and never be able to change it again?

They are called constants. A constant in Ruby is defined by a UPPER_CASE name.

VARIABLE = "foo"

It is worth to mention that, technically, in Ruby there is no way to prevent a variable to be changed. In fact, if you try to re-assign a value to a constant you will get a warning, not an error.

➜  ~  irb
2.1.5 :001 > VARIABLE = "foo"
=> "foo"
2.1.5 :002 > VARIABLE = "bar"
(irb):2: warning: already initialized constant VARIABLE
(irb):1: warning: previous definition of VARIABLE was here
=> "bar"

It's also worth to note that using constants will warn you if you try to replace the value of the constant, but not if you change the constant value in place.

2.1.5 :001 > VARIABLE = "foo"
=> "foo"
2.1.5 :002 > VARIABLE.upcase!
=> "FOO"
2.1.5 :003 > VARIABLE
=> "FOO"

In order to prevent changes to the value referenced by the constant, you can freeze the value once assigned.

2.1.5 :001 > VARIABLE = "foo".freeze
=> "foo"
2.1.5 :002 > VARIABLE.upcase!
RuntimeError: can't modify frozen String
from (irb):2:in `upcase!'
from (irb):2
from /Users/weppos/.rvm/rubies/ruby-2.1.5/bin/irb:11:in `<main>'
2.1.5 :003 > VARIABLE
=> "foo"

Here's an example inside a class.

class MyClass
MY_CONSTANT = "foo"
end

MyClass::MY_CONSTANT
# => "foo"

Accessing a class's constants

What you posted should work perfectly:

class Foo
CONSTANT_NAME = ["a", "b", "c"]
end

Foo::CONSTANT_NAME
# => ["a", "b", "c"]

Class method vs constant in Ruby/Rails

The latter is better. If it were a method, a new array and new strings will be created every time it is called, which is a waste of resource.



Related Topics



Leave a reply



Submit