Ruby is a multi-platform open-source, dynamic object-oriented interpreted language, designed to be simplistic and productive. It was created by Yukihiro Matsumoto (Matz) in 1995.
According to its creator, Ruby was influenced by Perl, Smalltalk, Eiffel, Ada, and Lisp. It supports multiple programming paradigms, including functional, object-oriented, and imperative. It also has a dynamic type system and automatic memory management.
Class names in Ruby are Constants, so the first letter should be a capital.
class Cat # correct
end
class dog # wrong, throws an error
end
Hashes in Ruby map keys to values using a hash table.
Any hashable object can be used as keys. However, it's very common to use a Symbol
as it is generally more efficient in several Ruby versions, due to the reduced object allocation.
{ key1: "foo", key2: "baz" }
Be careful about operator precedence when you have a line with multiple methods chained, like:
str = "abcdefg"
puts str.gsub(/./) do |match|
rand(2).zero? ? match.upcase : match.downcase
end
Instead of printing something like abCDeFg
, like you'd expect, it prints something like #<Enumerator:0x00000000af42b28>
-- this is because do ... end
has lower precedence than methods, which means that gsub
only sees the /./
argument, and not the block argument. It returns an enumerator. The block ends up passed to puts
, which ignores it and just displays the result of gsub(/./)
.
To fix this, either wrap the gsub
call in parentheses or use { ... }
instead.
1. A Ruby symbol is an object with O(1) comparison
To compare two strings, we potentially need to look at every character. For two strings of length N, this will require N+1 comparisons
def string_compare str1, str2
if str1.length != str2.length
return false
end
for i in 0...str1.length
return false if str1[i] != str2[i]
end
return true
end
string_compare "foobar", "foobar"
But since every appearance of :foobar refers to the same object, we can compare symbols by looking at object IDs. We can do this with a single comparison.(O(1))
def symbol_compare sym1, sym2
sym1.object_id == sym2.object_id
end
symbol_compare :foobar, :foobar
2. A Ruby symbol is a label in a free-form enumeration
In C++, we can use “enumerations” to represent families of related constants:
enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status = CLOSED;
But because Ruby is a dynamic language, we don’t worry about declaring a BugStatus type, or keeping track of the legal values. Instead, we represent the enumeration values as symbols:
original_status = :open
current_status = :closed
3. A Ruby symbol is a constant, unique name
In Ruby, we can change the contents of a string:
"foobar"[0] = ?b # "boo"
But we can’t change the contents of a symbol:
:foobar[0] = ?b # Raises an error
4. A Ruby symbol is the keyword for a keyword argument
When passing keyword arguments to a Ruby function, we specify the keywords using symbols:
# Build a URL for 'bug' using Rails.
url_for :controller => 'bug',
:action => 'show',
:id => bug.id
5. A Ruby symbol is an excellent choice for a hash key
Typically, we’ll use symbols to represent the keys of a hash table:
options = {}
options[:auto_save] = true
options[:show_comments] = false
An exception is an object that represents the occurrence of an exceptional condition. In other words, it indicates that something went wrong.
In Ruby, exceptions are often referred to as errors. That's because the base Exception
class exists as a top-level exception object element, but user-defined execution exceptions are generally StandardError
or descendants.
A method is a named block of code, associated with one or more objects and generally identified by a list of parameters in addition to the name.
def hello(name)
"Hello, #{name}"
end
A method invocation specifies the method name, the object on which it is to be invoked (sometimes called the receiver), and zero or more argument values that are assigned to the named method parameters. The value of the last expression evaluated in the method becomes the value of the method invocation expression.
hello("World")
# => "Hello, World"
When the receiver is not explicit, it is self
.
self
# => main
self.hello("World")
# => "Hello, World"
As explained in the Ruby Programming Language book, many languages distinguish between functions, which have no associated object, and methods, which are invoked on a receiver object. Because Ruby is a purely object-oriented language, all methods are true methods and are associated with at least one object.
Type | Method Signature | Call Example | Assignments |
---|---|---|---|
Required | def fn(a,b,c) | fn(2,3,5) | a=2, b=3, c=5 |
Variadic | def fn(*rest) | fn(2,3,5) | rest=[2, 3, 5] |
Default | def fn(a=0,b=1) | fn(2,3) | a=2, b=3 |
Keyword | def fn(a:0,b:1) | fn(a:2,b:3) | a=2, b=3 |
These argument types can be combined in virtually any way you can imagine to create variadic functions. The minimum number of arguments to the function will equal the amount of required arguments in the signature. Extra arguments will be assigned to default parameters first, then to the *rest
parameter.
Type | Method Signature | Call Example | Assignments |
---|---|---|---|
R,D,V,R | def fn(a,b=1,*mid,z) | fn(2,97) | a=2, b=1, mid=[], z=97 |
fn(2,3,97) | a=2, b=3, mid=[], z=97 | ||
fn(2,3,5,97) | a=2, b=3, mid=[5], z=97 | ||
fn(2,3,5,7,97) | a=2, b=3, mid=[5,7], z=97 | ||
R,K,K | def fn(a,g:6,h:7) | fn(2) | a=2, g=6, h=7 |
fn(2,h:19) | a=2, g=6, h=19 | ||
fn(2,g:17,h:19) | a=2, g=17, h=19 | ||
VK | def fn(**ks) | fn(a:2,g:17,h:19) | ks={a:2, g:17, h:19} |
fn(four:4,five:5) | ks={four:4, five:5} |
Always call super, at the bottom of this function. This saves silent failure when something is called and you don't get an error.
For example, this method_missing is going to cause problems:
class Animal
def method_missing(method, *args, &block)
say, speak = method.to_s.split("_")
if say == "say"
speak
end
end
end
=> Animal.new.foobar
=> nil # This should really be raising an error
method_missing
is a good tool to use when appropriate, but has two costs you should consider. First, method_missing
is less efficient -- ruby must search the class and all of its ancestors before it can fall back on this approach; this performance penalty may be trivial in a simple case, but can add up. Second and more broadly, this is a form of meta-programming that has great power that comes with responsibility to ensure that the implementation is secure, properly handles malicious inputs, unexpected inputs, and so on.
You should also override respond_to_missing?
like so:
class Animal
def respond_to_missing?(method, include_private = false)
method.to_s.start_with?("say_") || super
end
end
=> Animal.new.respond_to?(:say_moo) # => true
Ruby includes several built-in classes to represent numbers:
Numeric
Integer
Fixnum # 1
Bignum # 10000000000000000000
Float # 1.0
Complex # (1+0i)
Rational # Rational(2, 3) == 2/3
BigDecimal # not loaded by default
The most common are:
Fixnum
to represent, for instance positive and negative integersFloat
to represent floating point numbersBigDecimal
is the only one not loaded by default. You can load it with:
require "bigdecimal"
Note that in ruby 2.4+, Fixnum
and Bignum
are unified; all integers are now just members of the Integer
class. For backwards compatibility, Fixnum == Bignum == Integer
.
x <=> y
should return a negative number if x < y
, zero if x == y
and a positive number if x > y
.
Most operators are actually just methods, so x + y
is calling the +
method of x
with argument y
, which would be written x.+(y)
. If you write a method of your own having semantic meaning of a given operator, you can implement your variant in the class.
As a silly example:
# A class that lets you operate on numbers by name.
class NamedInteger
name_to_value = { 'one' => 1, 'two' => 2, ... }
# define the plus method
def + (left_addend, right_addend)
name_to_value(left_addend) + name_to_value(right_addend)
end
...
end
&&
vs. and
, ||
vs. or
Note that there are two ways to express booleans, either &&
or and
, and ||
or or
-- they are often interchangeable, but not always. We'll refer to these as "character" and "word" variants.
The character variants have higher precedence so reduce the need for parentheses in more complex statements helps avoid unexpected errors.
The word variants were originally intended as control flow operators rather than boolean operators. That is, they were designed to be used in chained method statements:
raise 'an error' and return
While they can be used as boolean operators, their lower precedence makes them unpredictable.
Secondly, many rubyists prefer the character variant when creating a boolean expression (one that evaluates to true
or false
) such as x.nil? || x.empty?
. On the other hand, the word variants are preferred in cases where a series of methods are being evaluated, and one may fail. For example a common idiom using the word variant for methods that return nil
on failure might look like:
def deliver_email
# If the first fails, try the backup, and if that works, all good
deliver_by_primary or deliver_by_backup and return
# error handling code
end
Module names in Ruby are constants, so they have to start with a capital letter.
module foo; end # Syntax error: class/module name must be CONSTANT
Constants are useful in Ruby when you have values that you do not want to be mistakenly changed in a program, such as API keys.
Class variables are shared in the class hierarchy. This can result in surprising behavior.
class A
@@variable = :x
def self.variable
@@variable
end
end
class B < A
@@variable = :y
end
A.variable # :y
Classes are objects, so instance variables can be used to provide state that is specific to each class.
class A
@variable = :x
def self.variable
@variable
end
end
class B < A
@variable = :y
end
A.variable # :x
Let get user profile path in a dynamic way for scripting under windows
Singleton classes only have one instance: their corresponding object. This can be verified by querying Ruby's ObjectSpace
:
instances = ObjectSpace.each_object object.singleton_class
instances.count # => 1
instances.include? object # => true
Using <
, they can also be verified to be subclasses of the object's actual class:
object.singleton_class < object.class # => true
References:
Keyword arguments were introduced in Ruby 2.0, and improved in Ruby 2.1 with the addition of required keyword arguments.
A simple method with a keyword argument looks like the following one:
def say(message: "Hello World")
puts message
end
say
# => "Hello World"
say message: "Today is Monday"
# => "Today is Monday"
As a reminder, the same method without keyword argument would have been:
def say(message = "Hello World")
puts message
end
say
# => "Hello World"
say "Today is Monday"
# => "Today is Monday"
You can simulate keyword argument in previous Ruby versions using a Hash parameter. This is still a very common practice, especially in libraries that provides compatibility with pre-2.0 Ruby versions:
def say(options = {})
message = options.fetch(:message, "Hello World")
puts
end
say
# => "Hello World"
say message: "Today is Monday"
# => "Today is Monday"
Before using DateTime you need to require 'date'
As a rule of thumb, avoid using double-negations in code. Rubocop says that double negations are unnecessarily complex and can often be replaced with something more readable.
Instead of writing
def user_exists?
!!user
end
use
def user_exists?
!user.nil?
end
Monkey patching is often used to change the behavior of existing ruby code, from gems for instance.
For instance, see this gist.
It can also be used to extend existing ruby classes like Rails does with ActiveSupport, here is an example of that.
Refinements are scope lexically, meaning they're in effect from the time they're activated (with the using
keyword) until control shifts. Usually control is changed by the end of a module, class, or file.
Monkey patching, while convenient, has some pitfalls that aren't immediately obvious. Most notably, a patch like that in the example pollutes the global scope. If two modules both add Hash#symbolize
, only the last module required actually applies its change; the rest are erased.
Furthermore, if there's an error in a patched method, the stacktrace simply points to the patched class. This implies that there's a bug in the Hash
class itself (which there is now).
Lastly, because Ruby is very flexible with what containers to hold, a method that seems very straightforward when you write it has lots of undefined functionality. For instance, creating Array#sum
is good for an array of numbers, but breaks when given an array of a custom class.
A safer alternative is refinements, available in Ruby >= 2.0.
Conventions:
.erb
: e.g. .js.erb
, .html.erb
, .css.erb
, etc.Alias of Random::DEFAULT.rand. This uses a pseudo-random number generator which approximates true randomness
Exec:
Exec is very limited in functionality and when executed will exit the Ruby program and run the command.
The System Command:
The System command runs in a sub-shell instead of replacing the current process and returns true or nill. The system command is, like backticks, a blocking operation where the main application waits until the result of the system operation completes. Here the main operation never needs to worry about capturing an exception raised from the child process.
The output of system function will always be true or nil depending on whether or not the script has been executed without error. Therefore, every error while executing the script will not be passed to our application. The main operation never needs to worry about capturing an exception raised from the child process. In this case the output is nil because the child process raised an exception.
This is a blocking operation where the Ruby program will wait until the operation of the command completes before going on.
The system operation use fork to fork the current process and then execute the given operation using exec.
The backticks (`):
The backtick character is usualy located under the escape key on the keyboard.
Backticks runs in a sub-shell instead of replacing the current process and returns the result of the command.
Here we can get the output of the command but the program will crash when an exception is generated.
If there is an exception in the sub-process then that exception is given to the main process and the main process might terminate if exception is not handled.
This is a blocking operation where the Ruby program will wait until the operation of the command completes before going on.
The system operation use fork to fork the current process and then execute the given operation using exec.
IO.popen:
IO.popen runs in a sub-process. Here the sub-process standard input and standard output are connected to the IO object.
Popen3:
Popen3 allows you to access the standard input, standard output and standard error.
The subprocess's standard input and output will be returned into IO objects.
$? (same as $CHILD_STATUS)
Can be used with the backticks, system() or %x{} operations and will give the status of the last system executed command.
This might be usefull to access the exitstatus
and the pid
properties.
$?.exitstatus