Last active
October 15, 2025 14:00
-
-
Save cleicar/a7f30af76f063ae02d3e82fb14e3e32a to your computer and use it in GitHub Desktop.
Ruby 3.0 to 3.3 Notable Changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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 | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # -------------------------------------------------------------------------------------------- | |
| # 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