Ruby
WebOverview
Dynamic, object-oriented language designed for developer happiness. Known for Ruby on Rails and an emphasis on convention over configuration.
Resources
Popular learning and reference links:
Installation & Getting Started
Ruby is pre-installed on macOS. Use a version manager like rbenv or asdf for managing versions.
# macOS (Homebrew)
brew install ruby
# rbenv — recommended version manager
brew install rbenv ruby-build
rbenv install 3.3.0
rbenv global 3.3.0
# Or use asdf
asdf plugin add ruby
asdf install ruby 3.3.0
asdf global ruby 3.3.0
# Ubuntu / Debian
sudo apt install ruby-full
# Check version
ruby --version
# IRB — interactive Ruby (REPL)
irb
# Pry — enhanced REPL
gem install pry
pry
# Run a script
ruby script.rb
# Quick one-liner
ruby -e "puts 'Hello, Ruby!'"
# Run with warnings
ruby -w script.rb Project Scaffolding
Use bundler for dependency management and Rails or Sinatra for web projects.
# Basic Ruby project
mkdir my-project && cd my-project
bundle init # Creates Gemfile
# Ruby on Rails — full-stack web framework
gem install rails
rails new my-app
cd my-app && rails server
# Rails with specific options
rails new my-api --api # API-only
rails new my-app --database=postgresql # PostgreSQL
# Sinatra — lightweight web framework
mkdir my-app && cd my-app
bundle init
# Add: gem 'sinatra' to Gemfile
bundle install
ruby app.rb
# Gem scaffold (create a new gem/library)
bundle gem my-gem
# Creates lib/, spec/, Gemfile, .gemspec Package Management
RubyGems is the package manager. Bundler manages project dependencies via a Gemfile.
# Install a gem globally
gem install rails
gem install pry
# Bundler — project dependency management
gem install bundler
# Install all dependencies from Gemfile
bundle install
# Add a gem to the project
bundle add sidekiq
# Update dependencies
bundle update
bundle update rails # Update specific gem
# Execute a command in bundle context
bundle exec rails server
bundle exec rspec
# Gemfile
source 'https://rubygems.org'
ruby '3.3.0'
gem 'rails', '~> 7.1'
gem 'pg'
gem 'puma'
gem 'redis'
group :development, :test do
gem 'rspec-rails'
gem 'pry-byebug'
gem 'factory_bot_rails'
end
group :development do
gem 'rubocop', require: false
end Tooling & Formatter/Linter
RuboCop is the standard linter and formatter. Sorbet adds static typing.
# RuboCop — linter & formatter
gem install rubocop
rubocop # Lint
rubocop -a # Auto-fix safe cops
rubocop -A # Auto-fix all cops (including unsafe)
# RuboCop with Rails extensions
gem install rubocop-rails rubocop-rspec rubocop-performance
rubocop --require rubocop-rails
# Standard Ruby — opinionated RuboCop config (zero config)
gem install standard
standardrb # Lint
standardrb --fix # Auto-fix
# Sorbet — static type checker
gem install sorbet sorbet-runtime
srb init # Initialize in project
srb tc # Type check
# Steep — alternative type checker (uses RBS)
gem install steep
steep check
# .rubocop.yml
require:
- rubocop-rails
- rubocop-rspec
AllCops:
NewCops: enable
TargetRubyVersion: 3.3
Style/StringLiterals:
EnforcedStyle: single_quotes
Metrics/MethodLength:
Max: 15 Build & Compile Model
Interpreted. Ruby code is parsed into an AST, compiled to bytecode, and executed by YARV (Yet Another Ruby VM). JIT compilation available since Ruby 2.6.
# No compile step — run directly
ruby app.rb
# Ruby with JIT (YJIT — default since Ruby 3.3)
ruby --yjit app.rb
# Check if YJIT is enabled
ruby -e "puts RubyVM::YJIT.enabled?"
# MJIT (older JIT, deprecated in 3.3)
ruby --mjit app.rb
# Precompile to bytecode (for inspection)
ruby --dump=insns script.rb
Execution model:
- Source → Lexer → Parser → AST → YARV bytecode → VM execution
- YJIT (Ruby 3.1+): Lazy Basic Block Versioning JIT compiler, written in Rust
- YJIT is enabled by default in Ruby 3.3+ and gives 15-25% speedup for Rails apps
- mruby: Lightweight Ruby for embedded systems
- TruffleRuby: GraalVM-based implementation with aggressive JIT
- JRuby: Ruby on the JVM — true threads, Java interop
Libraries & Frameworks
Ruby has a rich ecosystem centered around web development and developer tooling:
Web Frameworks - Rails, Sinatra, Hanami, Roda, Grape (API)
Background Jobs - Sidekiq, GoodJob, Solid Queue, Delayed Job, Resque
Database / ORM - ActiveRecord (Rails), Sequel, ROM (Ruby Object Mapper)
API - GraphQL Ruby, Grape, JSONAPI::Resources, jbuilder
Authentication - Devise, Rodauth, OmniAuth, Sorcery
Testing - RSpec, Minitest, Capybara, FactoryBot, VCR
HTTP Clients - Faraday, HTTParty, Net::HTTP (stdlib), Typhoeus
CLI - Thor, TTY toolkit, OptionParser (stdlib)
Utilities - Nokogiri (HTML/XML), Pry (debugging), Dry-rb (functional patterns), ActiveSupport
DevOps - Capistrano (deployment), Chef, Puppet, Vagrant
Testing
RSpec and Minitest are the two main testing frameworks. RSpec is more popular in the Rails community.
# RSpec
RSpec.describe 'Math' do
it 'adds numbers' do
expect(1 + 2).to eq(3)
end
it 'handles arrays' do
expect([1, 2, 3]).to include(2)
expect([1, 2, 3]).to have_attributes(length: 3)
end
context 'with negative numbers' do
it 'returns negative sum' do
expect(-1 + -2).to eq(-3)
end
end
end
# Minitest
require 'minitest/autorun'
class MathTest < Minitest::Test
def test_addition
assert_equal 3, 1 + 2
end
def test_array_includes
assert_includes [1, 2, 3], 2
end
end
# Run tests
bundle exec rspec # RSpec
bundle exec rspec spec/models/ # Specific directory
ruby -Itest test/math_test.rb # Minitest
bundle exec rails test # Rails default (Minitest)
# With coverage
# Add simplecov gem, then: COVERAGE=true bundle exec rspec Debugging
debug gem (Ruby 3.1+ built-in) and Pry are the primary debugging tools.
# Built-in debug gem (Ruby 3.1+)
require 'debug'
def process(data)
binding.break # Breakpoint — opens debugger
data.map { |x| x * 2 }
end
# Pry — enhanced REPL debugger
require 'pry'
def process(data)
binding.pry # Breakpoint — opens Pry session
data.map { |x| x * 2 }
end
# Basic debugging
puts variable.inspect
p variable # Shorthand for puts .inspect
pp complex_object # Pretty print
warn "debug message" # Print to stderr
# debug gem commands (at breakpoint)
# n / next — step over
# s / step — step into
# c / continue — continue execution
# bt — backtrace
# info — show local variables
# eval expr — evaluate expression
# Start script in debug mode
ruby -r debug script.rb
rdbg script.rb
# VS Code — install "Ruby LSP" extension
# launch.json:
{
"type": "ruby_lsp",
"request": "launch",
"program": "${workspaceFolder}/app.rb"
}
# Byebug (older, still used)
gem install byebug
# require 'byebug'; byebug # in code Variables
Variables don’t need type declarations. Naming conventions indicate scope: local, @instance, @@class, $global, CONSTANT.
# Local variables
name = 'Alice'
age = 30
is_active = true
# Constants (uppercase start)
MAX_SIZE = 100
PI = 3.14159
# Instance variables
@name = 'Alice'
# Class variables
@@count = 0
# Global variables (avoid)
$debug = true
# Multiple assignment
a, b, c = 1, 2, 3
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
# Frozen (immutable) strings
name = 'Alice'.freeze
# name << ' Bob' # => FrozenError
# String interpolation
greeting = "Hello, #{name}!" Types
Ruby is dynamically typed. Everything is an object — even integers and booleans. Optional type annotations via RBS or Sorbet.
# Core types — all are objects
42.class # Integer
3.14.class # Float
'hello'.class # String
true.class # TrueClass
nil.class # NilClass
:symbol.class # Symbol
[1, 2].class # Array
{ a: 1 }.class # Hash
(1..5).class # Range
# Type checking
42.is_a?(Integer) # true
42.is_a?(Numeric) # true (parent class)
'hi'.respond_to?(:upcase) # true (duck typing)
# Symbols — immutable, interned strings
:name
:status
:'multi word'
# Ranges
(1..5).to_a # [1, 2, 3, 4, 5] (inclusive)
(1...5).to_a # [1, 2, 3, 4] (exclusive)
# RBS — type signature files (Ruby 3.0+)
# sig/app.rbs:
# class User
# attr_reader name: String
# def initialize: (String name) -> void
# end Data Structures
Arrays, Hashes, Sets, Structs, and Data (Ruby 3.2+) are the core data structures.
# Arrays — ordered, mixed types
arr = [1, 'two', :three]
arr.push(4) # or arr << 4
arr.pop # Remove last
arr.shift # Remove first
arr.unshift(0) # Prepend
arr.include?(1) # true
arr.flatten # Flatten nested arrays
arr.compact # Remove nils
arr.uniq # Remove duplicates
# Hashes — key-value pairs
hash = { name: 'Alice', age: 30 }
hash[:email] = 'a@b.com'
hash.delete(:age)
hash.keys # [:name, :email]
hash.values
hash.fetch(:name, 'default')
hash.merge(other_hash)
# Set
require 'set'
set = Set.new([1, 2, 3, 3]) # {1, 2, 3}
set.add(4)
set.include?(2) # true
set & other_set # Intersection
set | other_set # Union
# Struct — lightweight data class
User = Struct.new(:name, :age, keyword_init: true)
user = User.new(name: 'Alice', age: 30)
# Data (Ruby 3.2+) — immutable value object
Point = Data.define(:x, :y)
p = Point.new(x: 1, y: 2)
# p.x = 3 # => NoMethodError (immutable)
# OpenStruct — dynamic attributes
require 'ostruct'
obj = OpenStruct.new(name: 'Alice')
obj.age = 30 Functions
Methods are defined with def. Ruby also supports blocks, procs, and lambdas.
# Method definition
def greet(name)
"Hello, #{name}!" # Implicit return (last expression)
end
# Default parameters
def power(base, exp = 2)
base ** exp
end
# Keyword arguments
def create_user(name:, age:, role: 'user')
{ name: name, age: age, role: role }
end
create_user(name: 'Alice', age: 30)
# Splat (rest) parameters
def sum(*nums)
nums.reduce(0, :+)
end
# Double splat (keyword rest)
def config(**options)
options
end
# Blocks
def with_logging
puts 'Start'
yield # Execute the block
puts 'End'
end
with_logging { puts 'Working...' }
# Procs and Lambdas
double = ->(x) { x * 2 } # Lambda
double.call(5) # 10
double.(5) # 10 (shorthand)
square = proc { |x| x ** 2 } # Proc
square.call(4) # 16 Conditionals
Ruby has if/elsif/else, unless, case/when, and pattern matching (Ruby 3.0+).
# If / elsif / else
if x > 0
puts 'positive'
elsif x == 0
puts 'zero'
else
puts 'negative'
end
# Inline if / unless
puts 'positive' if x > 0
puts 'not zero' unless x.zero?
# Ternary
label = x > 0 ? 'positive' : 'non-positive'
# Case / when
case color
when 'red'
puts '#f00'
when 'blue', 'navy'
puts '#00f'
else
puts 'unknown'
end
# Pattern matching (Ruby 3.0+)
case [1, 2, 3]
in [Integer => a, Integer => b, *rest]
puts "a=#{a}, b=#{b}"
end
case { name: 'Alice', age: 30 }
in { name: String => name, age: (18..) => age }
puts "#{name} is #{age}"
end
# Guard clauses (idiomatic Ruby)
def process(data)
return if data.nil?
return unless data.valid?
# ... process data
end Loops
Ruby favors iterators and blocks over traditional loops. each, map, select are idiomatic.
# each — the Ruby way
[1, 2, 3].each { |x| puts x }
# each with index
['a', 'b', 'c'].each_with_index do |item, i|
puts "#{i}: #{item}"
end
# times
5.times { |i| puts i }
# upto / downto
1.upto(5) { |i| puts i }
5.downto(1) { |i| puts i }
# Ranges
(1..5).each { |i| puts i }
# while / until
n = 0
while n < 3
n += 1
end
until n == 0
n -= 1
end
# loop (infinite)
loop do
break if some_condition
end
# Iterators (functional-style)
[1, 2, 3].map { |x| x * 2 } # [2, 4, 6]
[1, 2, 3, 4].select { |x| x.even? } # [2, 4]
[1, 2, 3].reduce(0) { |sum, x| sum + x } # 6
[1, 2, 3].reduce(:+) # 6 (shorthand) Generics & Type System
Ruby has no native generics. It relies on duck typing. Static type checking is available via RBS (built-in) and Sorbet (third-party).
# Duck typing — no generics needed
def first(items)
items.first
end
first([1, 2, 3]) # 1
first('hello') # 'h'
# respond_to? — check capabilities
def process(obj)
if obj.respond_to?(:each)
obj.each { |x| puts x }
else
puts obj
end
end
# Comparable / Enumerable — mixin-based polymorphism
class Temperature
include Comparable
attr_reader :degrees
def initialize(degrees)
@degrees = degrees
end
def <=>(other)
degrees <=> other.degrees
end
end
temps = [Temperature.new(30), Temperature.new(20)]
temps.sort # Works via Comparable
temps.min # Works via Comparable
# RBS — type signatures (Ruby 3.0+)
# sig/app.rbs
# class Array[unchecked out Elem]
# def first: () -> Elem?
# def map: [U] () { (Elem) -> U } -> Array[U]
# end
# Sorbet — static type checker
# typed: strict
class Box
extend T::Sig
extend T::Generic
Elem = type_member
sig { params(value: Elem).void }
def initialize(value)
@value = T.let(value, Elem)
end
end Inheritance & Composition
Single inheritance with modules (mixins) for composition. Ruby strongly favors composition over inheritance.
# Class inheritance
class Animal
attr_reader :name
def initialize(name)
@name = name
end
def speak
"#{name} makes a sound"
end
end
class Dog < Animal
def speak
"#{name} barks"
end
end
# Super
class Puppy < Dog
def initialize(name)
super(name)
@young = true
end
end
# Modules (mixins) — composition
module Serializable
def to_json
JSON.generate(to_h)
end
end
module Loggable
def log
puts "#{self.class}: #{inspect}"
end
end
class User < Animal
include Serializable # Instance methods
include Loggable
extend ClassMethods # Class methods
def to_h
{ name: name }
end
end
# Abstract-like pattern (no built-in abstract)
module Abstract
def self.included(klass)
klass.instance_method(:perform)
rescue NameError
raise NotImplementedError, "#{klass} must implement #perform"
end
end
# Open classes — extend existing classes
class String
def shout
upcase + '!!!'
end
end
'hello'.shout # "HELLO!!!" Functional Patterns
Ruby has strong functional programming support with blocks, procs, lambdas, and Enumerable methods.
# Higher-order functions
def apply(fn, x)
fn.call(x)
end
double = ->(x) { x * 2 }
apply(double, 5) # 10
# Map, select, reduce
nums = [1, 2, 3, 4, 5]
squared = nums.map { |x| x ** 2 }
evens = nums.select(&:even?)
sum = nums.reduce(:+)
# Method reference with &
['hello', 'world'].map(&:upcase) # ["HELLO", "WORLD"]
# Closures
def counter(start = 0)
count = start
-> { count += 1 }
end
inc = counter
inc.call # 1
inc.call # 2
# Currying
add = ->(a, b) { a + b }
add5 = add.curry.(5)
add5.(3) # 8
# Chaining with Enumerable
result = (1..10)
.select(&:odd?)
.map { |x| x ** 2 }
.reject { |x| x > 50 }
.sum
# Lazy evaluation
(1..Float::INFINITY)
.lazy
.select(&:odd?)
.map { |x| x ** 2 }
.first(5) # [1, 9, 25, 49, 81]
# Proc composition (Ruby 2.6+)
double = ->(x) { x * 2 }
add_one = ->(x) { x + 1 }
double_then_add = add_one << double # compose
double_then_add.(5) # 11 Concurrency
GIL (GVL) limits true parallelism in CRuby. Ractors (Ruby 3.0+) provide actor-based parallelism. Fibers enable cooperative concurrency.
# Threads — concurrent but GVL-limited for CPU
threads = 5.times.map do |i|
Thread.new { puts "Thread #{i}" }
end
threads.each(&:join)
# Mutex — thread safety
mutex = Mutex.new
counter = 0
10.times.map do
Thread.new { mutex.synchronize { counter += 1 } }
end.each(&:join)
# Fibers — cooperative concurrency
fiber = Fiber.new do
Fiber.yield 'first'
Fiber.yield 'second'
'third'
end
fiber.resume # 'first'
fiber.resume # 'second'
fiber.resume # 'third'
# Ractors (Ruby 3.0+) — true parallelism
ractors = 4.times.map do |i|
Ractor.new(i) do |n|
(1..10000).sum * n
end
end
results = ractors.map(&:take)
# Fiber Scheduler (Ruby 3.0+) — async I/O
# Used by async gem for non-blocking I/O
require 'async'
Async do |task|
task.async { fetch_url('https://example.com/a') }
task.async { fetch_url('https://example.com/b') }
end
# Sidekiq — background job processing
class HardWorker
include Sidekiq::Job
def perform(name, count)
# Runs in a separate process
end
end
HardWorker.perform_async('Bob', 5) Modules & Imports
Ruby uses require to load files and modules for namespacing. Bundler and autoloading (Zeitwerk in Rails) manage dependencies.
# require — load a file once
require 'json'
require 'net/http'
# require_relative — load relative to current file
require_relative 'lib/helper'
require_relative '../config/database'
# Modules as namespaces
module MyApp
module Models
class User
attr_accessor :name
end
end
end
user = MyApp::Models::User.new
# Include module methods
module Greetable
def greet
"Hello, #{name}!"
end
end
class User
include Greetable
attr_reader :name
def initialize(name) = @name = name
end
# Zeitwerk autoloading (Rails default)
# File: app/models/user.rb → class User
# File: app/services/auth/login.rb → class Auth::Login
# No require needed — autoloaded by convention
# Bundler — loads gems from Gemfile
require 'bundler/setup'
Bundler.require(:default)
# load — always re-executes file (useful for reloading)
load 'config.rb' Error Handling
Ruby uses begin/rescue/ensure for exception handling. raise throws exceptions.
# begin / rescue / ensure
begin
data = JSON.parse(input)
rescue JSON::ParserError => e
puts "JSON error: #{e.message}"
rescue StandardError => e
puts "Error: #{e.message}"
ensure
puts 'always runs'
end
# Inline rescue (simple cases)
value = Integer(input) rescue 0
# Method-level rescue
def divide(a, b)
a / b
rescue ZeroDivisionError
puts 'Cannot divide by zero'
nil
end
# Raising exceptions
def validate(age)
raise ArgumentError, 'Age must be positive' if age < 0
raise 'Something went wrong' # RuntimeError
end
# Custom exceptions
class ValidationError < StandardError
attr_reader :errors
def initialize(errors, message = 'Validation failed')
@errors = errors
super(message)
end
end
# Retry
attempts = 0
begin
attempts += 1
make_request
rescue Net::TimeoutError
retry if attempts < 3
end Memory Management
Garbage collected. CRuby uses a generational, incremental, compacting GC. Objects are allocated on the heap with automatic cleanup.
# Memory is automatically managed
data = (1..10000).to_a # Allocated
data = nil # Eligible for GC
# Check memory usage
puts "RSS: #{`ps -o rss= -p #{Process.pid}`.strip.to_i / 1024} MB"
# ObjectSpace — inspect live objects
require 'objspace'
ObjectSpace.count_objects
# => { TOTAL: 30000, FREE: 5000, T_OBJECT: 1200, ... }
ObjectSpace.memsize_of_all # Total memory of all objects
# GC control
GC.start # Force garbage collection
GC.disable # Disable GC (use carefully)
GC.enable
GC.stat # GC statistics
# WeakRef
require 'weakref'
obj = Object.new
weak = WeakRef.new(obj)
weak.weakref_alive? # true
Ruby GC details:
- Generational: Young objects collected more frequently than old ones
- Incremental: GC work is spread across multiple steps to reduce pauses
- Compacting (Ruby 2.7+):
GC.compactreduces memory fragmentation - Variable Width Allocation (Ruby 3.2+): Objects can be embedded in heap slots
- Tune via env vars:
RUBY_GC_HEAP_INIT_SLOTS,RUBY_GC_MALLOC_LIMIT, etc. - Jemalloc: Alternative allocator that reduces memory fragmentation (
LD_PRELOAD=libjemalloc.so)
Performance Profiling
ruby-prof, stackprof, and benchmark are the main profiling tools. rack-mini-profiler for web apps.
# Benchmark (stdlib)
require 'benchmark'
Benchmark.bm do |x|
x.report('sort') { (1..10000).to_a.shuffle.sort }
x.report('sort!') { (1..10000).to_a.shuffle.sort! }
end
# Benchmark.measure
result = Benchmark.measure { expensive_operation }
puts result # user, system, total, real time
# Benchmark/ips — iterations per second
require 'benchmark/ips'
Benchmark.ips do |x|
x.report('map') { [1,2,3].map { |i| i * 2 } }
x.report('each') { r = []; [1,2,3].each { |i| r << i * 2 }; r }
x.compare!
end
# StackProf — sampling profiler
gem install stackprof
# In code: StackProf.run(mode: :cpu, out: 'tmp/stackprof.dump') { work }
stackprof tmp/stackprof.dump --text
# ruby-prof — deterministic profiler
gem install ruby-prof
ruby-prof script.rb
# rack-mini-profiler — web request profiling
gem 'rack-mini-profiler'
# Shows timing badge on every page
# memory_profiler — memory allocation tracking
gem install memory_profiler
# MemoryProfiler.report { work }.pretty_print
# derailed_benchmarks — Rails memory/speed benchmarks
gem install derailed_benchmarks
bundle exec derailed exec perf:mem
bundle exec derailed exec perf:ips Interop
Ruby interops with C via native extensions, FFI, and system commands. JRuby provides Java interop.
# FFI — call C libraries
require 'ffi'
module LibM
extend FFI::Library
ffi_lib 'libm' # Or FFI::Library::LIBC
attach_function :sqrt, [:double], :double
attach_function :ceil, [:double], :double
end
LibM.sqrt(16.0) # 4.0
LibM.ceil(1.3) # 2.0
# System commands
output = `ls -la` # Backticks
output = %x(ls -la) # %x syntax
system('echo', 'hello') # Returns true/false
result = IO.popen('ls') { |io| io.read }
# Open3 — capture stdout, stderr, status
require 'open3'
stdout, stderr, status = Open3.capture3('git', 'status')
# C extensions (native)
# extconf.rb:
require 'mkmf'
create_makefile('my_extension')
# JRuby — Java interop
# java_import java.util.ArrayList
# list = ArrayList.new
# list.add('hello')
# Inline C (rice gem for C++ / fiddle for C)
require 'fiddle'
libm = Fiddle.dlopen('libm.so.6')
sqrt = Fiddle::Function.new(
libm['sqrt'],
[Fiddle::TYPE_DOUBLE],
Fiddle::TYPE_DOUBLE
)
sqrt.call(16.0) # 4.0
# JSON / YAML / CSV — stdlib
require 'json'
require 'yaml'
require 'csv'
data = JSON.parse(File.read('data.json')) Packaging & Distribution
Publish gems to RubyGems.org. Deploy apps via Docker, Heroku, or platform services.
# Create a gem
bundle gem my-gem
# Edit my-gem.gemspec, add code to lib/
# Build and publish
gem build my-gem.gemspec
gem push my-gem-1.0.0.gem
# Or with bundler
bundle exec rake release
# Tags, builds, and pushes to RubyGems.org
# my-gem.gemspec
Gem::Specification.new do |spec|
spec.name = 'my-gem'
spec.version = '1.0.0'
spec.authors = ['Your Name']
spec.summary = 'A useful gem'
spec.files = Dir['lib/**/*']
spec.require_paths = ['lib']
spec.add_dependency 'httparty', '~> 0.21'
spec.add_development_dependency 'rspec', '~> 3.12'
end
# Docker deployment
FROM ruby:3.3-alpine
RUN apk add --no-cache build-base postgresql-dev
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle install --without development test
COPY . .
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]
# Heroku
heroku create my-app
git push heroku main
# Kamal — Rails deployment tool (Docker-based)
gem install kamal
kamal setup
kamal deploy