Ruby’s Cool Features for Beginners

Hi, everyone! In this article, we’re gonna cover some features and concepts that differ Ruby from other programming languages, especially from languages that I’ve learned in the past, like Python and JavaScript.

1. Symbols

Symbols are strings-like built-in literals. You can create one by simply adding : followed by the name (E.g :user). Strings and Symbols are implemented in different ways, but, in general, symbols are immutable, so checking if two of them are equal is a really fast operation that makes them good to be used as identifiers in your application. In addition, they’re more efficient, since two symbols that are named equally will always point to the same object in the memory while using strings would create an entirely new object.

:my_symbol.object_id  # 2204828
:my_symbol.object_id  # 2204828

"my_string".object_id # 260
"my_string".object_id # 280

:my_symbol.equal? :my_symbol   # true
"my_string".equal? "my_string" # false

Symbols are widely used as keys in Hashes (basically, the Dictionaries for python or Objects in javascript).

I love symbols because it prevents unnecessary memory allocations and it also improves equality checking. Although, just using symbols won’t make your code run much faster, but every speed gain is welcome, since it doesn’t change the code readability nor has any other downside.

2. Variables prefixes

Variables may have prefixes that change how they behave in the application. This is nice because just by looking at the variable you can understand its scope and purpose. The basic prefixes are the ones used to specify instance variables and classes variables in a class (@ and @@):

class User
  @@user_count = 0 # Class variable

  def initialize(name)
    @name = name # Instance variable
    @@user_count += 1
  end
end

Another prefix that solves an annoying problem is the $ prefix. It’s used to specify global variables that are available in every scope, which is important because if we try to create a normal variable and use it inside a method or class, it won’t be able to distinguish a local variable from a global one. This also happens in languages like python (where the use of the global keyword is necessary to work around the problem).

# Wrong
counter = 0
def create_user
    counter += 1 # Will raise an exception because 'counter' wasn't defined in this scope
  # ...
end

# Right
$counter = 0
def create_user
    $counter += 1
  # ...
end

create_user();
$counter # 1
# Python example

counter = 0
def create_user():
    global counter # Specifies 'counter' as a global variable
    counter += 1

3. Methods unique syntax

Ruby has some cool (and strange) syntactic features that in general make code easier to read, even for beginners.

Parenthesis

Probably one of the strangest things is that you don’t need to add parenthesis to call a method. It’s still recommended to use them in some cases to avoid argument ambiguity, but, almost every single codebase makes use of this feature.

def sum(n1, n2)
  return n1 + n2
end

sum 10, 20 # 30

This makes the code look cleaner in comparison to other languages:

post '/users/', params: data
some.random.method.chain data
// Javascript

post("/users/", { params: data });
some().random().method().chain(data);

Return

Another big difference is the return keyword: it’s no longer necessary to explicitly write this (in most cases). This is possible because ruby uses an expression-evaluation system, in which every expression is evaluated and the last one that runs has its value returned.

def my_method
    1 # Evaluates to 1 (Integer)
    "foo" # Evaluates to "foo"
    some.random.call # Evaluates to whatever it returns
    true # Evaluates to true, and returns it
end

my_method  # true

In some cases though, it will be necessary to stop the method flow and return a value in some way, E.g. inside a loop. In this kind of situation, you can use return to immediately stop the method’s execution.

? and !

To finish this section, another nice practice related to methods is to name them with either the ? or the !suffix (which is impossible in a lot of languages). This is especially cool ’cause it makes the method call more semantic. ? is used when the method returns a boolean value (like a real true/false question) and ! is to denote that the method can be destructive (like changing the original array).

number = 2
number.even? #true

numbers = [1, 2, 3, 4]
numbers.map! { |n| n ** 2 }

numbers # [1, 4, 9, 16]

4. Everything is an object

This is one of the first things everyone learns about this language, but trust me, the fact that "Everything is an object in Ruby" leads to some bizarre pieces of code.

First of all, every object derives from the Object class through the inheritance chain. This means that there are some basic methods that every object responds to (some examples are class, object_id, nil?). To give you an idea of the implications this has, even classes and modules are indeed, objects.

class MyClass; end
module MyModule; end

number = 1
string = "lorem"
array = []
hash = {}

MyClass.class # Class
MyModule.class # Module
string.class # String
number.class # Integer
array.class # Array
hash.class # Hash

Self

In this OOP context, maybe you’re wondering how would you reference the object inside it. In Ruby, that’s the job of the self keyword, it represents the object in the current scope, like in python, or the this keyword in javascript.

class Person
    def initialize
        puts self # <Person:some_random_id>
    end
end

Be careful, when it comes to self, it can lead to some unexpected results. self refers to the object in the current scope, thus, if you put self inside a class it points to the class object, not its instance.

class Person
    puts self # Person

    def initialize
        puts self # <Person:some_random_id>
    end
end

OBS: Note that in the previous example, we used an outside method (puts) inside the class body. That’s because, in Ruby, a class or module body is just like any other block, you can use any statement inside (like if and for).

5. Modules

Modules are blocks that store methods and constants (other modules or classes). This definition may sound familiar, but different from classes, modules can’t create instances of themselves (under the hood, every class is a module, only the opposite is not true). In general, modules have 2 main uses: acting like namespaces and like mixins.

