-->
2018-05-26
Then they come up to me and say, “I was surprised by this feature of the language, so therefore Ruby violates the principle of least surprise.” Wait. Wait. The principle of least surprise is not for you only. The principle of least surprise means principle of least my surprise. And it means the principle of least surprise after you learn Ruby very well.
- Yukihiro Matsumoto [source]
Here’s a grab bag of patterns I’ve picked up over 4 years of working with Ruby.
—
||=
)Commonly used to conditionally assign a variable whose current value is nil. Takes advantage of the fact that nil
evaluates as false
in Ruby.
foo = nil
foo ||= 1 #=> 1
foo ||= 2 #=> 1
foo == 1 #=> true
See also: &&=
.
One of the less intuitive patterns in Ruby is &:symbol
, which is shorthand for Symbol#to_proc
(documentation).
[1, 2, 3].map(&:to_s) #=> ["1", "2", "3"]
[1, 2, 3].select(&:even?) #=> [2]
# equivalent to the above:
[1, 2, 3].map { |i| i.to_s } #=> ["1", "2", "3"]
[1, 2, 3].select { |i| i.even? } #=> [2]
Prefixing an object with &
calls to_proc
on that object. Symbol#to_proc
returns a Proc
that accepts a single argument and then calls the method named by the symbol on that argument.
Struct
# Variant 1
Car = Struct.new(:make, :model)
rental = Car.new('Honda', 'CRV')
rental.make #=> 'Honda'
rental.model #=> 'CRV'
# Variant 2
require 'date'
class Person < Struct.new(:name, :birthdate)
def birth_year
birthdate.year
end
end
bob = Person.new('Bob', Date.new(1984, 01, 02))
bob.birth_year #=> 1984
Inheriting from a Struct can save you some of the boilerplate associated with reading and writing attributes (documentation).
Here’s an example of the *
(splat) operator used for array expansion:
# Array expansion
a = [1, 2]
b = [0, *a] # => [0, 1, 2]
def foo(a, b)
"#{a}, #{b}"
end
c = [1, 2]
foo(*c) #=> "1, 2]
# Double splat
def bar(a:, b:)
"#{a}, #{b}"
end
d = { a: 1, b: 2 }
bar(**d) #=> "1, 2"
This is just a taste of how splats can be used. See this great post for more.
&&
vs and
, ||
vs or
A common pitfall for those with a Python background is misuse of the and
, or
keywords. At first glance they appear to have the same behavior as &&
and ||
, but they actually have lower precedence which can lead to surprising behavior. Here’s a demo:
x = true && false #=> false
x #=> false
y = true and false #=> false
y #=> true
Note that x
is false and y
is true. Take a look at the ruby operator precedence docs. The or
, and
operations are lower than =
, which is lower than ||
and &&
. If you’re unfamiliar with programming language operator precedence think of PEMDAS. When I get confused about precedence, I like to wrap operations in parenthesis so I can visualize the order of operations. Here’s the above example with parenthesis added to show order of operations:
# `&&` has a higher precedence than `=`, so its evaluated first
x = (true && false)
# `=` has a higher precedence than `end`, so its evaluated first
(y = true) and false
Why does Ruby have these super low precedence boolean operators? They’re sometimes used for program control flow. Check out the render and return Rails pattern.
class Foo
class << self
def bar
1
end
end
end
Foo.bar #=> 1
You probably know <<
as the shovel operator, useful for inserting objects into arrays ([1, 2] << 3
). The class << self
notation and the jargon associated with it (eigenclass, singleton class, metaclass) can be pretty obscure. A good starting point is to know that this syntax lets you implement class methods. My above example can be rewritten as the slightly more familiar:
class Foo
def self.bar
1
end
end
Foo.bar #=> 1
There’s a lot to discuss on this subject but I want to focus on a very practical question: when do Rubyists use one form over the other? The key for me is @@
. You’ve probably seen these used for “class variables.”
Here’s an abstract example demonstrating the pitfalls of using @@
for class variables:
class ParentClass
def self.set_count(count)
@@count = count
end
def self.count
@@count
end
end
ParentClass.set_count(3)
ParentClass.count #=> 3
class ChildClass < ParentClass; end
ChildClass.set_count(10)
ParentClass.count #=> 10
Did you expect calling set_count
on the Child
class to update the count in the Parent
class? @@
variables aren’t class variables, they’re class heirarchy variables. They almost behave more like global variables across the class hierarchy than as a class variable.
So what do you do if you want class variable semantics in ruby?
class ParentClass
class << self
def set_count(count)
@count = count
end
def count
@count
end
end
end
ParentClass.set_count(3)
ParentClass.count #=> 3
class ChildClass < ParentClass; end
ChildClass.set_count(10)
ParentClass.count #=> 3
ChildClass.count #=> 10
There’s a ton of great content out there explaining eigenclasses. Here’s a good place to start: https://www.devalot.com/articles/2008/09/ruby-singleton.