Ruby

Hire the Top 3% of Freelance Ruby Developers

Toptal is a marketplace for top Ruby developers, engineers, programmers, coders, architects, and consultants. Top companies and start-ups choose Toptal Ruby freelancers for their mission-critical software development projects.

No-Risk Trial, Pay Only If Satisfied.

Clients Rate Toptal Ruby Developers4.4 / 5.0on average across 666 reviews as of Oct 22, 2020

Hire Freelance Ruby Developers and Engineers

David Leong

Freelance Ruby Developer

United StatesToptal Member Since October 23, 2017

David is a full-stack engineer with over 12 years of experience in web development, managing all aspects of the development process. He's worked on 50+ projects and solved complex business problems in various industries including marketing, healthcare, eCommerce, real estate, and car insurance. David has consistently saved costs while increasing profits. He's ready for an opportunity to use his skills to grow your revenue exponentially.

Show More

Clemens Helm

Freelance Ruby Developer

AustriaToptal Member Since September 29, 2017

Clemens has been working as a full-stack web developer for over a decade and a half. He's passionate about designing and building high-quality products. What he loves most about freelancing is the opportunity to encounter various types of projects, people, and cultures. Working this way, he constantly gains experience and knowledge for his next projects.

Show More

Duarte Henriques

Freelance Ruby Developer

PortugalToptal Member Since December 23, 2016

Duarte is a senior developer with over ten years experience building web applications, using rails and react. He has managed engineering teams, but still loves to code. Whether building product from scratch or integrating an existing team, he will be productive from Day One.

Show More

Fayaz Abdul

Freelance Ruby Developer

United KingdomToptal Member Since September 12, 2019

Fayaz is a DevOps engineer and architect with experience in designing end-to-end solutions. He has extensive cloud and infrastructure expertise with multiple certifications in AWS, Azure, and Google Cloud. He specializes in monitoring tools (ELK, Zabbix, Prometheus) and automation with Python, Ansible, Terraform, and Docker. He's implemented Jenkins CI/CD pipelines to build, test and deploy large apps including microservices with zero downtime. 

Show More

Rodrigo Souto

Freelance Ruby Developer

BrazilToptal Member Since August 6, 2019

Rodrigo is an active software developer who's spent the past several years mainly building complex web apps. He’s an all-around developer who’s used front-end, back-end, and infrastructure technologies while working with teams of all sizes. Along with being passionate about coding and solving problems and strongly committed to communication and organization, Rodrigo values product quality, transparency, Agile development, and client satisfaction.

Show More

Arnaud Mesureur

Freelance Ruby Developer

AustraliaToptal Member Since August 22, 2019

Arnaud has a proven track record of building scalable and robust web applications solving challenging problems in continually evolving and fast-paced environments. His experience ranges from creating a high-end web performance analytics platform to being the head of engineering of a unique online business marketplace visited by tens of thousands of people each day and generating millions in revenue.

Show More

Piotr Pałka

Freelance Ruby Developer

PolandToptal Member Since September 11, 2017

Piotr is an experienced full-stack software engineer and IT consultant, who understands client perspectives, and turns ideas into high-quality products. He has expertise in both enterprise and startup environments. Having worked for corporations, banks, and software houses, Piotr has developed products used worldwide and helped startups and internal corporate systems using a variety of tech stacks.

Show More

Joao da Silva Fernandes

Freelance Ruby Developer

PortugalToptal Member Since July 16, 2019

João is a full-stack developer focused on Ruby on Rails, who also delves into the front end (with React lately) and infrastructure. He started his career mainly working in startup environments until 2017 when he joined Xing, Germany's most active professional network platform. João strives for long-term maintainability through code quality and a proper delivery process where testing, code reviews, and commit history are prioritized.

Show More

Max Marcon

Freelance Ruby Developer

GermanyToptal Member Since June 14, 2018

A motivated full-stack software engineer with a Ph.D. in computer science and years of experience in the design, development, and testing of innovative solutions, Max enjoys working on multiple aspects of a system while always keeping the big picture in mind. He never gets tired of applying his curiosity, scientific mindset, and passion for technology to new challenging projects.

Show More

Danilo Cabello

Freelance Ruby Developer

CanadaToptal Member Since August 6, 2013

For over the past decade, Danilo has been working as a software developer creating web apps and software that can scale. At his current employment, he’s successfully occupied several roles such as full-stack developer, team lead, manager, back-end developer, and he’s now working on a team that is responsible for the development tools. Danilo champions best practices and helps developers to be more productive by providing reusable software.

Show More

Leonardo de Freitas Amaral

Freelance Ruby Developer

BrazilToptal Member Since March 1, 2016

Leonardo has over a decade of software testing experience in Agile testing, load testing, continuous delivery, and mostly testing automation. He’s capable of working with a wide variety of software quality tools and environments. Leonardo is certified in Tester Foundation Level and Tester Advanced Level and also has a technical license in software analysis/development and graduate studies in business management specialization.

Show More

Sign up now to see more profiles.

Start Hiring

A Hiring Guide

Guide to Hiring a Great Ruby Developer

The Ruby language was designed from the beginning with a clear philosophy: the open-source language should make programmers happy. The result is a unique language full of quirky syntax and semantics that can sometimes seem a bit magical. While Ruby is often overshadowed by a certain web framework built on top of it, the language itself is versatile and well-suited to a wide variety of applications. The questions presented in this guide can be highly effective in evaluating the breadth and depth of a developer's knowledge of the Ruby language.

Read Hiring Guide

Ruby Hiring Resources

Trustpilot
Toptal in the press

... allows corporations to quickly assemble teams that have the right skills for specific projects.

Despite accelerating demand for coders, Toptal prides itself on almost Ivy League-level vetting.

Our clients
Building a cross-platform app to be used worldwide
Thierry Jakicevic
Building a cross-platform app to be used worldwide
1
2
3
Creating an app for the game
Conor Kenney
Creating an app for the game
1
2
3
Leading a digital transformation
Elmar Platzer
Leading a digital transformation
1
2
3
Testimonials

Tripcents wouldn't exist without Toptal. Toptal Projects enabled us to rapidly develop our foundation with a product manager, lead developer, and senior designer. In just over 60 days we went from concept to Alpha. The speed, knowledge, expertise, and flexibility is second to none. The Toptal team were as part of tripcents as any in-house team member of tripcents. They contributed and took ownership of the development just like everyone else. We will continue to use Toptal. As a start up, they are our secret weapon.

Brantley Pace, CEO & Co-Founder

Tripcents

In addition to sharp technical skills, Faruk had a great attitude and is a really exceptional communicator. He always let us know where he was with his work, thoroughly and reliably. That's not always the case, and it made working remotely considerably easier. He was an easy integration into the team.

Leif Abraham, Co-Founder

AND CO Ventures Inc

I am more than pleased with our experience with Toptal. The professional I got to work with was on the phone with me within a couple of hours. I knew after discussing my project with him that he was the candidate I wanted. I hired him immediately and he wasted no time in getting to my project, even going the extra mile by adding some great design elements that enhanced our overall look.

Paul Fenley, Director

K Dunn & Associates

The developers I was paired with were incredible -- smart, driven, and responsive. It used to be hard to find quality engineers and consultants. Now it isn't.

Ryan Rockefeller, CEO

Radeeus

Toptal understood our project needs immediately. We were matched with an exceptional freelancer from Argentina who, from Day 1, immersed himself in our industry, blended seamlessly with our team, understood our vision, and produced top-notch results. Toptal makes connecting with superior developers and programmers very easy.

Jason Kulik, Co-Founder

ProHatch

As a small company with limited resources we can't afford to make expensive mistakes. Toptal provided us with an experienced programmer who was able to hit the ground running and begin contributing immediately. It has been a great experience and one we'd repeat again in a heartbeat.

Stuart Pocknee , Principal

Site Specific Software Solutions

We used Toptal to hire a developer with extensive Amazon Web Services experience. We interviewed four candidates, one of which turned out to be a great fit for our requirements. The process was quick and effective.

Abner Guzmán Rivera, CTO and Chief Scientist

Photo Kharma

Sergio was an awesome developer to work with. Top notch, responsive, and got the work done efficiently.

Dennis Baldwin, Chief Technologist and Co-Founder

PriceBlink

Working with Marcin is a joy. He is competent, professional, flexible, and extremely quick to understand what is required and how to implement it.

