04 February 2015

What About Ruby?

Diving into a new language

What About Ruby

For the past few weeks I have been playing around with Ruby, a totally new language for me. I thought that it would be a good idea to list the most important aspects and useful information that I am learning about it and share it with you:

Interpreted language. A program doesn’t need to be compiled previously into machine-language instructions.

Object-oriented language. Everything is an object and every operation is a method call on some object. It is class-based, meaning that objects are instances of classes, which determines their type.

Dynamic language. It executes at runtime common programming behaviours that static programming languages perform during compilation. These behaviours could include extension of the program, by adding new code, by extending objects and definitions, or by modifying the type system (metaprogramming). It also can ask objects about themselves (reflection).

Dynamically typed aka duck typing, objects have types but variables don’t.

Naming Conventions

ClassNames use UpperCamelCase:

class TodoList
  ...
end

Methods and variables use snake_case:

def add_todo_item ... end
def mark_item_complete! ... end   # => changes object's state
def complete? ... end             # => returns a boolean

Types of variables:

TEST_MODE = true    # => CONSTANTS (scoped)
$TEST_MODE = true   # => $GLOBALS (not scoped)
number = 0          # => local variables
@number = 0         # => instance variables

Symbols are like immutable strings whose value is itself. When a symbol is used it denotes specialness. In terms of memory, a symbol will be just saved once, while a string will be saved everytime.

favorite_framework = :rails
:rails.to_s() == "rails"
"rails".to_sym() == :rails
:rails == "rails" # => false

Variables, Arrays, Hashes

There are no declarations:

  • local variables must be assigned before use
  • instance & classes variables == nil until assigned

Variables in Ruby can contain different values and different types of values over time:

x = 10; x = 'foo'; x = [1, 2, 3]

Arrays can have different types of objects inside. Find below a list of the most useful methods:

array = [1, 'two', :three]
grocery_list = ["milk", "eggs", "bread"]

# Accessing items in arrays
grocery_list[0]
grocery_list.at(1)
grocery_list.first
grocery_list.last
grocery_list[-1] # last item of the array

# Adding items to arrays
grocery_list << "carrots"
grocery_list.push("potatoes") # adds item at the end
grocery_list.unshift ("celery") # adds item at the beginning
grocery_list += ["ice-cream", "pie"]
grocery_list.insert(2, "oatmeal") # insert at the index of 2

# Removing items from arrays
grocery_list.shift # drops first item in array
grocery_list.drop(2) # drops 2 last items in the array
grocery_list.slice(0,3)
grocery_list.pop # drops the last item in the array

# Common arrays methods
grocery_list.length # => 3
grocery_list.count("eggs") # how many occurrences
grocery_list.include?("eggs") # => true or false

Hashes are also a very important part of the language, called associative arrays in other languages such as PHP, from where I come from now. Find below a list of the most useful methods:

x = { 'a' => 1, :b => [2, 3] }
hash = { "item" => "Bread", "quantity" => 1 }

# Accessing items in hashes
x[:b][0] == 2
hash.fetch("quantity") # => 1
hash["quantity"] # => 1
hash.values_at("item", "quantity") # => ["Bread", 1]

# Adding items to hashes
hash.store("calories", 100) # (new key, new value)
hash.merge({"calories" => 100})

# Working with hash keys
hash.keys # => ["item", "quantity"]
hash.has_key?("item") # => true
hash.key?("item") # => true
hash.member?("brand") # => false

# Working with hash values
hash.values # => ["Bread", 1]
hash.has_value?("brand") # => false
hash.value?(1) # => true

# Common arrays methods
hash.length # returns the number of key-value pairs
hash.invert # inverts keys and values

Methods

When using methods it is important to remember that everything (except fixnums) is pass-by-reference:

def foo(x, y)
  return [x, y+1]
end

def foo(x, y=0)   # y is optional, 0 if omitted
  [x, y+1]        # implicit return, last exp returned as result
end

def foo(x, y=0) ; [x, y+1] ; end

# Calling a method
a, b = foo(x, y)
a, b = foo(x)        # when optional arg used

Strings & Regular Expressions

Strings could be expressed in different ways:

"string", %Q{string}, 'string', %q{string}

