O que é Ruby on Rails?
Rails é um framework web full-stack para Ruby. Ele já vem com tudo o que uma aplicação web baseada em banco de dados precisa: um ORM, um roteador, uma camada de visualização, jobs em segundo plano, mailers, caching, WebSockets e um ambiente de testes. Em vez de escolher e conectar cada uma dessas peças manualmente, você herda uma stack coerente que foi projetada para trabalhar em conjunto.
Sua ideia central é a convenção sobre a configuração. O framework assume uma estrutura sensata — Post vive em app/models/post.rb, mapeia para uma tabela posts e é servido por PostsController — e você só escreve configurações quando decide desviar desse padrão. A equipe do Rails chama essa filosofia de omakase, a escolha do chef: confie nos padrões e você avançará mais rápido.
Essa troca é a razão pela qual o Rails impulsiona produtos desde Basecamp e GitHub até Shopify e HEY há vinte anos, e por que um único desenvolvedor ainda consegue entregar uma aplicação robusta em um único fim de semana.
Convenção sobre configuração
Todo projeto Rails possui o mesmo esqueleto, então você nunca fica na dúvida sobre onde colocar cada coisa.
app/
├── controllers/ # request handling
├── models/ # domain and persistence
├── views/ # templates
├── jobs/ # background work
├── mailers/ # email
└── javascript/ # Stimulus controllers
config/
├── routes.rb # the URL map
└── database.yml # connection settings
db/
└── migrate/ # schema history
A vantagem disso é a previsibilidade. Um novo desenvolvedor abre o repositório e já sabe que o model User está em app/models/user.rb e que sua tabela é users. A nomenclatura não é um debate de estilo; é uma interface da qual o framework depende.
O ciclo de requisição MVC
O Rails é um framework Model-View-Controller, e cada requisição HTTP segue o mesmo caminho:
- O Routing associa a URL a uma action do controller.
- O controller carrega ou altera dados através dos models.
- Os Models aplicam as regras de domínio e se comunicam com o banco de dados.
- Uma view renderiza a resposta, geralmente como HTML.
# config/routes.rb
Rails.application.routes.draw do
resources :posts
root "posts#index"
end
Uma requisição para GET /posts chega ao PostsController#index, que prepara @posts, após o qual app/views/posts/index.html.erb é renderizado. Manter cada camada focada em sua responsabilidade é o que torna um app Rails sustentável à medida que ele cresce.
A linha de comando é o framework
Você quase não toca no Rails através de arquivos de configuração. A maior parte do trabalho acontece no terminal.
rails new shop --database=postgresql
cd shop
bin/rails generate scaffold Post title:string body:text published:boolean
bin/rails generate model Comment post:references body:text
bin/rails generate controller Pages home about
bin/rails db:create db:migrate
bin/rails server
O generate escreve o model, a migration, o controller, as rotas, as views e os testes para você, e a saída do generator informa o que foi alterado. Leia cada arquivo gerado antes de mantê-lo — a maneira mais rápida de aprender Rails é estudar o que ele escreve e, em seguida, deletar o que você não precisa.
Active Record: a camada de model
O Active Record é o ORM. Uma classe de model encapsula uma tabela do banco de dados, e uma instância encapsula uma linha.
Migrations
Migrations são alterações de schema versionadas e reversíveis armazenadas em db/migrate. Elas são a única maneira suportada de alterar o banco de dados.
class CreatePosts < ActiveRecord::Migration[8.0]
def change
create_table :posts do |t|
t.string :title, null: false
t.text :body, null: false
t.boolean :published, default: false, null: false
t.references :author, null: false, foreign_key: { to_table: :users }
t.timestamps
end
add_index :posts, %i[published created_at]
end
end
Execute bin/rails db:migrate para aplicá-la e bin/rails db:rollback para desfazê-la. Como change é reversível, o Rails sabe como reverter as operações sem que você precise escrever um método down.
Associations
Associations descrevem relacionamentos e geram métodos convenientes.
class User < ApplicationRecord
has_many :posts, dependent: :destroy
has_many :comments, through: :posts
end
class Post < ApplicationRecord
belongs_to :author, class_name: "User", inverse_of: :posts
has_many :comments, dependent: :destroy
has_one_attached :cover
end
Com isso configurado, user.posts, post.author e post.comments.create(body: "...") funcionam, e dependent: :destroy mantém linhas órfãs fora do banco de dados.
Validations e scopes
Validations protegem o domínio na camada de model, por onde passa cada escrita.
class Post < ApplicationRecord
validates :title, presence: true, length: { maximum: 120 }
validates :body, presence: true
validates :slug, uniqueness: true
scope :published, -> { where(published: true) }
scope :recent, -> { order(created_at: :desc) }
before_validation :generate_slug, on: :create
private
def generate_slug
self.slug ||= title.to_s.parameterize
end
end
Scopes retornam relations, portanto, eles são compostíveis: Post.published.recent.limit(20) lê-se como uma frase e é executado como uma única query. Quando um scope ultrapassa uma ou duas linhas, promova-o a um método de classe para que permaneça legível.
Evitando queries N+1
O problema de performance mais comum no Rails é a query N+1: uma query para uma lista e, depois, mais uma para a association de cada linha.
# Two queries total, no matter how many posts.
posts = Post.includes(:author, :comments).recent
posts.each do |post|
puts "#{post.title} by #{post.author.name}"
end
Use includes quando for acessar a association, preload quando quiser uma query separada, e eager_load quando também filtrar pela association. Adicione a gem bullet ao ambiente de development e ela sinalizará cada N+1 antes que chegue à produção.
Controllers e rotas RESTful
resources declara as rotas CRUD convencionais e as mapeia para ações do controller com nomes correspondentes.
# config/routes.rb
Rails.application.routes.draw do
resources :posts do
resources :comments, only: %i[index create destroy]
end
namespace :api do
namespace :v1 do
resources :posts, only: %i[index show create update destroy]
end
end
end
Os controllers devem permanecer “magros” (thin). Eles carregam os dados, chamam o domínio e escolhem uma resposta. A lógica de negócio pertence aos models ou objetos de serviço, não às ações.
class PostsController < ApplicationController
before_action :set_post, only: %i[show update destroy]
def index
@posts = Post.published.includes(:author).recent
end
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post, notice: "Post created."
else
render :new, status: :unprocessable_entity
end
end
private
def set_post
@post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :body, :published, tag_ids: [])
end
end
Strong parameters não são opcionais. params.require(:post).permit(...) é a fronteira que impede que um cliente defina colunas que você nunca pretendeu, como admin ou role.
Views, ERB e layouts
Views são templates ERB: HTML com Ruby incorporado. O Rails escapa a saída por padrão, o que elimina toda uma classe de bugs de cross-site scripting.
<%# app/views/posts/index.html.erb %>
<h1>Posts</h1>
<%= link_to "New post", new_post_path %>
<ul>
<% @posts.each do |post| %>
<li>
<%= link_to post.title, post %>
<span><%= pluralize(post.comments.size, "comment") %></span>
</li>
<% end %>
</ul>
Use <%= %> para imprimir a saída e <% %> para executar sem imprimir. Um layout em app/views/layouts/application.html.erb envolve todas as páginas, e partials — arquivos que começam com um underscore — permitem que você reutilize a marcação com render "post", post: post ou simplesmente render @posts.
Hotwire: Turbo e Stimulus
O Rails moderno adiciona interatividade sem a necessidade de uma single-page app. O Turbo intercepta links e formulários e troca fragmentos de página via rede; o Stimulus adiciona pequenos comportamentos em JavaScript onde eles são genuinamente necessários.
<%# Turbo Frames scope an update to one part of the page %>
<%= turbo_frame_tag "comments" do %>
<%= render @post.comments %>
<% end %>
// app/javascript/controllers/dropdown_controller.js
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["menu"];
toggle() {
this.menuTarget.classList.toggle("hidden");
}
}
Isso é aprimoramento progressivo na prática: o HTML renderizado no servidor é a base, e o JavaScript o aprimora. A maioria dos apps Rails nunca precisa de um roteador no client-side.
Assets e import maps
O Rails 7 substituiu o antigo pipeline do Sprockets por import maps por padrão e oferece o Propshaft como um asset pipeline mais simples. Você pode fixar (pin) uma biblioteca sem a necessidade de uma etapa de build com Node.
bin/importmap pin local-time
bin/rails assets:precompile
Se você prefere um bundler, bin/rails javascript:install:esbuild ou :vite altera a configuração. O princípio é o mesmo: servir o que o navegador entende e manter a toolchain fora do caminho até que você precise dela.
Background jobs
Qualquer tarefa lenta — enviar e-mail, chamar uma API, gerar um relatório — deve ficar em um job, não na requisição. O Active Job é a interface comum; o adapter decide onde os jobs são executados.
class PublishPostJob < ApplicationJob
queue_as :default
retry_on Net::ReadTimeout, wait: :polynomially_longer, attempts: 5
def perform(post_id)
Post.find(post_id).publish!
end
end
PublishPostJob.perform_later(post.id)
# config/application.rb
config.active_job.queue_adapter = :solid_queue # or :sidekiq
O Rails 8 já vem com o Solid Queue, que armazena os jobs no banco de dados e dispensa o uso de Redis, reduzindo a complexidade de apps que rodam em um único servidor. O Sidekiq continua sendo uma escolha robusta para altos volumes.
Testes
O Rails gera testes para tudo o que cria. O Minitest já vem integrado e é rápido; o RSpec é a alternativa popular com uma sintaxe mais expressiva.
# test/models/post_test.rb
require "test_helper"
class PostTest < ActiveSupport::TestCase
test "requires a title" do
post = Post.new(body: "Hello")
assert_not post.valid?
assert_includes post.errors[:title], "can't be blank"
end
end
# spec/models/post_spec.rb
RSpec.describe Post, type: :model do
it "is invalid without a title" do
expect(Post.new(body: "Hello")).not_to be_valid
end
end
Testes de controller fazem requisições reais no processo, o que captura bugs de roteamento e parâmetros que os testes unitários deixam passar.
# test/controllers/posts_controller_test.rb
class PostsControllerTest < ActionDispatch::IntegrationTest
test "lists published posts" do
Post.create!(title: "Hi", body: "There", published: true)
get posts_url
assert_response :success
end
end
Execute tudo com bin/rails test ou bundle exec rspec. Adicione bin/rails test:system para testes de sistema via browser quando um fluxo for importante o suficiente para ser testado de ponta a ponta.
O console do Rails
bin/rails console inicia a sua aplicação com todos os models e helpers disponíveis. É a maneira mais rápida de explorar dados, testar uma query e reproduzir um bug.
bin/rails console
bin/rails console --sandbox # rolls back all changes on exit
Post.published.recent.limit(5)
Post.where(published: false).update_all(published: true)
Post.includes(:author).find_each { |post| puts post.title }
--sandbox funciona como uma rede de segurança enquanto você experimenta: qualquer gravação é revertida (rolled back) quando você sai.
Melhores práticas
- Mantenha os controllers enxutos; mova a lógica de domínio para models ou service objects.
- Utilize eager load em associações com
includese monitore os logs para evitar queries N+1. - Altere o banco de dados apenas através de migrations e faça o commit delas junto com o código.
- Use strong parameters para cada operação de escrita e valide na camada de model.
- Adicione índices no banco de dados para as colunas que você utiliza em filtros e ordenações.
- Delegue tarefas demoradas para background jobs em vez de processá-las no ciclo de request.
- Escreva um teste para cada bug que você corrigir e mantenha a suite de testes rápida.
Erros comuns
- Renderizar uma lista sem
includese enviar um problema de N+1 para produção. - Colocar lógica de negócio em controllers até que as actions tenham centenas de linhas.
- Usar
params[:post]diretamente em vez de strong parameters. - Editar
db/schema.rbmanualmente em vez de escrever uma migration. - Esquecer as opções de
dependent:e deixar registros órfãos para trás. - Recorrer a um service object ou a uma gem antes que o model realmente precise disso.
- Executar queries pesadas em views em vez de carregar os dados no controller.
Próximos passos
O Rails ensina uma forma durável de pensar: recursos, convenções e um lugar único para cada responsabilidade. Se você gosta do modelo “batteries-included”, o Laravel aplica a mesma filosofia em PHP, enquanto o Django faz o mesmo em Python. Como o roteamento do Rails é baseado em recursos, o guia de REST APIs explica os conceitos fundamentais. E, já que a maioria dos apps Rails roda em um banco de dados relacional, aprofunde seus conhecimentos em PostgreSQL a seguir.