Domande colloquio Ruby on Rails: Top 25 nel 2026
Le 25 domande più frequenti per i colloqui Ruby on Rails. Architettura MVC, Active Record, migration, testing RSpec, API REST con risposte dettagliate ed esempi di codice.

I colloqui Ruby on Rails valutano la padronanza del framework Ruby più popolare, la comprensione dell'architettura MVC, dell'ORM Active Record e la capacità di costruire applicazioni web robuste seguendo la filosofia "Convention over Configuration". Questa guida copre le 25 domande più frequenti, dai fondamenti di Rails ai pattern avanzati di produzione.
I recruiter apprezzano i candidati che comprendono la filosofia Rails: "Convention over Configuration", DRY (Don't Repeat Yourself) e i pattern Rails Way. Spiegare perché Rails compie certe scelte architetturali fa la differenza.
Fondamenti di Ruby on Rails
Domanda 1: Spiegare il pattern MVC in Ruby on Rails
Il pattern Model-View-Controller (MVC) è il cuore architetturale di Rails. Separa le responsabilità in tre livelli distinti per una migliore manutenibilità e testabilità del codice.
# app/models/article.rb
# Il Model gestisce i dati e la logica di business
class Article < ApplicationRecord
# Validazioni dei dati
validates :title, presence: true, length: { minimum: 5 }
validates :body, presence: true
# Associazioni con altri modelli
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
has_many :tags, through: :article_tags
# Scope per query riutilizzabili
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
# Callback del ciclo di vita
before_save :generate_slug
private
def generate_slug
self.slug = title.parameterize if title_changed?
end
end# app/controllers/articles_controller.rb
# Il Controller riceve le richieste e orchestra la risposta
class ArticlesController < ApplicationController
before_action :authenticate_user!, except: [:index, :show]
before_action :set_article, only: [:show, :edit, :update, :destroy]
def index
@articles = Article.published.recent.includes(:author)
end
def show
@comments = @article.comments.includes(:user)
end
def create
@article = current_user.articles.build(article_params)
if @article.save
redirect_to @article, notice: 'Articolo creato con successo.'
else
render :new, status: :unprocessable_entity
end
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published, tag_ids: [])
end
end<%# app/views/articles/show.html.erb %>
<%# La View visualizza i dati in formato HTML %>
<article class="article-detail">
<header>
<h1><%= @article.title %></h1>
<p class="meta">
Da <%= @article.author.name %> •
<%= l @article.created_at, format: :long %>
</p>
</header>
<div class="content">
<%= simple_format @article.body %>
</div>
<%# Partial per i commenti %>
<%= render @comments %>
</article>Il flusso tipico: la richiesta arriva al Router, che la indirizza al Controller appropriato. Il Controller interagisce con il Model per recuperare o modificare i dati, poi passa questi dati alla View per il rendering HTML.
Domanda 2: Cos'è Active Record e come funziona l'ORM di Rails?
Active Record è l'ORM (Object-Relational Mapping) di Rails che implementa il pattern Active Record. Ogni classe Model rappresenta una tabella del database, e ogni istanza rappresenta una riga.
# app/models/user.rb
# Active Record mappa automaticamente le colonne agli attributi
class User < ApplicationRecord
# La tabella 'users' è associata automaticamente
# Colonne: id, email, name, created_at, updated_at
has_secure_password # BCrypt per la password
has_many :articles, foreign_key: :author_id
has_one :profile, dependent: :destroy
has_and_belongs_to_many :roles
# Validazioni
validates :email, presence: true,
uniqueness: { case_sensitive: false },
format: { with: URI::MailTo::EMAIL_REGEXP }
# Callback
before_save :normalize_email
# Metodi di classe per le query
def self.admins
joins(:roles).where(roles: { name: 'admin' })
end
private
def normalize_email
self.email = email.downcase.strip
end
end# Esempi di query Active Record
# Console Rails o all'interno di un service
# Creazione
user = User.create!(email: 'dev@example.com', name: 'Alice', password: 'secret123')
# Lettura con condizioni
active_users = User.where(active: true).order(:name)
user = User.find_by(email: 'dev@example.com')
# Query concatenate (lazy evaluation)
recent_admins = User.admins
.where('created_at > ?', 1.month.ago)
.includes(:profile)
.limit(10)
# Prevenzione N+1 con eager loading
articles = Article.includes(:author, :comments).published
# Aggiornamento
user.update!(name: 'Alice Martin')
# Transazioni
User.transaction do
user.debit_balance!(100)
recipient.credit_balance!(100)
Payment.create!(from: user, to: recipient, amount: 100)
endActive Record converte i metodi Ruby in query SQL ottimizzate. Metodi come where, joins, includes sono lazy: la query viene eseguita solo durante l'iterazione o alla chiamata di to_a.
Domanda 3: Spiegare il sistema di migration di Rails
Le migration permettono di versionare lo schema del database con Ruby. Sono reversibili e consentono un'evoluzione controllata della struttura dei dati.
# db/migrate/20260203100000_create_products.rb
# Migration per creare una tabella
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :name, null: false
t.text :description
t.decimal :price, precision: 10, scale: 2, null: false
t.integer :stock_quantity, default: 0
t.references :category, null: false, foreign_key: true
t.boolean :active, default: true
t.timestamps # created_at e updated_at automatici
end
# Indici per le performance
add_index :products, :name
add_index :products, [:category_id, :active]
end
end# db/migrate/20260203110000_add_slug_to_products.rb
# Migration per modificare una tabella esistente
class AddSlugToProducts < ActiveRecord::Migration[7.1]
def change
add_column :products, :slug, :string
add_index :products, :slug, unique: true
# Popolare gli slug esistenti
reversible do |dir|
dir.up do
Product.find_each do |product|
product.update_column(:slug, product.name.parameterize)
end
end
end
# Rendere non nullabile dopo il popolamento
change_column_null :products, :slug, false
end
end# Comandi essenziali per le migration
rails db:migrate # Esegue le migration in sospeso
rails db:rollback # Annulla l'ultima migration
rails db:rollback STEP=3 # Annulla le ultime 3 migration
rails db:migrate:status # Visualizza lo stato delle migration
rails db:seed # Esegue db/seeds.rb
rails db:reset # Drop, create, migrate, seedLe migration devono essere reversibili. Il metodo change è intelligente e può invertire automaticamente le operazioni comuni. Per casi complessi, usare up e down separatamente.
Active Record avanzato
Domanda 4: Come ottimizzare le query N+1 in Rails?
Il problema N+1 si verifica quando una query iniziale è seguita da N query aggiuntive per caricare le associazioni. Rails offre diversi metodi di eager loading per risolvere questo problema.
# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def index
# ❌ PROBLEMA N+1: 1 query + N query per ordine
# @orders = Order.all
# Nella view: order.user.name genera una query per ordine
# ✅ SOLUZIONE con includes (eager loading)
@orders = Order.includes(:user, :items)
.where(status: 'completed')
.order(created_at: :desc)
# Genera solo 3 query in totale
end
def show
# includes: carica le associazioni separatamente (2-3 query)
@order = Order.includes(items: :product).find(params[:id])
# preload: forza il caricamento separato
@order = Order.preload(:items, :user).find(params[:id])
# eager_load: forza un LEFT OUTER JOIN (1 query)
@order = Order.eager_load(:items).find(params[:id])
end
end# app/models/order.rb
class Order < ApplicationRecord
belongs_to :user
has_many :items, class_name: 'OrderItem'
has_many :products, through: :items
# Scope con includes di default
scope :with_details, -> { includes(:user, items: :product) }
# Counter cache per evitare query COUNT
# Richiede: add_column :users, :orders_count, :integer, default: 0
belongs_to :user, counter_cache: true
end# Rilevamento N+1 con la gem Bullet (sviluppo)
# config/environments/development.rb
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.rails_logger = true
end
# Bullet mostrerà avvisi quando:
# - Viene rilevata una query N+1
# - È presente eager loading non necessario
# - Dovrebbe essere usato un counter cacheLa regola: usare includes di default (Rails sceglie la strategia ottimale), preload quando si vogliono forzare query separate, eager_load quando si filtra sulle associazioni.
Domanda 5: Spiegare Scope e Query Object in Rails
Gli scope incapsulano condizioni di query riutilizzabili. Per query complesse, i Query Object offrono migliore organizzazione e testabilità.
# app/models/product.rb
class Product < ApplicationRecord
# Scope semplici
scope :active, -> { where(active: true) }
scope :in_stock, -> { where('stock_quantity > 0') }
scope :featured, -> { where(featured: true) }
# Scope con parametri
scope :cheaper_than, ->(price) { where('price < ?', price) }
scope :in_category, ->(category) { where(category: category) }
# Scope concatenabili
scope :available, -> { active.in_stock }
# Scope con joins
scope :with_recent_orders, -> {
joins(:order_items)
.where('order_items.created_at > ?', 30.days.ago)
.distinct
}
# Scope con subquery
scope :bestsellers, -> {
where(id: OrderItem.group(:product_id)
.order('COUNT(*) DESC')
.limit(10)
.select(:product_id))
}
end# app/queries/products_search_query.rb
# Query Object per ricerche complesse
class ProductsSearchQuery
def initialize(relation = Product.all)
@relation = relation
end
def call(params)
@relation = filter_by_category(params[:category])
@relation = filter_by_price_range(params[:min_price], params[:max_price])
@relation = filter_by_search(params[:q])
@relation = apply_sorting(params[:sort])
@relation
end
private
def filter_by_category(category)
return @relation if category.blank?
@relation.where(category_id: category)
end
def filter_by_price_range(min, max)
@relation = @relation.where('price >= ?', min) if min.present?
@relation = @relation.where('price <= ?', max) if max.present?
@relation
end
def filter_by_search(query)
return @relation if query.blank?
@relation.where('name ILIKE ? OR description ILIKE ?',
"%#{query}%", "%#{query}%")
end
def apply_sorting(sort)
case sort
when 'price_asc' then @relation.order(price: :asc)
when 'price_desc' then @relation.order(price: :desc)
when 'newest' then @relation.order(created_at: :desc)
else @relation.order(:name)
end
end
end
# Utilizzo nel controller
@products = ProductsSearchQuery.new(Product.active).call(params)Gli scope sono perfetti per condizioni semplici e riutilizzabili. I Query Object si adattano a ricerche complesse con più filtri opzionali e logica di composizione.
Pronto a superare i tuoi colloqui su Ruby on Rails?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Routing e Controller
Domanda 6: Come funziona il routing RESTful in Rails?
Rails incoraggia rotte RESTful che mappano i verbi HTTP alle azioni CRUD. Il router traduce gli URL in chiamate specifiche al controller.
# config/routes.rb
Rails.application.routes.draw do
# Rotte RESTful standard (7 azioni)
resources :articles do
# Rotte annidate
resources :comments, only: [:create, :destroy]
# Rotte member (agiscono su un'istanza)
member do
post :publish
delete :archive
end
# Rotte collection (agiscono sulla collezione)
collection do
get :drafts
get :search
end
end
# Rotte API con namespace
namespace :api do
namespace :v1 do
resources :products, only: [:index, :show, :create, :update] do
resources :reviews, shallow: true
end
end
end
# Rotta personalizzata
get 'dashboard', to: 'dashboard#index'
# Vincoli sulle rotte
constraints(SubdomainConstraint.new) do
resources :admin_settings
end
# Rotta principale
root 'home#index'
end# rails routes - Mostra tutte le rotte generate
#
# Verb URI Pattern Controller#Action
# GET /articles articles#index
# POST /articles articles#create
# GET /articles/new articles#new
# GET /articles/:id/edit articles#edit
# GET /articles/:id articles#show
# PATCH /articles/:id articles#update
# DELETE /articles/:id articles#destroy
# POST /articles/:id/publish articles#publish
# GET /articles/drafts articles#draftsGli helper di rotta generati (article_path(@article), new_article_path) permettono di referenziare gli URL in modo dinamico e manutenibile.
Domanda 7: Spiegare callback e filter nei controller
I callback (before_action, after_action, around_action) permettono di eseguire codice prima, dopo o intorno alle azioni del controller.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Protezione CSRF abilitata di default
protect_from_forgery with: :exception
# Callback globale per l'autenticazione
before_action :authenticate_user!
# Gestione globale degli errori
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActionController::ParameterMissing, with: :bad_request
private
def not_found
render json: { error: 'Risorsa non trovata' }, status: :not_found
end
def bad_request(exception)
render json: { error: exception.message }, status: :bad_request
end
end# app/controllers/admin/products_controller.rb
class Admin::ProductsController < ApplicationController
# Callback con opzioni
before_action :require_admin
before_action :set_product, only: [:show, :edit, :update, :destroy]
after_action :log_activity, only: [:create, :update, :destroy]
# Callback condizionale
before_action :check_stock, only: [:update], if: :stock_changed?
def create
@product = Product.new(product_params)
if @product.save
redirect_to [:admin, @product], notice: 'Prodotto creato.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @product.update(product_params)
redirect_to [:admin, @product], notice: 'Prodotto aggiornato.'
else
render :edit, status: :unprocessable_entity
end
end
private
def require_admin
redirect_to root_path unless current_user&.admin?
end
def set_product
@product = Product.find(params[:id])
end
def stock_changed?
params[:product][:stock_quantity].present?
end
def log_activity
ActivityLog.create!(
user: current_user,
action: action_name,
resource: @product
)
end
def product_params
params.require(:product).permit(:name, :price, :description, :stock_quantity)
end
endI callback vengono eseguiti nell'ordine di dichiarazione. Usare skip_before_action nelle sottoclassi per disabilitare callback ereditati. Evitare callback con troppa logica di business: preferire i Service Object.
Service e architettura
Domanda 8: Come implementare i Service Object in Rails?
I Service Object incapsulano logica di business complessa che non appartiene né ai Model né ai Controller. Migliorano la testabilità e seguono il principio di singola responsabilità.
# app/services/order_processor.rb
# Service Object con interfaccia standardizzata
class OrderProcessor
def initialize(order, payment_method:)
@order = order
@payment_method = payment_method
end
def call
return failure('Ordine già elaborato') if @order.processed?
ActiveRecord::Base.transaction do
validate_stock!
process_payment!
update_inventory!
send_confirmation!
@order.update!(status: 'completed', processed_at: Time.current)
end
success(@order)
rescue PaymentError => e
failure("Pagamento fallito: #{e.message}")
rescue InsufficientStockError => e
failure("Stock insufficiente: #{e.message}")
end
private
def validate_stock!
@order.items.each do |item|
unless item.product.stock_quantity >= item.quantity
raise InsufficientStockError, item.product.name
end
end
end
def process_payment!
result = PaymentGateway.charge(
amount: @order.total,
method: @payment_method,
description: "Ordine ##{@order.id}"
)
raise PaymentError, result.error unless result.success?
@order.update!(payment_reference: result.transaction_id)
end
def update_inventory!
@order.items.each do |item|
item.product.decrement!(:stock_quantity, item.quantity)
end
end
def send_confirmation!
OrderMailer.confirmation(@order).deliver_later
end
def success(data)
Result.new(success: true, data: data)
end
def failure(error)
Result.new(success: false, error: error)
end
Result = Struct.new(:success, :data, :error, keyword_init: true) do
def success? = success
def failure? = !success
end
end# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
def create
@order = current_user.orders.build(order_params)
if @order.save
result = OrderProcessor.new(@order, payment_method: params[:payment_method]).call
if result.success?
redirect_to @order, notice: 'Ordine confermato!'
else
@order.update!(status: 'payment_failed')
flash.now[:alert] = result.error
render :new, status: :unprocessable_entity
end
else
render :new, status: :unprocessable_entity
end
end
endIl pattern Service Object segue una convenzione semplice: una classe, una responsabilità, un metodo pubblico call. Restituire un oggetto Result permette una gestione pulita di successo e fallimento.
Domanda 9: Spiegare i Concern in Rails
I Concern permettono di estrarre e condividere codice tra Model o Controller. Utilizzano ActiveSupport::Concern per una sintassi di inclusione pulita.
# app/models/concerns/sluggable.rb
# Concern riutilizzabile per generare slug
module Sluggable
extend ActiveSupport::Concern
included do
# Codice eseguito all'inclusione
before_validation :generate_slug, if: :should_generate_slug?
validates :slug, presence: true, uniqueness: true
end
# Metodi di classe
class_methods do
def find_by_slug!(slug)
find_by!(slug: slug)
end
def sluggable_source(column = :title)
@sluggable_source = column
end
def sluggable_source_column
@sluggable_source || :title
end
end
# Metodi di istanza
def to_param
slug
end
private
def should_generate_slug?
slug.blank? || send("#{self.class.sluggable_source_column}_changed?")
end
def generate_slug
source = send(self.class.sluggable_source_column)
return if source.blank?
base_slug = source.parameterize
self.slug = unique_slug(base_slug)
end
def unique_slug(base)
slug = base
counter = 1
while self.class.where(slug: slug).where.not(id: id).exists?
slug = "#{base}-#{counter}"
counter += 1
end
slug
end
end# app/models/article.rb
class Article < ApplicationRecord
include Sluggable
sluggable_source :title # Opzionale, :title di default
end
# app/models/product.rb
class Product < ApplicationRecord
include Sluggable
sluggable_source :name
end# app/controllers/concerns/pagination.rb
# Concern per i controller
module Pagination
extend ActiveSupport::Concern
included do
helper_method :page_param, :per_page_param
end
private
def paginate(relation)
relation.page(page_param).per(per_page_param)
end
def page_param
params[:page]&.to_i || 1
end
def per_page_param
[params[:per_page]&.to_i || 25, 100].min
end
endI Concern sono utili per codice realmente condiviso. Evitare di creare Concern solo per "accorciare" un Model: ciò nasconde la complessità senza ridurla.
Testing con RSpec
Domanda 10: Come strutturare i test RSpec in Rails?
RSpec è il framework di testing standard per Rails. Una buona struttura di test include Model spec, Controller spec, Service spec e test di integrazione.
# spec/models/user_spec.rb
require 'rails_helper'
RSpec.describe User, type: :model do
# Factory con FactoryBot
let(:user) { build(:user) }
let(:admin) { build(:user, :admin) }
describe 'validations' do
it { is_expected.to validate_presence_of(:email) }
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
it 'valida il formato dell’email' do
user.email = 'invalid'
expect(user).not_to be_valid
expect(user.errors[:email]).to include('is invalid')
end
end
describe 'associations' do
it { is_expected.to have_many(:articles).dependent(:destroy) }
it { is_expected.to have_one(:profile) }
it { is_expected.to belong_to(:organization).optional }
end
describe '#full_name' do
it 'restituisce nome e cognome combinati' do
user = build(:user, first_name: 'John', last_name: 'Doe')
expect(user.full_name).to eq('John Doe')
end
it 'gestisce il cognome mancante' do
user = build(:user, first_name: 'John', last_name: nil)
expect(user.full_name).to eq('John')
end
end
describe '.active' do
it 'restituisce solo gli utenti attivi' do
active = create(:user, active: true)
inactive = create(:user, active: false)
expect(User.active).to include(active)
expect(User.active).not_to include(inactive)
end
end
end# spec/services/order_processor_spec.rb
require 'rails_helper'
RSpec.describe OrderProcessor do
let(:user) { create(:user) }
let(:product) { create(:product, stock_quantity: 10, price: 100) }
let(:order) { create(:order, user: user, items: [build(:order_item, product: product, quantity: 2)]) }
subject { described_class.new(order, payment_method: 'card') }
describe '#call' do
context 'quando l’ordine è valido' do
before do
allow(PaymentGateway).to receive(:charge).and_return(
OpenStruct.new(success?: true, transaction_id: 'txn_123')
)
end
it 'elabora l’ordine con successo' do
result = subject.call
expect(result).to be_success
expect(order.reload.status).to eq('completed')
end
it 'decrementa lo stock del prodotto' do
expect { subject.call }.to change { product.reload.stock_quantity }.by(-2)
end
it 'invia l’email di conferma' do
expect { subject.call }
.to have_enqueued_mail(OrderMailer, :confirmation)
.with(order)
end
end
context 'quando il pagamento fallisce' do
before do
allow(PaymentGateway).to receive(:charge).and_return(
OpenStruct.new(success?: false, error: 'Card declined')
)
end
it 'restituisce un risultato di fallimento' do
result = subject.call
expect(result).to be_failure
expect(result.error).to include('Card declined')
end
it 'non aggiorna lo stato dell’ordine' do
expect { subject.call }.not_to change { order.reload.status }
end
end
end
end# spec/requests/api/v1/products_spec.rb
require 'rails_helper'
RSpec.describe 'API V1 Products', type: :request do
let(:user) { create(:user) }
let(:headers) { { 'Authorization' => "Bearer #{user.api_token}" } }
describe 'GET /api/v1/products' do
let!(:products) { create_list(:product, 3, :active) }
it 'restituisce la lista dei prodotti' do
get '/api/v1/products', headers: headers
expect(response).to have_http_status(:ok)
expect(json_response['data'].size).to eq(3)
end
it 'filtra per categoria' do
category = create(:category)
categorized = create(:product, category: category)
get '/api/v1/products', params: { category_id: category.id }, headers: headers
expect(json_response['data'].map { |p| p['id'] }).to eq([categorized.id])
end
end
describe 'POST /api/v1/products' do
let(:valid_params) do
{ product: { name: 'Nuovo Prodotto', price: 99.99, category_id: create(:category).id } }
end
it 'crea un nuovo prodotto' do
expect {
post '/api/v1/products', params: valid_params, headers: headers
}.to change(Product, :count).by(1)
expect(response).to have_http_status(:created)
end
end
endBuone pratiche: usare let per i dati, describe per metodi/contesti, context per le condizioni e it per le asserzioni specifiche. Un test dovrebbe testare una sola cosa.
Domanda 11: Come usare le factory con FactoryBot?
FactoryBot permette di creare dati di test in modo dichiarativo e manutenibile. Le factory sostituiscono le fixture statiche.
# spec/factories/users.rb
FactoryBot.define do
factory :user do
# Sequenze per garantire l'unicità
sequence(:email) { |n| "user#{n}@example.com" }
first_name { Faker::Name.first_name }
last_name { Faker::Name.last_name }
password { 'password123' }
confirmed_at { Time.current }
# Trait per le variazioni
trait :admin do
role { 'admin' }
after(:create) do |user|
user.permissions.create!(name: 'admin_access')
end
end
trait :unconfirmed do
confirmed_at { nil }
end
trait :with_profile do
after(:create) do |user|
create(:profile, user: user)
end
end
trait :with_articles do
transient do
articles_count { 3 }
end
after(:create) do |user, evaluator|
create_list(:article, evaluator.articles_count, author: user)
end
end
# Factory ereditata
factory :admin_user do
admin
with_profile
end
end
end# spec/factories/orders.rb
FactoryBot.define do
factory :order do
user
status { 'pending' }
trait :with_items do
transient do
items_count { 2 }
end
after(:create) do |order, evaluator|
create_list(:order_item, evaluator.items_count, order: order)
order.recalculate_total!
end
end
trait :completed do
status { 'completed' }
processed_at { Time.current }
with_items
end
trait :high_value do
after(:create) do |order|
create(:order_item, order: order, quantity: 10, unit_price: 500)
order.recalculate_total!
end
end
end
end# Utilizzo nei test
RSpec.describe OrderProcessor do
# build: istanza non persistita
let(:user) { build(:user) }
# create: persistita su DB
let(:order) { create(:order, :with_items, user: user) }
# create_list: più istanze
let(:products) { create_list(:product, 5) }
# Combinare i trait
let(:admin) { create(:user, :admin, :with_profile) }
# Sovrascrivere gli attributi
let(:expensive_order) { create(:order, :with_items, items_count: 10) }
# build_stubbed: più veloce, per i test unitari
let(:stubbed_user) { build_stubbed(:user) }
endPreferire build o build_stubbed rispetto a create quando la persistenza non è necessaria: questo accelera significativamente i test.
Background Job
Domanda 12: Come usare Active Job e Sidekiq in Rails?
Active Job offre un'interfaccia unificata per i job in background, indipendentemente dal backend (Sidekiq, Resque, ecc.). Sidekiq è la scelta più popolare per le sue performance con Redis.
# app/jobs/process_order_job.rb
class ProcessOrderJob < ApplicationJob
queue_as :default
# Configurazione dei retry
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10
discard_on ActiveJob::DeserializationError
# Opzioni Sidekiq (se backend Sidekiq)
sidekiq_options retry: 5, backtrace: true
def perform(order_id)
order = Order.find(order_id)
OrderProcessor.new(order).call
rescue ActiveRecord::RecordNotFound
# Ordine eliminato tra l'accodamento e l'esecuzione
Rails.logger.warn("Order #{order_id} not found, skipping job")
end
end# app/jobs/batch_email_job.rb
class BatchEmailJob < ApplicationJob
queue_as :mailers
# Limitazione del rate con Sidekiq Enterprise o gem throttle
sidekiq_options throttle: { threshold: 100, period: 1.minute }
def perform(user_ids, template_id)
template = EmailTemplate.find(template_id)
User.where(id: user_ids).find_each do |user|
UserMailer.custom_email(user, template).deliver_later
end
end
end# Accodare i job
# Immediato
ProcessOrderJob.perform_later(order.id)
# Ritardato
ProcessOrderJob.set(wait: 5.minutes).perform_later(order.id)
# A un orario specifico
ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order.id)
# Coda specifica
ProcessOrderJob.set(queue: :critical).perform_later(order.id)
# Sincrono (per test o debug)
ProcessOrderJob.perform_now(order.id)# config/sidekiq.yml
:concurrency: 10
:queues:
- [critical, 3] # Alta priorità, peso 3
- [default, 2] # Media priorità, peso 2
- [mailers, 1] # Bassa priorità, peso 1
- [low, 1]
:schedule:
cleanup_job:
cron: '0 3 * * *' # Ogni giorno alle 3:00
class: CleanupJobActive Job astrae il backend, ma accedere a funzionalità specifiche (batch, rate limiting) spesso richiede accoppiamento con il backend scelto.
Pronto a superare i tuoi colloqui su Ruby on Rails?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Sviluppo API
Domanda 13: Come costruire un'API RESTful con Rails?
Rails facilita la costruzione di API JSON con controller API-only e serializer. Una buona API è versionata, documentata e sicura.
# app/controllers/api/v1/base_controller.rb
module Api
module V1
class BaseController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
before_action :authenticate_token!
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
rescue_from ActionController::ParameterMissing, with: :bad_request
private
def authenticate_token!
authenticate_or_request_with_http_token do |token, options|
@current_user = User.find_by(api_token: token)
end
end
def current_user
@current_user
end
def not_found(exception)
render json: { error: 'Risorsa non trovata', details: exception.message },
status: :not_found
end
def unprocessable_entity(exception)
render json: { error: 'Validazione fallita', details: exception.record.errors },
status: :unprocessable_entity
end
def bad_request(exception)
render json: { error: 'Richiesta non valida', details: exception.message },
status: :bad_request
end
end
end
end# app/controllers/api/v1/products_controller.rb
module Api
module V1
class ProductsController < BaseController
before_action :set_product, only: [:show, :update, :destroy]
def index
@products = Product.active
.includes(:category)
.page(params[:page])
.per(params[:per_page] || 20)
render json: {
data: ProductSerializer.new(@products).serializable_hash,
meta: pagination_meta(@products)
}
end
def show
render json: ProductSerializer.new(@product, include: [:category, :reviews])
end
def create
@product = Product.new(product_params)
@product.save!
render json: ProductSerializer.new(@product), status: :created
end
def update
@product.update!(product_params)
render json: ProductSerializer.new(@product)
end
def destroy
@product.destroy!
head :no_content
end
private
def set_product
@product = Product.find(params[:id])
end
def product_params
params.require(:product).permit(:name, :description, :price, :category_id)
end
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count
}
end
end
end
end# app/serializers/product_serializer.rb
# Con la gem jsonapi-serializer
class ProductSerializer
include JSONAPI::Serializer
attributes :id, :name, :description, :price, :created_at
attribute :formatted_price do |product|
"$#{product.price.to_f.round(2)}"
end
belongs_to :category
has_many :reviews
link :self do |product|
Rails.application.routes.url_helpers.api_v1_product_url(product)
end
endBuone pratiche per le API: versionare tramite namespace, usare codici HTTP appropriati, paginare le collezioni e fornire messaggi di errore chiari.
Domanda 14: Come implementare l'autenticazione JWT in Rails?
JWT (JSON Web Tokens) è un metodo di autenticazione stateless popolare per le API. Il token codifica l'identità e la validità dell'utente.
# app/services/jwt_service.rb
class JwtService
SECRET_KEY = Rails.application.credentials.secret_key_base
ALGORITHM = 'HS256'.freeze
class << self
def encode(payload, exp = 24.hours.from_now)
payload[:exp] = exp.to_i
payload[:iat] = Time.current.to_i
JWT.encode(payload, SECRET_KEY, ALGORITHM)
end
def decode(token)
decoded = JWT.decode(token, SECRET_KEY, true, algorithm: ALGORITHM)
HashWithIndifferentAccess.new(decoded.first)
rescue JWT::ExpiredSignature
raise AuthenticationError, 'Token scaduto'
rescue JWT::DecodeError
raise AuthenticationError, 'Token non valido'
end
end
end# app/controllers/api/v1/auth_controller.rb
module Api
module V1
class AuthController < ActionController::API
def login
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
token = JwtService.encode(user_id: user.id)
render json: {
token: token,
user: UserSerializer.new(user),
expires_at: 24.hours.from_now
}
else
render json: { error: 'Credenziali non valide' }, status: :unauthorized
end
end
def refresh
token = JwtService.encode(user_id: current_user.id)
render json: { token: token, expires_at: 24.hours.from_now }
end
end
end
end# app/controllers/concerns/jwt_authenticatable.rb
module JwtAuthenticatable
extend ActiveSupport::Concern
included do
before_action :authenticate_jwt!
end
private
def authenticate_jwt!
header = request.headers['Authorization']
token = header&.split(' ')&.last
raise AuthenticationError, 'Token assente' unless token
decoded = JwtService.decode(token)
@current_user = User.find(decoded[:user_id])
rescue AuthenticationError => e
render json: { error: e.message }, status: :unauthorized
rescue ActiveRecord::RecordNotFound
render json: { error: 'Utente non trovato' }, status: :unauthorized
end
def current_user
@current_user
end
endPer la produzione, considerare: refresh token, blacklisting dei token al logout e tempi di scadenza brevi. Gem come devise-jwt semplificano l'implementazione.
Cache e performance
Domanda 15: Come implementare il caching in Rails?
Rails offre diversi livelli di caching: fragment caching, Russian Doll caching, low-level caching. La scelta dipende dal caso d'uso.
# config/environments/production.rb
config.action_controller.perform_caching = true
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
namespace: 'myapp:cache',
expires_in: 1.day,
race_condition_ttl: 10.seconds
}<%# app/views/products/index.html.erb %>
<%# Fragment caching con chiave di cache automatica %>
<% @products.each do |product| %>
<%# Cache basata su updated_at del prodotto %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
<%# Russian Doll caching - cache annidata %>
<% cache ['v1', @category] do %>
<h2><%= @category.name %></h2>
<% @category.products.each do |product| %>
<% cache ['v1', product] do %>
<%= render product %>
<% end %>
<% end %>
<% end %>
<%# Cache condizionale %>
<% cache_if current_user.nil?, @product do %>
<%= render @product %>
<% end %># app/models/product.rb
class Product < ApplicationRecord
# Touch sul padre per invalidare la cache Russian Doll
belongs_to :category, touch: true
# Chiave di cache personalizzata
def cache_key_with_version
"#{super}/#{reviews.maximum(:updated_at)&.to_i}"
end
end# Low-level caching nei service
class DashboardStatsService
def call
Rails.cache.fetch('dashboard:stats', expires_in: 15.minutes) do
{
total_users: User.count,
active_users: User.where('last_sign_in_at > ?', 30.days.ago).count,
total_orders: Order.completed.count,
revenue_mtd: Order.completed.where(created_at: Time.current.beginning_of_month..).sum(:total)
}
end
end
end
# Cache con protezione contro race condition
Rails.cache.fetch('popular_products', expires_in: 1.hour, race_condition_ttl: 10.seconds) do
Product.bestsellers.limit(10).to_a
end
# Invalidazione esplicita
Rails.cache.delete('dashboard:stats')
Rails.cache.delete_matched('products:*')Il Russian Doll caching è efficace perché solo i frammenti modificati vengono rigenerati. Usare touch: true sulle associazioni per propagare l'invalidazione.
Domanda 16: Come ottimizzare le performance di un'applicazione Rails?
L'ottimizzazione di Rails copre diversi aspetti: query DB, cache, asset e architettura. Un approccio metodico con monitoraggio è essenziale.
# Ottimizzazione del database
# config/database.yml
production:
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
prepared_statements: true
advisory_locks: true
# app/models/order.rb
class Order < ApplicationRecord
# Indici composti per query frequenti
# add_index :orders, [:user_id, :status, :created_at]
# Selezionare solo le colonne necessarie
scope :summary, -> { select(:id, :status, :total, :created_at) }
# Elaborazione batch per grandi volumi
def self.process_pending
pending.find_each(batch_size: 1000) do |order|
ProcessOrderJob.perform_later(order.id)
end
end
# Evitare calcoli ripetitivi
def self.revenue_by_month
completed
.group("DATE_TRUNC('month', created_at)")
.sum(:total)
end
end# Ottimizzazione della memoria
# config/puma.rb
workers ENV.fetch("WEB_CONCURRENCY") { 2 }
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
threads threads_count, threads_count
preload_app!
before_fork do
ActiveRecord::Base.connection_pool.disconnect!
end
on_worker_boot do
ActiveRecord::Base.establish_connection
end# Profiling con rack-mini-profiler
# Gemfile
group :development do
gem 'rack-mini-profiler'
gem 'memory_profiler'
gem 'stackprof'
end
# config/initializers/mini_profiler.rb
if defined?(Rack::MiniProfiler)
Rack::MiniProfiler.config.position = 'bottom-right'
Rack::MiniProfiler.config.start_hidden = true
end# Lazy loading e paginazione
class ProductsController < ApplicationController
def index
@products = Product.active
.includes(:category, :primary_image)
.page(params[:page])
.per(24)
# Prefetch per la pagina successiva
if @products.next_page
Rails.cache.fetch("products:page:#{@products.next_page}", expires_in: 5.minutes) do
Product.active.page(@products.next_page).per(24).to_a
end
end
end
endStrumenti essenziali: rack-mini-profiler per il profiling, bullet per la rilevazione di N+1, New Relic o Scout per il monitoraggio in produzione.
Sicurezza
Domanda 17: Quali sono le best practice di sicurezza in Rails?
Rails include protezioni di default contro le vulnerabilità più comuni. Comprendere e configurare correttamente queste protezioni è cruciale.
# Protezione CSRF
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Abilitata di default, solleva un'eccezione se il token non è valido
protect_from_forgery with: :exception
# Per le API, usare :null_session
# protect_from_forgery with: :null_session
end
# Nelle view, il token è incluso automaticamente nei form
# <%= form_with ... %> include authenticity_token
# Per le richieste AJAX
# Aggiungere l'header X-CSRF-Token con il valore di csrf_meta_tags# Prevenzione SQL Injection
# ✅ Parametri interpolati con escape automatico
User.where('email = ?', params[:email])
User.where(email: params[:email])
# ❌ PERICOLO - Interpolazione diretta
User.where("email = '#{params[:email]}'")
# ✅ Per clausole ORDER dinamiche
ALLOWED_SORTS = %w[name created_at price].freeze
sort_column = ALLOWED_SORTS.include?(params[:sort]) ? params[:sort] : 'name'
Product.order(sort_column)# Protezione XSS
# Rails fa l'escape dell'HTML nelle view automaticamente
# ✅ Escape automatico
<%= user.name %>
# ❌ Pericoloso - contenuto senza escape
<%== user.bio %>
<%= raw user.bio %>
<%= user.bio.html_safe %>
# ✅ Per HTML sicuro, usare sanitize
<%= sanitize user.bio, tags: %w[p br strong em] %># Strong Parameters
class UsersController < ApplicationController
def update
@user.update!(user_params)
end
private
def user_params
# Whitelist esplicita degli attributi consentiti
params.require(:user).permit(:name, :email, :avatar)
# Solo per amministratori
if current_user.admin?
params.require(:user).permit(:name, :email, :role, :active)
else
params.require(:user).permit(:name, :email)
end
end
end# Header di sicurezza
# config/initializers/secure_headers.rb
Rails.application.config.action_dispatch.default_headers = {
'X-Frame-Options' => 'SAMEORIGIN',
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff',
'X-Download-Options' => 'noopen',
'X-Permitted-Cross-Domain-Policies' => 'none',
'Referrer-Policy' => 'strict-origin-when-cross-origin'
}
# Content Security Policy
Rails.application.config.content_security_policy do |policy|
policy.default_src :self
policy.script_src :self
policy.style_src :self, :unsafe_inline
policy.img_src :self, :data, 'https:'
endAuditare regolarmente con brakeman (analisi statica di sicurezza) e mantenere aggiornate le gem con bundle audit.
Domanda 18: Come gestire autenticazione e autorizzazione in Rails?
L'autenticazione verifica l'identità, l'autorizzazione controlla i permessi. Devise gestisce l'auth, Pundit o CanCanCan gestiscono l'autorizzazione.
# Setup di Devise
# app/models/user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:confirmable, :lockable, :trackable
enum role: { user: 0, moderator: 1, admin: 2 }
def admin?
role == 'admin'
end
end# Policy Pundit
# app/policies/article_policy.rb
class ArticlePolicy < ApplicationPolicy
def index?
true
end
def show?
record.published? || owner_or_admin?
end
def create?
user.present?
end
def update?
owner_or_admin?
end
def destroy?
owner_or_admin?
end
def publish?
user&.admin? || user&.moderator?
end
# Scope per le collezioni
class Scope < Scope
def resolve
if user&.admin?
scope.all
elsif user
scope.where(published: true).or(scope.where(author: user))
else
scope.where(published: true)
end
end
end
private
def owner_or_admin?
user&.admin? || record.author == user
end
end# Controller con Pundit
class ArticlesController < ApplicationController
include Pundit::Authorization
after_action :verify_authorized, except: :index
after_action :verify_policy_scoped, only: :index
def index
@articles = policy_scope(Article).includes(:author).page(params[:page])
end
def show
@article = Article.find(params[:id])
authorize @article
end
def update
@article = Article.find(params[:id])
authorize @article
if @article.update(article_params)
redirect_to @article, notice: 'Articolo aggiornato.'
else
render :edit, status: :unprocessable_entity
end
end
def publish
@article = Article.find(params[:id])
authorize @article
@article.update!(published: true, published_at: Time.current)
redirect_to @article, notice: 'Articolo pubblicato.'
end
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
flash[:alert] = "Non sei autorizzato a eseguire questa azione."
redirect_back(fallback_location: root_path)
end
endPundit è più esplicito e testabile rispetto a CanCanCan. Ogni azione ha un metodo di policy corrispondente, e gli scope filtrano automaticamente le collezioni.
Rails avanzato
Domanda 19: Spiegare il pattern Repository in Rails
Il pattern Repository isola la logica di accesso ai dati dal resto dell'applicazione. Sebbene Rails usi Active Record (un pattern diverso), Repository può essere utile per casi complessi.
# app/repositories/base_repository.rb
class BaseRepository
def initialize(model_class)
@model_class = model_class
end
def all
@model_class.all
end
def find(id)
@model_class.find(id)
end
def find_by(attributes)
@model_class.find_by(attributes)
end
def create(attributes)
@model_class.create(attributes)
end
def update(record, attributes)
record.update(attributes)
end
def delete(record)
record.destroy
end
end# app/repositories/product_repository.rb
class ProductRepository < BaseRepository
def initialize
super(Product)
end
def active
@model_class.where(active: true)
end
def in_category(category_id)
@model_class.where(category_id: category_id)
end
def search(query)
@model_class.where('name ILIKE ? OR description ILIKE ?',
"%#{query}%", "%#{query}%")
end
def with_stock
@model_class.where('stock_quantity > 0')
end
def bestsellers(limit: 10)
@model_class
.joins(:order_items)
.group(:id)
.order('COUNT(order_items.id) DESC')
.limit(limit)
end
def for_homepage
active
.with_stock
.includes(:category, :primary_image)
.order(featured: :desc, created_at: :desc)
.limit(12)
end
end# Utilizzo in un service
class ProductSearchService
def initialize(repository: ProductRepository.new)
@repository = repository
end
def call(params)
products = @repository.active
products = products.in_category(params[:category]) if params[:category]
products = products.search(params[:query]) if params[:query].present?
products = products.with_stock if params[:in_stock]
products
end
end
# Facilita il testing con i mock
RSpec.describe ProductSearchService do
let(:repository) { instance_double(ProductRepository) }
let(:service) { described_class.new(repository: repository) }
it 'filtra per categoria' do
products = double('products')
allow(repository).to receive(:active).and_return(products)
allow(products).to receive(:in_category).with(1).and_return(products)
service.call(category: 1)
expect(products).to have_received(:in_category).with(1)
end
endRepository è opzionale in Rails poiché Active Record è già un eccellente pattern. Usarlo per query complesse o quando l'isolamento dello storage è importante.
Domanda 20: Come implementare il pattern CQRS in Rails?
CQRS (Command Query Responsibility Segregation) separa le operazioni di lettura e scrittura. In Rails, ciò si traduce in classi distinte per query e command.
# app/commands/base_command.rb
class BaseCommand
include ActiveModel::Validations
def self.call(*args)
new(*args).call
end
def call
return failure(errors) unless valid?
execute
end
private
def execute
raise NotImplementedError
end
def success(data = nil)
CommandResult.success(data)
end
def failure(errors)
CommandResult.failure(errors)
end
end
CommandResult = Struct.new(:success, :data, :errors, keyword_init: true) do
def success? = success
def failure? = !success
def self.success(data)
new(success: true, data: data, errors: [])
end
def self.failure(errors)
new(success: false, data: nil, errors: Array(errors))
end
end# app/commands/orders/create_order_command.rb
module Orders
class CreateOrderCommand < BaseCommand
attr_reader :user, :items, :shipping_address
validates :user, presence: true
validates :items, presence: true
validate :validate_items_availability
def initialize(user:, items:, shipping_address:)
@user = user
@items = items
@shipping_address = shipping_address
end
private
def execute
order = nil
ActiveRecord::Base.transaction do
order = Order.create!(
user: user,
shipping_address: shipping_address,
status: 'pending'
)
items.each do |item|
order.items.create!(
product_id: item[:product_id],
quantity: item[:quantity],
unit_price: Product.find(item[:product_id]).price
)
end
order.calculate_total!
end
OrderCreatedEvent.broadcast(order)
success(order)
rescue ActiveRecord::RecordInvalid => e
failure(e.message)
end
def validate_items_availability
items.each do |item|
product = Product.find_by(id: item[:product_id])
unless product&.stock_quantity&.>= item[:quantity]
errors.add(:items, "Prodotto #{item[:product_id]} non disponibile")
end
end
end
end
end# app/queries/orders/user_orders_query.rb
module Orders
class UserOrdersQuery
def initialize(user, params = {})
@user = user
@params = params
end
def call
orders = @user.orders.includes(:items, items: :product)
orders = apply_status_filter(orders)
orders = apply_date_filter(orders)
orders = apply_sorting(orders)
orders.page(@params[:page]).per(@params[:per_page] || 20)
end
private
def apply_status_filter(orders)
return orders unless @params[:status]
orders.where(status: @params[:status])
end
def apply_date_filter(orders)
orders = orders.where('created_at >= ?', @params[:from]) if @params[:from]
orders = orders.where('created_at <= ?', @params[:to]) if @params[:to]
orders
end
def apply_sorting(orders)
case @params[:sort]
when 'oldest' then orders.order(created_at: :asc)
when 'total_desc' then orders.order(total: :desc)
else orders.order(created_at: :desc)
end
end
end
end# Controller che usa CQRS
class OrdersController < ApplicationController
def index
@orders = Orders::UserOrdersQuery.new(current_user, filter_params).call
end
def create
result = Orders::CreateOrderCommand.call(
user: current_user,
items: order_params[:items],
shipping_address: order_params[:shipping_address]
)
if result.success?
redirect_to result.data, notice: 'Ordine creato!'
else
flash.now[:alert] = result.errors.join(', ')
render :new, status: :unprocessable_entity
end
end
endCQRS brilla in applicazioni complesse con esigenze asimmetriche di lettura/scrittura. Per CRUD semplici, è over-engineering.
Domanda 21: Come gestire i WebSocket con Action Cable?
Action Cable integra i WebSocket in Rails per la comunicazione bidirezionale in tempo reale. Utilizza Redis per la sincronizzazione tra server.
# app/channels/application_cable/connection.rb
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
# Tramite cookie di sessione
if verified_user = User.find_by(id: cookies.encrypted[:user_id])
verified_user
# Tramite JWT per le API
elsif verified_user = verify_jwt_token
verified_user
else
reject_unauthorized_connection
end
end
def verify_jwt_token
token = request.params[:token]
return nil unless token
decoded = JwtService.decode(token)
User.find(decoded[:user_id])
rescue
nil
end
end
end# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
@room = ChatRoom.find(params[:room_id])
# Verificare i permessi
unless @room.accessible_by?(current_user)
reject
return
end
stream_for @room
# Notificare gli altri della presenza
broadcast_presence(:joined)
end
def unsubscribed
broadcast_presence(:left) if @room
end
def send_message(data)
message = @room.messages.create!(
user: current_user,
content: data['content']
)
# Trasmettere a tutti gli iscritti
ChatChannel.broadcast_to(@room, {
type: 'message',
message: MessageSerializer.new(message).as_json
})
end
def typing
ChatChannel.broadcast_to(@room, {
type: 'typing',
user: current_user.name
})
end
private
def broadcast_presence(action)
ChatChannel.broadcast_to(@room, {
type: 'presence',
action: action,
user: current_user.name,
online_count: @room.online_users_count
})
end
endimport consumer from "./consumer"
const chatChannel = consumer.subscriptions.create(
{ channel: "ChatChannel", room_id: roomId },
{
connected() {
console.log("Connected to chat")
},
disconnected() {
console.log("Disconnected from chat")
},
received(data) {
switch(data.type) {
case 'message':
this.appendMessage(data.message)
break
case 'typing':
this.showTypingIndicator(data.user)
break
case 'presence':
this.updatePresence(data)
break
}
},
sendMessage(content) {
this.perform('send_message', { content: content })
},
notifyTyping() {
this.perform('typing')
}
}
)Action Cable gestisce automaticamente le riconnessioni e la sincronizzazione. In produzione, configurare Redis come adapter e scalare in base alle connessioni concorrenti.
Domanda 22: Come implementare la multi-tenancy in Rails?
La multi-tenancy permette a un'applicazione di servire più client (tenant) isolati. Tre approcci principali: a livello di database, a livello di schema o a livello di riga.
# Multitenancy a livello di riga con ActsAsTenant o manuale
# app/models/concerns/tenant_scoped.rb
module TenantScoped
extend ActiveSupport::Concern
included do
belongs_to :tenant
# Scope di default sul tenant corrente
default_scope -> { where(tenant: Current.tenant) if Current.tenant }
# Validazione del tenant
before_validation :set_tenant, on: :create
end
private
def set_tenant
self.tenant ||= Current.tenant
end
end
# app/models/current.rb
class Current < ActiveSupport::CurrentAttributes
attribute :tenant, :user
end# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_current_tenant
private
def set_current_tenant
Current.tenant = resolve_tenant
Current.user = current_user
end
def resolve_tenant
# Tramite sottodominio
if request.subdomain.present? && request.subdomain != 'www'
Tenant.find_by!(subdomain: request.subdomain)
# Tramite header (per le API)
elsif request.headers['X-Tenant-ID'].present?
Tenant.find(request.headers['X-Tenant-ID'])
# Tramite utente
elsif current_user
current_user.tenant
end
rescue ActiveRecord::RecordNotFound
redirect_to root_url(subdomain: 'www'), alert: 'Tenant non trovato'
end
end# app/models/project.rb
class Project < ApplicationRecord
include TenantScoped
has_many :tasks
belongs_to :owner, class_name: 'User'
end
# app/models/user.rb
class User < ApplicationRecord
include TenantScoped
has_many :projects, foreign_key: :owner_id
# Gli amministratori possono appartenere a più tenant
has_many :tenant_memberships
has_many :accessible_tenants, through: :tenant_memberships, source: :tenant
end# A livello di schema con la gem Apartment (PostgreSQL)
# config/initializers/apartment.rb
Apartment.configure do |config|
config.excluded_models = %w[Tenant User]
config.tenant_names = -> { Tenant.pluck(:subdomain) }
end
# Utilizzo
Apartment::Tenant.switch('acme') do
# Tutte le query in questo blocco usano lo schema 'acme'
Project.all # SELECT * FROM acme.projects
endIl livello di riga è il più semplice ma richiede attenzione costante alle perdite. Il livello di schema offre un isolamento migliore ma complica le migration. Scegliere in base alle esigenze di sicurezza e scalabilità.
Domanda 23: Come configurare un'architettura a microservizi con Rails?
Rails può servire come base per un'architettura a microservizi con comunicazione tramite HTTP/gRPC o code di messaggi. La chiave è definire bene i confini.
# Client di servizio HTTP
# app/services/payment_service_client.rb
class PaymentServiceClient
include HTTParty
base_uri ENV.fetch('PAYMENT_SERVICE_URL')
def initialize
@options = {
headers: {
'Content-Type' => 'application/json',
'X-Service-Token' => ENV.fetch('SERVICE_TOKEN')
},
timeout: 10
}
end
def create_charge(amount:, currency:, source:, metadata: {})
response = self.class.post('/charges', @options.merge(
body: { amount: amount, currency: currency, source: source, metadata: metadata }.to_json
))
handle_response(response)
end
def get_charge(charge_id)
response = self.class.get("/charges/#{charge_id}", @options)
handle_response(response)
end
private
def handle_response(response)
case response.code
when 200..299
ServiceResult.success(response.parsed_response)
when 400..499
ServiceResult.failure(response.parsed_response['error'], code: response.code)
else
ServiceResult.failure('Servizio non disponibile', code: response.code)
end
rescue Net::OpenTimeout, Net::ReadTimeout
ServiceResult.failure('Timeout del servizio')
end
end# Comunicazione event-driven con Sidekiq/Redis
# app/events/order_events.rb
module OrderEvents
class Created
include Wisper::Publisher
def call(order)
broadcast(:order_created, order)
end
end
end
# app/listeners/inventory_listener.rb
class InventoryListener
def order_created(order)
order.items.each do |item|
InventoryServiceClient.new.reserve_stock(
product_id: item.product_id,
quantity: item.quantity,
reference: order.id
)
end
end
end
# config/initializers/wisper.rb
Wisper.subscribe(InventoryListener.new, async: true)
Wisper.subscribe(NotificationListener.new, async: true)# Pattern API Gateway
# app/controllers/api/v1/gateway_controller.rb
module Api
module V1
class GatewayController < BaseController
# Aggregare più servizi
def dashboard
results = Parallel.map([:orders, :inventory, :analytics], in_threads: 3) do |service|
fetch_from_service(service)
end
render json: {
orders: results[0],
inventory: results[1],
analytics: results[2]
}
end
private
def fetch_from_service(service)
case service
when :orders
OrderServiceClient.new.recent_orders(limit: 5)
when :inventory
InventoryServiceClient.new.low_stock_alerts
when :analytics
AnalyticsServiceClient.new.daily_summary
end
rescue => e
{ error: "#{service} non disponibile", message: e.message }
end
end
end
endPer i microservizi Rails: definire contratti API chiari (OpenAPI), implementare circuit breaker (gem circuitbox) e usare il tracing distribuito (gem opentelemetry).
Domanda 24: Come fare il deploy di un'applicazione Rails in produzione?
Il deploy moderno di Rails utilizza container o PaaS. Una configurazione di produzione robusta copre asset, database e monitoraggio.
# config/environments/production.rb
Rails.application.configure do
config.cache_classes = true
config.eager_load = true
config.consider_all_requests_local = false
# Asset
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
config.assets.compile = false
config.assets.digest = true
# Logging
config.log_level = ENV.fetch('LOG_LEVEL', 'info').to_sym
config.log_tags = [:request_id]
config.logger = ActiveSupport::Logger.new(STDOUT)
.tap { |logger| logger.formatter = Logger::Formatter.new }
.then { |logger| ActiveSupport::TaggedLogging.new(logger) }
# Cache
config.cache_store = :redis_cache_store, {
url: ENV['REDIS_URL'],
expires_in: 1.day
}
# Forzare SSL
config.force_ssl = true
config.ssl_options = { hsts: { subdomains: true } }
# Action Mailer
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: ENV['SMTP_HOST'],
port: ENV['SMTP_PORT'],
user_name: ENV['SMTP_USER'],
password: ENV['SMTP_PASSWORD'],
authentication: :plain,
enable_starttls_auto: true
}
end# Dockerfile
FROM ruby:3.3-alpine AS builder
RUN apk add --no-cache build-base postgresql-dev nodejs yarn
WORKDIR /app
COPY Gemfile Gemfile.lock ./
RUN bundle config set --local deployment true && \
bundle config set --local without 'development test' && \
bundle install
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN bundle exec rails assets:precompile
# Immagine di produzione
FROM ruby:3.3-alpine
RUN apk add --no-cache postgresql-client tzdata
WORKDIR /app
COPY /app /app
COPY /usr/local/bundle /usr/local/bundle
ENV RAILS_ENV=production
ENV RAILS_LOG_TO_STDOUT=true
EXPOSE 3000
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]# docker-compose.production.yml
version: '3.8'
services:
web:
build: .
environment:
- DATABASE_URL=postgres://user:pass@db/app_production
- REDIS_URL=redis://redis:6379/0
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
depends_on:
- db
- redis
deploy:
replicas: 3
resources:
limits:
memory: 512M
sidekiq:
build: .
command: bundle exec sidekiq
environment:
- DATABASE_URL=postgres://user:pass@db/app_production
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
deploy:
replicas: 2
db:
image: postgres:15-alpine
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:Checklist di produzione: SSL obbligatorio, segreti tramite ENV, health check, backup automatici del DB, monitoraggio (APM + log + metriche) e alerting configurato.
Domanda 25: Quali sono le novità di Rails 7+ da conoscere?
Rails 7+ porta cambiamenti significativi: Hotwire di default, import map, credenziali criptate migliorate e numerose ottimizzazioni.
# Hotwire - Turbo Frames
# app/views/articles/index.html.erb
<%= turbo_frame_tag "articles" do %>
<% @articles.each do |article| %>
<%= turbo_frame_tag dom_id(article) do %>
<%= render article %>
<% end %>
<% end %>
<%= link_to "Carica altri", articles_path(page: @page + 1),
data: { turbo_frame: "articles" } %>
<% end %>
# Turbo Streams per gli aggiornamenti in tempo reale
# app/controllers/comments_controller.rb
def create
@comment = @article.comments.create!(comment_params.merge(user: current_user))
respond_to do |format|
format.turbo_stream
format.html { redirect_to @article }
end
end
# app/views/comments/create.turbo_stream.erb
<%= turbo_stream.append "comments", @comment %>
<%= turbo_stream.update "comments_count", @article.comments.count %>
<%= turbo_stream.replace "comment_form", partial: "comments/form", locals: { comment: Comment.new } %># Controller Stimulus
# app/javascript/controllers/search_controller.js
import { Controller } from "@hotwired/stimulus"
import { debounce } from "lodash-es"
export default class extends Controller {
static targets = ["input", "results"]
static values = { url: String }
connect() {
this.search = debounce(this.search.bind(this), 300)
}
async search() {
const query = this.inputTarget.value
if (query.length < 2) return
const response = await fetch(`${this.urlValue}?q=${encodeURIComponent(query)}`)
this.resultsTarget.innerHTML = await response.text()
}
}# Import Maps (senza bundler JavaScript)
# config/importmap.rb
pin "application"
pin "@hotwired/turbo-rails", to: "turbo.min.js"
pin "@hotwired/stimulus", to: "stimulus.min.js"
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js"
pin_all_from "app/javascript/controllers", under: "controllers"
# Pin da CDN
pin "lodash-es", to: "https://ga.jspm.io/npm:lodash-es@4.17.21/lodash.js"# Active Record Encryption (Rails 7+)
# app/models/user.rb
class User < ApplicationRecord
encrypts :email, deterministic: true # Permette le ricerche
encrypts :phone_number # Non deterministico di default
encrypts :ssn, deterministic: true, downcase: true
end
# config/credentials.yml.enc
active_record_encryption:
primary_key: abc123...
deterministic_key: def456...
key_derivation_salt: ghi789...# Miglioramenti all'interfaccia di query
# Rails 7.1+
# Query asincrone
users = User.where(active: true).load_async
# Continuare a elaborare mentre la query è in esecuzione
# Accedere ai risultati con users.to_a
# Common Table Expressions (CTE)
User.with(
recent_orders: Order.where('created_at > ?', 30.days.ago)
).joins('JOIN recent_orders ON recent_orders.user_id = users.id')
# Rilevamento automatico di inverse_of
class Author < ApplicationRecord
has_many :books # inverse_of rilevato automaticamente
end
# Strict loading di default (evita N+1)
class ApplicationRecord < ActiveRecord::Base
self.strict_loading_by_default = true
endRails 7+ favorisce la semplicità (niente Webpack di default) e l'HTML-over-the-wire con Hotwire. Questo approccio riduce la complessità di JavaScript offrendo al contempo un'esperienza utente moderna.
Conclusione
I colloqui Ruby on Rails valutano la padronanza dell'intero framework e la comprensione delle sue convenzioni. Punti chiave da ricordare:
✅ Fondamenti: MVC, Active Record, migration, validazioni e associazioni
✅ Architettura: Service Object, Concern, Query Object e pattern CQRS
✅ Performance: query N+1, caching (fragment, Russian Doll, low-level), eager loading
✅ Testing: RSpec, FactoryBot, request spec e best practice di testing
✅ Sicurezza: CSRF, SQL injection, XSS, Strong Parameters e autenticazione/autorizzazione
✅ API: design RESTful, JWT, serializer e versionamento
✅ Produzione: background job, WebSocket, deploy e monitoraggio
La filosofia Rails (Convention over Configuration, DRY e Rails Way) guida tutte le decisioni architetturali. Padroneggiare questi principi e sapere quando deviarne dimostra una solida competenza.
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
Tag
Condividi
Articoli correlati

ActiveRecord: risolvere i problemi di query N+1 in Ruby on Rails
Guida completa per individuare e risolvere le query N+1 in Rails con ActiveRecord. Padroneggia includes, preload, eager_load e gli strumenti di rilevamento automatico.

Ruby on Rails 7: Hotwire e Turbo per Applicazioni Reattive
Guida completa a Hotwire e Turbo in Rails 7. Costruire applicazioni reattive senza scrivere JavaScript con Turbo Drive, Frames e Streams.

Rails API Mode nel 2026: API RESTful, Serializzazione e Domande da Colloquio
Guida completa alla modalità API-only di Rails 8.1 nel 2026. Configurazione, serializzazione con Alba e jsonapi-serializer, autenticazione JWT, gestione errori strutturata e domande frequenti nei colloqui tecnici Ruby on Rails.