Ruby 3.0 (w/ rvm)
> rvm get head
> rvm install 3.0
> rvm use 3.0
> ruby --version
ruby 3.0.0p0 (2020-12-25 revision
95aff21468) [x86_64-darwin19]
Slide 9
Slide 9 text
Rightward Assignments
1 42 => answer
2 p answer #=> 42
3
4 {first: "John", last: "Doe"} => {first:,last:}
5 p first #=> "John"
6 p last #=> "Doe"
Slide 10
Slide 10 text
Find Pattern
1 case ["a", 1, "b", "c", 2, "d", "e", "f", 3]
2 in [*pre, String => x, String => y, *post]
3 p pre #=> ["a", 1]
4 p x #=> "b"
5 p y #=> "c"
6 p post #=> [2, "d", "e", "f", 3]
7 end
Slide 11
Slide 11 text
Endless Method
Definitions
1 def square(x) = x * x
2
3 puts "7 square = #{square(7)}"
Slide 12
Slide 12 text
Ractor
• The feature formerly known as
“guilds”.
Slide 13
Slide 13 text
Ractor
• Ractor is an Actor-model like
concurrent abstraction.
Slide 14
Slide 14 text
Concurrency vs.
Parallelism
Slide 15
Slide 15 text
Concurrency vs.
Parallelism
Fibers
Threads
Ractors
Slide 16
Slide 16 text
Ractor
• Ractor is designed to provide
a parallel execution feature
of Ruby without thread-safety
concerns.
Slide 17
Slide 17 text
Ractor
• Ractor does not use the GVL.
Each ractor has its own GVL,
so you can have multiple
threads within your ractor.
Slide 18
Slide 18 text
What is the GVL?
(Global VM Lock)
• “The Ruby Virtual Machine is
not internally thread-safe,
[…] so we use a global lock
around it so that only one
thread can access it in
parallel.” Nate Berkopec
Ractor by design:
“… without thread-safety concerns”
• Shareable objects
• Unshareable objects
Slide 22
Slide 22 text
Threads (This is OK)
1 GOOD = 'good'.freeze
2 BAD = 'bad'
3
4 t = Thread.new do
5 puts "GOOD=#{GOOD}"
6 puts "BAD=#{BAD}"
7 end
8
9 t.join
GOOD=good
BAD=bad
Slide 23
Slide 23 text
1 GOOD = 'good'.freeze
2 BAD = 'bad'
3
4 r = Fiber.new do
5 puts "GOOD=#{GOOD}"
6 puts "BAD=#{BAD}"
7 end
8
9 r.resume
GOOD=good
BAD=bad
Fibers (This is OK)
Slide 24
Slide 24 text
1 GOOD = 'good'.freeze
2 BAD = 'bad'
3
4 r = Ractor.new do
5 puts "GOOD=#{GOOD}"
6 puts "BAD=#{BAD}"
7 end
8
9 r.take
Ractor (This is NOT OK)
Slide 25
Slide 25 text
:267: warning: Ractor is experimental, and the behavior may change
in future versions of Ruby! Also there are many implementation issues.
GOOD=good
# terminated with exception (report_on_exception is
true):
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
:694:in `take': thrown by remote Ractor. (Ractor::RemoteError)
from constants_with_ractor.rb:9:in `'
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
Ractor
Slide 26
Slide 26 text
:267: warning: Ractor is experimental, and the behavior may change
in future versions of Ruby! Also there are many implementation issues.
GOOD=good
# terminated with exception (report_on_exception is
true):
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
:694:in `take': thrown by remote Ractor. (Ractor::RemoteError)
from constants_with_ractor.rb:9:in `'
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
Ractor
Slide 27
Slide 27 text
:267: warning: Ractor is experimental, and the behavior may change
in future versions of Ruby! Also there are many implementation issues.
GOOD=good
# terminated with exception (report_on_exception is
true):
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
:694:in `take': thrown by remote Ractor. (Ractor::RemoteError)
from constants_with_ractor.rb:9:in `'
constants_with_ractor.rb:6:in `block in ': can not access non-shareable objects
in constant Object::BAD by non-main Ractor. (Ractor::IsolationError)
Ractor
Slide 28
Slide 28 text
When and why might Ractors be
better than existing Ruby threads?
• “The lack of GVL allows them
to fully use more cores. So in
cases where Ruby can't easily
use all your cores, Ractors
can be better than threads.”
Noah Gibbs
Slide 29
Slide 29 text
Ractor:
Shareable vs. Unshareable Objects
• Shareable objects:
• Immutable objects (frozen objects only
refer to shareable objects)
• Class/module objects
• Special shareable objects (Ractor
objects, and so on)
• Unshareable objects: Everything else
Slide 30
Slide 30 text
Pattern Matching
• You might be familiar with the case/when flow:
1 object = "a string"
2
3 case object
4 when String
5 puts "object is a String"
6 else
7 puts "object is not a String"
8 end
$ ruby case_when.rb
object is a String
Slide 31
Slide 31 text
Pattern Matching
• Ruby 3.0 introduces the case/in flow which uses pattern matching:
1 # example based on https://docs.ruby-lang.
2 org/en/3.0.0/doc/syntax/pattern_matching_rdoc.
3 html
4
5 def connect(config)
6 case config
7 in db: {user:} # matches subhash and puts
8 matched value in variable user
9 puts "Connect with user '#{user}'"
10 in connection: {username: }
11 puts "Connect with connection and user '#{username}'"
12 else
13 puts "Unrecognized structure of config"
14 end
15 end
16
17 connect(db: {user: 'admin', password: ‘abc123'})
$ ruby case_in.rb
Connect with user 'admin'
Slide 32
Slide 32 text
• any Ruby object (matched by the === operator, like in when); (Value pattern)
• array pattern: [, , , ...]; (Array
pattern)
• find pattern: [*variable, , , , ...,
*variable]; (Find pattern)
• hash pattern: {key: , key: , ...}; (Hash pattern)
• combination of patterns with |; (Alternative pattern)
• variable capture: => variable or variable; (As pattern, Variable
pattern)
Patterns can be:
Slide 33
Slide 33 text
Variable Binding
1 case [17, 76]
2 in Integer => a, Integer => b
3 puts "a=#{a}, b= #{b}"
4 else
5 puts "not matched"
6 end
7
8 case {a: 1, b: 7, c: 76}
9 in a: Integer => m, b: Integer => n, c: Integer => o
10 puts "m: #{m}, n: #{n}, o: #{o}, "
11 else
12 "not matched"
13 end
$ ruby binding.rb
a=17, b= 76
m: 1, n: 7, o: 76,
Slide 34
Slide 34 text
You can even use it in an if as a “one-liner”:
1 first_name = "John"
2
3 if {first_name: first_name} in {first_name: "John"}
4
5 puts "First name is John!"
6 end
7
8 if {first_name: first_name} in {first_name: "Bob"}
9
10 puts "First name is Bob!"
11 else
12 puts "First name is not Bob!"
13 end
if_in.rb:3: warning: One-line pattern matching is experimental, and the behavior may change in future versions
of Ruby!
if_in.rb:7: warning: One-line pattern matching is experimental, and the behavior may change in future versions
of Ruby!
First name is John!
First name is not Bob!
Slide 35
Slide 35 text
If you want to skip the warnings:
1 Warning[:experimental] = false
Slide 36
Slide 36 text
Pattern Matching: Advanced Example
Slide 37
Slide 37 text
Pattern Matching: Poker Hand
Slide 38
Slide 38 text
Elixir-like Method
Overloading Not Supported:
1 def process(%{"animal" => animal}) do
2 IO.puts("The animal is: #{animal}")
3 end
4
5 def process(%{"plant" => plant}) do
6 IO.puts("The plant is: #{plant}")
7 end
8
9 def process(%{"person" => person}) do
10 IO.puts("The person is: #{person}")
11 end
•
Slide 39
Slide 39 text
Ruby 3x3
• “The goal is to make Ruby 3
run three times faster as
compared to Ruby 2.0.” *
Matz, 2016 (source)
Slide 40
Slide 40 text
Ruby 3x3
Slide 41
Slide 41 text
What is a JIT?
• JIT stands for “just-in-time”
compilation. A JIT allows an
interpreted language such as
Ruby to optimize frequently
run methods so they run faster
for future calls.
Slide 42
Slide 42 text
Ruby’s MJIT
• The M is for methods
• It requires more memory to keep
its method operations cache
• It has been available since
Ruby 2.6 and it has been
optimized on Ruby 3.0
Slide 43
Slide 43 text
JIT: How do I use it?
# enabled.rb
1 puts "MJIT is enabled: #{RubyVM::MJIT.enabled?}”
$ ruby --jit enabled.rb
MJIT is enabled: true
$ ruby enabled.rb
MJIT is enabled: false
Slide 44
Slide 44 text
JIT: How do I use it in
Rails?
$ RUBYOPT=--jit rails server
Slide 45
Slide 45 text
Ruby 3x3
Slide 46
Slide 46 text
Ruby 3x3
Slide 47
Slide 47 text
Memory:
Automatic Garbage Compaction
• You no longer have to manually
trigger the garbage compactor
with:
1 GC.compact # available since Ruby 2.6
2
3 puts “Yay, a tidier heap sparks joy!"
•
Slide 48
Slide 48 text
Memory:
Automatic Garbage Compaction
• Most objects will be
automatically transferred to
the heap and compacted to
improve memory usage and
performance
Slide 49
Slide 49 text
Concurrency / Parallelism
• “It’s multi-core age today.
Concurrency is very important.
With Ractor, along with Async
Fiber, Ruby will be a real
concurrent language.”
Matz
Slide 50
Slide 50 text
Fiber Scheduler
• It can be used to intercept
blocking operations and yield.
Slide 51
Slide 51 text
Fiber vs Thread
• Fibers are more lightweight.
They eat less memory.
• The OS decides when to run/
pause your threads.
• You have more control over
when to run/pause your fibers.
Fiber Scheduler
3 Thread.new do # in this thread, we'll have non-blocking fibers
4
5 Fiber.set_scheduler Scheduler.new
6
7 %w[2.6 2.7 3.0].each do |version|
8 Fiber.schedule do # Runs block of code in a separate Fiber
9
10 t = Time.now
11 # ...
16 Net::HTTP.get('rubyreferences.github.io', "/rubychanges/#{version}.html")
19 puts '%s: finished in %.3f' % [version,
20 Time.now - t]
21 end
22 end
23 end.join # At the END of the thread code, Scheduler will be called to dispatch
24
25 # all waiting fibers in a non-blocking manner
26
27
28 puts 'Total: finished in %.3f' % (Time.now -
29 start)
30
31 # Prints:
32 # 2.6: finished in 0.139
33 # 2.7: finished in 0.141
34 # 3.0: finished in 0.143
35 # Total: finished in 0.146
Slide 54
Slide 54 text
Don’t Wait For Me! Scalable Concurrency
for Ruby 3 by Samuel Williams
Slide 55
Slide 55 text
Fiber Scheduler (Why?)
• You don’t have to manually yield.
• You end up with cleaner code in
your fibers.
• There is a scheduler implementation
in the `async` gem. (source)
Slide 56
Slide 56 text
Static Analysis
• “Ruby seeks the future with
static type checking, without
type declaration, using abstract
interpretation. RBS & TypeProf
are the first step to the
future. More steps to come.”
Matz
Slide 57
Slide 57 text
Static Analysis: RBS
• RBS is a language to describe
the structure of Ruby
programs.
• Ruby 3.0 ships with the `rbs`
gem which includes the `rbs`
CLI command.
Slide 58
Slide 58 text
Static Analysis: RBS
1 # test.rb
2 class User
3 def initialize(name:, age:)
4 @name, @age = name, age
5 end
6
7 attr_reader :name, :age
8 end
9
10 User.new(name: "John", age: 38)
Static Analysis: RBS
$ rbs help
Usage: rbs [options...] [command...]
Available commands: ast, list, ancestors, methods, method, validate, constant,
paths, prototype, vendor, parse, test, version, help.
Options:
-r LIBRARY Load RBS files of the library
-I DIR Load RBS files from the directory
--no-stdlib Skip loading standard library signatures
--repo DIR Add RBS repository
--log-level LEVEL Specify log level (defaults to `warn`)
--log-output OUTPUT Specify the file to output log (defaults
to stderr)
Slide 61
Slide 61 text
TypeProf
• TypeProf is a type analysis
tool bundled in the Ruby 3.0
package.
Slide 62
Slide 62 text
TypeProf
• It reads plain (non-type-
annotated) Ruby code, analyzes
what methods are defined and
how they are used, and
generates a prototype of type
signature in RBS format.
Static Analysis:
Pragmatic View (Why?)
• Exhaustiveness Checking (Sorbet)
• Automatic Documentation of Public
Method Signatures
Slide 67
Slide 67 text
JIT Improvements
• Significantly decreased the
size of JIT-ed code (less
memory-intensive)
• Still not ready for optimizing
workloads like Rails
Slide 68
Slide 68 text
And more...
• IRB Performance Improvement
• Keyword arguments are separated from
other arguments
• Pattern matching (case/in) is no
longer experimental
• Arguments forwarding now supports
leading arguments
Slide 69
Slide 69 text
Positional and Keyword
Arguments: ArgumentError
1 # This method accepts only a keyword argument
2 def foo(k: 1)
3 p k
4 end
5
6 h = { k: 42 }
7
8 # This method call passes a positional Hash argument
9 # In Ruby 2.7: The Hash is automatically converted to a keyword argument
10 # In Ruby 3.0: This call raises an ArgumentError
11 foo(h)
12 # => demo.rb:11: warning: Using the last argument as keyword parameters is
13 deprecated; maybe ** should be added to the call
14 # demo.rb:2: warning: The called method `foo' is defined here
15 # 42
16
17 # If you want to keep the behavior in Ruby 3.0, use double splat
18 foo(**h) #=> 42
Slide 70
Slide 70 text
Positional and Keyword
Arguments: ArgumentError
• Best case scenario: Many
ArgumentErrors all over the
logs.
• Worst case scenario: Weird,
buggy behavior with Ruby 3.0.
Slide 71
Slide 71 text
Upgrading to
Ruby 3.0
Slide 72
Slide 72 text
Notes
• Upgrade to Ruby 2.7 first. You get slightly better performance
than in 2.6. Also, you will see a bunch of deprecation warnings re:
positional and keyword arguments.
• Fix deprecation warnings re: positional arguments.
• Proactively check that your dependencies work with Ruby 3.0. Maybe
your first OSS contribution?
• Backtraces in Ruby 3.0 behave like in Ruby 2.4: an error message
and the line number where the exception occurs are printed first,
and its callers are printed later
Horizontal Sharding
• Horizontal sharding is when you
split up your database to
reduce the number of rows on
each database server, but
maintain the same schema across
"shards". This is commonly
called “multi-tenant" sharding.
Shard Use Case
1 ActiveRecord::Base.connected_to(role: :reading,
2 shard: :shard_one) do
3 Record.first # lookup record from read replica of shard one
4 end
5
Slide 79
Slide 79 text
Strict Loading
• `config.active_record.strict_loading_by_default`
is a boolean value that either enables or
disables strict_loading mode by default.
Defaults to `false`.
Slide 80
Slide 80 text
Strict Loading
1 class Developer < ApplicationRecord
2 self.strict_loading_by_default = true
3
4 has_many :projects
5 end
6
7 dev = Developer.first
8 dev.projects.first
9 # raises ActiveRecord::StrictLoadingViolationError Exception: Developer is
10 # marked as strict_loading and Project cannot be lazily loaded.
11
12 dev = Developer.includes(:projects).first
13 dev.projects.first
14 # this works
Slide 81
Slide 81 text
Strict Loading: Why?
• It’s a strict way to make sure you don’t end up
with N+1 queries.
Slide 82
Slide 82 text
Delegated Types
• “This is similar to what's called multi-table
inheritance in Django, but instead of actual
inheritance, this approach uses delegation to
form the hierarchy and share responsibilities.”
DHH
• “It’s like ‘single-table inheritance’ but
without inheritance (it uses delegation instead)
and with multiple tables.” Ernesto
Slide 83
Slide 83 text
Delegated Types
1 # Schema: entries[ id, account_id, creator_id,
2 created_at, updated_at, entryable_type,
3 entryable_id ]
4 class Entry < ApplicationRecord
5 belongs_to :account
6 belongs_to :creator
7 delegated_type :entryable, types: %w[ Message Comment ]
8
9 end
10
11 # To be included in delegated classes
12 module Entryable
13 extend ActiveSupport::Concern
14
15 included do
16 has_one :entry, as: :entryable, touch: true
17 end
18 end
Slide 84
Slide 84 text
Delegated Types
1 # Schema: messages[ id, subject ]
2 class Message < ApplicationRecord
3 include Entryable
4 has_rich_text :content
5 end
6
7 # Schema: comments[ id, content ]
8 class Comment < ApplicationRecord
9 include Entryable
10 end
Slide 85
Slide 85 text
Delegated Types: Why?
• In case you need more flexibility and single-
table inheritance doesn’t seem like the best way
to go.
Destroy Associations
(Async)
1 class Account < ApplicationRecord
2 belongs_to :supplier, dependent: :destroy_async
3
4 end
5
6 # `:destroy_async` will enqueue a job to
7 destroy associated records in the background.
Slide 88
Slide 88 text
Destroy Async: Why?
• It can save you time (you don’t have to
implement this in your application code) and it
can quickly save you from request time outs
after deleting a “god record”
Slide 89
Slide 89 text
ActiveStorage
• Permanent URLs for public storage blobs.
Services can be configured in `config/
storage.yml` with a new key `public: true |
false` to indicate whether a service holds
public blobs or private blobs. Public
services will always return a permanent URL.
• You can now configure different services for
different attachments. More granular control
for services per attachment.
Slide 90
Slide 90 text
ActiveStorage
1 # If you need the attachment to use a service
2 # which differs from the globally configured one,
3 # pass the +:service+ option. For instance:
4 class User < ActiveRecord::Base
5 has_one_attached :avatar, service: :s3
6 end
Slide 91
Slide 91 text
ActiveStorage: Why?
• It’s good to see some forward progress in
feature development, especially increasing
the flexibility of ActiveStorage.
Slide 92
Slide 92 text
ActiveRecord: Performance
Improvements
• Avoid making queries where the value is an
empty array. (https://github.com/rails/rails/
pull/37266)
• Speed up queries when Rails knows that all
values in the query are integers. (https://
github.com/rails/rails/pull/39009)
Slide 93
Slide 93 text
Classic Autoloader
Deprecated
• New Rails projects discouraged from using
the classic autoloader.
• The default autoloader will be `zeitwerk`
for all new Rails applications.
Slide 94
Slide 94 text
Disallowed Deprecation
Support
• It allows the configuration of rules to
match deprecation warnings that should not be
allowed within the app.
Disallowed Deprecation
Support
1 if Rails.env.production?
2 ActiveSupport::Deprecation.disallowed_behavior = [:log]
3 else
4 ActiveSupport::Deprecation.disallowed_behavior = [:raise]
5 end
Slide 97
Slide 97 text
Disallowed Deprecation
Support: Why?
• When we eliminate deprecation warnings that
appear in our app we want to be sure that the
deprecations are never re-introduced. This
will be very useful in future Ruby/Rails
upgrade projects.
Slide 98
Slide 98 text
Upgrading to
Rails 6.1
Slide 99
Slide 99 text
Notes
• Upgrade to Rails 6.0 first
• Address all deprecation warnings in the Rails 6.0 test and
production logs
• Proactively check that our dependencies work with Rails 6.1: Maybe
your first OSS contribution? Check your Gemfile.lock for
incompatibilities by using RailsBump
• Focus on “Removals” in the Rails 6.1 release notes. Most notable:
Remove deprecated ActiveRecord::Base#update_attributes and
ActiveRecord::Base#update_attributes!