Blocks
A block is simply Ruby code enclosed between do and end:
['Ruby', 'Elixir', 'Dart'].each do |lang|
puts "Learning {lang}"
end
=>
Learning Ruby
Learning Elixir
Learning Dart
If it fits on one line, you can use curly braces {}:
['Ruby', 'Elixir', 'Dart'].each { |lang| puts "Learning {lang}" }
yield
yield is a special Ruby operator that executes the code passed as a block to a method:
def execute_block
puts 'Start execution'
yield
puts 'Stop execution'
end
execute_block { puts " >>> Running code from block" }
=>
Start execution
>>> Running code from block
Stop execution
You can also pass parameters to a block:
def execute_block_with_params(command)
puts 'Start execution'
puts yield('Run', command)
puts 'Stop execution'
end
execute_block_with_params('ls') { |op, cmd| "{op} this command: {cmd}" }
=>
Start execution
Run this command: ls
Stop execution
If the block expects 3 parameters but only 2 are passed via yield, the third will be nil.
If you use yield without passing a block, you'll get an error:
LocalJumpError: no block given (yield)
To avoid this, check whether a block was given using block_given?:
def execute_block
if block_given?
puts yield
else
puts "no block"
end
end
You can also pass a block as the last parameter using the & prefix:
def fetch_option(key, &block)
if block_given?
options = block.call
puts "{key} value is {options[key]}"
else
puts 'no block'
end
end
fetch_option(:age) { Hash[name: "Alex", age: 28] }
=> age value is 28
Proc Objects
A Proc works like an anonymous function:
pr = Proc.new { puts "This is Proc" }
pr.call
pr = proc { puts "Short proc" }
pr.call
Procs can accept parameters:
print_name = Proc.new { |name| puts name }
print_name.call('Alex')
Procs can be called in multiple ways:
sum = Proc.new { |a, b| a + b }
sum.call(1, 2) # => 3
sum.(1, 2) # => 3
sum[1, 2] # => 3
sum.yield(1, 2) # => 3
Extra arguments are ignored; missing ones become nil.
Proc with return
If a Proc created inside a method returns with return, the method exits immediately:
def print_numbers(n1, n2)
block = Proc.new { return 99 }
puts n1
block.call
puts n2 # never reached
end
print_numbers(1, 10)
=>
1
99
Proc with Iterators
add_two = Proc.new { |i| i + 2 }
[1, 2, 3].map(&add_two)
=> [3, 4, 5]
['ruby', 'rails'].map(&:upcase)
=> ['RUBY', 'RAILS']
Lambda
Lambda is a Proc with stricter argument checking:
sum = -> (a, b) { a + b }
sum.call(1, 2) # => 3
sum[1, 2] # => 3
sum.(1, 2) # => 3
Unlike Proc, Lambda raises ArgumentError if the argument count is wrong:
sum.call(1) # => ArgumentError: wrong number of arguments (given 1, expected 2)
sum.call(1, 2, 3) # => ArgumentError: wrong number of arguments (given 3, expected 2)
Method Objects
The method helper lets you call a method as a Proc object:
class Person
def say(word)
puts word
end
end
mp = Person.new.method(:say)
mp.call("Hello")
=> Hello
Iterators and the Enumerable Module
Iterators come from the Enumerable module and let you traverse arrays, hashes, strings, and more — just like loops, but more idiomatic in Ruby.
[1, 2, 3].each { |i| puts i }
[1, 2, 3].map { |i| i * 10 }
=> [10, 20, 30]
[1, 2, 3, 4, 5].select { |i| i > 2 }
=> [3, 4, 5]
[1, 2, 3, 4, 5].inject(:+)
=> 15
You can chain iterators:
[1, 2, 3, 4, 5].map { |i| i * 2 }.map { |i| i * 10 }.select { |i| i > 50 }.sum
=> 240
Iterators work on various objects:
"Alex".each_char.with_index(1) do |ch, index|
puts "{index}. {ch}"
end
=>
1. A
2. l
3. e
4. x
{ a: 1, b: 2 }.each { |k, v| puts "{k} - {v}" }
3.times { puts "Hello" }
Custom Iterators with yield
def simple_each
i = 0
loop do
yield self[i]
i += 1
break if self.size == i
end
end
[1, 2, 3].simple_each { |i| i * 10 }
Loops
Ruby has several loop operators, though iterators are preferred in practice:
loop— infinite loop, exit withbreakwhile— runs while condition istrueuntil— runs while condition isfalsefor in— iterates over a collection
i = 0
loop do
puts i
i += 1
break if i > 5
end
i = 0
while i <= 5 do
puts i
i += 1
end
for value in [1, 2, 3] do
puts value
end
Enumerators
Every iterator returns an Enumerator object. You can step through it manually with .next:
enum = [1, 2, 3].each
enum.next # => 1
enum.next # => 2
enum.next # => 3
enum.next # => StopIteration: iteration reached an end
Files
The File class (part of IO) lets you work with files.
Writing to a file:
File.open("/tmp/my_file.txt", "a+") do |f|
f.write("Hello, World")
end
File modes:
r - read only, from the beginning
r+ - read/write, from the beginning
w - write only, truncates existing file or creates new
w+ - read/write, truncates existing file or creates new
a - write only, appends to existing file or creates new
a+ - read/write, appends to existing file or creates new
Opening and closing manually:
f = File.open("/tmp/my_file2.txt", "a+")
f.write "Ruby"
f.close
Reading a file:
File.read("/tmp/my_file2.txt")
File.open("/tmp/my_file2.txt", "r+") do |file|
arr = file.readlines
puts arr.inspect
end
Other file operations:
File.rename("/tmp/my_file2.txt", "/tmp/my_file.txt")
File.size("/tmp/my_file.txt")
File.exists?("/tmp/my_file.txt")
File.extname("/tmp/my_file.txt")
File.dirname("/tmp/my_file.txt")
Directory operations:
Dir.glob("*") # list all files
Dir.glob("*.rb") # list Ruby files
Using FileUtils for shell-like operations:
require 'fileutils'
FileUtils.touch("/tmp/my_file.txt")
FileUtils.pwd()
FileUtils.mkdir("temp_dir")