Skip to content

Instantly share code, notes, and snippets.

@cleicar
Last active October 15, 2025 14:00
Show Gist options
  • Save cleicar/a7f30af76f063ae02d3e82fb14e3e32a to your computer and use it in GitHub Desktop.
Save cleicar/a7f30af76f063ae02d3e82fb14e3e32a to your computer and use it in GitHub Desktop.
Ruby 3.0 to 3.3 Notable Changes
# --------------------------------------------------------------------------------------------
# Anonymous Argument Forwarding (Ruby 3.0)
# --------------------------------------------------------------------------------------------
# It allows us to forward all arguments to another method without having to list them all out.
# --------------------------------------------------------------------------------------------
# When introduced in 2.7 it was experimental and was able to forward only all-or-nothing.
# --------------------------------------------------------------------------------------------
def pass_it_along(...)
you_take_care_of_it(...)
end
def you_take_care_of_it(*args, **kwargs, &block)
puts [args, kwargs, block.()]
end
pass_it_along("hello", abc: 123) { "I'm not a blockhead!" }
# => ["hello"] {"abc"=>123} "I'm not a blockhead!"
# It turned out to be not enough.
# This did limit the ability to add anything extra in method definitions or invocations
# --------------------------------------------------------------------------------------------
# Since Ruby 3.0 we can prefix now with positional arguments if we wish,
# for cases where we need to do something with those values before forwarding them.
# --------------------------------------------------------------------------------------------
def pass_it_along(str, ...)
you_take_care_of_it(str.upcase, ...)
end
def you_take_care_of_it(*args, **kwargs, &block)
puts [args, kwargs, block.()]
end
pass_it_along("hello", abc: 123) { "I'm not a blockhead!" }
#=> ["HELLO"] {"abc"=>123} "I'm not a blockhead!"
# --------------------------------------------------------------------------------------------
# Then Ruby 3.1 gave us an “anonymous” block operator
# --------------------------------------------------------------------------------------------
def block_party(&)
lets_party(&)
end
def lets_party
puts "Oh yeah, #{yield}!"
end
block_party { "baby" }
# => "Oh yeah, baby!"
# --------------------------------------------------------------------------------------------
# Then Ruby 3.2 gave us anonymous positional and keyword forwarding as well,
# using the splat operator
# --------------------------------------------------------------------------------------------
def pass_it_along(*, **)
you_take_care_of_it(*, **)
end
def you_take_care_of_it(*args, abc: 0)
puts "#{args} #{abc}"
end
pass_it_along("hello", abc: 123)
# --------------------------------------------------------------------------------------------
# Data vs Struct (Ruby 3.2)
# --------------------------------------------------------------------------------------------
# In Ruby 3.2 Data was introduced as a new core class to represent simple immutable value object.
# Usefull for the same scenarios of Struct, where can create mutable objects.
Measure = Data.define(:amount, :unit)
distance = Measure.new(100, 'km')
puts distance
#=> #<data Measure amount=100, unit="km">
weight = Measure.new(amount: 50, unit: 'kg')
puts weight
#=> #<data Measure amount=50, unit="kg">
puts weight.with(amount: 40)
#=> #<data Measure amount=40, unit="kg">
puts weight.amount
#=> 50
puts weight.amount = 40
#=> NoMethodError: undefined method `amount=' for an instance of Measure
# --------------------------------------------------------------------------------------------
# Extending the Data class
# --------------------------------------------------------------------------------------------
# We can also create a new class that extends Data.define and add methods to it:
class Project < Data.define(:title, :description)
STATUS = %i[draft in_progress completed].freeze
def cover
"#{title} #{description}"
end
def status = :draft
def success? = status == :completed
end
# we can use keyword arguments
project = Project.new(title: "Title", description: "Example description")
puts project.cover
# => Title Example description
puts project.status
# =>draft
puts project.success?
# => false
# --------------------------------------------------------------------------------------------
# Endless method definition (Ruby 3.0)
# --------------------------------------------------------------------------------------------
# Methods of exactly one statement now can be defined with the following syntax:
def square(x) = x * x
# --------------------------------------------------------------------------------------------
# This will give us much cleaner/shorter classes.
# --------------------------------------------------------------------------------------------
# Before
module ApplicantPortal
class User < ApplicantPortal::ApplicationRecord
def active?
true
end
def active_session
customer_sessions.active.last
end
def portal_access?
true
end
end
end
# Now
module ApplicantPortal
class User < ApplicantPortal::ApplicationRecord
def active? = true
def active_session = customer_sessions.active.last
def portal_access? = true
end
end
# --------------------------------------------------------------------------------------------
# Pattern Matching (Ruby 3.0)
# --------------------------------------------------------------------------------------------
# It allows us to match against a pattern and extract the values from that pattern. Representes a more declarative way to destructure arrays and hashes.
# Is basically the action of creating patterns to which certain data must conform (or ‘fit’) and
# then verifying whether such conformity to the described pattern has occurred.
# --------------------------------------------------------------------------------------------
# When introduced in 2.7 it was experimental and now is considered stable.
# --------------------------------------------------------------------------------------------
case { name: "Alice", age: 30 }
in { name:, age: }
puts "#{name} is #{age} years old"
end
case [1, 2, 3]
in [Integer, *]
"matched"
else
"not matched"
end
#=> "matched"
data = { a: 1, b: 2, c: 3 }
case data
in { a: Integer, ** }
"matched"
else
"not matched"
end
#=> "matched"
# --------------------------------------------------------------------------------------------
# Short Hash & Method Syntax (Ruby 3.1)
# --------------------------------------------------------------------------------------------
# It’s a cleaner way to write keyword arguments and hashes, allowing us to omit values where
# the variable name is the same as the key.
name = "Alice"
age = 30
# Before
user = { name: name, age: age }
# Now
user = { name:, age: }
user.dig(:name)
# => "Alice"
# --------------------------------------------------------------------------------------------
# Also works for method definitions:
# --------------------------------------------------------------------------------------------
# Before
NitroAuth::SessionUser.new(id: id, name: name, email: email, record: self)
# Now
NitroAuth::SessionUser.new(id:, name:, email:, record: self)
# note that we still have control to change the value for any of the arguments
# --------------------------------------------------------------------------------------------
# YJIT - Yet Another Ruby Just-In-Time Compiler (Ruby 3.3)
# --------------------------------------------------------------------------------------------
# YJIT is a new JIT compiler for Ruby 3.3.
# It is a major performance improvement for Ruby applications.
# --------------------------------------------------------------------------------------------
# Compiles Ruby bytecode into native machine code at runtime, significantly improving performance.
# It was Written in C (3.1), then rewritten in Rust (3.3).
# Real performance improvements (5–40% faster)
# Faster startup and better memory efficiency
# Supports more Ruby instructions with every release
# Can be enabled with one environment variable: RUBY_YJIT_ENABLE=1
# --------------------------------------------------------------------------------------------
require "benchmark"
def fib(n)
return n if n < 2
fib(n - 1) + fib(n - 2)
end
Benchmark.bm do |x|
x.report("fib(35):") { fib(35) }
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment