# Perguntas de entrevista Ruby on Rails: Top 25 em 2026 > As 25 perguntas de entrevista Ruby on Rails mais cobradas. Arquitetura MVC, Active Record, migrations, testing RSpec, APIs REST com respostas detalhadas e exemplos de código. - Published: 2026-02-03 - Updated: 2026-04-27 - Author: Anthony Fillion-Maillet - Tags: ruby on rails, ruby, entrevista, active record, entrevista técnica - Reading time: 22 min --- As entrevistas de Ruby on Rails avaliam o domínio do framework Ruby mais popular, a compreensão da arquitetura MVC, do ORM Active Record, e a capacidade de construir aplicações web robustas seguindo a filosofia "Convention over Configuration". Este guia cobre as 25 perguntas mais cobradas, dos fundamentos do Rails aos padrões avançados de produção. > **Dica para a entrevista** > > Os recrutadores valorizam candidatos que entendem a filosofia Rails: "Convention over Configuration", DRY (Don't Repeat Yourself) e os padrões Rails Way. Explicar por que o Rails toma certas decisões arquiteturais faz a diferença. ## Fundamentos de Ruby on Rails ### Pergunta 1: Explique o padrão MVC no Ruby on Rails O padrão Model-View-Controller (MVC) é o núcleo arquitetural do Rails. Ele separa as responsabilidades em três camadas distintas para melhor manutenibilidade e testabilidade do código. ```ruby # app/models/article.rb # O Model gerencia os dados e a lógica de negócio class Article < ApplicationRecord # Validações de dados validates :title, presence: true, length: { minimum: 5 } validates :body, presence: true # Associações com outros modelos belongs_to :author, class_name: 'User' has_many :comments, dependent: :destroy has_many :tags, through: :article_tags # Scopes para consultas reutilizáveis scope :published, -> { where(published: true) } scope :recent, -> { order(created_at: :desc).limit(10) } # Callbacks do ciclo de vida before_save :generate_slug private def generate_slug self.slug = title.parameterize if title_changed? end end ``` ```ruby # app/controllers/articles_controller.rb # O Controller recebe as requisições e orquestra a resposta 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: 'Artigo criado com sucesso.' 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 ``` ```erb <%# app/views/articles/show.html.erb %> <%# A View exibe os dados em formato HTML %>

<%= @article.title %>

Por <%= @article.author.name %> • <%= l @article.created_at, format: :long %>