André Fischer, CTO

POSTIFY

We needed a expert engineer who could start on our project immediately. Simanas exceeded our expectations with his work. Not having to interview and chase down an expert developer was an excellent time-saver and made everyone feel more comfortable with our choice to switch platforms to utilize a more robust language. Toptal made the process easy and convenient. Toptal is now the first place we look for expert-level help.

Derek Minor, Senior VP of Web Development

Networld Media Group

Toptal's developers and architects have been both very professional and easy to work with. The solution they produced was fairly priced and top quality, reducing our time to launch. Thanks again, Toptal.

Jeremy Wessels, CEO

Kognosi

We had a great experience with Toptal. They paired us with the perfect developer for our application and made the process very easy. It was also easy to extend beyond the initial time frame, and we were able to keep the same contractor throughout our project. We definitely recommend Toptal for finding high quality talent quickly and seamlessly.

Ryan Morrissey, CTO

Applied Business Technologies, LLC

I'm incredibly impressed with Toptal. Our developer communicates with me every day, and is a very powerful coder. He's a true professional and his work is just excellent. 5 stars for Toptal.

Pietro Casoar, CEO

Ronin Play Pty Ltd

Working with Toptal has been a great experience. Prior to using them, I had spent quite some time interviewing other freelancers and wasn't finding what I needed. After engaging with Toptal, they matched me up with the perfect developer in a matter of days. The developer I'm working with not only delivers quality code, but he also makes suggestions on things that I hadn't thought of. It's clear to me that Amaury knows what he is doing. Highly recommended!

George Cheng, CEO

Bulavard, Inc.

As a Toptal qualified front-end developer, I also run my own consulting practice. When clients come to me for help filling key roles on their team, Toptal is the only place I feel comfortable recommending. Toptal's entire candidate pool is the best of the best. Toptal is the best value for money I've found in nearly half a decade of professional online work.

Ethan Brooks, CTO

Langlotz Patent & Trademark Works, Inc.

In Higgle's early days, we needed the best-in-class developers, at affordable rates, in a timely fashion. Toptal delivered!

Lara Aldag, CEO

Higgle

Toptal makes finding a candidate extremely easy and gives you peace-of-mind that they have the skills to deliver. I would definitely recommend their services to anyone looking for highly-skilled developers.

Michael Gluckman, Data Manager

Mxit

Toptal’s ability to rapidly match our project with the best developers was just superb. The developers have become part of our team, and I’m amazed at the level of professional commitment each of them has demonstrated. For those looking to work remotely with the best engineers, look no further than Toptal.

Laurent Alis, Founder

Livepress

Toptal makes finding qualified engineers a breeze. We needed an experienced ASP.NET MVC architect to guide the development of our start-up app, and Toptal had three great candidates for us in less than a week. After making our selection, the engineer was online immediately and hit the ground running. It was so much faster and easier than having to discover and vet candidates ourselves.

Jeff Kelly, Co-Founder

Concerted Solutions

We needed some short-term work in Scala, and Toptal found us a great developer within 24 hours. This simply would not have been possible via any other platform.

Franco Arda, Co-Founder

WhatAdsWork.com

How to Hire Ruby Developers through Toptal

1

Talk to One of Our Industry Experts

A Toptal director of engineering will work with you to understand your goals, technical needs, and team dynamics.
2

Work With Hand-Selected Talent

Within days, we'll introduce you to the right Ruby developer for your project. Average time to match is under 24 hours.
3

The Right Fit, Guaranteed

Work with your new Ruby developer for a trial period (pay only if satisfied), ensuring they're the right fit before starting the engagement.

FAQs

  • How are Toptal Ruby developers different?

    At Toptal, we thoroughly screen our Ruby developers to ensure we only match you with talent of the highest caliber. Of the more than 100,000 people who apply to join the Toptal network each year, fewer than 3% make the cut. You'll work with engineering experts (never generalized recruiters or HR reps) to understand your goals, technical needs, and team dynamics. The end result: expert vetted talent from our network, custom matched to fit your business needs. Start now.

  • Can I hire Ruby developers in less than 48 hours through Toptal?

    Depending on availability and how fast you can progress, you could start working with a Ruby developer within 48 hours of signing up. Start now.

  • What is the no-risk trial period for Toptal Ruby developers?

    We make sure that each engagement between you and your Ruby developer begins with a trial period of up to two weeks. This means that you have time to confirm the engagement will be successful. If you're completely satisfied with the results, we'll bill you for the time and continue the engagement for as long as you'd like. If you're not completely satisfied, you won't be billed. From there, we can either part ways, or we can provide you with another expert who may be a better fit and with whom we will begin a second, no-risk trial. Start now.

