devxlogo

Ruby—A Diamond of a Programming Language, Part 2

Ruby—A Diamond of a Programming Language, Part 2

uby is an object-oriented, metaprogramming language that has garnered a lot of attention in software development circles as of late. Metaprogramming is a means of writing software programs that write or manipulate other programs, thereby making coding faster and more reliable. Part 1 examined Ruby setup, general syntax, and object-oriented aspects. This article, part 2, looks at some other powerful features built into the Ruby programming language. Again, Ruby is based on and has adopted features from many languages including Lisp, Smalltalk, Python and Perl. So in many ways, Ruby is a “best of breed” language that has been assembled from the best and most powerful programming features found in its predecessors.

Numbers, Strings, and other “Standard” Types in Ruby
Everything in Ruby is an object. Maybe more precisely, everything in Ruby is treated as a full fledged object. So, the number 4, the fixed point number 3.14 and the string “Hi” are all objects in Ruby. Obviously, they are somewhat “special” objects since you don’t have to use the new method to create one. Instead, you use the literal 4, for example, to create an instance of the object representing the number 4.

However, when most people learn a new programming language it is helpful to understand “standard” types. So in this section, numbers, strings, Booleans, and a few other base Ruby types are explored.

Numerics
Essentially, numbers in Ruby are classified as either integers or floats. Integers are further subdivided. There are “regular sized” integers and really big integers. Because everything is an object, floats and integers (whether big or small) are defined by classes (see Figure 1). Numeric is the super class for all numbers. Float and Integer classes are subclasses of Numeric. Fixnum and Bignum are subtypes of Integer that define the “regular size” and bigger integer numbers.

Figure 1. Ruby Numbers: The Numeric class is the super class for all Ruby numbers. Concrete instances of numbers are either of type Bignum or Fixnum integers or Float.

Literals are used to represent instances of these classes. The code below in the interactive Ruby shell (irb) shows literal instances of Float, Fixnum and Bignum. Notice that methods (in this case the class method) can be called on the literal. Again, everything in Ruby is an object so you will find all sorts of methods for numeric instances.

irb(main):001:0> 3.class=> Fixnumirb(main):002:0> 3.4.class=> Floatirb(main):003:0> 10000000000000000000.class=> Bignum

There is some additional syntax for creating numbers as shown in the code listing below. The letter ‘E’ can be used to represent numbers in exponential notation. Numbers can be proceeded with 0 to represent a number in octal, 0x to represent a number in hex, and 0b to represent a number in binary form. For clarity, underscores can be used in numbers as separators. Be careful not to use a comma as a separator when writing literals. In some cases, this can actually produce an array, which will be discussed below. Finally, a question mark in front of a character (or control or meta character combination) creates a Fixnum instance corresponding to the ASCII character/escape sequence value for the character(s).

 3.14E5 #exponential notation=> 314000.0irb(main):002:0> 054	 #octal=> 44irb(main):003:0> 0x5A #hex=> 90irb(main):004:0> 0b1011 #binary=> 11irb(main):005:0> 10_000 #10,000 with underscore separator=> 10000irb(main):006:0> i=10,000 #creates an array not 10000 Fixnum=> [10, 0]irb(main):007:0> i.class=> Arrayirb(main):008:0> ?Z #Fixnum ASCII value=> 90irb(main):009:0> ?Z.class=> Fixnumirb(main):010:0> ?C-s #ASCII value of Control-s=> 19

What’s the real difference between Fixnum and Bignum instances? Fixnum integers can be stored in the machines word minus a bit (usually 16, 32, or 64 bits) while Bignum instances are integers that exceed that sized storage space. The good news for developers is that you don’t have to worry about the size of the integer (see the code sample below). Ruby automatically takes care of any conversion across the Fixnum/Bignum barrier for you!

irb(main):001:0> i=4=> 4irb(main):002:0> i.class=> Fixnumirb(main):003:0> i=i+100000000000000=> 100000000000004irb(main):004:0> i.class=> Bignumirb(main):005:0> i=i-100000000000000=> 4irb(main):006:0> i.class=> Fixnum

Strings
Strings are arbitrary sequence of bytes in Ruby. Usually they are a sequence of characters. In Ruby, instances of the String class can be created using a literal or the new method on the String class.

irb(main):001:0> s1="Hello World"=> "Hello World"irb(main):002:0> s2=String.new("Hello World")=> "Hello World"