<%= simple_format @article.body %>
<%# Partial para os comentários %> <%= render @comments %>
``` O fluxo típico: a requisição chega ao Router, que despacha para o Controller apropriado. O Controller interage com o Model para recuperar ou modificar os dados, depois passa esses dados para a View para o renderização HTML. ### Pergunta 2: O que é Active Record e como funciona o ORM do Rails? Active Record é o ORM (Object-Relational Mapping) do Rails que implementa o padrão Active Record. Cada classe Model representa uma tabela do banco de dados, e cada instância representa uma linha. ```ruby # app/models/user.rb # Active Record mapeia automaticamente colunas para atributos class User < ApplicationRecord # A tabela 'users' é associada automaticamente # Colunas: id, email, name, created_at, updated_at has_secure_password # BCrypt para a senha has_many :articles, foreign_key: :author_id has_one :profile, dependent: :destroy has_and_belongs_to_many :roles # Validações validates :email, presence: true, uniqueness: { case_sensitive: false }, format: { with: URI::MailTo::EMAIL_REGEXP } # Callbacks before_save :normalize_email # Métodos de classe para consultas def self.admins joins(:roles).where(roles: { name: 'admin' }) end private def normalize_email self.email = email.downcase.strip end end ``` ```ruby # Exemplos de consultas Active Record # Console Rails ou dentro de um service # Criação user = User.create!(email: 'dev@example.com', name: 'Alice', password: 'secret123') # Leitura com condições active_users = User.where(active: true).order(:name) user = User.find_by(email: 'dev@example.com') # Consultas encadeadas (avaliação preguiçosa) recent_admins = User.admins .where('created_at > ?', 1.month.ago) .includes(:profile) .limit(10) # Prevenção de N+1 com eager loading articles = Article.includes(:author, :comments).published # Atualização user.update!(name: 'Alice Martin') # Transações User.transaction do user.debit_balance!(100) recipient.credit_balance!(100) Payment.create!(from: user, to: recipient, amount: 100) end ``` Active Record converte os métodos Ruby em consultas SQL otimizadas. Métodos como `where`, `joins`, `includes` são preguiçosos: a consulta só é executada na iteração ou na chamada de `to_a`. ### Pergunta 3: Explique o sistema de migrations do Rails As migrations permitem versionar o schema do banco de dados com Ruby. Elas são reversíveis e permitem uma evolução controlada da estrutura de dados. ```ruby # db/migrate/20260203100000_create_products.rb # Migration para criar uma tabela 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 automáticos end # Índices para performance add_index :products, :name add_index :products, [:category_id, :active] end end ``` ```ruby # db/migrate/20260203110000_add_slug_to_products.rb # Migration para modificar uma tabela existente class AddSlugToProducts < ActiveRecord::Migration[7.1] def change add_column :products, :slug, :string add_index :products, :slug, unique: true # Preencher slugs existentes reversible do |dir| dir.up do Product.find_each do |product| product.update_column(:slug, product.name.parameterize) end end end # Tornar não nulo após o preenchimento change_column_null :products, :slug, false end end ``` ```bash # Comandos essenciais de migration rails db:migrate # Executar migrations pendentes rails db:rollback # Desfazer a última migration rails db:rollback STEP=3 # Desfazer as últimas 3 migrations rails db:migrate:status # Ver o status das migrations rails db:seed # Executar db/seeds.rb rails db:reset # Drop, create, migrate, seed ``` As migrations devem ser reversíveis. O método `change` é inteligente e pode reverter automaticamente as operações comuns. Para casos complexos, usar `up` e `down` separadamente. ## Active Record avançado ### Pergunta 4: Como otimizar queries N+1 no Rails? O problema N+1 ocorre quando uma consulta inicial é seguida por N consultas adicionais para carregar as associações. O Rails fornece vários métodos de eager loading para resolver esse problema. ```ruby # app/controllers/orders_controller.rb class OrdersController < ApplicationController def index # ❌ PROBLEMA N+1: 1 consulta + N consultas por pedido # @orders = Order.all # Na view: order.user.name gera uma consulta por pedido # ✅ SOLUÇÃO com includes (eager loading) @orders = Order.includes(:user, :items) .where(status: 'completed') .order(created_at: :desc) # Gera apenas 3 consultas no total end def show # includes: carrega as associações separadamente (2-3 consultas) @order = Order.includes(items: :product).find(params[:id]) # preload: força o carregamento separado @order = Order.preload(:items, :user).find(params[:id]) # eager_load: força um LEFT OUTER JOIN (1 consulta) @order = Order.eager_load(:items).find(params[:id]) end end ``` ```ruby # app/models/order.rb class Order < ApplicationRecord belongs_to :user has_many :items, class_name: 'OrderItem' has_many :products, through: :items # Scope com includes por padrão scope :with_details, -> { includes(:user, items: :product) } # Counter cache para evitar consultas COUNT # Requer: add_column :users, :orders_count, :integer, default: 0 belongs_to :user, counter_cache: true end ``` ```ruby # Detecção de N+1 com a gem Bullet (desenvolvimento) # config/environments/development.rb config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.bullet_logger = true Bullet.rails_logger = true end # O Bullet exibirá alertas quando: # - Uma consulta N+1 for detectada # - Houver eager loading desnecessário # - Um counter cache deveria ser usado ``` A regra: usar `includes` por padrão (o Rails escolhe a estratégia ótima), `preload` quando se quer forçar consultas separadas, `eager_load` quando se filtra sobre as associações. ### Pergunta 5: Explique Scopes e Query Objects no Rails Os scopes encapsulam condições de consulta reutilizáveis. Para consultas complexas, os Query Objects oferecem melhor organização e testabilidade. ```ruby # app/models/product.rb class Product < ApplicationRecord # Scopes simples scope :active, -> { where(active: true) } scope :in_stock, -> { where('stock_quantity > 0') } scope :featured, -> { where(featured: true) } # Scopes com parâmetros scope :cheaper_than, ->(price) { where('price < ?', price) } scope :in_category, ->(category) { where(category: category) } # Scopes encadeáveis scope :available, -> { active.in_stock } # Scope com joins scope :with_recent_orders, -> { joins(:order_items) .where('order_items.created_at > ?', 30.days.ago) .distinct } # Scope com subconsulta scope :bestsellers, -> { where(id: OrderItem.group(:product_id) .order('COUNT(*) DESC') .limit(10) .select(:product_id)) } end ``` ```ruby # app/queries/products_search_query.rb # Query Object para buscas complexas 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 # Uso no controller @products = ProductsSearchQuery.new(Product.active).call(params) ``` Os scopes são perfeitos para condições simples e reutilizáveis. Os Query Objects são adequados para buscas complexas com múltiplos filtros opcionais e lógica de composição. ## Routing e Controllers ### Pergunta 6: Como funciona o routing RESTful no Rails? O Rails incentiva rotas RESTful que mapeiam os verbos HTTP às ações CRUD. O router traduz as URLs em chamadas específicas ao controller. ```ruby # config/routes.rb Rails.application.routes.draw do # Rotas RESTful padrão (7 ações) resources :articles do # Rotas aninhadas resources :comments, only: [:create, :destroy] # Rotas de membro (atuam sobre uma instância) member do post :publish delete :archive end # Rotas de coleção (atuam sobre a coleção) collection do get :drafts get :search end end # Rotas de API com namespace namespace :api do namespace :v1 do resources :products, only: [:index, :show, :create, :update] do resources :reviews, shallow: true end end end # Rota personalizada get 'dashboard', to: 'dashboard#index' # Restrições de rota constraints(SubdomainConstraint.new) do resources :admin_settings end # Rota raiz root 'home#index' end ``` ```bash # rails routes - Exibe todas as rotas geradas # # 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#drafts ``` Os helpers de rota gerados (`article_path(@article)`, `new_article_path`) permitem referenciar as URLs de forma dinâmica e manutenível. ### Pergunta 7: Explique callbacks e filtros nos controllers Os callbacks (before_action, after_action, around_action) permitem executar código antes, depois ou ao redor das ações do controller. ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Proteção CSRF habilitada por padrão protect_from_forgery with: :exception # Callback global para autenticação before_action :authenticate_user! # Tratamento global de erros rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActionController::ParameterMissing, with: :bad_request private def not_found render json: { error: 'Recurso não encontrado' }, status: :not_found end def bad_request(exception) render json: { error: exception.message }, status: :bad_request end end ``` ```ruby # app/controllers/admin/products_controller.rb class Admin::ProductsController < ApplicationController # Callbacks com opções before_action :require_admin before_action :set_product, only: [:show, :edit, :update, :destroy] after_action :log_activity, only: [:create, :update, :destroy] # Callback condicional before_action :check_stock, only: [:update], if: :stock_changed? def create @product = Product.new(product_params) if @product.save redirect_to [:admin, @product], notice: 'Produto criado.' else render :new, status: :unprocessable_entity end end def update if @product.update(product_params) redirect_to [:admin, @product], notice: 'Produto atualizado.' 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 end ``` Os callbacks são executados na ordem de declaração. Usar `skip_before_action` nas subclasses para desabilitar callbacks herdados. Evitar callbacks com muita lógica de negócio: prefira Service Objects. ## Services e arquitetura ### Pergunta 8: Como implementar Service Objects no Rails? Os Service Objects encapsulam lógica de negócio complexa que não pertence nem aos Models nem aos Controllers. Eles melhoram a testabilidade e seguem o princípio da responsabilidade única. ```ruby # app/services/order_processor.rb # Service Object com interface padronizada class OrderProcessor def initialize(order, payment_method:) @order = order @payment_method = payment_method end def call return failure('Pedido já processado') 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("Falha no pagamento: #{e.message}") rescue InsufficientStockError => e failure("Estoque insuficiente: #{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: "Pedido ##{@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 ``` ```ruby # 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: 'Pedido confirmado!' 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 end ``` O padrão Service Object segue uma convenção simples: uma classe, uma responsabilidade, um método público `call`. Retornar um objeto Result permite tratar de forma limpa sucesso e falha. ### Pergunta 9: Explique Concerns no Rails Os Concerns permitem extrair e compartilhar código entre Models ou Controllers. Eles usam `ActiveSupport::Concern` para uma sintaxe de inclusão limpa. ```ruby # app/models/concerns/sluggable.rb # Concern reutilizável para gerar slugs module Sluggable extend ActiveSupport::Concern included do # Código executado na inclusão before_validation :generate_slug, if: :should_generate_slug? validates :slug, presence: true, uniqueness: true end # Métodos de 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 # Métodos de instância 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 ``` ```ruby # app/models/article.rb class Article < ApplicationRecord include Sluggable sluggable_source :title # Opcional, :title por padrão end # app/models/product.rb class Product < ApplicationRecord include Sluggable sluggable_source :name end ``` ```ruby # app/controllers/concerns/pagination.rb # Concern para controllers 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 end ``` Os Concerns são úteis para código verdadeiramente compartilhado. Evitar criar Concerns apenas para "encurtar" um Model: isso esconde a complexidade sem reduzi-la. ## Testing com RSpec ### Pergunta 10: Como estruturar testes RSpec no Rails? O RSpec é o framework de testing padrão do Rails. Uma boa estrutura de testes inclui Model specs, Controller specs, Service specs e testes de integração. ```ruby # spec/models/user_spec.rb require 'rails_helper' RSpec.describe User, type: :model do # Factories com 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 o formato do 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 'retorna o nome e sobrenome combinados' do user = build(:user, first_name: 'John', last_name: 'Doe') expect(user.full_name).to eq('John Doe') end it 'lida com a ausência do sobrenome' do user = build(:user, first_name: 'John', last_name: nil) expect(user.full_name).to eq('John') end end describe '.active' do it 'retorna apenas os usuários ativos' 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 ``` ```ruby # 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 o pedido é válido' do before do allow(PaymentGateway).to receive(:charge).and_return( OpenStruct.new(success?: true, transaction_id: 'txn_123') ) end it 'processa o pedido com sucesso' do result = subject.call expect(result).to be_success expect(order.reload.status).to eq('completed') end it 'decrementa o estoque do produto' do expect { subject.call }.to change { product.reload.stock_quantity }.by(-2) end it 'envia o email de confirmação' do expect { subject.call } .to have_enqueued_mail(OrderMailer, :confirmation) .with(order) end end context 'quando o pagamento falha' do before do allow(PaymentGateway).to receive(:charge).and_return( OpenStruct.new(success?: false, error: 'Card declined') ) end it 'retorna um resultado de falha' do result = subject.call expect(result).to be_failure expect(result.error).to include('Card declined') end it 'não atualiza o status do pedido' do expect { subject.call }.not_to change { order.reload.status } end end end end ``` ```ruby # 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 'retorna a lista de produtos' 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 por 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: 'Novo Produto', price: 99.99, category_id: create(:category).id } } end it 'cria um novo produto' 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 end ``` Boas práticas: usar `let` para os dados, `describe` para métodos/contextos, `context` para condições, e `it` para asserções específicas. Cada teste deve testar uma única coisa. ### Pergunta 11: Como usar factories com FactoryBot? O FactoryBot permite criar dados de teste de forma declarativa e manutenível. As factories substituem as fixtures estáticas. ```ruby # spec/factories/users.rb FactoryBot.define do factory :user do # Sequências para garantir unicidade 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 } # Traits para variações 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 herdada factory :admin_user do admin with_profile end end end ``` ```ruby # 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 ``` ```ruby # Uso nos testes RSpec.describe OrderProcessor do # build: instância não persistida let(:user) { build(:user) } # create: persistida no BD let(:order) { create(:order, :with_items, user: user) } # create_list: múltiplas instâncias let(:products) { create_list(:product, 5) } # Combinar traits let(:admin) { create(:user, :admin, :with_profile) } # Sobrescrever atributos let(:expensive_order) { create(:order, :with_items, items_count: 10) } # build_stubbed: mais rápido, para testes unitários let(:stubbed_user) { build_stubbed(:user) } end ``` Prefira `build` ou `build_stubbed` em vez de `create` quando a persistência não for necessária: isso acelera significativamente os testes. ## Background Jobs ### Pergunta 12: Como usar Active Job e Sidekiq no Rails? O Active Job fornece uma interface unificada para jobs em segundo plano, independentemente do backend (Sidekiq, Resque, etc.). Sidekiq é a escolha popular pela sua performance com Redis. ```ruby # app/jobs/process_order_job.rb class ProcessOrderJob < ApplicationJob queue_as :default # Configuração de retry retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3 retry_on Net::OpenTimeout, wait: :polynomially_longer, attempts: 10 discard_on ActiveJob::DeserializationError # Opções do 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 # Pedido removido entre o enfileiramento e a execução Rails.logger.warn("Order #{order_id} not found, skipping job") end end ``` ```ruby # app/jobs/batch_email_job.rb class BatchEmailJob < ApplicationJob queue_as :mailers # Limitação de taxa com Sidekiq Enterprise ou 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 ``` ```ruby # Enfileirar jobs # Imediato ProcessOrderJob.perform_later(order.id) # Atrasado ProcessOrderJob.set(wait: 5.minutes).perform_later(order.id) # Em horário específico ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order.id) # Fila específica ProcessOrderJob.set(queue: :critical).perform_later(order.id) # Síncrono (para testes ou debug) ProcessOrderJob.perform_now(order.id) ``` ```ruby # config/sidekiq.yml :concurrency: 10 :queues: - [critical, 3] # Alta prioridade, peso 3 - [default, 2] # Prioridade média, peso 2 - [mailers, 1] # Prioridade baixa, peso 1 - [low, 1] :schedule: cleanup_job: cron: '0 3 * * *' # Todo dia às 3h class: CleanupJob ``` O Active Job abstrai o backend, mas acessar funcionalidades específicas (batches, rate limiting) frequentemente exige acoplamento ao backend escolhido. ## Desenvolvimento de API ### Pergunta 13: Como construir uma API RESTful com Rails? O Rails facilita a construção de APIs JSON com Controllers API-only e serializers. Uma boa API é versionada, documentada e segura. ```ruby # 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: 'Recurso não encontrado', details: exception.message }, status: :not_found end def unprocessable_entity(exception) render json: { error: 'Validação falhou', details: exception.record.errors }, status: :unprocessable_entity end def bad_request(exception) render json: { error: 'Requisição inválida', details: exception.message }, status: :bad_request end end end end ``` ```ruby # 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 ``` ```ruby # app/serializers/product_serializer.rb # Com a 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 end ``` Boas práticas de API: versionar via namespace, usar códigos HTTP apropriados, paginar coleções e fornecer mensagens de erro claras. ### Pergunta 14: Como implementar autenticação JWT no Rails? JWT (JSON Web Tokens) é um método popular de autenticação stateless para APIs. O token codifica a identidade do usuário e sua validade. ```ruby # 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 expirado' rescue JWT::DecodeError raise AuthenticationError, 'Token inválido' end end end ``` ```ruby # 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: 'Credenciais inválidas' }, 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 ``` ```ruby # 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 ausente' 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: 'Usuário não encontrado' }, status: :unauthorized end def current_user @current_user end end ``` Para produção, considerar: refresh tokens, blacklisting de tokens no logout e tempos de expiração curtos. Gems como `devise-jwt` simplificam a implementação. ## Cache e performance ### Pergunta 15: Como implementar cache no Rails? O Rails oferece vários níveis de cache: fragment caching, Russian Doll caching, low-level caching. A escolha depende do caso de uso. ```ruby # 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 } ``` ```erb <%# app/views/products/index.html.erb %> <%# Fragment caching com chave de cache automática %> <% @products.each do |product| %> <%# Cache baseado em updated_at do produto %> <% cache product do %> <%= render product %> <% end %> <% end %> <%# Russian Doll caching - cache aninhado %> <% cache ['v1', @category] do %>