1.5KShares
Ruby

How to Hire a Great Ruby Developer

The Challenge

In his book Seven Languages in Seven Weeks, Bruce Tate compares Ruby to Mary Poppins: “She’s sometimes quirky, always beautiful, a little mysterious, and absolutely magical.” If we really had to pick a character to compare with this language, we could hardly make a better choice. The comparison, of course, stems from the philosophy of Ruby’s design: the language should make programmers happy.

This ideology inspired David Heinemeier Hansson to pick Ruby as his language of choice when he wrote the first version of Basecamp and extracted the Ruby on Rails framework from the project. Since the language became popular due to the success of Ruby on Rails development, it’s common to hear people misusing “Ruby” and “Rails” synonymously, or to miscategorize Ruby as a “web language.”

As experienced Rubyists know, the language is not in any way limited to web development or web application development, and can be used for almost anything: writing native smartphone apps or other mobile app development, data-processing scripts, or as a tool to teach people to program.

Even though the language markets itself as being developer-friendly, it can provide a few surprises for programmers coming from the other languages, because it changes the semantics of many familiar concepts in unexpected ways.

Mastering Ruby can be hard for a few reasons, and the popularity of Rails is one of them. Namely, many developers learned just enough Ruby to start using Rails and stopped exploring the language after that. Due to Ruby’s magical typing scheme, it can be hard for these RoR developers to understand what is pure Ruby and what is an extension built into Rails. Given the many extensions Rails provides, the motivation to study the basics of the language is diminished.

Additional complexity lies in the multiple faces of Ruby. While it is primarily an object-oriented language, it also allows writing programs in the functional programming style, which can be leveraged for improved expressiveness and code readability. This means that Ruby provides many ways to do a single thing, and thus the “Class A” Ruby programmer needs to understand all of the different personalities of the language.

It doesn’t help that today we’ve got multiple implementations of Ruby, including MRI, JRuby, Rubinius, and mruby. Thus, to make good decisions and judgments, Ruby developers need to be, at the very least, aware of the different implementations, and their strengths and weaknesses.

The following guide gives you some questions for inspiration when preparing an interview for top-notch Ruby developers. You should not assess candidates based purely on their ability to “correctly” answer each question. That would be missing the point, because not every top candidate will actually know all the details about the language, nor does knowing all the details guarantee you the best developer. Instead, use the questions to see how the candidate thinks about the problem at hand, how they work through difficult problems, and how they weigh alternatives and make judgment calls. For example, if the candidate doesn’t know the answer, ask how they would get it, provided they had a computer with internet access and a Ruby interpreter installed. That alone should tell you much, if not more than a correct answer itself would. You should also check out Toptal’s general guide on hiring candidates, published on our blog: In Search of the Elite Few.

Note that this guide focuses on pure Ruby. If you’re looking to hire Ruby on Rails developers full-time or part-time, you should also take a look at our Ruby on Rails Hiring Guide.

Welcome to Ruby

Consider this section a warm-up for the discussion: it will help you see if the candidate ever wrote anything more than a simple Rails application.

Q: What are the differences between classes and modules? What are the similarities?

Modules in Ruby have two purposes: namespacing and mixins.

Consider an application that processes maps with points of interest. Two programmers working on this app could come up with two different classes and give them the same name. Let’s say the first one creates a class Point, which represents a point in a plane defined by x and y coordinates. The programmer then uses this class when rendering maps as images. The other programmer’s Point class models a point of interest on the map and has attributes specifying the latitude, the longitude, and the description.

When a Ruby interpreter finds multiple class definitions with the same name, it assumes that the programmer’s intent is to “reopen” the class defined before and add the new methods to the previous class. This could cause various types of issues down the road, so it’s best to put unrelated parts of an app in different namespaces in order to reduce a chance of naming collisions. To do this in Ruby we can put separate behavior in different modules, e.g. Rendering and Geo:

module Rendering
  Point = Struct.new(:x, :y)
end

module Geo
  Point = Struct.new(:lat, :long, :description)
end

Now the full names of the classes would change to Rendering::Point and Geo::Point so there would be no conflict.

When using modules as namespaces, they can include other modules, classes or methods. Later, one can include the module in other contexts (different modules or classes) to remove the need to fully spell-out their names.

Classes can contain modules, other classes, and methods, but one can’t include them in the same way they would include a module.

As mixins, modules often group methods that can be included into the other classes. However, we cannot create instances of modules. Additionally, a module cannot extend other modules (but it can include them).

Classes can combine methods, they can be instantiated, and they can be extended by other classes.

In Ruby, almost everything is an object, so classes and modules are objects as well. Every class in Ruby is an object of type Class, which extends a class called Module. Looking at this inheritance hierarchy, we can clearly see that modules provide just a subset of features provided by classes.

Q: How does Ruby look up a method to invoke?

Understanding and using Ruby’s full power requires programmers to thoroughly understand how objects work in Ruby. This is important not only for wielding a great power like metaprogramming, but also to understand concepts that might look weird at first, like definition of the class methods.

Given this snippet of code:

mike = Actor.new(first_name: 'Mike', last_name: 'Myers')
mike.full_name

Where would Ruby look for a definition of the method full_name?

The first place where Ruby would look up the method is in the object’s own metaclass or eigenclass. This is a class that backs every single object and that contains methods defined on that object directly, distinct from all other instances.

For example, the following definition adds a method full_name to the metaclass of object mike:

def mike.full_name
  "#{first_name} #{last_name}"
end

Since the metaclass is a class specific to this object, no other instances of class Actor would have this method.

If Ruby can’t find a method in the object’s metaclass, it would start looking for the method in the ancestors of the object’s class (i.e. the ancestors of Actor).

So, what are the ancestors of a class? We can ask Ruby to tell us!

Actor.ancestors
# => [Actor, Object, Kernel, BasicObject]

We can see that the list of ancestors of any class in Ruby begins with the class itself, includes all ancestor classes (Object, ‘BasicObject’), but also modules that are included in any of the classes in the inheritance hierarchy (e.g. Kernel). This is something that a Ruby programmer should understand.

Since Ruby 2.0, it’s up to the programmer to decide where to place the modules in the ancestors list.

Let’s take a look at two examples. We’ll assume this is Ruby version 2.0 or above:

Example 1: Including the FullName module into the class Actor.

class Actor < Person
  include FullName
end

If we now look up the ancestors list of the class Actor, we’ll see that the module FullName appears between the Actor and the Person classes:

Actor.ancestors
# => [Actor, FullName, Person, Object, Kernel, BasicObject]

Example 2: Prepending the FullName module to the class Actor.

class Actor < Person
  prepend FullName
end

By prepending FullName, we’ve told Ruby to put the module before the class itself in the ancestors list:

Actor.ancestors
# => [FullName, Actor, Person, Object, Kernel, BasicObject]

If Ruby searches the entire ancestor list and can’t find the method by the given name, Ruby will internally send another message (method call) to the object: method_missing?. The system will repeat the lookup for this method, and will find it at least in the Object class (sooner, if the programmer has defined it in an earlier ancestor) and execute it.

Q: Does Ruby support multiple inheritance? Is it needed at all?

Like other modern languages, Ruby supports only single inheritance, and that’s quoted as a feature by Yukihiro Matsumoto, the language’s creator.

There’s no need for Ruby to support multiple inheritance, since everything that could be done with it can be achieved with duck-typing and modules (i.e., mixins).

Since Ruby is a dynamically-typed language, there’s no need for objects to be of a specific type in order for a program to run. Instead, Ruby leverages duck-typing: if an object quacks like duck, and walks like a duck, it’s a duck. That is, we can ask any object if it knows how to respond to a message. Or we can simply send a message and trust the object to figure out how to handle it.

Another use of multiple inheritance is to share code among different classes which cannot be modeled as a chain. To achieve that, we can write the code we want to share as a method in a module and include it into other classes as needed. The concept that allows grouping of methods to be included in multiple classes is the definition of a mixin.