# using double-quotes when writing a string
# will cause variables to be interpolated.
a = 41 ; "The asnwer is #{a+1}."

# Common string methods
name = "Miriam"
name.upcase
name.downcase
name.reverse # => "mairiM"
name.chars # => ["M", "i", "r", "i", "a", "m"]

full_name = "Miriam Tocino"
full_name.split # => ["Miriam", "Tocino"]

Here it comes a very useful site when working with regular expressions in ruby www.rubular.com. Now let’s match a string against a regexp:

# Creating regular expressions
/(.*)$/i
%r{(.*)$}i # i means ignore case
Regexp.new('(.*)$', Regexp::IGNORECASE)

# Example
"miriam.tocino@gmail.com" =~ /(.*)@(.*)\.COM$/i
/(.*)@(.*)\.COM$/i =~ "miriam.tocino@gmail.com"
# If not match, value is false
# If match, value is non-false,
# and $1...$n capture parenthesized groups
# ($1 == 'miriam.tocino', $2 == 'gmail')

Classes & Inheritances

This is a good example to summarise the definition of a class taken from this course I followed and that I totally recommend if you are starting out with the notion of services:

class SavingsAccount < Account  # inheritance
  # constructor used when SavingsAccount.new(...) called
  def initialize(balance=0)     # optional argument
    @balance = balance          # note instance vs local variable
  end

  def balance     # instance method
    @balance      # instance var: visible only to this object
  end

  def balance=(new_amount)      # note method name: like setter
    @balance = new_amount
  end

  # An instance method
  def deposit(amount)
    @balance += amount
  end

  @@bank_name = "MyBank.com"  # class (static) variable

  # A class method
  def self.bank_name    # note difference in method def
    @@bank_name
  end
  # OR
  def SavingsAccount.bank_name : @@bank_name ; end

end

Attributes Readers & Accessors

In Ruby there is a shortcut to get and set instance variables. It is important to note that attr_accessor is NOT part of the language, but a plain method that uses metaprogramming to create getters and setters for object attributes on the fly.

class SavingsAccount < Account
  def initialize(balance=0)
    @balance = balance
  end

  attr_accessor :balance  # allows instance var to be read and set
  # OR
  attr_reader :balance    # allows instance var to be read
  attr_writer :balance    # allows instance var to be set
#  def balance
#    @balance
#  end

#  def balance=(new_amount)
#    @balance = new_amount
#  end
end

Iteration

Iterators let objects manage their own traversal. This tip I read is interesting and worth to remember:

If you’re iterating with an index, you’re probably doing it wrong

I will let you know what’s my experience with it after some time working with the language ☺

# range traversal
(1..10).each do |x| ... end
(1..10).each { |x| ... }
1.upto(10) do |x| ... end

# array traversal
my_array.each do |elt| ... end

# hash traversal
hash.each_key do |key| ... end
hash.each_pair do |key,val| ... end

10.times {...} # iterator of arity zero
10.times do ... end

Modules & Classes

A module is a collection of methods that aren’t a class, which means that you can’t instantiate it. There are extensively used to mix its methods into a class.

class A ; include MyModule ; end
A.foo
# 1 - will search A,
# 2 - then MyModule, then
# 3 - method_missing in A & B, then
# 4 - A's ancestor

So then, the question would be… how do you choose between a module or a class?

Modules reuse high-level behaviours that could conceptually apply to many classes. Some examples of modules would be: Enumerable or Comparable. Modules use mixin (include Module) as mechanism.

Classes reuse implementation. The subclass will reuse or override any of the superclass methods. The mechanism used to do so is inheritance (class Submodule < Module).

Remarkably often, composition will be preferred over inheritance.

Expression Orientation

Some useful methods related to orientation of objects:

x = ['apple','cherry','apple','banana']
x.sort # => ['apple','apple','banana','cherry']
x.uniq.reverse # => ['banana','cherry','apple']
x.reverse! # modifies x !!!

x.map do |fruit|
  fruit.reverse
end.sort
# => ['ananab','elppa','elppa','yrrehc']

x.collect { |f| f.include?("e") } # => [true, true, true, false]
x.any? { |f| f.length > 5 } # => true

For sure there is still a lot to be added here, but do you think it is OK for a quick introduction or am I missing something crucial? ☺

If you liked this article, share it with your followers