Ruby, An Introduction to a Programmer’s Best Friend

When I was first introduced to Ruby, I asked, “Why Ruby?” I got two common replies:
- It makes you think about programming differently (especially if you come from a non-OOP background).
- It makes you happy.
Ruby was designed from a programmer’s point-of-view for productivity. Its syntax is elegant and it’s truly object-oriented.
Ruby is a dynamic, reflective, object-oriented, general-purpose programming language. It was designed and developed in the mid-1990s by Yukihiro “Matz” Matsumoto in Japan.
Matz’s idea for Ruby is clear from the below statement:
I was talking with my colleague about the possibility of an object-oriented scripting language. I knew Perl (Perl4, not Perl5), but I didn’t like it really, because it had the smell of a toy language (it still has). The object-oriented language seemed very promising. I knew Python then. But I didn’t like it, because I didn’t think it was a true object-oriented language — OO features appeared to be add-on to the language. As a language maniac and OO fan for 15 years, I really wanted a genuine object-oriented, easy-to-use scripting language. I looked for but couldn’t find one. So I decided to make it.
Here, I’ll walk you through an introduction to Ruby.
Let’s start off with the basics.
Strings:
1 2 3 4 5 6 7 8 9 10 |
2.1.2 :001 > name = "Matz" => "Matz" 2.1.2 :002 > puts "Hello, #{name}" Hello, Matz => nil 2.1.2 :003 > puts "Hello, " + "#{name}" Hello, Matz => nil 2.1.2 :004 > "I l" + "o" * 5 + "ve Ruby!" => "I looooove Ruby!" |
There are some nice things happening here. The basic String interpolations work as you’d expect. You can concatenate Strings together with the + method. You can even “multiply” Strings.
So what’s the cool stuff? The way that it is implemented. For instance, the + method acts on the String object “Hello, “”. The meaning of + depends on the object receiving that method. Here, it is an object of type String, where it means “add String ‘Matz’ to String ‘Hello, ‘“”.
Conventionally, methods with a ? at the end return boolean values. And methods with a ! perform some “dangerous” operation like mutating the String. |
Everything is an object, including integers and floating point numbers. We’ll come back to this in a bit. For people familiar with object oriented programming, it’s nice to see that everything is object oriented, even basic operations like the addition of numbers.
Also notice that for every statement, the Ruby interpreter returns something, even if that something is nil. In Ruby, every statement is an expression and will result in a value.
Identifiers with their first character in the uppercase are constants. |
Coming back to our String example, if we had an integer on the left hand side of a +, the + would result in addition of that and whatever was on the right hand side. Since Ruby allows you to reopen classes, you could open up the Fixnum class and redefine the + method to mean… subtraction! Although you might get fired, I’m sure it’d be fun for your entire team. Lol.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
Greet 2.1.2 :001 > class Greet 2.1.2 :002?> attr_accessor :name 2.1.2 :003?> 2.1.2 :004 > def initialize(name="Nobody") 2.1.2 :005?> @name = name 2.1.2 :006?> end 2.1.2 :007?> 2.1.2 :011?> 2.1.2 :012 > def say_goodbye(another) 2.1.2 :013?> "Goodbye, #{another}" 2.1.2 :014?> end 2.1.2 :015?> 2.1.2 :023?> def say_hello(another) 2.1.2 :024?> "Hey %s! I'm %s" % [ another, @name ] 2.1.2 :025?> end 2.1.2 :019?> end => :say_hello |
Greet is a simple class with a bunch of methods in it.
Variable Scope
There are four variable scopes:
– Global ($)
Global variables are available for use throughout your program.
– Class (@@)
Class variables are available to all instances (objects) of a class.
– Instance (@)
Instance variables are available only inside the current instance, ie., each instance has its own copy.
– Local
Local variables are limited in scope to within a method or a block.
The attr_accessor method sets up getter and setter methods for a list of variables. Simple things, but it saves you the time you’d spend doing something like:
1 2 3 4 5 6 7 |
def foo @foo end def foo=(new_value) @foo = new_value end |
‘… for every damn variable.
Did you notice how the attr_accessor accepted :name. That’s a symbol. It’s like a String, but it’s used like an identifier. They’re different from Strings and all symbols with the same name share the same memory.
the initialize method in a class is run on instantiation, ie., when the object is created. Objects are created with the .new method defined on the class constant.
You can omit the return keyword in Ruby. The last line executed in a block of code is the value that is returned. |
Hold on, you can use methods on classes?! Remember I told you that everything was an object? Well, your class definition happens to be an object of type Class with the constant Greet pointing to it. Don’t believe me? Try it out for yourself.
1 2 3 4 |
2.1.2 :041 > Greet.class => Class 2.1.2 :043 > Greet.methods.sort => [:!, :!=, :!~, :<, :<=, :<=>, :==, :===, :=~, :>, :>=, :__id__, :__send__, :allocate, :ancestors, :autoload, :autoload?, :class, :class_eval, :class_exec, :class_variable_defined?, :class_variable_get, :class_variable_set, :class_variables, :clone, :const_defined?, :const_get, :const_missing, :const_set, :constants, :define_singleton_method, :display, :dup, :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :include, :include?, :included_modules, :inspect, :instance_eval, :instance_exec, :instance_method, :instance_methods, :instance_of?, :instance_variable_defined?, :instance_variable_get, :instance_variable_set, :instance_variables, :is_a?, :kind_of?, :method, :method_defined?, :methods, :module_eval, :module_exec, :name, :new, :nil?, :object_id, :prepend, :private_class_method, :private_constant, :private_instance_methods, :private_method_defined?, :private_methods, :protected_instance_methods, :protected_method_defined?, :protected_methods, :public_class_method, :public_constant, :public_instance_method, :public_instance_methods, :public_method, :public_method_defined?, :public_methods, :public_send, :remove_class_variable, :remove_instance_variable, :respond_to?, :send, :singleton_class, :singleton_class?, :singleton_method, :singleton_methods, :superclass, :taint, :tainted?, :tap, :to_enum, :to_s, :trust, :untaint, :untrust, :untrusted?] |
The new method takes arguments which are supplied to the initialize method. Default values for method parameters can be specified using param=default_value in the method definition.
g is an instance of Greet.
1 2 |
2.1.2 :020 > g = Greet.new => #<Greet:0x00000001992e50 @name="Nobody"> |
Our getter and setter methods in action:
1 2 3 4 5 6 7 8 |
2.1.2 :032 > g.name = "Tom" => "Tom" 2.1.2 :033 > g.name => "Tom" 2.1.2 :034 > g.say_hello("Jim") => "Hey Jim! I'm Tom" |
The last method show how you can use format specifiers and an Array of values to make up a String.
Arrays and Hashes
Arrays and hashes are used to store related data in Ruby. You can create a new Array or a Hash with:
1 2 |
i_am_an_array = Array.new i_am_a_hash = Hash.new |
Of course, they’re used quite often and can be created literally like this:
1 2 |
new_array = [] # an empty array. new_hash = {} # an empty hash |
1 2 3 4 |
2.1.2 :013 > foo = "hex" => "hex" 2.1.2 :014 > bar = [10, 3.14, foo, ["I'm", "a", "nested", "array"]] => [10, 3.14, "hex", ["I'm", "a", "nested", "array"]] |
Arrays are nothing but a collection of references to objects. So they can store anything you’d like, including other arrays.
1 2 3 4 |
2.1.2 :015 > foo.reverse! => "xeh" 2.1.2 :016 > bar => [10, 3.14, <b>"xeh"</b>, ["I'm", "a", "nested", "array"]] |
Just references to objects.
You can slice an Array any way you like:
1 2 3 4 5 6 |
2.1.2 :030 > bar[0] => 10 2.1.2 :031 > bar[-1] => ["I'm", "a", "nested", "array"] 2.1.2 :032 > bar[2..-1] # This is a range. More on this later => ["xeh", ["I'm", "a", "nested", "array"]] |
Hashes are associative Arrays (like dictionaries) and hold key-value pairs. The keys and values can be an object of any type.
1 2 |
2.1.2 :028 > a_hash = {:name => "NASA", 4 => "four", "array" => bar } => {:name=>"NASA", 4=>"four", "array"=>[10, 3.14, "xeh", ["I'm", "a", "nested", "array"]]} |
Usually though, they’re used with symbols as keys, so much so that they have their own shorthand syntax:
1 2 |
2.1.2 :029 > another_hash = { organization: "NASA", country: "USA" } => {:organization=>"NASA", :country=>"USA"} |
Values can be accessed like this:
1 2 |
2.1.2 :037 > a_hash[:name] => "NASA" |
Printing Objects
What if I want to print an instance of Greet? How does Ruby know what to print? Remember numbers are just objects as well, so I can do puts 23 and it outputs the String “23”. How does Ruby know how to convert an object of Fixnum and make a String out of it? If you look at all the methods in the Fixnum class, there’s a to_s method. puts simply calls the to_s method on the number 23.
Therefore, all we need to make instances of Greet printable is to define a to_s method. Let’s reopen Greet and add a to_s method.
1 2 3 4 5 6 7 8 9 |
2.1.2 :054 > class Greet 2.1.2 :055?> def to_s 2.1.2 :056?> "My name is #{@name}" 2.1.2 :057?> end 2.1.2 :058?> end => :to_s 2.1.2 :059 > puts g My name is Tom => nil |
if, unless, while, until, for
1 2 3 4 5 6 |
if condition # do something elsif # do another else # do something else |
You can do this as well:
1 |
puts "I'm true today" if condition |
You can use the unless keyword in place of if when you do not want to perform an action if the condition is true.
Looping in Ruby is usually done using iterators, but the language does provide the while and for loops.
1 2 3 4 5 6 7 |
i = 0 while i < 5 print "#{i}, " i += 1 end 0, 1, 2, 3, 4, |
You can use until to loop while the condition is false, similar to a while loop. |
For loops:
1 2 3 4 |
fruits = ["apples", "grapes", "pineapples", "oranges"] for fruit in fruits puts fruit end |
Range
Ranges in Ruby are used to define a set of values with a starting and an end. So if I wanted to loop over values from 0 to 5 I’d do this:
1 2 3 4 |
2.1.2 :035 > for i in (0..5) 2.1.2 :036?> print "#{i}, " 2.1.2 :037?> end 0, 1, 2, 3, 4, 5, => 0..5 |
You can have character ranges as well.
1 2 3 4 |
2.1.2 :039 > ('a'..'e').to_a => ["a", "b", "c", "d", "e"] 2.1.2 :040 > ('a'...'e').to_a => ["a", "b", "c", "d"] |
Two dots: Inclusive of last value.
Three dots: Exclusive of last value.
Blocks and Iterators
Shouldn’t an object know how to iterate over itself? Just like a String should know how long it is.
“I am thirty-one characters long”.length
You can just as simply use .size for the length. Ruby, in some places, where it makes sense, has multiple methods that do the same thing. Use what feels natural. |
Blocks and iterators in Ruby are really powerful and really useful. Most times you really aren’t interested in maintaining an Array index externally. You just want to loop over the damn thing! Here’s how you’d do it in Ruby:
1 2 3 4 5 6 7 8 9 10 |
2.1.2 :009 > fruits = ["apples", "grapes", "pineapples", "oranges"] => ["apples", "grapes", "pineapples", "oranges"] 2.1.2 :010 > fruits.each do |fruit| 2.1.2 :011 > puts "I like #{fruit}" 2.1.2 :012?> end I like apples I like grapes I like pineapples I like oranges => ["apples", "grapes", "pineapples", "oranges"] |
It basically takes every element in the Array fruits and provides it as fruit to the block of code between the do … end. This is a block.
A block is basically a chunk of code that you pass to a method, not different from how you’d pass data as arguments. Let’s have a look at how you’d make your own iterator to give you a better understanding.
Let’s make an iterator that processes only every other element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
2.1.2 :001 > class Array 2.1.2 :002?> def every_other 2.1.2 :003?> current_index = 0 2.1.2 :004?> new_array = [] 2.1.2 :005?> while current_index < self.size 2.1.2 :006?> new_array << <b>yield</b>(self[current_index]) 2.1.2 :007?> current_index += 2 2.1.2 :008?> end 2.1.2 :009?> return new_array 2.1.2 :010?> end 2.1.2 :011?> end => :every_other 2.1.2 :012 > [1, 2, 3, 4, 5].every_other { |i| i ** 2 } => [1, 9, 25] |
That might have been a bit overwhelming, so let’s break it down.
We reopen the Array class and on line 5 we create a while loop. It loops as long as current_index is smaller than the size of the original Array.
Line 6 is where the magic happens. We can call the block we passed to every_other with yield along with any arguments. Here, we pass an alternate element on each loop. Again, the elements are passed through the block of code that we passed and we append the value returned by the block to new_array.
We then return the new_array once we are done.
{ |var1, var2| statements } is the same as do |var1, var2| statement end |
On line 12, we call every_other on the Array [1, 2, 3, 4, 5] and pass it a block that takes one argument. That’s the argument we provide to yield in the method definition.
Basically, we took every alternate element and ran each of them through the block i ** 2.
For a more in-depth look at blocks, procs and lambdas, have a look at this brilliant article on Ruby Blocks, Procs, and Lambdas.
Comparable
Let’s say you define a new class and you want to sort an Array of those objects. Imagine the amount of work that you’d have to do. Ruby has an elegant solution.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
2.1.2 :024 > class Person 2.1.2 :025?> attr_accessor :name, :age 2.1.2 :026?> 2.1.2 :027 > include Comparable 2.1.2 :028?> def initialize(name, age) 2.1.2 :029?> @name, @age = name, age 2.1.2 :030?> end 2.1.2 :031?> 2.1.2 :032 > def <=> other 2.1.2 :033?> self.age <=> other.age 2.1.2 :034?> end 2.1.2 :035?> end => :<=> 2.1.2 :036 > people = [] => [] 2.1.2 :037 > people.push(Person.new("Ram", 30), Person.new("James", 40), Person.new("Angela", 28)) => [#<Person:0x0000000122de30 @name="Ram", @age=30>, #<Person:0x0000000121ff38 @name="James", @age=40>, #<Person:0x0000000121fb78 @name="Angela", @age=28>] 2.1.2 :038 > people.sort => [#<Person:0x0000000121fb78 @name="Angela", @age=28>, #<Person:0x0000000122de30 @name="Ram", @age=30>, #<Person:0x0000000121ff38 @name="James", @age=40>] |
Okay, so we have a person class. Each person has a name and an age. You include the Comparable module which comes with a whole suite of goodness. All it requires you to do is define a method, ⇔, that returns -1, 0, or +1 for less than, equal to or greater than cases.
On line 32, we do just that. Other is another object that has an age attribute. We compare the age values of this person and the other person. Since the ⇔ method has already been defined for numbers, we need not bother ourselves with anything else.
We create a list of people and call sort on it. And voila! All the persons are sorted by their ages.
Similarly, you can make your class enumerable, but we’ll leave that for another time.
Check out this page for more information |
Composition vs Inheritance
Zed Shaw has a brilliant section devoted to this. While inheritance is a useful, composition is usually a better way to achieve the same sort of functionality without having two classes that are tightly coupled and probably hard to test.
Ruby has only single inheritance, so even if you go with inheritance, you won’t end up with the diamond problems.
You saw how Ruby uses composition is the previous section when we included the comparable module into our class.
1 2 3 4 5 6 7 8 9 10 11 |
module Foo # a bunch of methods end module Bar # some more methods end class Baz include Foo end |
an_object = Baz.new # an_object now comes with all the methods in Foo.
an_object.extend(Bar) # an_object now has the methods in Bar, but all other instances of Baz are unaffected
Ruby supports a lot of advanced metaprogramming features. It allows things like evaluating a String to define a new method during runtime. |
You can use the method respond_to? to check if an object responds to a particular method.
Regular Expressions
Ruby has a Perl-like regex syntax, with regular expression literals written using the /…/ syntax.
A lot of programming involves dealing with Strings of text, so regular expressions are extremely useful
1 2 3 4 5 |
if filename =~ /.+\.rb$/ puts "This is a Ruby file." else puts "This isn't a Ruby file." end |
Ruby comes with a neat command-line debugger, quite similar to GDB, so if you’re ever stuck, you can always run your program with the debug flag. |
The Ruby documentation is brilliantly written, so if you want to learn more about a particular part of Ruby, that should be where you first look.
Gems
Ruby comes with its own package manager. The Gemfile in your project maintains a list of gems (packages) that your code depends on, and a single command will install all the necessary packages that your program needs to run. Version numbers may be included
You can even have groups of gems different for development and production, too!
Rails and DSLs
Rails was Ruby’s killer app and what made it the cool kid on the block back around 2006. Since then Ruby has matured as a language and Rails as a framework. Rails wouldn’t be what it is without Ruby. Ruby allowed Rails to make elegant uses of its syntax and structure. If you are a web developer, you have got to go try out Rails for yourself. ‘Nuff said.
Rails and other applications make use of Domain Specific Languages when it’s better to take the programming language to the background. Ruby, with its strong support for metaprogramming, allows you to transform what would otherwise be a mess.
Here’s more on how to create your own internal DSL.
Conclusion
Ruby is a brilliant language…
At the end of the day, even if you don’t use Ruby in your day-to-day life, learning it will open your mind up to some brilliant concepts and will definitely change how you think about programming.
“A language that doesn’t affect the way you think about programming is not worth knowing” — Alan Perlis
Ruby lives up to its name of being a language for the programmer. It doesn’t make you want to pull your hair out. You will just want to write more Ruby code.
For some very interesting articles for people who are already familiar with Ruby, you should see this page.
Darshan is a engineering student and web developer with a passion for computers, flying and hacking.