We should note that including a module into a class adds it to the class’ ancestors list:

class Actor < Person
  include FullName
end

Actor.ancestors
# => [Actor, FullName, Person, Object, Kernel, BasicObject]

Which means that once the module has been mixed in, there’s no difference between the methods that come from a superclass, and those coming from a mixin.

Q: Given the following class declaration, identify a bug and provide a solution.

class Worker
  attr_reader :data

  def initialize(d)
    data = d
  end

  def call
    # do a process that requires @data
  end

  private

  def data=(d)
    @data = clean(d)
  end

  def clean(d)
    # return sanitized data
  end
end

The problem is in the initialize method, which tries to assign the argument d using the private attribute writer data=. However, the setter won’t get invoked, because Ruby will treat data = d as a local variable initialization. When Ruby encounters an identifier beginning with a lowercase character or an underscore on a left-hand side of an assignment operator, Ruby will create and initialize a local variable. Please note that this behavior is inconsistent with the way Ruby handles the same identifiers in other contexts: if the identifier does not reference a defined local variable, Ruby will try to call a method with the given name.

To make it clear that we want to call the writer method (i.e. the method ending with the = character), we need to prepend the name with self:

def initialize(d)
  self.data = d
end

This may seem counter-intuitive, given the rule that private methods cannot be called with explicit receiver (even if it’s self). However, the rule comes with an exception: the private writer methods (i.e., methods ending with =) can be invoked with self.

Another way to fix the bug would be to directly assign the value to the instance variable @data in the initializer:

def initialize(d)
  @data = clean(d)
end

Seasoned Rubyist, or a seasoned programmer learning Ruby?

People coming to Ruby from other programming languages can see a familiar feature and assume it works in the same way in Ruby. Unfortunately, that’s not always the case, and there are a few concepts in Ruby that can be surprising to developers who have experience with some other language. The following questions aim to separate experienced Rubyists from people who blindly follow familiar concepts and apply them to Ruby.

Q: Explain the difference between throw/catch and raise/rescue in Ruby.

Like most modern Object-Oriented Programming (OOP) languages, Ruby has a mechanism for signaling exceptional situations and handling those exceptions in other blocks of code. To signal an error, a Ruby programmer would use the raise method, which aborts the execution of the current block of code and unwinds the call stack looking for a rescue statement that can handle the raised exception.

In that sense, the raise method performs the same job as the throw statement does in C++ and languages inspired by it, whereas rescue corresponds to the catch block. These operations should be used for signaling the exceptional state and handling it, they should not be used for normal flow control.

However, Ruby also has throw and catch methods, which can put newcomers off balance, because they are used for flow control, and should not be used for error handling.

You can think of throw as a GOTO statement and catch as a method that sets up a label. However, in contrast to traditional GOTO, which can jump to any other point in the program, throw can only be used inside a catch block, as we’ll demonstrate momentarily.

It’s important to understand the syntactical differences: rescue is a keyword, not a method we could override (raise is a method, though). We can combine multiple rescue statements one after another to handle different types of errors, and we always put the rescue keyword after a block that will, potentially, raise an exception.

For instance:

begin
  response = Net::HTTP.get(uri)
rescue Timeout::Error
  puts "A timeout occurred, please try later."
rescue Net::HTTPBadResponse
  puts "Server returned a malformed response."
rescue
  puts "An unknown error occurred."
end

On the other hand, catch is a method that accepts two arguments: a symbol that can be caught (a “label”), and a block inside which we can use throw. The throw method can also accept two arguments: the first is mandatory, and should be a symbol that matches the catch label to which the program should jump; the second is optional and will become the return value of the catch statement. The throw/catch pair can be useful when we need to break out of a nested loop, like in this example of looking for a free product:

free_product = catch(:found) do
  shops.each do |shop|
    shop.products.each do |product|
      if product.free?
        throw :found, product
      end
    end
  end
end

if free_product
  puts "Found a free product: #{free_product}!"
end

Q: Is there a difference between the Boolean &&/|| operators and the written versions and/or?

There is definitely a big difference between these two. Even though && and and (or or and ||) are semantically equal (both being short-circuit Boolean operators), they have different operator precedence.

The English operators and and or have very small precedence. Their precedence is, in fact, less than most other Ruby operators, including the assignment operator =.

