As a language, Ruby prioritizes programmer productivity over program optimization. This means that Ruby may not always run a program in the fastest way possible, but it strives to be a language that programmers find easy and fun to use.
The more intuitive a language’s syntax is, the more productive its users can be.
- Additional notes
- Data types
- Enumerable
- Control Flow
- Code Looping
- Methods
- Blocks
- Procs
- Lambdas
- Closures
- Partial application
- Currying
- Lazy Evaluation
- Sorting
- Object Oriented Programming
Additional notes
- By convention, class names start with a capital letter and use CamelCase instead of relying_on_underscores.
- String interpolation is done by using the
#{}
when inside a string,name = "Nikola Tesla" puts "#{name} is the best scientific of the last century"
When working with only string we can also use
+
and<<
, but if we want to use other data types we have to convert them to strings with.to_s
balance = 15100 last_expenses = [100, 256, 7863] puts "Your balance is: " + balance.to_s + " and your expenses where " << last_expenses.to_s Your balance is: 15100 and your expenses where [100, 256, 7863] => nil
but that is not the Rubyist way, we better use the interpolation notation
puts "Your balance is: #{balance} and your expenses where #{last_expenses}"
which give us a smaller and clearer way to output information.
- In Ruby, curly braces (
{}
) are generally interchangeable with the keywordsdo
(to open the block) andend
(to close it).loop { print "Hello, world!" } loop do print "Hello, world" end
As a good practice, is common to use
{}
when there is a single line of code, anddo-end
when it is necessary to execute multiple code lines. - String interpolation in Ruby is done with
#{}
, examplewho = "World" puts "Hello #{who}!" => nil
- You can use
!!
to know the boolean value of an element, e.g!!nil #=> false !!false #=> false !!0 #=> true !!"" #=> true !!true #=> true
false
(not true) andnil
(nothing at all) are the only falsely values in Ruby. - Combined comparison operator
>
The combined comparison operator returns:0
when both operands are equal1
when first operand is greater-1
when second operand is greater
1 > 10 => -1 10 > 1 => 1 10 > 10 => 0
- Method names usually are semantically relevant:
- Method names ending with
?
return a boolean value. - Method names ending with
!
change the current data instead of creating a new copy
- Method names ending with
- Ruby methods will implicitly return the result of the last evaluated
expression. This means that instead of
def add(a,b) return a + b end
you can simply write:
def add(a,b) a + b end
- Conditional assignment
||=
will only assign a value to a variable if it hasn’t been assigned before.favorite_book = nil puts favorite_book favorite_book ||= "Cat's Cradle" puts favorite_book => Cat's Cradle favorite_book ||= "Why's (Poignant) Guide to Ruby" puts favorite_book => Cat's Cradle favorite_book = "Why's (Poignant) Guide to Ruby" puts favorite_book => Why's (Poignant) Guide to Ruby
- Maintaining the principle that there are multiple ways to do things in Ruby,
where besides the
.push
method we can also use the<<
operator to append an element to the end of the array. The good news is that it also works on strings, e.g.[1, 2, 3] << 4 => [1, 2, 3, 4] "hello" << " world!" => "hello world!"
Data types
- Booleans
true false
- Numbers
2 4 6
, Ruby allows us to write large numbers like1_000_000
which makes them easier to read.
Strings (or Text literals)
String data types are a sequence of characters, each of which occupies 1 byte of memory. Technically, you could represent the string using an array(or some collection) of characters, similar to that of classic languages like C.
Ruby provides 3 ways of including string literals into your source code:
-
Single quoted strings (rubocop recommended)
The easiest way of adding text is by surrounding it with single quote
'
symbols. However characters like an apostrophe and a backslash within the string need to be escaped if they are present.'Hello! Programmer. How\'s it going?'
-
Double quoted strings
Double quoted strings are more flexible, and they allow special escape sequences, e.g.
\t
,\n
, which don’t work with single quoted strings. More importantly, they allow the embedding of arbitrary expressions. When a string is created, the expression in the string is evaluated, converted to a string and inserted into the text in place of the expression, i.e interpolation."Hello! There is a tab \t here. Did you see?" "My name is Circle, and I love Pi. Pi is equal to #{Math::PI}"
-
Here documents
This is helpful for putting large amounts of text without worrying about escape sequences or string evaluation. “Here documents” begin with
<<-
followed immediately by and identifier or string that specifies the ending delimiter (No space is allowed preventing ambiguity with the left-shift operator). The text of the string literal begins on the next line and continues until the text of the delimiter appears on a line by itself.document = <<-HERE This is a string literal. It has two lines and abruptly ends with a newline... HERE
Encoding
In Ruby, strings are objects of the String class, which defines a powerful set of operations and methods for manipulating text (indexing, searching, modifying, etc.)
Since Ruby 1.9, strings have additional encoding information attached to the bytes which provides information on how to interpret them, e.g.
str = "With ♥!"
print("My String's encoding: ", str.encoding.name)
print("\nMy String's size: ", str.size)
print("\nMy String's bytesize: ", str.bytesize)
produces this output:
My String's encoding: UTF-8
My String's size: 7
My String's bytesize: 9
Some observations about the above code:
- The string literal creates an object which has several accessible methods.
- The string hash attached encoding information, indicating it’s a UTF-8 string.
- A String’s size corresponds to the number of characters we see.
- A String’s bytesize corresponds to the actual space taken by the characters in memory ( the ♥ requires 3 bytes instead of 1).
Although UTF-8 is the most popular (and recommended) encoding style for
content, Ruby supports 100 other encodings (try puts Encoding.list
). With this
in mind we should learn how to convert between different
encodings.
def transcode(iso_8859_1_string)
iso_8859_1_string.force_encoding(Encoding::UTF_8)
end
String Indexing
The String class provides a convenient array-like square bracket []
operation,
which allows you to extract portions of the string as well as altering the
content when used on the left side of an assignment.
str = 'Hello World!'
str[str.size-1] # strings are 0-indexed
=> "!"
str[-1] # first character from the end of the string
=> "!"
str[-2] # second last character
=> "d"
str[0] # first character
=> "H"
More often, you’d want to extract specific portions of the string rather than individual characters. To do this use comma separated operands between the square brackets. The first operand specifies an index (which may be negative), and the second specifies a length (which must be non-negative).
str = 'Hello World!'
str[0, 4] # first four characters
=> "Hell"
str[6, 3] # 3 characters starting from index 6
=> "Wor"
str[-1, 1] # 1 character starting from the END of string
=> "!"
str[-3, 3] # 3 characters starting from the last 3 characters of the string
=> "!"
str[-1, 3] # 3 characters starting from the END of string
=> "!"
The same way we can assign/delete/insert characters.
str = 'Hello'
str[str.size, 0] = ' World!' # append by assigning at the end of the string
str
=> "Hello World!"
str[5, 0] = "," # insert a comma after the 5th position (without replacing something)
str
=> "Hello, World!"
str[5, 7] = "" # delete 7 characters starting from index 5 (replace 7 characters with an empty string starting from index 5)
str
=> "Hello!"
str[1, 4] = "allowing" # replace 4 characters with the string starting at index 1
str
=> "Hallowing!"
Iterating on Strings
Since Ruby 1.9, we have explicit choices to iterate over a string bases on what we need to iterate:
.each_byte
iterates sequentially through the individual bytes that comprise a string..each_char
iterates the characters and is more efficient than [] operator or character indexing..each_codepoint
iterates over the ordinal values of characters in the string..each_line
iterate the lines.
money = "¥1000"
money.each_byte {|x| p x} # first char represented by two bytes
194
165
49
48
48
48
money.each_char {|x| p x} # prints each character
"¥"
"1"
"0"
"0"
"0"
def count_multibyte_char(string)
multibyte = 0
string.each_char { |c| multibyte += 1 if c.bytesize > 1 }
multibyte
end
count_multibyte_char('¥1000')
=> 1
String methods
Text info can be read from varied sources and is often unsuitable for direct processing or usage by core functions. This necessitates methods for post-processing and data-fixing.
-
String.chomp(separator=$/)
Returns a new string with the given separator removed from the end of the string (if present). If
$/
has not been changed from the default Ruby record separator, thenchomp
also removes carriage return characters, i.e it will remove\n
,\r
,\r\n
."Hello World! \r\n".chomp => "Hello World! " "Hello World!".chomp("orld!") => "Hello W" "hello \n there".chomp => "hello \n there"
-
String.strip
Returns a new string with the leading and trailing whitespace removed.
" hello ".strip => "hello" "\tgoodbye\r\n".strip => "goodbye"
-
String.chop
Returns a new string with the last character removed. Note that carriage returns (
\n
,\r
,\r\n
) are treated as single characters and, in the case they are not present, a character from the string will be removed."string\n".chop => "string" "string".chop => "strin"
A use example:
def process_text(strings_array)
strings_array.inject("") do |processed, string|
processed << " " << string.strip.chomp
end.strip
end
There are also methods for search and replace portions of the string based on a text or pattern.
-
String.include?(string)
Returns
true
if str contains the given string or character."hello".include? "lo" => true "hello".include? "ol" => false
-
String.gsub(pattern, <hash|replacement>)
Returns a new string with all the occurrences of the pattern substituted for the second argument: The pattern is typically a Regexp, but a string can also be used.
"hello".gsub(/[aeiou]/, '*') => "h*ll*" "hello".gsub(/([aeiou])/, '') => "hll"
Either method will depend upon the problem you are trying to solve, and the nature of input-output behavior you desire, e.g.
def strike(string)
"<strike>#{string}</strike>"
end
def mask_article(string, words)
words.inject(string) { |masked, word| masked.gsub(word, strike(word)) }
end
strike("Meow!")
=> "<strike>Meow!</strike>"
strike("Foolan Barik")
=> "<strike>Foolan Barik</strike>"
mask_article("Hello World! This is crap!", ["crap"])
=> "Hello World! This is <strike>crap</strike>!"
Arrays
You can make an array of any collection of Ruby objects, e.g
[1, 2, 3, 5]
=> [1, 2, 3, 5]
["a", "b", "z"]
=> ["a", "b", "z"]
[true, false, false, true]
=> [true, false, false, true]
[true, 235, "Hello world", [2, 6, false]]
=> [true, 235, "Hello world", [2, 6, false]]
Array initialization
The are various method to initialize a ruby array:
- Initialize an empty array
array = Array.new # or array = []
- Initialize and array with exactly one
nil
elementarray = Array.new(1) # or array = [nil]
- Initialize an array with exactly two elements with value 10
array = Array.new(2, 10) # or array = [10, 10]
Accessing array elements
You can access array elements by using the []
operator or the .at()
method
in each one using the index of the element.
The []
operator is the most powerful option to access arrays elements, as it
can take various arguments:
arr = [ 9, 5, 1, 2, 3, 4, 0, -1 ]
arr[4] # a number which is the position of element
=> 3
arr[1..3] # a range where both index are inclusive
=> [ 5, 1, 2 ]
arr[1...3] # a range where the las index is excluded
=> [ 5, 1 ]
arr[1, 4] # a start index and the length of the range
=> [ 5, 1, 2, 3 ]
Some other ways to access array objects in Ruby are:
arr = [ 9, 5, 1, 2, 3, 4, 0, -1 ]
arr[-1] # access the elements from the end of the list, use negative indices
=> -1
arr.first # first element of the array
=> 9
arr.last # last element of the array
=> -1
arr.take(3) # the first `n` elements of the array
=> [ 9, 5, 1 ]
array.drop(3) # everything but the first `n` elements of the array
=> [ 2, 3, 4, 0, -1 ]
Adding array elements
Arrays provide a variety of methods that allow to add elements to them.
arr = [ 1, 2 ]
arr.push(3) # allow us to append elements to the array
=> [ 1, 2, 3 ]
arr.unshift(0) # allows us to prepend elements to the array
=> [ 0, 1, 2, 3 ]
# insert allows us to add elements starting from a given position, shifting elements after the given index in the process.
arr.insert(2, 1.1, 1.5, 1.9)
=> [ 0, 1, 1.1, 1.5, 1.9, 2, 3 ]
Removing array elements
The array class has various methods of removing elements from the array.
arr = [ 5, 6, 5, 4, 3, 1, 2, 5, 4, 3, 3, 3 ]
# pop removes elements from the end of the array and returns them
arr.pop
=> 3
arr
=> [ 5, 6, 5, 4, 3, 1, 2, 5, 4, 3, 3 ]
arr.pop(2)
=> [ 3, 3 ]
arr
=> [ 5, 6, 5, 4, 3, 1, 2, 5, 4 ]
# shift removes elements form the beggining of the array and returns them
arr.shift
=> 5
arr
=> [ 6, 5, 4, 3, 1, 2, 5, 4 ]
# delete_at deletes one element at a given position
arr.delete_at(2)
=> [ 6, 5, 3, 1, 2, 5, 4 ]
# delete deletes all occurrences of a given element
arr.delete(5)
=> 5
arr
=> [ 6, 3, 1, 2, 4 ]
Selecting array’s elements
The array class also allows to select and return a subset of an array based on some criteria defined in a block.
An element in a block is
select
ed,reject
ed, deleted or kept based on thetrue
orfalse
value generated by the block on that element.
arr = [ 3, 4, 2, 1, 2, 3, 4, 5, 6 ]
arr.select { |i| i > 2 } # select elements that satisfy a given criteria
=> [ 3, 4, 3, 4, 5, 6 ]
arr.reject { |i| i > 2 } # reject elements that satisfy a given criteria
=> [ 2, 1, 2 ]
arr.drop_while { |i| i > 2 } # drop elements till the block returns false for the first time
=> [ 1, 2, 3, 4, 5, 6 ]
arr
=> [ 3, 4, 2, 1, 2, 3, 4, 5, 6 ]
As noted in the last example, all of this methods are non-destructive, meaning that they return a new array containing all the elements of the array that satisfy the block without modifying the array itself.
For destructive behavior, you can use
arr = [ 3, 4, 2, 1, 2, 3, 4, 5, 6 ]
arr.delete_if { |i| i < 2 } # remove any element that satisfy the block
=> [ 3, 4, 2, 2, 3, 4, 5, 6 ]
arr # note that the array was changed
=> [ 3, 4, 2, 2, 3, 4, 5, 6 ]
arr.keep_if { |i| i < 4 }
=> [ 3, 2, 2, 3 ]
arr # note that the array was changed
=> [ 3, 2, 2, 3 ]
Although ruby provide us with this methods is more common to use the enforced
versions of some methods (the same method name but ending with a !
), e.g
arr = [ 3, 4, 2, 1, 2, 3, 4, 5, 6 ]
arr.reject! { |i| i < 2 } # remove any element that satisfy the block
=> [ 3, 4, 2, 2, 3, 4, 5, 6 ]
arr
=> [ 3, 4, 2, 2, 3, 4, 5, 6 ]
arr.select! { |i| i < 4 }
=> [ 3, 2, 2, 3 ]
arr
=> [ 3, 2, 2, 3 ]
Hashes
Are collections of key-value pairs which are similar to arrays, but instead of using integers to index and object, hashes use any object as its index. Note that keys must be unique in a hash but values can repeat.
Initializing hashes
- Literal Notation
hash = {
"key1" => "value1",
key2 => value2,
key3 => value3
}
- Hash contructor notation (Using the
new
operator)
my_hash = Hash.new
my_hash["key1"] = "value1"
my_hash[true] = 2
my_hash["key3"] = [2, 4, 5, 6]
my_hash[4] = false
If you try to access a key that doesn’t exist, unlike other languages that
return an error of some kind, Ruby returns the special value nil
, unless
the hash was created with the Hash contructor and was initialized, e.g.
# literal notation
hash = {}
hash["key"]
=> nil
# class constructor
hash = Hash.new(0) # the default value of every key is set to 0
hash["key"]
=> 0
You can also set the default value for hashes’ keys after the hash was declared:
hash = Hash.new
hash.default = 0
hash["key"]
=> 0
Another way of creating a hash populated with the given objects is to use:
Hash[ key, value, ... ] -> new_hash
Hash[ [key, value], ... ] -> new_hash
e.g.
Hash[ "a", 1, "b", 2, "c", 3, "d", 4, "e", 5 ]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5}
Hash[("a".."e").to_a.zip((1..5).to_a)]
=> {"a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5}
Although when can certainly use strings as Ruby keys, the Rubyist’s way is to use symbols.
menagerie = {
:foxes => 2,
:giraffe => 1,
:weezards => 17,
:elves => 1,
:canaries => 4,
:ham => 1
}
Iterating over Hashes
To iterate on hashes we have a variety of methods:
.each
can be used in two wayshash.each { |key, value| puts "#{key}: #{value}" }
or
hash.each { |touple| puts "#{touple[0]}: #{touple[1]}" } # here touple[0] is the key and touple[1] is the value
- If we only want to iterate over just keys or just values
.each_key
and.each_value
my_hash = { one: 1, two: 2, three: 3 } my_hash.each_key { |k| print k, " " } => one two three my_hash.each_value { |v| print v, " " } => 1 2 3
- If we want to be selective
.select
grades = { alice: 100, bob: 92, chris: 95, dave: 97 } grades.select { |name, grade| grade 97 } => { :bob => 92, :chris => 95 } grades.select { |k, v| k == :alice } => { :alice => 100 }
Addition, Deletion and Selection over Hashes
Considering the hash object:
hash = Hash.new
hash.default = 0
- We can add a new key-value pair by using:
hash["key"] = "value"
or
hash.store("key", "value")
- An existing key can be deleted using the
delete
methodhash.delete("key")
- For destructive selection, we can use
.keep_if
or.delete_if
, similar to how it was used for arrays.h = { 1 => 1, 2 => 4, 3 => 9, 4 => 16, 5 => 25 } => {1=>1, 2=>4, 3=>9, 4=>16, 5=>25} h.keep_if { |key, value| key.even? } # or h.delete_if { |key, value| key.odd? } => {2=>4, 4=>16}
Symbols
Although you can think of a Ruby symbol as a sort of name, it’s important to remember that symbols aren’t strings, two key differences are:
- While there can be multiple instances of a string with the same value,
there’s only one instance of any particular symbol at a given time.
puts "string".object_id => 18821600 puts "string".object_id => 18821340 puts :symbol.object_id => 802268 puts :symbol.object_id => 802268
- A symbol is an special kind of object that exist without a lot of baggage.
Symbols make good hash keys for a few reasons:
- They’re immutable, meaning they can’t be changed once they’re created
- Only one copy of any symbol exists at a given time, so they save memory
- Symbol-as-keys are faster than strings-as-keys because the two above reasons.
Symbols always start with a colon :
, they must be valid Ruby variable names,
i.e the first character after the colon has to be a letter or underscore _
;
after that, any combination of letters, numbers and underscores is allowed.
The hash rocker syntax
:key => "value"
changed in Ruby 1.9, the new syntax is similar to the one used by Javascript objectshash = { one: 1, two: 2, three: 3 }
Converting between strings and symbols
:sasquatch.to_s
=> "sasquatch"
"sasquatch".to_sym
=> :sasquatch
There are always many ways of accomplishing something in Ruby, converting to
symbols is no different, besides using .to_sym
, you can also use .intern
(this will internalize the string into a symbol and works just like .to_sym
)
"sasquatch".intern
=> :sasquatch
Call and response (passing methods as symbols)
symbols are awesome for referencing method names. The method .respond_to?
takes a symbol as an argument and returns true
if an object can receive that
method and false
otherwise, e.g.
[1, 2, 3].responds_to?(:push)
=> true
# we can call `push` on an array object
[1, 2, 3].respond_to?(:to_sym)
=> false
# since you can't convert an array in to a symbol.
Symbols and Procs
We can also convert symbols to procs using &
.
strings = ['1', '2', '3']
nums = strings.map(&:to_i)
=> [1, 2, 3]
Here we are using :
to pass the .to_i
method as a symbol, then &
is used
to convert the symbol to a proc and passed to map
which usually receives a
block (remember a proc is a named block) as an argument.
Enumerable
There are various methods to iterate over a collection like unless
, loop
and
the most commonly used each
.
Ruby, however, provides an Enumerable
module which packages a bunch of methods
which can be sued with any other class by including it (referred to as a
mixin in). That means that programmers don’t have to write all those
methods many different times for different objects.
As long as the custom object defines an
each
method and includesEnumerable
module, it can get access to all of its magic, i.eeach
is central to all of the methods provided byEnumerable
class.
Iterating with enumerable
-
each
Probably the most common iterator used in Ruby, required by Enumerable, it calls the given block once for each element in the collection, passing that element as a parameter. It returns the collection itself regardless of what is done by the block passed.
a = [ 1, 2, 3, 4, 5 ] a.each { |i| print "#{i} -- " } 1 -- 2 -- 3 -- 4 -- 5 -- => [1, 2, 3, 4, 5]
-
each_with_index
One of the
Enumerable
useful methods iseach_with_index
which allows us to iterate over items along with an index keeping count of the item.colors = %w[red green blue] colors.each_with_index { |item, index| p "#{index}:#{item}" } "0:red" "1:green" "2:blue"
The classic triumvirate
Beside simple methods to iterate over objects, Ruby Enumerable provides a number of important higher order constructs:
-
collect
a.k.a.map
Takes a function and maps (applies) it to a collection of values one by one and returns the collection result. This single powerful method helps us to operate on a large number of values at once.
[1,2,3].map { |x| 2*x } => [2, 4, 6] {:a=>1, :b=>2, :c=>3}.collect { |key, value| 2*value } => [2, 4, 6]
Note that these methods are different from each in the respect that these methods return a new collection while former returns the original collection, irrespective of whatever happens inside the block.
-
reduce
a.k.ainject
A common scenario that arises when using a collection of any sort, is to perform a single type of operation with all the elements and collect the result.
For example, the sum of all the elements in a collection and return the result.
# get the sum of all the integers from 1 to 5 (0..5).reduce { |accumulator, element| accumulator + element } => 15 # when there's only one operation when can use a symbol operator (0..5).reduce(:+) => 15
We can also pass an initial value to the accumulator
(0..5).reduce(2) { |accumulator, element| accumulator + element } => 17 (0..5).reduce(3, :+) => 18
Checking for validity
Ruby offers various enumerables on collections that check for validity of the objects within it, e.g.
arr = [1, 2, 3, 4, 5, 6]
=> [1, 2, 3, 4, 5, 6]
hash = { "a" => 1, "b" => 2, "c" => 3 }
=> {"a"=>1, "b"=>2, "c"=>3}
- The
.any?
method returnstrue
if the block ever returns a value other thanfalse
ornil
for any element passed to it:arr.any? { |a| a.even? } # check if any number in the array is even => true hash.any? { |key, value| value.is_a? String } # check if any value if a string => false
- The
.all?
method returnstrue
if the block never returnsfalse
ornil
for any collection’s element passed to it:arr.all? { |a| a.is_a? Integer } # check if all elements are Integers => true hash.all? { |key, value| key.is_a? String } # check if all keys are strings => true
- The
.none?
method returnstrue
if the block never returnstrue
for any element passed to it:arr.none? { |a| a.nil? } # check if none of the elements are nil => true hash.none? { |key, value| value < 3 } # check if all values are less than 3
- The
.find
method returns the first element for which block is notfalse
:arr.find { |a| a > 5 } # returns the first element greater than 5, nil if none => 6 hash.find { |key, value| key == "b" } # returns the first match (an array), nil if none => ["b", 2]
Grouping
Another function often used with data collections is one which groups the elements according to some evaluation result.
- The
.group_by
method groups the collection by result of the block. It returns a hash where the keys are the evaluated result from the block and the values are arrays of elements in the collection that correspond to the key, e.g.(1..5).group_by { |x| x % 2 } => {1=>[1, 3, 5], 0=>[2, 4]} (1..5).group_by { |x| x.even? } => {false=>[1, 3, 5], true=>[2, 4]}
It’s use with a hash it’s similar, in this example the goal is to group students into two categories corresponding to their marks obtained.
marks = {"Ramesh":23, "Vivek":40, "Harsh":88, "Mohammad":60} def group_by_marks(marks, pass_marks) marks.group_by do |_student, score| score > pass_marks ? 'Passed' : 'Failed' end end group_by_marks(marks, 30) => {"Failed"=>[["Ramesh", 23]], "Passed"=>[["Vivek", 40], ["Harsh", 88], ["Mohammad", 60]]}
Control Flow
Control Structures
-
if-elsif-else-end
(execute code when conditionals are true)if boolean # code to execute when conditional is true elsif ther conditional> # code to execute when other conditional is true else # code to execute when all conditionals are false end
-
unless-else-end
(complement ofif
)unless boolean # code to execute when conditional is false else # code to execute contional is true end
One-liners
if
expression if boolean
example
puts "Hello world!" if 2 5 Hello world! => nil puts "Hello world!" if 2 > 5 => nil
unless
expression unless boolean
example
puts "Hello world!" unless 2 5 => nil puts "Hello world!" unless 2 > 5 Hello world! => nil
Remember that the order is important as Ruby will expect: an expression followed by
if
followed by a boolean. It’s also important to notice that we don’t need anend
when we write one-liners.
Ternary operator
# boolean ? expression_if_true : expression_if_false
puts 3 < 4 ? "3 is less than 4!" : "3 is not less than 4."
The case statement
case language
when "JS"
puts "Websites!"
when "Python"
puts "Science!"
when "Ruby"
puts "Web apps!"
else
puts "I don't know!"
end
Boolean operators
-
And
&&
A B result true true true true false false false true false false false false Example
(2 5) && (-11 < 5) => true (2 5) && (-11 < -15) => false (2 > 5) && (-11 > -15) => false (2 > 5) && (-11 -15) => false
-
Or
||
A B result true true true true false true false true true false false false Example
(2 5) || (-11 < 5) => true (2 5) || (-11 < -15) => true (2 > 5) || (-11 > -15) => true (2 > 5) || (-11 -15) => false
Note than Ruby in evaluates the operators
||
and&&
via short-circuit evalutation.
That means that Ruby doesn’t look at both expressions unless it has to, if it sees
false && true
it stops reading as soon as it sees &&
because it knows flase &&
anything
must be false, the same way
true || false
it stops reading as soon as it sees ||
because it knows true ||
anything
must be false.
Note than in the case of
||
if the first argument istrue
the whole operation is evaluated totrue
without checking the value of the second argument
Note than in the case of
&&
if the first argument isfalse
the whole operation is evaluated tofalse
without checking the value of the second argument
-
Not
!
A ~A true false false true !(2 5) => false !(2 > 5) => true
Code Looping
-
while-end
(execute code while the conditional istrue
)while onditional> # some code end
Example
a = 0 while a 10 puts a a += 1 end
-
until-end
(complement towhile
)while onditional> # some code end
Example
a = 0 until a >= 10 puts a a += 1 end
-
loop
(the simplest but least powerful lopping method)loop { print "Hello, world!" }
Be careful of infinite loops,
loop
requires a conditional breaker to terminate its executiona = 0 loop do print a break if a >= 10 a += 1 end
for-end
(when you know exactly how many times your code must run)for i in 0...5 puts i end 0 1 2 3 4 => 0...5
.times
iterator (super compact for loop)3.times { puts "Hello, world!" } Hello, world! Hello, world! Hello, world! => 3
.each
iterator (execute some code on each item of a collection)(1..5).each { |i| puts i*2 } 2 4 6 8 10 => 1..5 (1..5).each do |i| puts "#{i} + #{2*i} = #{3*i}" end 1 + 2 = 3 2 + 4 = 6 3 + 6 = 9 4 + 8 = 12 5 + 10 = 15 => 1..5
The right tool for the job
Although Ruby includes for
loops, the Rubyist way is to not use them, there
are better tools available to us.
- If we want to do something a specific number of times, we can use the
.times
method:3.times { puts "Odelay!" } Odelay! Odelay! Odelay! => 3
- If we know the range of numbers we would like to include, we can use
.upto
and.downto
, instead of trying to use afor
loop that stops when a counter variable hits a certain value.98.upto(100) { |num| puts num } 98 99 100 => 98
- If we want to repeat an action for every element in a collection, we can use
.each
:[1, 2, 3].each { |n| puts n * 10 } 10 20 30 => [1, 2, 3]
Methods
In simpler terms, a method is a group of several expressions (block, so to say) that can be referred with a name, can be passed some arguments (input) and are associated with one or more objects.
A method is a helpful way in which we can abstract similar computations into logical chunks of code that otherwise would be very clumsy and difficult to manage.
Methods, in a way, also behave like a black box where the programmer should ideally be concerned only with a basic description of the box — what it does, what it’s input and what is the expected output —, without having to worry about how it does it.
This allows to focus more on the functionality of a program instead of the implementation details.
Methods are defined using the def
keyword followed by the method name,
optional parameter names (input) written between parentheses ()
, a block of
code between the def
and end
lines, and a returning output (usually implied
in the block).
Note that the argument(s) is the data passed to the method when is called, and the parameter(s) is the name given to the input(s) when the method was defined (i.e the method declaration).
def square(number)
number ** 2
end
square(5)
=> 25
In the example anterior square
is the name of the method, number
is the
parameter name, number ** 2
is the method’s block, and the 5
in square(5)
is the argument passed to the method.
Default Parameters
Default parameters are defined when the method is declared, and are only used if the method has not received the correct number of arguments, in which case the value of the argument defaults to the one defined in the declaration, e.g.
def alphabetize(arr, rev=false)
if rev
arr.sort { |item1, item2| item2 > item1 }
else
arr.sort { |item1, item2| item1 > item2 }
end
end
books = ["Heart of Darkness", "Code Complete", "The Lorax", "The Prophet", "Absalom, Absalom!"]
puts "A-Z: #{alphabetize(books)}"
puts "Z-A: #{alphabetize(books, true)}"
Splat arguments
Splat arguments are preceded by a *
, which tells the program that the method
can receive one or more arguments, it must always be the last parameter.
def what_up(greeting, *friends)
friends.each { |friend| puts "#{greeting}, #{friend}!" }
end
what_up("Whats up", "Ian", "Zoe", "Zenas", "Eleanor")
Whats up, Ian!
Whats up, Zoe!
Whats up, Zenas!
Whats up, Eleanor!
=> ["Ian", "Zoe", "Zenas", "Eleanor"]
def sum(first, *rest)
rest.reduce(first) { |memo, new_num| memo + new_num }
end
sum(1)
=> 1
sum(1, 2)
=> 3
sum(1, 2, 3)
=> 6
Keyword arguments a.k.a Named parameters
Although splat arguments may seem as a handy feature to have, except a few circumstances, you are never going to use that many variables for your method.
Also, if you are passing several different types of variables to the method which then will be assigned to a array, it can be difficult for the programmer invoking the method to remember the proper order for those arguments.
Ruby allows you to (partially) mitigate this problem by passing a Hash
as an
argument or one of the arguments, e.g.
def fetch_file(uri, options)
if options.has_key?(:proxy)
# do something
end
end
or using the
.fetch
method:
def foo(options = {})
bar = options.fetch(:bar, 'default')
puts bar
end
foo
=> default
foo(bar: 'baz')
=> baz
The main problem with this technique, is that you cannot easily set default values for arguments. Fortunately Ruby 2.1 introduced first-class support for keyword arguments.
In computer programming, named parameters or keyword arguments refers to a computer language’s support for functions calls that clearly state the name of each parameter within the function call itself.
We can rewrite the method of the last example as
def foo(bar: 'default')
puts bar
end
foo
=> default
foo(bar: 'baz')
=> baz
We can also define required keyword arguments, with a trailing colon:
def foo(bar:)
puts bar
end
foo
=> ArgumentError: missing keyword: bar
foo(bar: 'baz')
=> baz
We can also mix ordinary arguments with keyword arguments
def foo(x, str: 'foo', num: 424245)
[x, str, num]
end
foo(13)
=> [13, 'foo', 424245]
foo(13, str: 'bar')
=> [13, 'bar', 424245]
If you want to mix keyword arguments with an options’ hash, you will have to use the notation (**name):
def foo(str: "foo", num: 424242, **options)
[str, num, options]
end
foo
=> ['foo', 424242, {}]
foo(check: true)
=> ['foo', 424242, {check: true}]
Because using:
def foo(str: "foo", num: 424242, options = {})
will raise an error like: syntax error, unexpected ')', expecting
end-of-input
.
Ruby blocks can also be defined using keyword arguments:
define_method(:foo) do |bar: 'default'|
puts bar
end
foo
=> default
foo(bar: 'baz')
=> baz
define_method(:foo) { |str: 'foo', num: 424245| [str, num] }
foo
=> ['foo', 424245]
Keyword arguments vs options hash
With first-class keyword arguments in the language, we don’t have to write the boilerplate code to extract hash options. Unnecessary boilerplate code increases the opportunity for typos and bugs.
Note that the calling code is syntactically equal to calling a method with has arguments, which makes for an easy transition from options hashes to keyword arguments.
Keyword arguments vs positional arguments
Assume we have a method with positional arguments:
def mysterious_total(subtotal, tax, discount)
subtotal + tax -discount
end
mysterious_total(100, 10, 5)
=> 105
This method does it job, but as a reader of the code using the
mysterious_total
method, I have no idea what those arguments mean without
looking up the implementation of the method.
By using keyword arguments, we know what the arguments mean without looking up the implementation of the called method:
def obvious_total(subtotal:, tax:, discount:)
subtotal + tax - discount
end
obvious_total(subtotal: 100, tax: 10, discount: 5)
=> 105
keyword arguments allows us to switch the order of the arguments, without affecting the behavior of the method:
obvious_total(subtotal: 100, discount: 5, tax: 10)
=> 105
Blocks
Higher order functions are one of the key components of functional programming. A higher order function is a tool that takes other functions as parameters or returns them as a result.
Blocks are sets of instructions without a defined name (they are similar to
anonymous functions in Javascript). Blocks can be defined with either the
keywords do
and end
or curly braces {}
.
Passing a block to a method
A method can take a block as a parameter, e.g
[1, 2, 3, 4, 5].each { |i| puts i }
Here the bits between {}
is the block
Block argument are listed between vertical bars
|
, instead of parentheses.
Passing a block to a method is a great way of abstracting certain tasks from the method and defining those tasks when we call the method.
Yield
Methods that accept blocks have a way of transferring control from the calling
method to the block and back again. We can build into the methods we define by
using the yield
keyword.
def yield_name(name)
puts "In the method! Let's yield."
yield("Kim")
puts "In between the yields!"
yield(name)
puts "Block complete! Back in the method."
end
# pass a block to yield_name method
yield_name("Eric") { |n| puts "My name is #{n}." }
In the method! Let's yield.
My name is Kim.
In between the yields!
My name is Eric.
Block complete! Back in the method.
=> nil
def factorial(num)
puts yield
end
n = gets.to_i
# pass a block to factorial method
factorial(n) do |num|
(1..n).reduce(1) { |factorial, curr_num| factorial *= curr_num }
end
Procs
Blocks are not objects, this is one of the very few exceptions to the “everything is an object” rule in Ruby.
Because of this blocks can’t be saved to variables and don’t have all the powers and abilities of a real object, for that we use procs.
You can think of a proc as a saved block.
Procs are great for keeping you code DRY. With blocks, you have to write your code out each time you need it; with a proc, you write your code once and can use it many times.
There are several methods to crate a Proc
, the two most common are:
- Use the
Proc
class constructormultiples_of_3 = Proc.new { |n| n % 3 == 0 }
- Use the Kernel#proc method as a shorthand of
::new
(this is the recommended way)multiples_of_3 = proc { |n| n % 3 == 0 }
We can then pass the proc to a method that would otherwise take a block, and we don’t have to rewrite the block over and over!
print (1..100).to_a.select(&multiples_of_3)
print (1..10).to_a.collect(&multiples_of_3)
print (1..10).to_a.map(&multiples_of_3)
The
&
is used to convert themultiples_of_3
proc into a block, it’s use it necessary every time we pass a proc to method that expects a block.
We can also directly call a proc by using Ruby’s .call
method
test = Proc.new { puts "Hello!" }
test.call
Hello!
=> nil
def square_of_sum(my_array, proc_square, proc_sum)
sum = proc_sum.call(my_array)
proc_square.call(sum)
end
proc_square_number = proc { |num| num * num }
proc_sum_array = proc { |arr| arr.reduce(0) { |sum, n| sum + n } }
my_array = gets.split.map(&:to_i)
puts square_of_sum(my_array, proc_square_number, proc_sum_array)
Lambdas
Lambdas are anonymous functions. Lambdas in Ruby are objects of the class Proc. They are useful in most of the situations where you would use a proc.
strings = ["leonardo", "donatello", "raphael", "michaelangelo"]
symbolize = lambda { |string| string.to_sym }
symbols = strings.collect(&symbolize)
The simplest lambda takes no arguments and returns nothing:
# good practice: multi-line lambdas use 'lambda' and the do-end keywords
lambda do
end
# good practice: single line lambdas use '->' (lambda literal syntax) and curly braces `{}`
-> {}
with arguments
sum = -> (a,b) { a + b } # note that lambda literal syntax don't require args between `|`
sum.call(2, 3)
=> 5
sum = lambda { |a, b| a + b } # note the precence of `|` like a proc
sum.call(2, 3)
=> 5
Lambdas can be used as arguments to higher-order functions. They can also be used to construct the result of a higher-order function that needs to return a function.
- Lambda that takes no arguments
def area(long, base) -> { long * base } end x = 10.0 y = 20.0 area_rectangle = area(x, y).call area_triangle = 0.5 * area(x, y).() # prefer the use of `.call` instead of `.()` puts area_rectangle # 200.0 puts area_triangle # 100.0
- Lambda that takes one or more arguments
area = -> (long, base) { long * base } x = 10.0 y = 20.0 area_rectangle = area.(x, y) # prefer the use of `.call` instead of `.()` area_triangle = 0.5 * area.call(x, y) puts area_rectangle # 200.0 puts area_triangle # 100.0
Lambdas vs Procs
There are only two main differences between lambdas and procs:
- A lambda checks the number of arguments passed to it, while a proc does not.
A lambda will throw an error if you pass it the wrong number of arguments, whereas a proc will ignore unexpected arguments and assign
nil
to any that are missing.p = proc {|x, y| "x=#{x}, y=#{y}" } p.call(1, 2) #=> "x=1, y=2" p.call([1, 2]) #=> "x=1, y=2", array deconstructed p.call(1, 2, 8) #=> "x=1, y=2", extra argument discarded p.call(1) #=> "x=1, y=", nil substituted instead of error l = lambda {|x, y| "x=#{x}, y=#{y}" } l.call(1, 2) #=> "x=1, y=2" l.call([1, 2]) # ArgumentError: wrong number of arguments (given 1, expected 2) l.call(1, 2, 8) # ArgumentError: wrong number of arguments (given 3, expected 2) l.call(1) # ArgumentError: wrong number of arguments (given 1, expected 2)
-
When a lambda returns, it passes control back to the calling method: when a proc returns, it does so immediately, without going back to the calling method.
def batman_ironman_proc victor = proc { return 'Batman will win!' } victor.call # returns from method, but continues to execute the method 'Iron Man will win!' end puts batman_ironman_proc Batman will win! def batman_ironman_lambda victor = -> { return 'Batman will win!' } victor.call # just returns from lambda into method body 'Iron Man will win!' end puts batman_ironman_lambda Iron Man will win!
Here the proc says Batman will win because it returns immediately, without going
back to the batman_ironman_proc
method.
Quick Review
- A block is just a bit of code between do..end or {}. It’s not an object on its own, but it can be passed to methods like .each or .select.
- A proc is a saved block we can use over and over.
- A lambda is just like a proc, only it cares about the number of arguments it gets and it returns to its calling method rather than returning immediately.
There are obviously lots of cases in which blocks, procs, and lambdas can do similar work, but the exact circumstances of your program will help you decide which one you want to use.
Closures
A closure is a method that:
- Can be passed around like an object.
It can be treated like a variable, which can be assigned to another variable, passed as an argument to a method.
- Remembers the value of variables no longer in scope.
It remembers the values of all the variables that were in scope when the function was defined. It is then able to access those variables when it is called even if they are in a different scope.
def outer_function
some_state = { count: 0 }
{
increment: -> { some_state[:count] += 1 },
get_count: -> { some_state[:count] }
}
end
counter = outer_function
counter[:increment].call
counter[:increment].call
counter[:get_count].call #=> 2
counter[:increment].call
counter[:increment].call
counter[:get_count].call #=> 4
some_state
# undefined local variable or method `some_state' for main:Object (NameError)
Blocks, Procs and Lambdas are closures
def block_message_printer
message = 'Welcome to Block Message Printer'
yield if block_given?
puts "But in this function/method message is :: #{message}"
end
def proc_message_printer(my_proc)
message = 'Welcome to Proc Message Printer'
my_proc.call
puts "But in this function/method message is :: #{message}"
end
def lambda_message_printer(my_lambda)
message = 'Welcome to Lambda Message Printer'
my_lambda.call
puts "But in this function/method message is :: #{message}"
end
message = gets
block_message_printer { puts "This message remembers message :: #{message}" }
my_proc = proc { puts "This message remembers message :: #{message}" }
proc_message_printer(my_proc)
my_lambda = -> { puts "This message remembers message :: #{message}" }
lambda_message_printer(my_lambda)
Partial application
Partial application is a technique of fixing a number of arguments to a function, producing another function of smaller arguments, i.e a function that takes a function with multiple parameters and returns a function with fewer parameters.
Partial applications fixes (partially applies the function to) one or more arguments inside the returner function, and the returned function takes the remaining parameters as arguments in order to complete the function application.
In Ruby to use Partial Application, we create a lambda that takes a parameter and returns a lambda that does something with it.
def factorial(num)
(1..num).inject { | result, num| result * num }
end
combination = lambda do |num|
-> (new_num) { factorial(num) / (factorial(new_num) * factorial(num - new_num)) }
end
n = gets.to_i
r = gets.to_i
nCr = combination.(n)
puts nCr.(r)
Currying
Is a technique for converting function calls with N arguments into chains of N functions calls with a single argument for each function call.
Currying always returns another function with only one argument until all of the arguments have been applied. So, we just keep calling the returned function until we’ve exhausted all the arguments and the final value gets returned.
multiply_numbers = -> (x, y, z) do
x * y * z
end
doubler = multiply_numbers.curry.call(2)
tripler = multiply_numbers.curry.call(3)
puts doubler.(4) #8
puts tripler.(4) #12
check the curry method for details
Lazy Evaluation
Lazy evaluation is an evaluation strategy that delays the assessment of an expression until its value is needed.
Ruby 2.0 introduced a lazy enumeration feature. Lazy evaluation increases performance by avoiding needless calculations, and it has the ability to create potentially infinite data structures.
power_array = -> (power, array_size) do
1.upto(Float::INFINITY).lazy.map { |x| x**power }.first(array_size)
end
puts power_array.(2 , 4) #[1, 4, 9, 16]
puts power_array.(2 , 10) #[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
puts power_array.(3, 5) #[1, 8, 27, 64, 125]
In this example,
lazy avoids
needless calculations to compute power_array. If we remove lazy from the above
code, then out code would try to compute all x ranging from 1 to
Float::INFINITY
.
To avoid timeouts and memory allocation exceptions, we use lazy. Now, our code will only compute up to first(array_size).
Sorting
Sorting algorithms can be a great introduction to computer science as a theoretical discipline, but Ruby already has a built-in sorting library, which implements a few of these algorithms.
Our job is to decide how to compare two items in the list (maybe passing a block to a method), and let Ruby decide which strategy to use.
Combined comparison operator
>
\ -0
when both operands are equal. \ -1
when first operand is greater \ --1
when second operand is greater
A block that is passed into the sort
method must return either 1
, 0
, or
-1
. Is should return -1
if the first block parameter should come before the
second, 1
if vice versa, and 0
if they are of equal weight, meaning one does
not come before the other.
Object Oriented Programming
Ruby is an object-oriented programming language, which means it manipulates programming constructs called objects. (Almost) everything in Ruby is an object!
-
Class definition
class
keyword and the name of the class (use CamelCase).class ClassName end
-
Object initialization
def initialize(args) end
Remember that an object is an instance of a class.
Scope
Another important aspect of Ruby classes is scope. The scope of a variable is the context in which it’s visible to the program.
Not all variables are accessible to all parts of a Ruby program at all times.
When dealing with classes, you can have variables:
- Instance variables that are only available to particular instances of a class.
- Class variables that are members of a certain class, and
- Global variables that are available everywhere,
- Local variables that are only available inside certain methods,
The same goes for methods:
- Global methods are available everywhere,
- Class methods are only available to members of a certain class, and
- Instance methods some are only available to particular instance objects.
Variable types
-
Instance variable
@
This variable is attached to the instance of the class, e.g.
This isn’t just a Ruby convention—it’s part of the syntax! Always start your instance variables with
@
.class Person def initialize(name) @name = name end end
This tells Ruby that whenever it creates a
Person
, it has to have a name, and each instance ofPerson
will have its own@name
. -
Class variables
Class variables are like instance variables, but instead of belonging to an instance of a class, they belong to the class itself. Class variables always start with two
@
s, like so:class MyClass @@class_variable end
Because there’s only one copy of a class variable shared by all instances of a class, we can use them to pull off some cool Ruby tricks. For example, we can use a class variable to keep track of the number of instances of that class we’ve created.
class Person @@people_count = 0 def initialize(name) @name = name @@people_count += 1 end def self.number_of_instances return @@people_count end end matz = Person.new("Yukihiro") dhh = Person.new("David") puts "Number of Person instances: #{Person.number_of_instances}"
-
Global variables
Can be divided in two ways.
- You just define the variable outside of any method or class
- If you want to make a variable global from inside a method or class, just
start it with a
$
, like so:$matz
class MyClass $my_variable = "Hello!" end puts $my_variable # Remember to access the variable with `$`
A caveat, though: global variables can be changed from anywhere in your program, and they are generally not a very good idea. It’s much better to create variables with limited scope that can only be changed from a few places!
Method types
- Global methods defined outside a class
-
Instance methods defined inside a class, are called from within an object, e.g
"hello".length
.length
is an instance method belonging to the object"hello"
- Global methods
Public vs Private
If you’re the only one writing and using your software, it’s okay for every part of your Ruby program to know what every other part is doing. But when other people are working on or using your programs, they may attempt to muck around with the way different parts of your program do their jobs. You don’t want that!.
For this reason, Ruby allows you to explicitly make some method public
and
others private
.
-
Public methods allows for an interface with the rest of the program; they say, “Hey, Ask me if you need to know something about my class or its instances”.
Methods are public by default in Ruby. If you don’t specify
public
orprivate
your methods will be public. -
Private methods, on the other hand, are for your classes to do their own work undisturbed. They don’t want anyone asking them anything, so they make themselves unreachable.
Private methods are private to the object where they are defined, you can only call these methods from other code inside the object.
In order to access private information, we have to create public methods that know how to get it. This separates the private implementation from the public interface.
attr_reader
, attr_writer
It’s a good practice in any Object Oriented Programming Language to use methods
to access attributes. For instance, if we want to access a @name
instance
variable, we have to write something like:
def name
@name
end
def name=(value)
@name = value
end
Fortunately Ruby’s motto is to make the programmer happier, and it comes with
the attr_reader
and attr_writer
which are public instance methods of the
class module. If we write
attr_reader :name
attr_writer :name
inside a class, Ruby does something like this for us automatically:
def name
@name
end
def name=(value)
@name = value
end
If we want to both read and write a particular variable, there’s an even shorter
shortcut, we can use attr_accessor
to make a variable readable and writable
in one fell swoop.
Inheritance
Inheritance is the process by which one class takes on the attributes and methods of another, and it’s used to express an is-a relationship.
In Ruby, the inheritance works like this (you can read <
as “inherits
from”):
class Application
def initialize(name)
@name = name
end
end
class MyApp < Application
end
Override
Sometimes you’ll want one class that inherits from another to not only take on the methods and attributes of its parent, but to override one or more of them.
You can explicitly create a method
inside your derived class with the same
name of the parent class, this new version of the method
will override the
inherited version for any object that is an instance of the derived class.
class Creature
def initialize(name)
@name = name
end
def fight
return "Punch to the chops!"
end
end
class Dragon < Creature
def fight
puts "Breathes fire!!"
end
end
Super
Sometimes you’ll be working with a derived class (or subclass) and realize that
you’ve overwritten a method or attribute defined in that class’ base class (also
called a parent or superclass) that you actually need. Have no fear! You can
directly access the attributes or methods of a superclass with Ruby’s built-in
super
keyword.
class Creature
def initialize(name)
@name = name
end
def fight
return "Punch to the chops!"
end
end
# Add your code below!
class Dragon < Creature
def fight
puts "Instead of breathing fire..."
super
end
end
When you call super from inside a method, that tells Ruby to look in the superclass of the current class and find a method with the same name as the one from which super is called. If it finds it, Ruby will use the superclass’ version of the method.
Modules
Modules are like a toolbox that contains a set of methods and constants.
There are lots of Ruby tools you might want to use, but it would clutter the interpreter to keep them around all the time. For that reason, we keep a bunch of them in modules and only pull in those module toolboxes when we need the constants and methods inside.
Modules are easy to make! You just use the module
keyword.
module Circle
PI = 3.141592653589793
def Circle.area(radius)
PI * radius**2
end
def Circle.circumference(radius)
2 * PI * radius
end
end
Like class names, module names are written in CapitalizedCamelCase rather than lowercase_with_underscores.
It doesn’t make sense to include variables in modules, since variables (by definition) change. Constants, however, are supposed to always remain the same, so including helpful constants in modules is a great idea.
Ruby doesn’t make you keep the same value for a constant once it’s initialized, but it will warn you if you try to change it.
Ruby constants are written in ALL_CAPS and are separated with underscores if
there’s more than one word, an example is the PI
constant that was defined in
the last example.
Score resolution operator
One of the main purposes of modules is to separate methods and constants into
named spaces. This is called namespacing, and it’s how Ruby doesn’t confuse
Math:PI
and Circle::PI
.
The ::
is called the scope resolution operator, which is the way we tell
Ruby where to look for a specific bit of code.
Requiring a Module
Some modules, like Math
, are already present in the interpreter. Others need
to be explicitly brought in, and we can do this by using the require
keyword,
e.g if we want to get today’s date we can use the Date
module.
require 'date'
puts Date.today
Including a Module
Any class that include
s a certain module can use those module’s methods and
constants without having to prepend the module’s name.
class Angle
include Math
attr_accessor :radians
def initialize(radians)
@radians = radians
end
def cosine
cos(@radians)
end
end
acute = Angle.new(1)
acute.cosine
Mixins
Any given Ruby class can have only one superclass. Some languages allow a class to have more than one parent, which is a model called multiple inheritance. This can get really ugly really fast, which is why Ruby disallows it.
However, there are instances where you want to incorporate data or behavior from several classes into a single class, and Ruby allows this through the use of mixins.
include
a module
When a module is used to mix additional behavior and information into a class, it’s called a mixin. Mixins allow us to customize a class without having to rewrite code!
module Action
def jump
@distance = rand(4) + 2
puts "I jumped forward #{@distance} feet!"
end
end
class Rabbit
include Action
attr_reader :name
def initialize(name)
@name = name
end
end
class Cricket
include Action
attr_reader :name
def initialize(name)
@name = name
end
end
peter = Rabbit.new("Peter")
jiminy = Cricket.new("Jiminy")
peter.jump
jiminy.jump
This is how we can mimic multiple inheritance in Ruby: by mixing in traits from various modules as needed, we can add any combination of behaviors to our classes as we like.
extend
a class
Whereas include
mixes a module’s methods in at the instance level (allowing
instances of a particular class to use the methods), the extend
keyword mixes
a module’s methods at the class level. This means that the class itself can
use the methods, as opposed to instances of the class.
module ThePresent
def now
puts "It's #{Time.new.hour > 12 ? Time.new.hour - 12 : Time.new.hour}:#{Time.new.min} #{Time.new.hour > 12 ? 'PM' : 'AM'} (GMT)."
end
end
class TheHereAnd
extend ThePresent
end
TheHereAnd.now
Here the TheHereAnd
class was extend
ed with ThePresent
module, allowing it
to use the now
method.