Last updated on July 28th, 2024 at 05:50 pm
The purpose of the following information is to help you learn the Ruby programming language. This material assumes that you already have familiarity with common computer programming concepts.
Interesting aspects of the Ruby language are:
- Ruby is a Metaprogramming language. Basically, this means that Ruby allows you to define classes and methods during runtime, which essentially means that “programs can write programs”. While metaprogramming can be a very intimidating topic, metaprogramming knowledge isn’t required to be able to program in Ruby.
- Ruby is an interpreted language, so we have the advantage of being able to experiment with language features directly at a interpreter prompt. As well, any changes we make in our programs are immediately available because they don’t have to be compiled. This also means that execution in Ruby is going to be slower than most compiled languages in most cases. In Ruby 3.0, which was released on Christmas day, 2020, the Ruby development team has worked hard to bring significant performance improvements, and continues to focus on performance improvements in just about every major release.
- Everything in Ruby is an object. Unlike many other languages, Ruby has no native Integer, Character or String types -> these are simply extendable classes like everything else.
- Ruby is NOT a strongly typed language, which means that objects cannot be assigned specific types, such as String or Integer unless you specify the class when creating the object (
varname = String("my string")
, for example). Once you assign a value to an object, the variable’s type can be changed at any time with a re-assignment. Normally, this is not a problem because the Ruby interpreter automatically recognizes most of the common object types (String, Integer, etc.), but it can lead to situations where a programmer accidentally changes the type of an object and then tries to execute an invalid method for the object. That said, there are many advantages to strongly typed languages and tools are available to assist in this direction. Have a look at TypeProf-IDE. - Ruby creates objects as they are used. This is sometimes annoying, as a typo on a variable name may sometimes lead to difficult to locate bugs. Fortunately, most development environments will recognize and flag situations where you try to use a variable that has not been assigned a value and some even let you know when a variable is never used after it is assigned a value.
- Ruby can return multiple objects from a method call. For example, I can have a Ruby statement such as “a, b, c = myMethod”.
- Ruby likes to have “positive” statements and will very often rewrite a negation into a separate method. The most obvious example is
if
andunless
, where theunless
statement is simply the negation of theif
statement, with the difference that whileelse
can be applied tounless
,elseif
cannot. Another example would be theselect
method, which is used to select elements of the Array or Hash that match a given criteria. It is relatively intuitive that if I want to select everything but elements that meet my criteria, I can simply negate the logic used in theselect
method. In Ruby, however, I can also use thereject
method, which is simply the negation of theselect
method. Is thereject
method absolutely necessary? Not at all. Negating theselect
method criteria would achieve the same result. In Ruby it is not uncommon to find classes that have over a hundred methods, some of which are simple derivations of other methods. - Ruby has a lot of redundant methods. What I mean by this is that you can often find a method by the same name with the same functionality under more than one class. For example, if I want to know if a file exits, I can use File.exist?, IO.exist? or Pathname.exist? They all do the same thing.
- Ruby can chain methods. It is very common to see statements like “
recipient = sender.method1(params1).method2.method3(params2)
“. The object returned by each method is then used as the object to which the next method is applied to. This is similar to what you find in Java and Python. - Ruby cannot overload methods. If you want to have a variation on a method that has different parameters, you will have to use a different name. This can be partially addressed by using default values for parameters or simply passing a hash of values instead of parameters.
- Like almost all other programming languages, numerous libraries have been created to expand Ruby’s functionality. These libraries are called Gems and are managed at the Ruby Gems web site. As of the writing of this page, over 11,000 gems were available. Be aware, however, that Ruby’s Gems suffer the same problems as all 3rd party open source libraries – dependency hell. While a gem may be great, there is no assurance that it or the gems it depends on will be maintained or that you won’t find yourself in a situation where gems have conflicting version requirements.
- Everything in Ruby runs under the MIT license. Essentially, that means you are free to do pretty much whatever you want with the programs you develop, without having any obligation to open source your work. Be careful, however, not to include gems or other sources that use GPL or other license schemes, as you may eventually be politely notified of a license violation and have to remove / replace the dependency.
- Ruby doesn’t come preinstalled on some operating systems. Fortunately, installation on most operating systems is well documented at https://www.ruby-lang.org/en/documentation/installation/
A few examples of what makes Ruby unique
When I first starting using Ruby, it was pretty much just another language to learn so that I can do my job as a programmer. Over time, however, I have grown to appreciate some things about Ruby that I feel make it unique and valuable. Following are a few simple examples:
Let’s say that I want to check a variable to see if it is nil and return it if it is not, and return a different value if the variable is nil. The following code would be common in just about any programming language.
def example
if myvar == nil
result = anothervar
else
result = myvar
end
return result
end
We start with the idea that the last statement of a block is implicitly considered the block’s return value. Thus, we can shorten this to:
def example
if myvar == nil
result = anothervar
else
result = myvar
end
result
end
The reader now yawns and asks if I actually have anything interesting to say… The next optimization comes from the fact that the contents of the if statements are considered return values for the if statement. Thus, we can shorten things a little bit more to…
def example
if myvar == nil
anothervar
else
myvar
end
end
Now, just about anybody who knows anything about programming will say that this screams for a ternary statement….
def example
myvar == nil ? anothervar : myvar
end
Ruby also has what is known as helper methods, one of which is to check to see if a variable is nil. Using this, we then have…
def example
myvar.nil? ? anothervar : myvar
end
Now, because Ruby uses “truthy” and “falsey” instead of true and false, you can simply OR the comparison and reduce the statement to….
def example
myvar || anothervar
end
This would say, “If myvar is truthy (i.e., not nil), return it, otherwise return anothervar”.
Another example would be how method chaining, helper methods and scopes can be extremely useful to quickly extract information. Have a look at the following statement
email_monthly_update(Company.active.map{|c| c.employees.subscribed_to_updates.pluck(:first_name, :last_name, :email}))
This example would first search the Companies table using the scope active
to find all active companies. Then for each company selected it would use the scope subscribed_to_updates
to find all the employees who have opted into monthly updates and the helper method pluck
to pull out all of their first names, last names and email addresses, and then finally use the method email_monthly_update
to send each person an email. Clearly, this example is a bit contrived (you would have to define the scopes and the final method) and can be accomplished in any programming language, but I would be challenged to find too many languages where this could be done so concisely and still be easily understandable.
Ruby Gripes
No programming language is perfect and Ruby is no exception. While I remain a big fan of Ruby, the power of the language brings some challenges with them.
- Ruby is getting BIG and CONFUSING – I was going through the release information for Ruby 3.1 and it was inescapable, for me at least, that while Ruby is a language that can be easily learned, it is not easily mastered. While a novice programmer can quickly come up to speed on Ruby, there are numerous pieces of magic that an advanced Ruby programmer can implement which make the code more difficult to maintain. For example, what does it mean when you pass the parameter value & or _1? How does the operator * differ from **? What is the difference between .. and …? What are Ractors and Fibers? While any programming language requires an investment to become proficient, the power of Ruby means that it will require a little more than other, less flexible, languages.
- More difficult to maintain – Any programming language can be badly implemented and result in code that is a nightmare to maintain. However, I maintain (no pun intended…) that the more “clever constructs” that you have, that would generally be used by someone who is more proficient with the language, the more confusion there will be on the part of the novice programmer and code maintenance is usually the first job any programmer gets. Ruby has many “clever constructs”.
- No Code Hiding – For many reasons, there is no way to “uglify” your Ruby code. Why would you want to uglify code? To provide a basic amount of protection to your intellectual property. If everything is delivered as an easily readable program, then anybody can copy your source code and use it as they please. Yes, uglified, and even compiled code, can be read, but uglification provides a basic level of protection against code theft. Obviously, this is not unique to Ruby and for most folks will probably fall into the “who cares” category.
- Confusing Gem Names – While most of us would probably intuit what a gem by the name of minitest would do, I challenge any newcomer to intuit what nokogiri, sprocket, kaminari, thor, farady, loofah, opal or even excon would do. While Ruby has no control over the naming of gems, and some would say that the colorful naming represents a spirit of freedom and creativity, it also adds another hurdle to using all the power of Ruby.
Have I screwed something up or missed something you consider important? I would love to hear from you if you feel anything in this site is incomplete, incorrect or misleading. Please drop a line using our contact form.