Considering that these operators are the Boolean operators in Python, seasoned Pythonistas can misuse them in Ruby, which could produce unexpected logical errors. Consider this idiom that’s very often used to access an attribute of an object that may not be initialized:

actors = [ Actor.new(first_name: 'Mike', last_name: 'Myers') ]
name = actors[0] && actors[0].name
# => "Mike"
puts name
# => Mike

If the actors array is empty, or contains nil as a first element, the Boolean statement will immediately resolve to this value, and the name variable will be nil. If the first element of the array is truthy, however, we will then send the message name to it, and assign the result to the variable name.

However, if we used the English version of the operator, we’d get a different result:

actors = [ Actor.new(first_name: 'Mike', last_name: 'Myers') ]
name = actors[0] and actors[0].name
# => "Mike"
puts name
# => #<Actor:0x007fa07a1b4a00>

We can see that the result of the expression in the second line and the value assigned to the variable in the end are different. Because = binds tighter than and, the operands were grouped around the assignment first, and only then around the and operator. We can illustrate this with parentheses:

(name = actors[0]) and actors[0].name

The reason to actually have both operators in the language is to allow the use of and and or as control flow commands in Perl style.

Q: Given below is a snippet of code that calculates a total discounted value among all products that have the discount of 30% or more in all shops. Rewrite this snippet using basic functional operations (map/filter/reduce/flatten/compact) if we assume shops is an array of Shop objects loaded in the memory, where each shop references a the list of products in the memory. Which version would you keep in the production-ready code?

total_discount = 0

shops.each do |shop|
  shop.products.each do |product|
    if product.discount >= 0.3
      total_discount += product.discount * product.price
    end
  end
end

Since we want to transform a list into a single value, this seems to be a job for the reduce method (or inject, an alias of reduce):

total_the discount = shops.reduce(0) { |total, shop|
  total + shop.products.reduce(0) { |subtotal, product|
    if product.discount >= 0.3
      subtotal + product.discount * product.price
    else
      subtotal
    end
  }
}

The whole calculation is now a single expression and there’s no need to initialize the accumulator variable in a separate line.

However, this approach might be somewhat harder to read, as we’ve got nested reduce operations. To simplify the code, we could first create a list of all products and then reduce it by chaining operations:

total_discount = shops.
  flat_map { |s| s.products }.
  reduce(0) { |total, product|
    if product.discount >= 0.3
      total + product.discount * product.price
    else
      total
    end
  }

It’s possible to further improve readability if we are willing to sacrifice some performance by splitting different kinds of operations into separate blocks:

total_discount = shops.
  flat_map { |s| s.products }.
  select { |p| p.discount >= 0.3 }.
  map { |p| p.discount * p.price }.
  reduce(0) { |sum, d| sum + d }

It is important to repeat that we’ve hindered the performance with this change: the new code will iterate multiple times through the same list of products. If this code is not in a performance-critical path, this decrease could be justified by the improvements in readability.

Algorithmically speaking, both operations are linear in time, so for a very large number of products, the style of the code may not be of crucial importance.

On the other hand, we’ve gained code that declaratively lists steps in the processing of the initial list, similarly to an SQL query.

A Ruby guru should know how to reason about the change, measure the performance of both solutions and make a decision relevant to the context. Your goal here should not be to wait for the “correct” answer, but instead to listen to the programmer to see how she or he judges the various options.

Q: What’s the difference between public, protected and private visibility in Ruby?

Method visibility specifiers is an area that often trips up newcomers to Ruby who are familiar with almost any other OOP language.

Public visibility is the default in Ruby, and it behaves just like it does in any other language. Public methods can be invoked by any object that can access the method’s object.

However, private visibility is different. In Ruby, private methods can be directly called only if we don’t explicitly specify the message receiver.

Consider this implementation of finding the n-th Fibonacci number using recursion with memoization:

class MemoizedFibonacci
  def initialize
    @memo = {}
  end

  def get(n)
    @memo[n] ||= calculate(n)
  end

  private

  def calculate(n)
    return 1 if n <= 1
    get(n-1) + get(n-2)
  end
end

We have a public method get and a private method calculate. The public method first checks if the result was previously calculated by looking it up in the @memo hash. If it wasn’t, it will call the calculate method and store the result in the hash.