Of course, the String defines a great many methods (and operators) for string instances. Also, strings can either be specified with single or double quotes. Double quotes allow for many escape characters and embedded expressions to be evaluated and used in the strings. With single quoted strings, what you see is what you get. For a better understanding, look at the following examples.

irb(main):001:0> str1='a 
 string'=> "a \n string"irb(main):002:0> str2="a 
 string"=> "a 
 string"irb(main):003:0> puts str1a 
 string=> nilirb(main):004:0> puts str2a  string=> nilirb(main):005:0> 'try to add #{2+2}'=> "try to add #{2+2}"irb(main):006:0> "try to add #{2+2}"=> "try to add 4"irb(main):007:0> this="that"=> "that"irb(main):008:0> 'when single quote rights #{this}'=> "when single quote rights #{this}"irb(main):009:0> "double quote rights #{this}"=> "double quote rights that"

Notice how text inside of double quotes is evaluated before displaying where character escapes (like
) and expressions (like #{2+2}) included. On the other hand, these items are treated literally in single quoted strings?

Besides the single and double quote characters to denote a string literal, there is another way to write string literals in Ruby. A percent sign and lowercase or uppercase letter Q can be used to write a string in either single-quote or double-quote style respectively.

irb(main):001:0> %q@this is a single quote string #{2+2} here@=> "this is a single quote string #{2+2} here"irb(main):002:0> %Q@this is a double quote string #{2+2} here@=> "this is a double quote string 4 here"

Note that the character following q% or Q% defines the beginning and end of the string literal. The @ symbol is used in this example as the delimiter marking the beginning and ending of the string.

It should be noted for those that come from other programming language backgrounds that Ruby does not differentiate between a string and character. In other words, there is no special class for single characters, they are just small strings.

Boolean
Lastly, before leaving the “standard” types arena, let’s look at Booleans. In Ruby, there are two classes for Boolean: TrueClass and FalseClass. There is only one instance (a singleton) for each of these classes; namely true and false. These are global values accessible anywhere in Ruby. There is also a class called the NilClass. NilClass also has only one instance?nil. Nil represents…well, nothing. In the case of Boolean logic, however, nil is synonymous with false.

irb(main):001:0> true|false=> trueirb(main):002:0> true&false=> falseirb(main):003:0> true|nil=> trueirb(main):004:0> true&nil=> false

Regular Expressions
Most programming languages utilize regular expressions. Ruby, having descended from many scripting languages, makes extensive use of regular expressions. A co-worker of mine once said “regular expressions aren’t.” Regular that is. In other words, regular expressions can take some effort and time to fully understand. You will only get a glimpse of the power of Ruby’s regular expressions here. You don’t have to use regular expressions to write your applications, but suffice it to say it can make writing applications that work with Strings, in particular, much tighter and easier to write. This section will get you started, but you have some homework to research expressions if you are going to be a Ruby guru.

A regular expression “is a string that describes or matches a set of strings” based on some defined syntax. Another term for regular expression is “pattern.” Regular expressions are used to do string manipulation or searching. You have probably used a regular expression when looking for files on your hard drive. For example, if you want to delete all the files that begin with “ruby” in a particular directory, you might use the rm ruby* command on your Windows or Unix box. In this simple example, ruby* is the regular expression you use to describe or match files.

Regular expressions in Ruby are defined between Tiger or Phil.

/Tiger|Phil/

You can now use this regular expression with a match operator (“=~”) in a conditional or loop statement to match or find other strings.

irb(main):001:0> golfer="Davis"if golfer =~ /Tiger|Phil/	puts "This is going to be a long drive."else	puts "And now a drive by " + golferend=> "Davis"And now a drive by Davis

Here is another regular expression that is a bit more complex: /[w._%-]+@[w.-]+.[a-zA-Z]{2,4}/. Can you guess what this expression represents? If you guessed an email address, you are right. This regular expression could be used to validate email addresses.

Editor’s Note: A sharp-eyed reader, Sam Livingston-Gray, sent us this alternative regular expression, which is a bit more rigid about validating email addresses. Our thanks to Sam for the suggestion.
     /A[w._%-]+@[w.-]+.[a-zA-Z]{2,4}z/

irb(main):001:0> emailRE= /[w._%-]+@[w.-]+.[a-zA-Z]{2,4}/email = "[email protected]"if email =~ emailRE	puts "This is a valid email address."else	puts "this is not a valid email address."endThis is a valid email address.irb(main):002:0>email = "###@spammer&&&.333"if email =~ emailRE	puts "This is a valid email address."else	puts "this is not a valid email address."endthis is not a valid email address.

Figure 2 dissects the email regular expression. As you can see, the regular expression language is quite rich and unfortunately cannot be covered in detail here. For more information, take a look at http://www.regular-expressions.info.

Figure 2. Email Regular Expression: The regular expression to describe email addresses may look complex but it is really just a set of patterns that describe each section of the email address – user, domain and qualifier.

And if you haven’t guessed it by now, regular expressions live up to the Ruby mantra “everything is an object” because regular expressions are objects too. In the code example below, a regular expression instance (from class Regexp) is used as a parameter to a String method, gsub, to replace “happy” and “joy” with “glad.”

irb(main):001:0> quote = "I am so happy. Happy, happy, joy, joy!"regx = /(h|H)appy|joy/quote.gsub(regx, "glad")=> "I am so happy. Happy, happy, joy, joy!"=> /(h|H)appy|joy/=> "I am so glad. glad, glad, glad, glad!"

When you use the =~ operator on the regular expression object, you can get information such as the index of the matching pattern string.

irb(main):001:0> /Tiger|Phil/=~"EyeOfTheTiger"=> 8

The power of regular expression in Ruby becomes pretty apparent if you have ever had to write an application that works heavily with strings. While a bit chewy, you owe it to yourself to fully explore regular expressions in Ruby before you get too deep in coding your first application. Take a look at the sample application attached to this article for more examples of regular expressions in Ruby.

Ranges
A very unusual but useful definition in Ruby is the concept of a range. A range is simply a set of sequential values. For example, the characters a through z define a range inclusive of all the lowercase characters in the English alphabet. Another example is the range of integers 1 through 10. A range can be created from any type of object given the type of object allows comparison using the Ruby comparison operator () and the succ method. The operator returns -1, 0 or +1 depending on whether the left-hand operand is less than, equal to or greater than the right-hand operand. For example “A”“B” both return -1. The succ method runs on the integer 4 (4.succ) would return 5 and succ run on a returns b.

Ranges can be created using the new method on the Range class or using special notation. Below, two identical ranges for uppercase letters are created in irb using parentheses and dot-dot shorthand notation.

irb(main):001:0> r1=Range.new("A","Z")=> "A".."Z"irb(main):002:0> r2=("A".."Z")=> "A".."Z"

When creating a range, a beginning and ending value must be specified; in this case A as the beginning value and Z as the ending value. When creating a range, you can also signify whether the range should be inclusive or exclusive of the end element. By default, as shown above, the range includes the end value. To exclude the end element from the range, use the exclusive parameter on the new method or triple-dot shorthand notation as shown below.

irb(main):001:0> r1=Range.new("A","Z",true)=> "A"..."Z"irb(main):002:0> r2=("A"..."Z")=> "A"..."Z"irb(main):003:0> r1.include?"Z"=> falseirb(main):004:0> r2.include?"Z"=> false

The include? method called on a range as shown above indicates whether the parameter is a member of the range. In the case shown, “Z” is not an element. There is an operator for this same method. “===” (that’s three equal signs together) performs the same task.

irb(main):005:0> r2==="Z"=≫ false

Ranges are used in many aspects of Ruby programming. In particular, they have two specific uses: as generators and predicates. As a generator, the method each on a range allows you to iterate through each element in the range. For example, say you wanted to determine the number of actual bytes in a range of kilobytes. The code shown run in the irb below uses a range as generator for kilobytes to bytes.

irb(main):008:0> kilobytes=(1..10)kilobytes.each{|x| puts x*1024}=> 1..1010242048307240965120614471688192921610240

In Ruby conditional logic ranges can be used as predicates, usually with the help of the === operator. For example, you could use a range predicates to test an integer reference against valid port numbers (0 through 65535) and reserved ports (0 through 1024 non-inclusive).

irb(main):001:0> proposedPort = 8080validPorts=(0..65535)reservedPorts=(0...1024)if (validPorts === proposedPort) & !(reservedPorts === proposedPort)	puts "Proposed port is ok to use."else	puts "Proposed port is not allowed to be used."end=> 8080=> 0..65535=> 0...1024Proposed port is ok to use.

Ranges are also very useful in accessing elements of data structures like arrays and hashes which is our next topic.

Data Structures
As with many programming languages, Ruby comes complete with built in data structures to contain and manage data and objects. Arrays are objects that hold a collection of other object references. Arrays are created using square bracket notation and separating object references in the list with commas.

presidents=["John","Richard","Gerald","Ronald","George","William"];

To make it easier to construct an array full of words as above, a special notation is provided to eliminate the double quotes and commas.

presidents= %w[ John Richard Gerald Ronald George William];

It other programming languages, the term “array” often implies a homogenous collection of objects. Not so in Ruby. In Ruby, an array can be a heterogeneous collection of object references. So, the following is a perfectly legal array.

order_date=Date.today()shirt_information=[14.5,”Long”,32,order_date]

Object references are stored sequentially and indexed in the array. Like Java, the indexes start at 0 and the index can be used to retrieve the object reference from the array. Below, the third element (at index of 2) is requested from the shirt_information array created above in the irb. Notice you can use either the square bracket notation or the at method to retrieve object references from the array?

irb(main):003:0> shirt_information[2]=> 32irb(main):004:0> shirt_information.at(2)=> 32

Interestingly, you can also reference elements in the array using a “negative index.” A negative index is counted from the end of the array.

irb(main):005:0> shirt_information[-3]=> "Long"

Arrays are dynamic, meaning the size of the array changes dynamically depending on your operations. You can add or replace an element in the array using the [index]= operator.

irb(main):013:0> shirt_information=> [14.5, "Long", 32, #]irb(main):014:0> shirt_information[1]="Medium" #change shirt length=> "Medium"irb(main):015:0> shirt_information[4]=49.99	 #add shirt cost=> 49.99irb(main):016:0> shirt_information=> [14.5, "Medium", 32, #, 49.99]

You can also use number pairs and ranges to create new arrays from portions of the array using a [start index, element count] notation or [beginning_index..ending_index] notation.

irb(main):019:0> shirt_information=> [14.5, "Long", 32, #, 49.99]irb(main):020:0> shirt_dimensions = shirt_information[0,3]=> [14.5, "Long", 32]irb(main):021:0> shirt_order = shirt_information[2..5]=> [32, #, 49.99]irb(main):030:0> shirt_information[-3,2]=> [32, #]

This notation combined with the assignment operator ([ ]=) gives rise to a very intricate element insert or replacement operations. A beginning/ending index or range can be used in the assignment operator. This can be best understood with some example code.

irb(main):001:0> test_array=["zero", "one", "two", "three", "four"]=> ["zero", "one", "two", "three", "four"]irb(main):002:0> #starting at the second element, replace the next two elements with a single elementirb(main):003:0* test_array[1,2]=1=> 1irb(main):004:0> test_array=> ["zero", 1, "three", "four"]irb(main):005:0> #insert a new element after the second element (zero as a second parameter indicates “insert”)irb(main):006:0* test_array[2,0]=2=> 2irb(main):007:0> test_array=> ["zero", 1, 2, "three", "four"]irb(main):008:0> #add an array of elements after element 5irb(main):009:0* test_array[5,0]=[5,6,7]=> [5, 6, 7]irb(main):010:0> test_array=> ["zero", 1, 2, "three", "four", 5, 6, 7]irb(main):011:0> #replace elements in the index range of 3..4 with the array assignedirb(main):012:0* test_array[3..4]=[3,4]=> [3, 4]irb(main):013:0> test_array=> ["zero", 1, 2, 3, 4, 5, 6, 7]

Finally, maybe some of the most powerful operations with Ruby arrays are found in the “mathematical” operators that create new arrays from existing arrays. For example, the + operator allows you to create a new array from two arrays concatenated together and the * operator allows you to duplicate or concatenate an array with itself so many times.

irb(main):033:0> shirt_information=> [14.5, "Long", 32, #, 49.99]irb(main):034:0> pant_information=[34,32,59.99,order_date]=> [34, 32, 59.99, #]irb(main):035:0> shirt_information + pant_information=> [14.5, "Long", 32, #, 49.99, 34, 32, 59.99, #]irb(main):036:0> shirt_information * 2=> [14.5, "Long", 32, #, 49.99, 14.5, "Long", 32, #, 49.99]irb(main):037:0> array1 = [2,4,6,8,10]=> [2, 4, 6, 8, 10]irb(main):038:0> array2=[3,6,9]=> [3, 6, 9]irb(main):039:0> array1 - array2=> [2, 4, 8, 10]

Closely related cousins to Arrays in Ruby are Hashes. The index used to sequence and refer to object references in an array was an integer. The Hash class in Ruby behaves like Array except that it allows any type of object “index” or key to reference objects in the collection. In other programming languages this might be called a dictionary or map or hash map. In general, when working with a hash, you supply two object references for every element in the collection. One object reference is the key and other is what the key points to called the value. The notation used to show what a key object points to is the => symbol. Key/value pairs can be collected between two curly brackets when creating a hash. As an example of a hash, use simple string objects as keys to reference Date objects (values) in a hash.

holidays={"New Year"=>Date.parse("2006-01-02"), "MLB Birthday"=> Date.parse("2006-01-16"), "Washington Birthday"=>Date.parse("2006-02-20"), "Memorial Day"=>Date.parse("2006-05-29"), “July4th”=>Date.parse(“2006-07-05”)}

To retrieve the Date object for Memorial Day, use the “Memorial Day” string key.

irb(main):004:0> holidays["Memorial Day"]=> #irb(main):005:0> holidays["Memorial Day"].to_s=> "2006-05-29"

The statement made earlier that “any” object could be used as a key in a Hash is restricted by the fact that objects that serve as keys must respond to the hash method with a hash value that does not change. The hash value of any object is a fix number created by Ruby to uniquely identify an object. The content of a collection object (like an array or hash) is used to determine its hash code. Because the content of a collection object can change, so too can their hash code change; thus instances of Array and Hash cannot serve as keys in a hash object. While hashes provide the convenience of allowing almost any object to serve as the index or key, their disadvantage is that they are not sequential or ordered as are arrays.

As you might expect, there are many additional methods on the Array and Hash classes that allow you to access and modify individual elements in the collection or change the entire collection itself. As shown here, arrays and hashes in Ruby are very powerful and dynamic data structures.

Once you have a collection of object references, one of the most common tasks in programming is to cycle through or “iterate” through the elements in the collection and perform some task. Ruby, like Java, C#, Lisp, and several other programming languages has a built in mechanism for iterating through the elements in an array or hash. The mechanism Ruby provides to iterate is a set of special methods on the collection objects. Methods like each, each_index, delete_if on Array and each, each_key, each_value, each_pair on Hash allow your code to cycle through and take action on the contents of the collection. In fact, many classes in Ruby contain iterator methods. String, for example provides iterator methods to perform tasks on strings separated by a designated character or by bytes. However, before you can understand iterator methods, you need to understand the concept of code blocks in Ruby. For each of the iterator methods are passed a block of code to execute when iterating over members of the collection.

Code Blocks, Iterators, and Procedure Objects
Everything in Ruby is an object? Just about, yep! Even a chunk of code can be an object! In Ruby, code objects are called code blocks. Think of code blocks as little program suitcases. They contain Ruby code and can travel to methods where they can be executed. Python, C, and Java developers may all find similarities to Ruby code blocks in things like function pointers, anonymous functions, inner classes and callbacks.

The syntax for a Ruby code block is just to put Ruby code between curly braces or do/end commands.

{ 	# this is a code block... }do	# ...and this is a code blockend

In a very simple example, { puts “hello world” } is a valid code block. How do you use these code blocks and pass them as a suitcase of code to a method? For that, first define a very simple method like the one below.

def someMethod	yieldend

The command yield transfer control to the code block that is passed to the method. The code below shows you how a code block is passed to the simple method above using a simple code block.

irb(main):001:0> someMethod {puts "hello world"}hello world

Each time yield is called, the code block passed to the method gets executed. Here is another example of a more complex method using a code block that does a little more work.

irb(main):001:0> def fibonacci (stop)	while stop  nilirb(main):006:0> i=0; j=1; fibonacci(j) {puts i; temp = i; i = j;j = temp + j}0112358

Code blocks are used throughout Ruby. Most importantly, code blocks are used internally by Ruby in the iterator methods of classes like Array, Hash, and even String. A code block is how you define what task to perform (typically a task on the elements) when iterating through all the elements in an array. To demonstrate code blocks and iterators, a small example is in order. Assuming you had defined a number of barn yard animal classes as those defined in Figure 3 and an Array of those animals, you could make each talk using a code block and an iterator on the array.

irb(main):031:0> barnYard = [Cow.new, Duck.new, Chicken.new, Horse.new, Dog.new]=> [#<0x58d2f48><0x58d2f30><0x58d2f00><0x58d2ee8><0x58d2ed0>





devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist