Variables & Assignment
Variable assignment
let count = 0;
const greeting = "Hello";
console.log(count);
console.log(greeting); count = 0
greeting = "Hello"
puts count
puts greeting Ruby has no
let, const, or var. Variables are created on first assignment. All local variables are mutable — Ruby has no equivalent of const for local variables (constants are a separate concept using UPPER_CASE).Constants
const MAX_RETRIES = 3;
const API_URL = "https://example.com";
console.log(MAX_RETRIES);
console.log(API_URL); MAX_RETRIES = 3
API_URL = "https://example.com"
puts MAX_RETRIES
puts API_URL Ruby constants start with an uppercase letter (SCREAMING_SNAKE_CASE by convention). Unlike JavaScript's
const, Ruby constants can be reassigned — Ruby issues a warning but does not raise an error. For true immutability, freeze the value: LIMIT = 100.freeze.Multiple assignment
const [first, second, ...rest] = [1, 2, 3, 4, 5];
console.log(first);
console.log(second);
console.log(rest); first, second, *rest = [1, 2, 3, 4, 5]
puts first
puts second
puts rest.inspect Ruby calls this "parallel assignment" or "multiple assignment." The
* splat captures remaining elements — equivalent to JavaScript's rest operator .... The splat can appear anywhere in the pattern, not just at the end.nil instead of null/undefined
let value = null;
console.log(value === null);
console.log(typeof undefined); value = nil
puts value.nil?
puts value.inspect Ruby has only
nil — there is no undefined. Accessing an uninitialized local variable raises NameError immediately. nil is an object (NilClass) with its own methods, including nil?, to_a (returns []), and to_s (returns "").Types & Truthiness
Falsy values
if (0) { console.log("JS: 0 is truthy"); }
else { console.log("JS: 0 is falsy"); }
if ("") { console.log("JS: '' is truthy"); }
else { console.log("JS: '' is falsy"); } if 0 then puts "Ruby: 0 is truthy"
else puts "Ruby: 0 is falsy"
end
if "" then puts "Ruby: '' is truthy"
else puts "Ruby: '' is falsy"
end In Ruby, only
false and nil are falsy. Everything else — 0, "", [], {} — is truthy. This is a frequent source of bugs for developers coming from JavaScript, where 0, "", null, undefined, and NaN are all falsy.Type checking
console.log(typeof 42);
console.log(typeof "hello");
console.log(typeof true); puts 42.is_a?(Integer)
puts "hello".is_a?(String)
puts true.is_a?(TrueClass) Ruby uses
is_a? (alias: kind_of?) to check types. There is no typeof operator. Every Ruby value is an object with a .class method — 42.class returns Integer, nil.class returns NilClass.Inspecting an object's class
console.log([1, 2].constructor.name);
console.log({}.constructor.name);
console.log((42).constructor.name); puts [1, 2].class
puts({}.class)
puts 42.class In Ruby,
.class returns the actual class object. Every value is an instance of a class: arrays are Array, plain hashes are Hash, and integers are Integer. Unlike JavaScript, there is no distinction between primitive types and objects — everything in Ruby is an object.Type conversion
console.log(Number("42"));
console.log(String(100));
console.log(Math.floor(3.14)); puts "42".to_i
puts 100.to_s
puts 3.14.to_i Ruby uses method-based conversion:
to_i (to integer), to_f (to float), to_s (to string), to_a (to array). There is no implicit coercion between types — "3" + 3 raises TypeError rather than producing "33" or 6.Strings
String interpolation
const name = "World";
console.log(`Hello, ${name}!`); name = "World"
puts "Hello, #{name}!" Ruby uses
"..." with #{} for interpolation. Single-quoted strings ('...') do not interpolate — 'Hello, #{name}' is literal text. In Ruby 4.0, string literals are frozen by default; use +"mutable" or String.new("mutable") to get a mutable string.Common string methods
const text = "Hello, World!";
console.log(text.toUpperCase());
console.log(text.includes("World"));
console.log(text.length); text = "Hello, World!"
puts text.upcase
puts text.include?("World")
puts text.length Ruby string methods use
snake_case rather than camelCase: upcase/downcase instead of toUpperCase/toLowerCase, include? instead of includes. Methods that return a boolean conventionally end with ?.String slicing
const text = "Hello, World!";
console.log(text.slice(7, 12));
console.log(text.slice(-1)); text = "Hello, World!"
puts text[7, 5]
puts text[-1] Ruby's
string[start, length] takes a start index and a length (not an end index), unlike JavaScript's slice(start, end). string[range] with a Range object also works. Negative indices count from the end in both languages.Multiline strings & heredocs
const message = `Line one
Line two
Line three`;
console.log(message); message = <<~MSG
Line one
Line two
Line three
MSG
puts message Ruby's
<<~HEREDOC (squiggly heredoc) strips leading whitespace, so the content can be indented with the surrounding code. The closing delimiter can be any word. Ruby also supports regular multiline strings using "...", but heredocs are idiomatic for longer text.split and join
const words = "one two three".split(" ");
console.log(words.join("-")); words = "one two three".split(" ")
puts words.join("-") Ruby's
split and join work identically to JavaScript's — these are among the smoothest transitions between the two languages. split with no arguments splits on whitespace and removes empty strings, like Python's default split behavior.Symbols
Symbols
const status = Symbol("active");
console.log(status.toString());
console.log(typeof status); status = :active
puts status
puts status.class Ruby symbols (
:name) are immutable, interned objects — :active always refers to the exact same object in memory, making them fast to compare. They are lighter than strings and widely used as hash keys, method names, and identifiers. Unlike JavaScript's Symbol(), Ruby symbols with the same name are identical: :foo.equal?(:foo) is always true.Symbols as hash keys
const person = { name: "Alice", age: 30 };
console.log(person.name);
console.log(person["age"]); person = { name: "Alice", age: 30 }
puts person[:name]
puts person[:age] Ruby's
{ key: value } hash syntax creates symbol keys (e.g., :name), but accessing them requires bracket notation: hash[:name] — not hash.name. This surprises JavaScript developers who expect dot-access on plain hashes.Symbol#to_proc shorthand
const words = ["hello", "world"];
console.log(words.map(word => word.toUpperCase()).join(", ")); words = ["hello", "world"]
puts words.map(&:upcase).join(", ") &:method_name converts a symbol to a proc that calls that method on each element — shorthand for { |item| item.method_name }. It is Ruby's most concise callback pattern and works with any method that takes no arguments: map(&:to_s), select(&:even?), reject(&:nil?).Arrays
Array basics
const numbers = [1, 2, 3];
console.log(numbers.length);
console.log(numbers[0]);
console.log(numbers[numbers.length - 1]); numbers = [1, 2, 3]
puts numbers.length
puts numbers.first
puts numbers.last Ruby arrays share the same literal syntax as JavaScript. In addition to index access (
numbers[0]), Ruby provides first and last as named methods — these read more clearly when the intent is to access the beginning or end of a collection.push, pop, and append
const items = [1, 2];
items.push(3);
console.log(items);
console.log(items.pop());
console.log(items); items = [1, 2]
items.push(3)
puts items.inspect
puts items.pop
puts items.inspect Ruby's
push and pop work identically to JavaScript's. Ruby also uses << as an alias for push — you will see items << 4 frequently in Ruby code. shift and unshift operate on the front of the array, just as in JavaScript.flatten and compact
const nested = [1, [2, [3]]];
console.log(nested.flat(Infinity));
console.log([1, null, 2, undefined, 3].filter(v => v != null)); nested = [1, [2, [3]]]
puts nested.flatten.inspect
puts [1, nil, 2, nil, 3].compact.inspect flatten removes all nesting (pass a depth integer to limit). compact removes nil values — the Ruby equivalent of filter(v => v != null). Note that compact only removes nil, not false or 0, since those are truthy in Ruby.Slicing arrays
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.slice(1, 3));
console.log(numbers.slice(-2)); numbers = [1, 2, 3, 4, 5]
puts numbers[1, 2].inspect
puts numbers[-2..].inspect Ruby's
array[start, length] takes a length argument (not an end index). array[range] with a Range object also works — the endless range n.. captures from index n to the end. JavaScript's slice(1, 3) (indices 1 and 2) maps to Ruby's [1, 2] (start 1, length 2).Hashes
Hash literals
const config = { host: "localhost", port: 3000 };
console.log(config.host);
console.log(config["port"]); config = { host: "localhost", port: 3000 }
puts config[:host]
puts config[:port] Ruby hashes look like JavaScript objects but require bracket notation for access:
hash[:key]. There is no dot-access on plain hashes. The { key: value } shorthand creates symbol keys — use { "key" => value } if you need string keys instead.Safe key access with fetch
const data = { name: "Alice" };
console.log(data.age ?? "unknown");
console.log(data.name); data = { name: "Alice" }
puts data.fetch(:age, "unknown")
puts data[:name] fetch raises KeyError when the key is missing (unlike [], which returns nil). Passing a second argument provides a default — similar to JavaScript's nullish coalescing ??. You can also pass a block: fetch(:key) { |k| compute_default(k) }.Merging hashes
const defaults = { color: "blue", size: 10 };
const custom = { size: 20 };
console.log({ ...defaults, ...custom }); defaults = { color: "blue", size: 10 }
custom = { size: 20 }
result = defaults.merge(custom)
puts result.inspect merge returns a new hash — when keys overlap, the argument hash wins, just like JavaScript's spread operator. merge! (or update) modifies the receiver in place. Ruby hashes maintain insertion order (since Ruby 1.9), matching JavaScript object behavior.Iterating over hashes
const scores = { alice: 95, bob: 87 };
Object.entries(scores).forEach(([name, score]) => {
console.log(`${name}: ${score}`);
}); scores = { alice: 95, bob: 87 }
scores.each do |name, score|
puts "#{name}: #{score}"
end Ruby hashes have a built-in
each that yields key-value pairs directly — no need for Object.entries. The block receives two parameters: the key and the value. map, select, reject, and other Enumerable methods also work on hashes.Ranges
Range literals
const numbers = Array.from({ length: 10 }, (_, index) => index + 1);
console.log(numbers.join(", "));
console.log(numbers.includes(5)); puts (1..10).to_a.join(", ")
puts (1..10).include?(5) Ruby has a built-in
Range type: 1..10 is inclusive (includes 10), and 1...10 is exclusive (excludes 10). Ranges work on any comparable type: 'a'..'z', Date.today..Date.today + 7. They are lazy — converting to an array with to_a is only needed when you want an actual array.Range step
const evens = Array.from({ length: 5 }, (_, index) => (index + 1) * 2);
console.log(evens.join(", ")); puts (2..10).step(2).to_a.join(", ") Range#step iterates with a custom increment. It returns an Enumerator (lazy), so chaining to_a, map, or each on it is normal. For floating-point steps, use (0.0..1.0).step(0.1).Ranges in case/when
function grade(score) {
if (score >= 90) return "A";
if (score >= 80) return "B";
if (score >= 70) return "C";
return "F";
}
console.log(grade(85)); def grade(score)
case score
when 90..100 then "A"
when 80...90 then "B"
when 70...80 then "C"
else "F"
end
end
puts grade(85) Ruby's
case/when uses === for matching, and Range#=== checks membership. This makes range-based conditionals extremely readable. The exclusive range 80...90 ensures score 90 matches the "A" arm above, not "B".Control Flow
if and unless
const active = false;
if (!active) {
console.log("inactive");
} active = false
unless active
puts "inactive"
end unless is the logical inverse of if — equivalent to if !condition. It reads more naturally when the condition is already negative. Similarly, until is the inverse of while. Avoid double-negatives: prefer unless active over if !active.if as an expression
const x = 10;
const label = x > 5 ? "big" : "small";
console.log(label); x = 10
label = if x > 5 then "big" else "small" end
puts label In Ruby,
if, unless, case, and begin are all expressions — they return the last evaluated value. Assigning an if to a variable is idiomatic Ruby, though the ternary operator ? : is also available for the same pattern.case/when vs switch
const day = "Mon";
let kind;
switch (day) {
case "Mon": case "Tue": case "Wed":
case "Thu": case "Fri":
kind = "weekday"; break;
default:
kind = "other";
}
console.log(kind); day = "Mon"
result = case day
when "Mon", "Tue", "Wed", "Thu", "Fri" then "weekday"
when "Sat", "Sun" then "weekend"
else "unknown"
end
puts result Ruby's
case/when does not fall through between cases — no break is needed. when accepts multiple comma-separated values, ranges, and regular expressions. The whole expression returns a value, so assigning it directly is idiomatic.Guard clauses & trailing if
function process(data) {
if (data === null || data === undefined) return "no data";
return data.toUpperCase();
}
console.log(process(null));
console.log(process("hello")); def process(data)
return "no data" if data.nil?
data.upcase
end
puts process(nil)
puts process("hello") Ruby allows
if and unless as trailing modifiers: return "no data" if data.nil?. This reads as plain English and is idiomatic for guard clauses. The last expression in a method is its return value — no return keyword needed for the happy path.Methods
Defining methods
function greet(name) {
return `Hello, ${name}!`;
}
console.log(greet("Alice")); def greet(name)
"Hello, #{name}!"
end
puts greet("Alice") Ruby methods return the last evaluated expression — no
return keyword is needed for the normal return path (use it only for early returns). Method names use snake_case by convention. Parentheses on method calls are optional when unambiguous.Default parameters
function greet(name = "stranger") {
return `Hello, ${name}!`;
}
console.log(greet());
console.log(greet("Alice")); def greet(name = "stranger")
"Hello, #{name}!"
end
puts greet
puts greet("Alice") Ruby default parameter syntax is identical to JavaScript's. The default can reference earlier parameters or any expression, including method calls. Unlike JavaScript, Ruby evaluates the default expression fresh each time the method is called.
Keyword arguments
function createUser({ name, role = "user" } = {}) {
return `${name} (${role})`;
}
console.log(createUser({ name: "Alice", role: "admin" })); def create_user(name:, role: "user")
"#{name} (#{role})"
end
puts create_user(name: "Alice", role: "admin") Ruby has first-class keyword arguments declared in the method signature with
:. Required keywords (no default) raise ArgumentError if omitted. Callers must use the keyword name — create_user(name: "Alice") — making calls self-documenting without relying on parameter order.Variadic arguments (splat)
function sum(...numbers) {
return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4)); def sum(*numbers)
numbers.sum
end
puts sum(1, 2, 3, 4) Ruby's
*args (splat) is the equivalent of JavaScript's ...args (rest). Inside the method, numbers is an ordinary array. Ruby also has **kwargs for capturing keyword arguments into a hash, and &block for capturing a passed block as a Proc.Predicate and bang methods
function isEmpty(arr) { return arr.length === 0; }
function uppercased(str) { return str.toUpperCase(); }
console.log(isEmpty([]));
console.log(uppercased("hello")); def empty?(collection)
collection.empty?
end
words = ["hello"]
puts empty?([])
puts words.map(&:upcase).join Ruby method names can end with
? (predicates that return boolean, e.g. empty?, nil?) or ! (methods that modify in place or raise instead of returning nil, e.g. sort!, map!). This is a naming convention — both characters are valid in method names.Blocks
Blocks
[1, 2, 3].forEach(number => {
console.log(number * 2);
}); [1, 2, 3].each do |number|
puts number * 2
end A Ruby block is an anonymous chunk of code passed to a method, delimited by
{ |params| ... } (single-line convention) or do |params| ... end (multi-line convention). Unlike JavaScript callbacks, blocks are not objects by default — they are passed implicitly and received by the method via yield.yield
function withLogging(callback) {
console.log("start");
callback();
console.log("end");
}
withLogging(() => console.log("work")); def with_logging
puts "start"
yield
puts "end"
end
with_logging { puts "work" } yield calls the block that was passed to the method. If no block is given, yield raises LocalJumpError. Use block_given? to check whether a block was passed before yielding. yield can pass arguments to the block: yield value passes value to the block parameter.Capturing a block as a Proc
function apply(value, transform) {
return transform(value);
}
console.log(apply(5, n => n * 2)); def apply(value, &transform)
transform.call(value)
end
puts apply(5) { |n| n * 2 } Adding
&block_name as the last parameter captures the block as a Proc object, which can be stored, passed to other methods, or called with .call(). This makes the block explicit and composable. &: also converts a Proc back to a block when passing it to another method.Iterators & Enumerable
map
const numbers = [1, 2, 3];
console.log(numbers.map(n => n * n).join(", ")); numbers = [1, 2, 3]
puts numbers.map { |n| n * n }.join(", ") Ruby's
map (also aliased as collect) works identically to JavaScript's. Both return a new array. map! modifies the array in place. Ruby's Enumerable module provides map and dozens of other iteration methods to any class that implements each.select and reject vs filter
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.filter(n => n % 2 === 0).join(", "));
console.log(numbers.filter(n => n % 2 !== 0).join(", ")); numbers = [1, 2, 3, 4, 5]
puts numbers.select { |n| n.even? }.join(", ")
puts numbers.reject { |n| n.even? }.join(", ") Ruby's
select (also filter) keeps elements where the block is truthy. reject is its complement — it removes elements where the block is truthy, equivalent to filter(x => !condition). Having both makes intent clearer: choose the one that reads more naturally for your condition.reduce / inject
const numbers = [1, 2, 3, 4];
console.log(numbers.reduce((total, n) => total + n, 0)); numbers = [1, 2, 3, 4]
puts numbers.reduce(0) { |total, n| total + n }
puts numbers.reduce(:+) Ruby's
reduce (also inject) puts the accumulator first in the block parameters: |total, element|. With a symbol argument, reduce(:+) is shorthand for reduce { |sum, n| sum + n }. If no initial value is given, the first element is used as the seed.each_with_index
["a", "b", "c"].forEach((item, index) => {
console.log(`${index}: ${item}`);
}); ["a", "b", "c"].each_with_index do |item, index|
puts "#{index}: #{item}"
end Both
each_with_index and JavaScript's forEach yield the element first and the index second. Ruby also provides each.with_index(start) to offset the index counter, and each_with_object for accumulating into a mutable object while iterating.each_with_object
const words = ["cat", "dog", "cow"];
const groups = words.reduce((acc, word) => {
const key = word[0];
acc[key] = (acc[key] || []);
acc[key].push(word);
return acc;
}, {});
console.log(JSON.stringify(groups)); words = ["cat", "dog", "cow"]
groups = words.each_with_object(Hash.new { |h, k| h[k] = [] }) do |word, hash|
hash[word[0]] << word
end
puts groups.inspect each_with_object passes an accumulator as the second block parameter and always returns it — you do not need to return it explicitly from the block. It is cleaner than reduce when building a mutable object. Hash.new { |h, k| h[k] = [] } creates a hash with a default value computed by a block.sort and sort_by
const words = ["banana", "kiwi", "strawberry", "fig"];
console.log(words.slice().sort().join(", "));
console.log(words.slice().sort((a, b) => a.length - b.length).join(", ")); words = ["banana", "kiwi", "strawberry", "fig"]
puts words.sort.join(", ")
puts words.sort_by { |word| word.length }.join(", ") sort_by extracts a sort key from each element — cleaner and faster than sort { |a, b| a.length <=> b.length }. The <=> spaceship operator returns -1, 0, or 1 and is the basis of all Ruby sorting. Ruby's sort does not mutate in place — use sort! for that.flat_map
const sentences = ["hello world", "foo bar"];
console.log(sentences.flatMap(sentence => sentence.split(" ")).join(", ")); sentences = ["hello world", "foo bar"]
puts sentences.flat_map { |sentence| sentence.split(" ") }.join(", ") flat_map (also collect_concat) maps then flattens one level. It is equivalent to .map { ... }.flatten(1) but more efficient. Use it whenever each element maps to an array and you want a single flat result.Procs & Lambdas
Proc
const double = x => x * 2;
console.log(double(5)); double = proc { |x| x * 2 }
puts double.call(5) A
Proc is a first-class function object. proc { |x| ... } creates a block-wrapper that can be stored and passed. Procs have "loose" argument handling — extra arguments are ignored and missing ones become nil. return inside a proc returns from the enclosing method, not just the proc.Lambda
const multiply = (x, y) => x * y;
console.log(multiply(3, 4)); multiply = ->(x, y) { x * y }
puts multiply.call(3, 4) -> (stabby lambda) is the modern lambda syntax. Lambdas enforce argument count — passing the wrong number raises ArgumentError, unlike proc. return inside a lambda returns from the lambda only, not the enclosing method. Lambdas are the closer Ruby equivalent to JavaScript arrow functions.Method references
function double(x) { return x * 2; }
console.log([1, 2, 3].map(double).join(", ")); def double(x) = x * 2
puts [1, 2, 3].map(&method(:double)).join(", ") method(:name) returns a Method object for a named method. Combined with &, it converts the method to a block — allowing named methods to be passed where blocks are expected. The one-liner method syntax def double(x) = x * 2 (Ruby 3.0+) is a concise way to define simple methods.Classes & OOP
Class definition
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} makes a sound`; }
}
const animal = new Animal("Cat");
console.log(animal.speak()); class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} makes a sound"
end
end
puts Animal.new("Cat").speak Ruby uses
initialize instead of constructor, and new is a class method rather than a keyword. Instance variables are prefixed with @. There is no this — you access the current object's state through @instance_vars and call its methods by name.attr_reader, attr_writer, attr_accessor
class Person {
constructor(name) { this._name = name; }
get name() { return this._name; }
set name(value) { this._name = value; }
}
const person = new Person("Alice");
console.log(person.name);
person.name = "Bob";
console.log(person.name); class Person
attr_accessor :name
def initialize(name)
@name = name
end
end
person = Person.new("Alice")
puts person.name
person.name = "Bob"
puts person.name attr_accessor :name generates both a getter (name) and a setter (name=). attr_reader generates only a getter; attr_writer only a setter. These replace explicit get name() / set name(v) definitions and are idiomatic Ruby.Inheritance
class Animal {
constructor(name) { this.name = name; }
speak() { return `${this.name} speaks`; }
}
class Dog extends Animal {
speak() { return `${this.name} barks`; }
}
console.log(new Dog("Rex").speak()); class Animal
def initialize(name)
@name = name
end
def speak
"#{@name} speaks"
end
end
class Dog < Animal
def speak
"#{@name} barks"
end
end
puts Dog.new("Rex").speak Ruby uses
< for single inheritance. super calls the parent method — bare super forwards all arguments; super(args) passes specific ones; super() passes no arguments. Ruby does not support multiple inheritance of classes — use modules (mixins) instead.Class (static) methods
class Counter {
static count = 0;
static increment() { return ++Counter.count; }
}
console.log(Counter.increment());
console.log(Counter.increment()); class Counter
@@count = 0
def self.increment
@@count += 1
end
end
puts Counter.increment
puts Counter.increment Ruby uses
def self.method_name to define class methods (equivalent to JavaScript's static). Class variables (@@name) are shared across the class and all subclasses — many Rubyists prefer class-level instance variables (@count defined in a class method) to avoid this surprising inheritance behavior.Modules & Mixins
Modules as namespaces
const Auth = {
login(username) { return `${username} logged in`; }
};
console.log(Auth.login("Alice")); module Auth
def self.login(username)
"#{username} logged in"
end
end
puts Auth.login("Alice") Ruby modules serve as namespaces (grouping related methods and constants under a common name) and as mixins (sharing behavior across classes). They cannot be instantiated —
Module.new is possible but Auth.new raises NoMethodError.include (mixin)
function withGreeting(name) {
return {
name,
greet() { return `Hello, I'm ${this.name}`; }
};
}
const person = withGreeting("Alice");
console.log(person.greet()); module Greetable
def greet
"Hello, I'm #{name}"
end
end
class Person
include Greetable
attr_reader :name
def initialize(name)
@name = name
end
end
puts Person.new("Alice").greet include brings a module's instance methods into a class. The module's methods look up self at runtime, so they can call methods defined on the including class — here, greet calls name, which is provided by attr_reader. A class can include multiple modules, making this Ruby's answer to multiple inheritance.Comparable mixin
class Weight {
constructor(value) { this.value = value; }
}
const weights = [new Weight(5), new Weight(2), new Weight(8)];
weights.sort((a, b) => a.value - b.value);
console.log(weights.map(w => w.value).join(", ")); class Weight
include Comparable
attr_reader :value
def initialize(value)
@value = value
end
def <=>(other)
value <=> other.value
end
end
weights = [Weight.new(5), Weight.new(2), Weight.new(8)]
puts weights.sort.map(&:value).join(", ") Including
Comparable and defining <=> (spaceship operator) gives the class <, >, <=, >=, between?, clamp, and sort support automatically. Enumerable similarly grants map, select, reduce, etc. to any class that defines each.Error Handling
begin/rescue vs try/catch
try {
JSON.parse("not json");
} catch (error) {
console.log("Caught:", error.message);
} begin
Integer("not a number")
rescue ArgumentError => error
puts "Caught: #{error.message}"
end Ruby's
rescue catches exceptions. You specify the exception class after rescue (like Java's typed catch) — rescuing without a class catches StandardError and its subclasses. Multiple rescue clauses can handle different exception types, matched top-to-bottom.ensure vs finally
try {
console.log("try");
throw new Error("oops");
} catch (e) {
console.log("catch:", e.message);
} finally {
console.log("finally");
} begin
puts "begin"
raise "oops"
rescue RuntimeError => error
puts "rescue: #{error.message}"
ensure
puts "ensure"
end ensure runs regardless of whether an exception was raised or rescued — equivalent to JavaScript's finally. It is commonly used for cleanup: closing files, releasing locks, or resetting state. The return value of ensure is discarded; it does not affect the value of the whole begin expression.Raising exceptions
function divide(dividend, divisor) {
if (divisor === 0) throw new Error("division by zero");
return dividend / divisor;
}
try {
console.log(divide(10, 0));
} catch (error) {
console.log(error.message);
} def divide(dividend, divisor)
raise ArgumentError, "division by zero" if divisor == 0
dividend / divisor
end
begin
puts divide(10, 0)
rescue ArgumentError => error
puts error.message
end Ruby uses
raise (or its alias fail) to throw exceptions — raise ExceptionClass, "message". Using a specific exception class lets callers rescue only the errors they expect. raise with no arguments inside a rescue block re-raises the current exception.Custom exception classes
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
try {
throw new ValidationError("invalid input");
} catch (error) {
console.log(`${error.name}: ${error.message}`);
} class ValidationError < StandardError; end
begin
raise ValidationError, "invalid input"
rescue ValidationError => error
puts "#{error.class}: #{error.message}"
end Custom exception classes inherit from
StandardError (not Exception, which also catches signals and fatal errors). A one-liner class body is idiomatic when no custom behavior is needed — StandardError provides message and backtrace automatically.Pattern Matching
Hash pattern matching
const response = { status: "success", result: 42 };
if (response.status === "success") {
console.log("Result:", response.result);
} response = { status: "success", result: 42 }
case response
in { status: "success", result: Integer => result }
puts "Result: #{result}"
in { status: "error", message: String => message }
puts "Error: #{message}"
end Ruby's
case/in (introduced in Ruby 3.0) matches and deconstructs in one step. Integer => result both checks the type and binds the value. Hash patterns match by key presence — extra keys in the hash are allowed by default. Unmatched patterns raise NoMatchingPatternError.Array pattern matching
const [x, y, z] = [10, 20, 30];
console.log(x, y, z); case [10, 20, 30]
in [x, y, z]
puts "#{x}, #{y}, #{z}"
end Array patterns in
case/in deconstruct positionally. Unlike regular destructuring assignment, array patterns can include type checks: [Integer => x, Integer => y] matches and validates. Patterns can also be nested for deep deconstruction.Find pattern
const data = [1, 2, "error", 3];
const message = data.find(item => typeof item === "string");
console.log(message); data = [1, 2, "error", 3]
case data
in [*, String => message, *]
puts message
end The find pattern
[*, target_pattern, *] searches for a matching element anywhere in an array — like Array#find but integrated into pattern matching with deconstruction and type checking. The * wildcards capture any number of elements before and after the match.