- Keyword arguments
- Exception raising
- Here doc
- Regexp
- Method chaining
- Range to array
- Block arguments
- Struct inheritance
- Inline rescue
- Null object pattern
- Constant scope
- Method missing
- Environment variables
- file paths
- puts multiple lines
- array
- range iteration
- string mutation
Ruby 2.1 introduced required keyword arguments. If a required argument is missing(the arguments defined with a trailing colon are required), Ruby will tell us by raising ArgumentError.
def foo(cake, bar:)
puts bar
end
foo # => ArgumentError: missing keyword: bar
foo(true) # => ArgumentError: missing keyword: bar
foo(true, 'baz') # => ArgumentError: missing keyword: bar
foo(true, bar: 'baz') # => 'baz'
def foo
raise ArgumentError
rescue => e
p e.class
end
The syntax lets you raise a class because it looks better, but actually instances of the exception classes are raised.
Exceptions are classes, classes are constants, and constants can be namespaced.
From the style guide:
raise SomeException, 'message'
# Consistent with `raise SomeException, 'message', backtrace`.
# signals a RuntimeError by default
raise 'message'
Calling raise
without passing any arguments results in Ruby re-raising the last exception.
Never rescue
Exception
, instead rescue the lowest class in the exception hierarchy you need to rescue.Exception
will catch syntax and other compiler issues, which might result in deploying a broken application.
To summarize:
- Bad
begin
something()
rescue Exception => e
...
end
The above code will rescue every exception. e.g. You will suppress Ruby's signals to the operating system.
- Good
begin
something()
rescue => e
...
end
# If you don't specify any exception class Ruby assumes you mean StandartError.
begin
something()
rescue StandartError => e
...
end
# A better way is to rescue specific errors e.g. rescue IOError
text = <<EOM
Foo.
Bar.
EOM
p text # => "Foo.\nBar.\n"
array = [1, 2, <<EOM, 3]
Foo.
Bar.
EOM
p array # => [1, 2, "Foo.\nBar.\n", 3]
A here-document can be used for multi-line comments:
<<-DOC
Warming up --------------------------------------
block
6.784k i/100ms
block to proc 6.014k i/100ms
Calculating -------------------------------------
block 69.994k (± 4.8%) i/s - 352.768k in 5.052372s
block to proc 61.122k (± 3.4%) i/s - 306.714k in 5.024221s
Comparison:
block: 69994.2 i/s
block to proc: 61122.5 i/s - 1.15x slower
DOC
Nevertheless, for multi-line comments, it is common to use:
##
# Usually multi-line comments
# are written this way
Ruby 2.3 introduced heredoc with a tilde:
<<~DOC
Cleaner indentation and no extra spaces printed
DOC
In addition to the match method, Ruby also features a pattern-matching operator,
=~
(equal sign and tilde):
puts 'Match!' if /foo/ =~ 'foo bar'
puts 'Match!' if 'foo bar' =~ /foo/
Where match
and =~
differ from each other is in what they return when there is
a match:
=~
returns the numerical index of the character in the string where the match startedmatch
returns an instance of the class MatchData
For example:
'foo bar foomobile' =~ /foo/ # => 0
/foo/.match('foo bar foomobile') # => #<MatchData "foo">
For a detailed introduction to regular expressions consult chapter 11(regexp
) from the book Well-Grounded Rubyist
.
Return self
in methods that you want to enable chaining.
For example:
class FooBar
def options
@options ||= []
end
def foo(args)
options << args
self
end
def bar(args)
options << args
end
end
FooBar.new().foo(1).foo(2) # => #<FooBar:0x0055a70e45f398 @options=[1, 2]>
FooBar.new().foo(1).foo(2).bar(3) # => [1, 2, 3]
# We cannot chain #bar
FooBar.new().foo(1).foo(2).bar(3).bar(4) # => NoMethodError: undefined method `bar' for [1, 2, 3]:Array
FooBar.new().foo(1).foo(2).bar(3).foo(1) # => NoMethodError: undefined method `foo' for [1, 2, 3]:Array
Array(1..5) # => [1, 2, 3, 4, 5]
(1..5).to_a # => [1, 2, 3, 4, 5]
[*1..5] # => [1, 2, 3, 4, 5]
- Some common ways to pass a block to a method are:
def foo(number, &block)
p number
block.call
end
foo(1) { p 'bar' }
foo 1 do
p 'bar'
end
foo(1) do
p 'bar'
end
- There are two ways to incorporate blocks
- implicit
def block_method return unless block_given? yield end
- explicit
def block_method(&the_block) return if the_block.nil? the_block.call end
The explicit way is more direct.
rescue
has a single line form.
foo = { bar: 1 }
foo[:bar].capitalize # => NoMethodError: undefined method `capitalize' for 1:Fixnum
foo[:bar].capitalize rescue 'I cannot capitalize this!!' # => "I cannot capitalize this!!"
In object-oriented computer programming, a Null Object is an object with no referenced value or with defined neutral ("null") behavior. The Null Object design pattern describes the uses of such objects and their behavior (or lack thereof).
Read more in wikipedia.
nil
values force us to use conditionals to check if a variable is nil. The Null Object pattern is based in the Special Case pattern and can be used to clean up some code.
The null object pattern is relatively simple:
- Identify a recurring special case where you check for nil values
- Represent that special case as an object in its own right
Generally, anytime we are checking for the same condition over and over again, that's an indication that we should think of creating a new Object for that condition.
Constants are any reference that begins with uppercase, including classes and modules. The constant scope rules are different than variable scope rules.
module ScopeModule
FOO = 'Outer foo'
class ScopeClass
p FOO # => Outer foo
FOO = 'Inner foo'
p FOO # => Inner foo
end
p FOO # => Outer foo
end
You can avoid overriding variables inside a block with this technique:
arr = [1, 2]
foo = 42
arr.each do |bar;foo|
foo = 10
p bar, foo
end
# it will print 1, 10 and 2, 10
p foo # => 42
Nevertheless, it is better to use proper variables names than the above technique.
method_missing
will capture any method call for which the receiver has no predefined method. You can override this method to add custom functionality.
class ComputerVoice
def speak(str)
p "Hello #{str}!"
end
def method_missing(method_id)
speak method_id
end
end
foo = ComputerVoice.new
foo.what # => "Hello what!"
foo.asd # => "Hello asd!"
You can set the environment variable in a terminal:
export FOO='test'
Now you have access to it in your irb:
ENV['FOO'] # => "test"
Setting environment variables in a terminal will last for that session only. To set it permanently add the command to your
~/.profile
file of your machine.
Ruby implements a File::expand_path
method which you can use to obtain the full path to the desired file.
For example let's suppose we want the views/hello_world.erb
path and we have a file structure like this:
my_framework/
├── Gemfile
├── Gemfile.lock
├── config.ru
├── hello_world.rb
└── views/
├── index.erb
├── hello_world.erb
└── not_found.erb
In this case we could use the File::expand_path
method.
path = File.expand_path("../hello_world.rb", __FILE__)
# => "/home/user/my_framework/views/hello_world.erb"
Above we can see that __FILE__
returns the relative path to the current file(index.erb
). This will give us the path /path/to/my_framework/index.erb
.
So we use this path as a starting point to find the full path.
You can use puts
to print strings in different lines:
puts " ", "Hello!", "Hello World!"
<<~OUTPUT
Hello!
Hello World!
=> nil
OUTPUT
We can create an Array with specified size and initialized with a value:
Array.new(9, 0)
=> [0, 0, 0, 0, 0, 0, 0, 0, 0]
We can use the #step
method to iterate over a range:
range = (1..10)
range.step(2) { |x| puts x } # => 1, 3, 5, 7, 9
puts
range.step(3) { |x| puts x } # => 1, 4, 7, 10
arr = []
s = 'abcd'
arr << s
s[0], s[1] = s[1], s[0]
p arr # => ["bacd"]
arr = []
s = 'abcd'
arr << s.dup
s[0], s[1] = s[1], s[0]
p arr # => ["abcd"]