Rails 8.1: Novità e Miglioramenti - Release Ottobre 2025
Scopri Rails 8.1: Job Continuations, Event Reporter, Local CI DSL, Kamal Secrets, Association Deprecations e tutte le novità di ottobre 2025.

Rails 8.1 è stato rilasciato il 22 ottobre 2025 con oltre 2500 commits e 500+ contributori. Questa release introduce Job Continuations per long-running jobs interrompibili, Event Reporter per structured logging, Local CI DSL, integrazione Kamal Secrets, Association Deprecations e supporto Markdown response.
🎯 Novità Principali
Job Continuations - Long-Running Jobs Interrompibili
La feature più richiesta: jobs che riprendono dall'ultimo step completato!
❌ Prima - Jobs fail on restart:
class ProcessImportJob < ApplicationJob
def perform(import_id)
import = Import.find(import_id)
# ❌ 50,000 records - 30 minuti
# Se server restart a 20 minuti → RESTART DA ZERO!
import.records.each do |record|
record.process
end
import.finalize
end
end
✅ Rails 8.1 - Job Continuations:
class ProcessImportJob < ApplicationJob
include ActiveJob::Continuable
def perform(import_id)
@import = Import.find(import_id)
# ✅ Step 1 - Initialize
step :initialize do
@import.initialize
end
# ✅ Step 2 - Process con cursor
step :process do |step|
@import.records.find_each(start: step.cursor) do |record|
record.process
step.advance!(from: record.id) # Save progress!
end
end
# ✅ Step 3 - Finalize (method format)
step :finalize
end
private
def finalize
@import.finalize
end
end
Come funziona:
# Job starts
ProcessImportJob.perform_later(123)
# Progress:
# - Step :initialize → Complete
# - Step :process → Processing... (cursor: 15,000/50,000)
# 🔄 SERVER RESTART!
# Job auto-resume!
# - Step :initialize → SKIPPED (completed)
# - Step :process → RESUMES from cursor 15,000!
# - Step :finalize → Execute dopo process
# ✅ No perdita di progresso!
Real-world example - Batch processing:
class ProcessVideosJob < ApplicationJob
include ActiveJob::Continuable
def perform(video_ids)
step :download do |step|
video_ids.each do |id|
download_video(id)
step.advance!(from: id)
end
end
step :transcode do |step|
video_ids.each do |id|
transcode_video(id, format: :hd)
step.advance!(from: id)
end
end
step :upload do |step|
video_ids.each do |id|
upload_to_cdn(id)
step.advance!(from: id)
end
end
step :notify
end
private
def notify
VideoChannel.broadcast_to(user, { status: 'complete' })
end
end
Vantaggi:
- ✅ Zero perdita: Jobs riprendono da ultimo checkpoint
- ✅ Kamal-ready: Perfect per deploy con 30s shutdown
- ✅ Granular: Step-by-step progress tracking
- ✅ Production-safe: Restart senza impatto
Event Reporter - Structured Logging
Structured events invece di log testuali!
❌ Prima - Text logs:
# Traditional logger
Rails.logger.info "User signup: id=#{user.id}, email=#{user.email}"
# ❌ Hard to parse, no structure
✅ Rails 8.1 - Event Reporter:
# ✅ Structured event
Rails.event.notify("user.signup",
user_id: user.id,
email: user.email,
plan: "premium"
)
Tagged events:
# Add context tags
Rails.event.tagged("graphql") do
Rails.event.notify("query.executed",
query: gql_query,
duration_ms: 45
)
end
# Event includes: { graphql: true, query: "...", duration_ms: 45 }
Persistent context:
# Set context for ALL subsequent events
Rails.event.set_context(
request_id: "abc123",
shop_id: 456,
user_id: 789
)
# Every event now includes this context!
Rails.event.notify("order.created", order_id: 999)
# → { order_id: 999, request_id: "abc123", shop_id: 456, user_id: 789 }
Custom subscribers:
# config/initializers/event_reporter.rb
class LogSubscriber
def emit(event)
payload = event[:payload].map { |key, value| "#{key}=#{value}" }.join(" ")
source = event[:source_location]
log = "[#{event[:name]}] #{payload} at #{source[:filepath]}:#{source[:line]}"
Rails.logger.info(log)
end
end
class DatadogSubscriber
def emit(event)
Datadog::Statsd.new.event(
event[:name],
event[:payload].to_json,
tags: event[:tags]
)
end
end
# Register subscribers
Rails.event.subscribe(LogSubscriber.new)
Rails.event.subscribe(DatadogSubscriber.new)
Built-in events:
# Rails emits structured events for:
# - user.signup
# - order.created
# - payment.processed
# - job.enqueued
# - job.performed
# - cache.hit
# - cache.miss
# - sql.query
# Subscribe to specific events
Rails.event.subscribe_to("sql.query") do |event|
if event[:duration_ms] > 100
SlowQueryAlert.notify(event[:sql])
end
end
Vantaggi:
- ✅ Structured: JSON-friendly, parseable
- ✅ Context-aware: Tags e context automatici
- ✅ Multi-output: Log + Datadog + CloudWatch
- ✅ Traceable: Source location inclusa
Local CI DSL
CI locale senza GitHub Actions/CircleCI!
Setup:
# config/ci.rb - New file!
CI.run do
step "Setup", "bin/setup --skip-server"
step "Style: Ruby", "bin/rubocop"
step "Security: Gem audit", "bin/bundler-audit"
step "Security: Importmap audit", "bin/importmap audit"
step "Security: Brakeman", "bin/brakeman --quiet --exit-on-error"
step "Tests: Rails", "bin/rails test"
step "Tests: System", "bin/rails test:system"
step "Tests: Seeds", "env RAILS_ENV=test bin/rails db:seed:replant"
# Conditional steps
if success?
step "Signoff: Ready to merge!", "gh signoff"
else
failure "Signoff: CI failed. Fix issues before merge."
end
end
Run CI:
# Execute full CI pipeline locally
bin/ci
# Output:
# ✓ Setup (2.1s)
# ✓ Style: Ruby (1.5s)
# ✓ Security: Gem audit (0.8s)
# ✓ Security: Importmap audit (0.5s)
# ✓ Security: Brakeman (3.2s)
# ✓ Tests: Rails (12.4s)
# ✓ Tests: System (8.9s)
# ✓ Tests: Seeds (1.2s)
# ✓ Signoff: Ready to merge! (0.3s)
#
# ✅ CI passed in 31.0s
Advanced configuration:
# config/ci.rb
CI.run do
# Parallel execution
parallel do
step "Rubocop", "bin/rubocop"
step "Tests: Models", "bin/rails test test/models"
step "Tests: Controllers", "bin/rails test test/controllers"
end
# Conditional steps
step "Deploy: Staging" if ENV['BRANCH'] == 'develop'
# Custom failure handling
on_failure do |failed_step|
SlackNotifier.ping("CI failed at: #{failed_step}")
end
end
Vantaggi:
- ✅ No cloud: CI completamente locale
- ✅ Fast feedback: No push/wait cycle
- ✅ Cost-free: No CI minutes billing
- ✅ Same env: Dev = CI environment
Kamal Secrets Integration
Kamal ora legge secrets da Rails credentials!
Setup:
# .kamal/secrets - New integration!
KAMAL_REGISTRY_PASSWORD=$(rails credentials:fetch kamal.registry_password)
DATABASE_URL=$(rails credentials:fetch production.database_url)
REDIS_URL=$(rails credentials:fetch production.redis_url)
SECRET_KEY_BASE=$(rails credentials:fetch secret_key_base)
Rails credentials:
# config/credentials.yml.enc
kamal:
registry_password: ghp_xxxxxxxxxxxx
production:
database_url: postgresql://...
redis_url: redis://...
secret_key_base: xxxxx
Kamal config:
# config/deploy.yml
service: myapp
image: myapp/production
servers:
web:
- 192.168.1.10
- 192.168.1.11
registry:
username: myapp
password:
- KAMAL_REGISTRY_PASSWORD
env:
secret:
- DATABASE_URL
- REDIS_URL
- SECRET_KEY_BASE
Deploy:
# Kamal legge secrets da Rails credentials!
kamal deploy
# ✅ No più .env files
# ✅ No più external secret stores
# ✅ Tutto in Rails credentials (encrypted)
Vantaggi:
- ✅ Low-fi: No AWS Secrets Manager/Vault
- ✅ Encrypted: Rails credentials già secure
- ✅ Simple: Single source of truth
- ✅ Git-safe: Encrypted credentials in repo
Association Deprecations
Depreca associazioni per smooth migrations!
class Author < ApplicationRecord
# ✅ Marca come deprecated
has_many :posts, deprecated: true
has_many :articles # New association
end
# Usage
author.posts
# DEPRECATION WARNING: Author#posts is deprecated. Use #articles instead.
# Backtrace: app/controllers/authors_controller.rb:15
Deprecation modes:
# Mode: :warn (default) - Logs warning
has_many :posts, deprecated: true
# Mode: :raise - Raises error
has_many :posts, deprecated: { mode: :raise }
# Mode: :notify - Sends to error tracker
has_many :posts, deprecated: { mode: :notify }
# Custom message
has_many :posts,
deprecated: {
message: "Use #articles instead. Will be removed in v2.0"
}
Configuration:
# config/environments/production.rb
config.active_record.deprecation_mode = :notify
# Enable/disable backtraces
config.active_record.deprecation_backtrace = false # Default
Real-world migration:
# Step 1: Deprecate old association
class User < ApplicationRecord
has_many :subscriptions, deprecated: true
has_many :active_subscriptions # New
end
# Step 2: Fix all usages (warnings help find them)
# user.subscriptions → user.active_subscriptions
# Step 3: Remove deprecated association (next major version)
class User < ApplicationRecord
has_many :active_subscriptions
end
Vantaggi:
- ✅ Smooth migration: Gradual refactor
- ✅ Traceable: Backtrace mostra dove usato
- ✅ Flexible: Warn, raise, o notify
- ✅ Safe: No breaking changes immediate
🚀 Additional Features
Kamal Local Registry
Deploy senza Docker Hub/GHCR!
# config/deploy.yml
registry:
local: true # ✅ No remote registry needed!
servers:
web:
- 192.168.1.10
# Build e deploy usando registry locale
kamal deploy
# ✅ Image pushed to local registry on servers
Markdown Response Format
Supporto nativo per markdown responses!
class Page < ApplicationRecord
def to_markdown
body # Returns markdown content
end
end
class PagesController < ActionController::Base
def show
@page = Page.find(params[:id])
respond_to do |format|
format.html
format.md # ✅ Nuovo formato!
end
end
end
# Request
GET /pages/1.md
# Response
Content-Type: text/markdown
# My Page Title
This is **bold** and this is *italic*.
dom_id Helper Globally Available
# Prima - solo in views
include ActionView::RecordIdentifier
# ✅ Rails 8.1 - disponibile ovunque!
user = User.new(id: 42)
dom_id(user) # => "user_42"
# Utile in Turbo Streams
turbo_stream.replace dom_id(@user), partial: "users/card"
PostgreSQL Nil Primary Key Handling
# ❌ Prima - Postgres error
user = User.new(id: nil)
user.save # PostgreSQL::NotNullViolation!
# ✅ Rails 8.1 - handled automatically
user = User.new(id: nil)
user.save # PostgreSQL usa DEFAULT value (sequence)
📊 Performance & Stability
Production-ready:
- ✅ Shopify: Rails 8.1 in prod da mesi
- ✅ HEY: Running stable in production
- ✅ 500+ contributors: Community-driven
- ✅ 2500+ commits: Extensive testing
🔄 Migration Guide
Upgrade
# Upgrade to Rails 8.1
bundle update rails
# Run migrations
rails app:update
# Check for deprecations
rails db:migrate
Enable Features
# config/application.rb
# Enable Job Continuations
config.active_job.continuations_enabled = true
# Enable Event Reporter
config.event_reporter.enabled = true
# Configure CI
# Create config/ci.rb (see examples above)
Requirements
- ✅ Ruby: 3.2+
- ✅ Rails: Upgrade from 8.0
- ✅ PostgreSQL: 13+ (for continuations)
⚠️ Deprecations
- ⚠️ Rails 7.0: End of Life (EOL)
- ⚠️ Rails 7.1: End of Life (EOL)
- ✅ Rails 8.0: Extended support (+6 months)
💡 Conclusioni
Rails 8.1 è una release production-focused:
✅ Job Continuations - Long-running jobs interrompibili e resumable ✅ Event Reporter - Structured logging per observability ✅ Local CI - CI senza cloud vendor lock-in ✅ Kamal Secrets - Deploy semplificato con credentials ✅ Association Deprecations - Smooth API migrations ✅ Markdown Support - Native markdown responses ✅ 500+ contributors - Community-driven improvements