Namespaces

These are just places where you can put your constants, typically written in uppercase. It can be really useful in some contexts, like states, codes, etc.

module LogLevels
    DEBUG = 1
    INFO = 2
    WARNING = 3
    ERROR = 4
    CRITICAL = 5
end

LogLevels::DEBUG # 1

Mixins

If you define a method inside a module, you won’t be able to call it directly (see Singleton methods section for more details), but it doesn’t mean that doing that is useless. Indeed, when we include a module inside a class, the methods defined inside it are also injected into the last one. This is a very common way to create re-usable functionalities for classes, but without inheritance:

module ValidatorMixin
  def validate
    puts 'Some validation process...'
  end
end

class Class1
  include ValidatorMixin
end

class Class2
  include ValidatorMixin
end

Class1.new.validate # 'Some validation process...'
Class2.new.validate # 'Some validation process...'

You can use classes as mixins too, but, using modules will ensure that no one will instantiate it.

When using the extend keyword instead of include, the "mixin methods" will also be injected in the class (or module) that calls it, but this time, it will add these methods to the class object itself, not to its instances.

module ValidatorMixin
    def validate
        puts 'Some validation process...'
    end
end

class Class1
    extend ValidatorMixin
end

Class1.validate # 'Some validation process...'
Class1.new.validate # '(NoMethodError)'

There’s ways to create "mixins" in other languages, such as python and javascript, but in ruby, it’s cleaner and isn’t necessary to use inheritance. You can do something similar in javascript, but honestly, looks like a "jury rig" (sorry guys, I never liked prototypes), so I still prefer ruby in this case.

# Python equivalent

class ValidatorMixin:
    def validate():
        print('Some validation process...')

class Employee(ValidatorMixin):
    pass

class Admin(validatorMixin):
    pass
// Javascript equivalent (without inheritance)

const validatorMixin = {
    validate: () => console.log("Some validation process..."),
};

class Employee {}
class Admin {}

Object.assign(Employee.prototype, validatorMixin);
Object.assign(Admin.prototype, validatorMixin);

6. Singleton methods

In ruby, it’s possible to define a method for a specific object adding it to the method definition followed by a dot and the method name. This kind of method is called singleton method and is possible to define one for every object (at least, for the non-literal ones):

class User; end

my_user = User.new

def my_user.hello
    puts 'Hello'
end

my_user.hello  # 'Hello'
User.new.hello # (NoMethodError)

As self points to the current context object, it is possible to define methods that are available directly on modules, or classes, using the same technique.

module Errors
    def self.raise_error(message)
        puts "Raising error: #{message}"
    end
end

# Or you could do:
module Errors; end

def Errors.raise_error(message)
    puts "Raising error: #{message}"
end

Errors.raise_error 'CRITICAL ERROR' # 'Raising error: CRITICAL ERROR'

In an OOP context, singleton methods act like class_methods or static_methods. But, what makes them so different is the ability to create a method for one specific object without changing its class. Some languages allow us to achieve similar results:

// Javascript
class GenericObject {}

const object1 = new GenericObject();
const object2 = new GenericObject();

object1.speak = () => console.log("Hi");

object1.speak(); // "HI"
object2.speak(); // (TypeError)

7. Classes, Methods, and Modules are open to changes

This topic impressed me when I read it for the first time, and if you’re a beginner in Ruby, maybe you’ll be impressed as well. Every single class, method, or module, including the built-in ones, can change at any moment of your code. The extreme majority of the programming languages don’t allow something like this, but Ruby does, and there are some useful libs based on this functionality (the most known one, probably, is the ActiveSupport from the RubyOnRails framework).

class Integer
    def factorial
        self <= 1 ? 1 : self * (self - 1).factorial
    end
end

1.factorial  # 1
5.factorial  # 120
10.factorial # 3628800

"With great powers, comes great responsibilities". Be careful when you change modules, classes, and methods that are working, just do it if it’s really necessary and you know how everything works, mainly the built-in ones.

8. Metaprogramming

Lastly, like other topics, this is not an exclusive ruby feature, but the way that metaprogramming is used is amazing. Metaprogramming means (beyond other things) dynamically changing, in runtime, the behavior of an object, like adding a method or redefining a constant (yes, this is possible) and it’s widely used in a lot of contexts, especially in frameworks like ActiveRecord (RubyOnRails).

class Programmer
  def initialize
    @languages = []
  end

  def learn_language(lang)
    @languages << lang

    # Defining new method
    define_singleton_method :"knows_#{lang}?" do
      true
    end
  end
end

dev = Programmer.new
dev.knows_ruby? # (NoMethodError)

dev.learn_language :ruby
dev.knows_ruby? # true

Since ruby’s philosophy is to make things easy, this is a really common and powerful way to create things for you when using a framework or module.

Conclusion

Ruby has some nice and powerful features that distinguish it from other general-use languages. Its simpler syntax makes it friendly for beginners (maybe even more than python), but it doesn’t make it a simplistic language. On the contrary, it enables really heavy and complex code to be written in such a way that everyone can understand it. Some core features, like OOP, mixins, metaprogramming, and open classes, make the language even more flexible and adaptable, which is amazing.

Nice references

We want to work with you. Check out our "What We Do" section!