Let’s try to make a slight modification to the get method:

def get(n)
  @memo[n] ||= self.calculate(n)
end

Newcomers know that self in Ruby is equivalent to this in languages like C++, C# and Java, so they are led to believe this change would have no effect. However, we’ve now added the explicit receiver, self, to the message calculate. Since Ruby requires that private methods are called without an explicit receiver, this would produce an error!

Another unexpected side effect of this rule is that private methods can be called from subclasses. In fact, many things we consider “keywords” in Ruby, are nothing but private methods in the module Kernel, which is included by the Object class and which are therefore inherited by every Ruby object. For example, when we raise an exception using raise, we’re actually calling a private method of a superclass!

Ruby is an open-minded language, so it doesn’t even let developers lock down their privates, so to speak.

This leaves the protected visibility. It behaves like public, but with an exception: protected methods can be called only from the methods of the same class or any of its subclasses. You will notice that in this sense, protected behaves in a similar manner to the private visibility of other languages. This means that protected method visibility should be used in classes which want to hide their state, but allow calling the method from other methods in the class that need it to produce copies or implement comparisons.

The Big Picture

Q: Name at least three different implementations of Ruby. Discuss the differences among them.

The standard Ruby implementation is called MRI (short for Matz’s Ruby Interpreter) or CRuby. As the names suggests, it was written by Yukihiro “Matz” Matsumoto in C. Being the primary implementation of Ruby by its author, it can’t be a surprise that it grows together with the language. Even though there is an ISO specification of the language (ISO/IEC 30170:2012), the spec was already obsolete with the release of Ruby 2.0. Thus, all new language features will first appear in MRI, and then they may get implemented in other interpreters. Being written in C, MRI can interoperate with other C code, and run gems written in C as well.

One of the most commonly cited problems with this implementation is the lack of support for true parallelization, since MRI Ruby supports only green threads and depends on a Global Interpreter Lock (GIL).

Rubinius is another implementation, based on LLVM, and written in C++ and Ruby. It improves concurrency by using native threads and a just-in-time (JIT) compiler. Additionally, the core library is mostly written in Ruby itself, making it easier to understand the internals, especially to folks not very comfortable with C.

The coupling between MRI and the language makes it hard to keep other implementations of Ruby up to date, which means they may lack features found in the recent versions of MRI. One of the first things the Rubinius team made was RubySpec: a runnable specification of the Ruby language, which would allow developers of the language’s forks to check their implementation against the “standard”. Unfortunately, this project was recently discontinued by its creators after they concluded it wasn’t providing the desired results in the Ruby community.

Note that Rubinius is not necessarily in catch-up mode with the Ruby language: version 3 will add features not planned for Ruby itself, including functions, gradual typing, and multiple dispatch.

Those who need to interface Java code with Ruby may find JRuby helpful. Like Rubinius, it offers improved concurrency options by relying on native threads, and JIT compilation of bytecode into machine code. As a bonus, it provides interoperability with existing Java Virtual Machine (JVM) code (you can use native Java classes and libraries) and the possibility to run Ruby on any JVM.

Since JRuby relies upon JVM, it means it can’t use Ruby gems written in pure C. While this implementation improves runtime speeds as a rule, it introduces slow starting time of applications.

Another noteworthy implementation is mruby: an embeddable subset of the Ruby ISO standard. Matz himself is leading its development, with the goal of enabling Ruby to run as an embedded language inside existing apps and games, providing scriptability and automation, and thus challenging Lua.

You can read much more about other Ruby implementations in our blog post about Ruby interpreters and runtimes.

Wrap-Up

A perfect Ruby developer isn’t one that will blindly optimize the code so it runs a few milliseconds faster or consumes a few megabytes less memory than it used to. The perfect candidate, in the United States or abroad, will know how to achieve that when performance is of essence, but will be able to recommend alternatives if it would sacrifice too much readability and maintainability. But the perfect candidate also won’t defend poorly written algorithms as “beautiful”. In Ruby, one could say, perfection is in compromises and good judgment calls. Remember that when talking to your candidates, and listen to their reasoning.

Happy interviewing!

Featured Toptal Ruby Publications

Top Ruby Developers are in High Demand.

Start Hiring