<%= @category.name %>

<% @category.products.each do |product| %> <% cache ['v1', product] do %> <%= render product %> <% end %> <% end %> <% end %> <%# Cache condicional %> <% cache_if current_user.nil?, @product do %> <%= render @product %> <% end %> ``` ```ruby # app/models/product.rb class Product < ApplicationRecord # Touch no pai para invalidar o cache Russian Doll belongs_to :category, touch: true # Chave de cache personalizada def cache_key_with_version "#{super}/#{reviews.maximum(:updated_at)&.to_i}" end end ``` ```ruby # Low-level caching em services 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 com proteção contra race conditions Rails.cache.fetch('popular_products', expires_in: 1.hour, race_condition_ttl: 10.seconds) do Product.bestsellers.limit(10).to_a end # Invalidação explícita Rails.cache.delete('dashboard:stats') Rails.cache.delete_matched('products:*') ``` O Russian Doll caching é eficaz porque apenas os fragmentos modificados são regenerados. Usar `touch: true` nas associações para propagar a invalidação. ### Pergunta 16: Como otimizar a performance de uma aplicação Rails? A otimização Rails cobre múltiplos aspectos: queries de BD, cache, assets e arquitetura. Uma abordagem metódica com monitoramento é essencial. ```ruby # Otimização do banco de dados # config/database.yml production: pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> prepared_statements: true advisory_locks: true # app/models/order.rb class Order < ApplicationRecord # Índices compostos para queries frequentes # add_index :orders, [:user_id, :status, :created_at] # Selecionar apenas as colunas necessárias scope :summary, -> { select(:id, :status, :total, :created_at) } # Processamento em lote para grandes volumes def self.process_pending pending.find_each(batch_size: 1000) do |order| ProcessOrderJob.perform_later(order.id) end end # Evitar cálculos repetitivos def self.revenue_by_month completed .group("DATE_TRUNC('month', created_at)") .sum(:total) end end ``` ```ruby # Otimização de memória # 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 ``` ```ruby # Profiling com 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 ``` ```ruby # Lazy loading e paginação class ProductsController < ApplicationController def index @products = Product.active .includes(:category, :primary_image) .page(params[:page]) .per(24) # Prefetch para a próxima página 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 end ``` Ferramentas essenciais: `rack-mini-profiler` para profiling, `bullet` para detecção de N+1, `New Relic` ou `Scout` para monitoramento em produção. ## Segurança ### Pergunta 17: Quais são as boas práticas de segurança no Rails? O Rails inclui proteções padrão contra vulnerabilidades comuns. Compreender e configurar corretamente essas proteções é crucial. ```ruby # Proteção CSRF # app/controllers/application_controller.rb class ApplicationController < ActionController::Base # Habilitada por padrão, lança exceção se o token for inválido protect_from_forgery with: :exception # Para APIs, usar :null_session # protect_from_forgery with: :null_session end # Nas views, o token é incluído automaticamente nos formulários # <%= form_with ... %> inclui authenticity_token # Para requisições AJAX # Adicionar o header X-CSRF-Token com o valor de csrf_meta_tags ``` ```ruby # Prevenção de SQL Injection # ✅ Parâmetros interpolados escapados automaticamente User.where('email = ?', params[:email]) User.where(email: params[:email]) # ❌ PERIGO - Interpolação direta User.where("email = '#{params[:email]}'") # ✅ Para cláusulas ORDER dinâmicas ALLOWED_SORTS = %w[name created_at price].freeze sort_column = ALLOWED_SORTS.include?(params[:sort]) ? params[:sort] : 'name' Product.order(sort_column) ``` ```ruby # Proteção XSS # O Rails escapa automaticamente o HTML nas views # ✅ Escapado automaticamente <%= user.name %> # ❌ Perigoso - conteúdo sem escape <%== user.bio %> <%= raw user.bio %> <%= user.bio.html_safe %> # ✅ Para HTML seguro, usar sanitize <%= sanitize user.bio, tags: %w[p br strong em] %> ``` ```ruby # Strong Parameters class UsersController < ApplicationController def update @user.update!(user_params) end private def user_params # Whitelist explícita dos atributos permitidos params.require(:user).permit(:name, :email, :avatar) # Apenas para administradores if current_user.admin? params.require(:user).permit(:name, :email, :role, :active) else params.require(:user).permit(:name, :email) end end end ``` ```ruby # Cabeçalhos de segurança # 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:' end ``` Auditar regularmente com `brakeman` (análise estática de segurança) e manter as gems atualizadas com `bundle audit`. ### Pergunta 18: Como tratar autenticação e autorização no Rails? A autenticação verifica a identidade, a autorização controla as permissões. O Devise gerencia a auth, Pundit ou CanCanCan gerenciam a autorização. ```ruby # Configuração do 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 ``` ```ruby # Políticas 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 para coleções 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 ``` ```ruby # Controller com 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: 'Artigo atualizado.' 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: 'Artigo publicado.' end rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized private def user_not_authorized flash[:alert] = "Você não está autorizado a executar esta ação." redirect_back(fallback_location: root_path) end end ``` O Pundit é mais explícito e testável que CanCanCan. Cada ação tem um método de policy correspondente, e os scopes filtram automaticamente as coleções. ## Rails avançado ### Pergunta 19: Explique o padrão Repository no Rails O padrão Repository isola a lógica de acesso a dados do resto da aplicação. Embora o Rails utilize Active Record (um padrão diferente), Repository pode ser útil em casos complexos. ```ruby # 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 ``` ```ruby # 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 ``` ```ruby # Uso em um 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 o teste com mocks RSpec.describe ProductSearchService do let(:repository) { instance_double(ProductRepository) } let(:service) { described_class.new(repository: repository) } it 'filtra por 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 end ``` Repository é opcional no Rails, já que o Active Record é um excelente padrão. Usar para queries complexas ou quando o isolamento do storage for importante. ### Pergunta 20: Como implementar o padrão CQRS no Rails? CQRS (Command Query Responsibility Segregation) separa as operações de leitura e escrita. No Rails, isso se traduz em classes distintas para queries e commands. ```ruby # 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 ``` ```ruby # 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, "Produto #{item[:product_id]} indisponível") end end end end end ``` ```ruby # 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 ``` ```ruby # Controller usando 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: 'Pedido criado!' else flash.now[:alert] = result.errors.join(', ') render :new, status: :unprocessable_entity end end end ``` CQRS brilha em aplicações complexas com necessidades assimétricas de leitura/escrita. Para CRUD simples, é over-engineering. ### Pergunta 21: Como tratar WebSockets com Action Cable? O Action Cable integra WebSockets ao Rails para comunicação bidirecional em tempo real. Ele utiliza Redis para sincronização entre servidores. ```ruby # 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 # Via cookie de sessão if verified_user = User.find_by(id: cookies.encrypted[:user_id]) verified_user # Via JWT para APIs 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 ``` ```ruby # app/channels/chat_channel.rb class ChatChannel < ApplicationCable::Channel def subscribed @room = ChatRoom.find(params[:room_id]) # Verificar permissões unless @room.accessible_by?(current_user) reject return end stream_for @room # Notificar os outros sobre a presença 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'] ) # Difundir para todos os assinantes 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 end ``` ```javascript // app/javascript/channels/chat_channel.js import 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') } } ) ``` O Action Cable trata automaticamente reconexões e sincronização. Em produção, configurar Redis como adapter e escalar conforme as conexões concorrentes. ### Pergunta 22: Como implementar multi-tenancy no Rails? A multi-tenancy permite que uma aplicação atenda múltiplos clientes (tenants) isolados. Três abordagens principais: a nível de banco, a nível de schema ou a nível de linha. ```ruby # Multitenancy a nível de linha com ActsAsTenant ou manual # app/models/concerns/tenant_scoped.rb module TenantScoped extend ActiveSupport::Concern included do belongs_to :tenant # Scope padrão para o tenant atual default_scope -> { where(tenant: Current.tenant) if Current.tenant } # Validação do 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 ``` ```ruby # 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 # Via subdomínio if request.subdomain.present? && request.subdomain != 'www' Tenant.find_by!(subdomain: request.subdomain) # Via header (para APIs) elsif request.headers['X-Tenant-ID'].present? Tenant.find(request.headers['X-Tenant-ID']) # Via usuário elsif current_user current_user.tenant end rescue ActiveRecord::RecordNotFound redirect_to root_url(subdomain: 'www'), alert: 'Tenant não encontrado' end end ``` ```ruby # 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 # Administradores podem pertencer a vários tenants has_many :tenant_memberships has_many :accessible_tenants, through: :tenant_memberships, source: :tenant end ``` ```ruby # A nível de schema com a gem Apartment (PostgreSQL) # config/initializers/apartment.rb Apartment.configure do |config| config.excluded_models = %w[Tenant User] config.tenant_names = -> { Tenant.pluck(:subdomain) } end # Uso Apartment::Tenant.switch('acme') do # Todas as queries neste bloco usam o schema 'acme' Project.all # SELECT * FROM acme.projects end ``` A nível de linha é a mais simples mas exige atenção constante a vazamentos. A nível de schema oferece melhor isolamento mas complica as migrations. Escolher conforme as necessidades de segurança e escalabilidade. ### Pergunta 23: Como configurar uma arquitetura de microsserviços com Rails? O Rails pode servir como base para uma arquitetura de microsserviços com comunicação via HTTP/gRPC ou filas de mensagens. A chave é definir bem os limites. ```ruby # Cliente de serviço 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('Serviço indisponível', code: response.code) end rescue Net::OpenTimeout, Net::ReadTimeout ServiceResult.failure('Timeout do serviço') end end ``` ```ruby # Comunicação orientada a eventos com 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) ``` ```ruby # Padrão API Gateway # app/controllers/api/v1/gateway_controller.rb module Api module V1 class GatewayController < BaseController # Agregar múltiplos serviços 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} indisponível", message: e.message } end end end end ``` Para microsserviços Rails: definir contratos de API claros (OpenAPI), implementar circuit breakers (gem `circuitbox`) e usar tracing distribuído (gem `opentelemetry`). ### Pergunta 24: Como fazer deploy de uma aplicação Rails em produção? O deploy moderno do Rails utiliza containers ou PaaS. Uma configuração robusta de produção cobre assets, banco de dados e monitoramento. ```ruby # config/environments/production.rb Rails.application.configure do config.cache_classes = true config.eager_load = true config.consider_all_requests_local = false # Assets 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 } # Forçar 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 # 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 # Imagem de produção FROM ruby:3.3-alpine RUN apk add --no-cache postgresql-client tzdata WORKDIR /app COPY --from=builder /app /app COPY --from=builder /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"] ``` ```yaml # 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 de produção: SSL obrigatório, secrets via ENV, health checks, backups automatizados de BD, monitoramento (APM + logs + métricas) e alertas configurados. ### Pergunta 25: Quais são as novidades do Rails 7+ que vale a pena conhecer? O Rails 7+ traz mudanças significativas: Hotwire por padrão, import maps, credenciais criptografadas aprimoradas e diversas otimizações. ```ruby # 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 "Carregar mais", articles_path(page: @page + 1), data: { turbo_frame: "articles" } %> <% end %> # Turbo Streams para atualizações em tempo real # 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 } %> ``` ```ruby # Controllers 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() } } ``` ```ruby # Import Maps (sem bundler de 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" # Pins via CDN pin "lodash-es", to: "https://ga.jspm.io/npm:lodash-es@4.17.21/lodash.js" ``` ```ruby # Active Record Encryption (Rails 7+) # app/models/user.rb class User < ApplicationRecord encrypts :email, deterministic: true # Permite buscas encrypts :phone_number # Não determinístico por padrão encrypts :ssn, deterministic: true, downcase: true end # config/credentials.yml.enc active_record_encryption: primary_key: abc123... deterministic_key: def456... key_derivation_salt: ghi789... ``` ```ruby # Melhorias na interface de queries # Rails 7.1+ # Queries assíncronas users = User.where(active: true).load_async # Continuar processando enquanto a query roda # Acessar os resultados com 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') # Detecção automática de inverse_of class Author < ApplicationRecord has_many :books # inverse_of detectado automaticamente end # Strict loading por padrão (evita N+1) class ApplicationRecord < ActiveRecord::Base self.strict_loading_by_default = true end ``` O Rails 7+ favorece a simplicidade (sem Webpack por padrão) e HTML-over-the-wire com Hotwire. Essa abordagem reduz a complexidade de JavaScript oferecendo uma experiência de usuário moderna. ## Conclusão As entrevistas de Ruby on Rails avaliam o domínio do framework completo e a compreensão de suas convenções. Pontos-chave a memorizar: ✅ **Fundamentos**: MVC, Active Record, migrations, validações e associações ✅ **Arquitetura**: Service Objects, Concerns, Query Objects e padrões CQRS ✅ **Performance**: queries N+1, caching (fragment, Russian Doll, low-level), eager loading ✅ **Testing**: RSpec, FactoryBot, request specs e boas práticas de testes ✅ **Segurança**: CSRF, SQL injection, XSS, Strong Parameters e autenticação/autorização ✅ **APIs**: design RESTful, JWT, serializers e versionamento ✅ **Produção**: background jobs, WebSockets, deploy e monitoramento A filosofia Rails (Convention over Configuration, DRY e Rails Way) orienta todas as decisões arquiteturais. Dominar esses princípios e saber quando se desviar deles demonstra uma expertise sólida. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pt/blog/ruby-on-rails/ruby-on-rails-interview-questions