v7.0.0
Veja mais em rubyonrails.org: Mais Ruby on Rails

Começando com Rails

Este guia aborda a instalação e a execução do Ruby on Rails.

Depois de ler este guia, você vai saber:

1 Premissas do Guia

Este guia é projetado para iniciantes que desejam criar uma aplicação Rails do zero. Ele não assume que você tenha nenhuma experiência anterior com Rails.

O Rails é um framework para aplicações web que é executado em cima da linguagem de programação Ruby. Se você não tem nenhuma experiência com Ruby, você vai achar a curva de aprendizado bastante íngrime começando direto com Rails. Existem diversas listas organizadas de materiais online para aprender Ruby:

Fique atento que alguns materiais, apesar de excelentes, envolvem versões antigas do Ruby e podem não incluir parte da sintaxe que você vai ver no seu dia-a-dia desenvolvendo com Rails.

2 O que é o Rails?

Rails é um framework de desenvolvimento de aplicações web escrito na linguagem de programação Ruby. Foi projetado para facilitar o desenvolvimento de aplicações web, criando premissas sobre tudo que uma pessoa desenvolvedora precisa para começar. Permite que você escreva menos código, enquanto realiza mais do que em muitas outras linguagens ou frameworks. Pessoas desenvolvedoras experientes em Rails, também dizem que desenvolver aplicações web ficou mais divertido.

Rails é um software opinativo. Assumindo que há uma "melhor" maneira para fazer as coisas, e foi projetado para encorajar essa maneira - e, em alguns casos para desencorajar alternativas. Se você aprender o "Rails Way", provavelmente terá um grande aumento de produtividade. Se você insistir nos velhos hábitos de outras linguagens, tentando usar os padrões que você aprendeu em outro lugar, você pode ter uma experiência menos feliz.

A filosofia do Rails possui dois princípios fundamentais:

  • Não repita a si mesmo: DRY (don't repeat yourself) é um conceito de desenvolvimento de software que estabelece que "Todo conhecimento deve possuir uma representação única, de autoridade e livre de ambiguidades em todo o sistema". Ao não escrever as mesmas informações repetidamente, o código fica mais fácil de manter, de expandir e com menos bugs.
  • Convenção sobre configuração: O Rails possui convenções sobre as melhores maneiras de fazer muitas coisas em uma aplicação web, devido a essas convenções você não precisa especificar detalhes através de arquivos de configuração infinitos.

3 Criando um Novo Projeto em Rails

A melhor forma de ler esse guia é seguir o passo à passo. Todos os passos são essenciais para rodar a aplicação de exemplo e nenhum código ou passos adicionais serão necessários.

Seguindo este guia, você irá criar um projeto em Rails chamado de blog, um weblog (muito) simples. Antes de você começar a construir a aplicação, você precisa ter certeza de ter o Rails instalado.

Os exemplos à seguir usam $ para representar seu prompt de terminal em um sistema operacional baseado em UNIX, mesmo que ele tenha sido customizado para parecer diferente. Se você está utilizando Windows, seu prompt será parecido com algo como C:\source_code>.

3.1 Instalando o Rails

Antes de você instalar o Rails, você deve validar para ter certeza que seu sistema tem os pré requisitos necessários instalados. Esses incluem:

  • Ruby
  • SQLite3
  • Node.js
  • Yarn
3.1.1 Instalando o Ruby

Abra o prompt de linha de comando. No macOS abra o Terminal.app; no Windows escolha "Executar" no menu inicial e digite cmd.exe. Qualquer comando que antecede o sinal de dólar $ deverá ser rodado em linha de comando. Verifique se você tem a versão atual do Ruby instalado:

$ ruby --version
ruby 2.7.0

O Rails necessita da versão Ruby 2.7.0 ou mais atual. É preferivel usar a última versão do Ruby. Se o número da versão retornada for menor que este número (como 2.3.7, e 1.8.7), você precisará instalar uma versão do Ruby mais atual.

Para instalar o Rails no Windows, você primeiro tem que instalar o Ruby Installer.

Para mais informações de instalação de outros Sistemas Operacionais, dê uma olhada em ruby-lang.org.

3.1.2 Instalando o SQLite3

Você também precisará instalar o banco de dados SQLite3. Muitos sistemas operacionais populares semelhantes ao UNIX são fornecidos com uma versão compatível do SQLite3. Em outros sistemas você pode achar mais instruções de instalação em SQLite3 website. Verifique se está corretamente instalado e carregado no seu PATH:

$ sqlite3 --version

O programa deverá reportar sua versão.

3.1.3 Instalando o Node.js e Yarn

Por fim, você precisará do Node.js e o Yarn instalados para gerenciar o JavaScript da sua aplicação.

Encontre as instruções de instalação no site do Node.js e verifique se está instalado corretamente com o seguinte comando:

$ node --version

A versão do Node.js deve ser impressa. Certifique-se de que é maior que 8.16.0.

Para instalar o Yarn, siga as instruções de instalação instruções no site do Yarn.

A execução deste comando deve imprimir a versão do Yarn:

$ yarn --version

Se aparecer algo como "1.22.0", o Yarn foi instalado corretamente.

3.1.4 Instalando o Rails

Para instalar o Rails, use o comando gem install fornecido pelo RubyGems:

$ gem install rails

Para verificar se você tem tudo instalado corretamente, você deve rodar o comando à seguir num novo terminal:

$ rails --version

Se esse comando retornar algo como "Rails 7.0.0", você está pronto para continuar.

3.2 Criando a Aplicação Blog

O Rails vem com vários scripts chamados generators que são projetados para tornar sua vida de desenvolvedor fácil, criando tudo que é necessário para começar a trabalhar em uma tarefa em particular. Um desses é o generator de nova aplicação, que irá te fornecer a base de uma nova aplicação em Rails para que você não precise escrever tudo sozinho.

Para utilizar esse generator, abra um terminal, navegue para um diretório onde você tenha permissão para criar arquivos, e rode:

$ rails new blog

Este comando irá criar uma aplicação em Rails chamada Blog em um diretório blog e irá instalar as dependências das gems que já foram mencionadas no Gemfile usando bundle install.

Você pode ver todas as opções de linha de comando gerador que a aplicação Rails aceita rodando o comando rails new --help.

Depois de criar a aplicação blog, entre em sua pasta:

$ cd blog

A pasta blog vai ter vários arquivos gerados e pastas que compõem a estrutura de uma aplicação Rails. A maior parte da execução deste tutorial será feito na pasta app, mas à seguir teremos um resumo básico das funções de cada um dos arquivos e pastas que o Rails gerou por padrão:

Arquivo/Pasta Objetivo
app/ Contém os controllers, models, views, helpers, mailers, channels, jobs, e assets para sua aplicação. Você irá se concentrar nesse diretório pelo restante desse guia.
bin/ Contém o script rails que inicializa sua aplicação e contém outros scripts que você utiliza para configurar, atualizar, colocar em produção ou executar sua aplicação.
config/ Contém configurações de rotas, banco de dados entre outros de sua aplicação. Este conteúdo é abordado com mais detalhes em Configuring Rails Applications.
config.ru Configuração Rack para servidores baseados em Rack usados para iniciar a aplicação. Para mais informações sobre o Rack, consulte Rack website.
db/ Contém o schema do seu banco de dados atual, assim como as migrations do banco de dados.
Gemfile
Gemfile.lock
Esses arquivos permitem que você especifique quais dependências de gem são necessárias na sua aplicação Rails. Esses arquivos são usados pela gem Bundler. Para mais informações sobre o Bundler, acesse o website do Bundler.
lib/ Módulos extendidos da sua aplicação.
log/ Arquivos de log da aplicação.
public/ Contém arquivos estáticos e assets compilados. Quando sua aplicação está rodando esse diretório é exposto como ele está.
Rakefile Este arquivo localiza e carrega tarefas que podem ser rodadas por linhas de comando. As tarefas são definidas nos componentes do Rails. Ao invés de editar o Rakefile, você deve criar suas próprias tarefas adicionando os arquivos no diretório lib/tasks da sua aplicação.
README.md Este é um manual de instruções para sua aplicação. Você deve editar este arquivo para informar o que seu aplicativo faz, como configurá-lo e assim por diante.
storage/ Arquivos de armazenamento ativo do serviço de disco. Mais informações em Active Storage Overview.
test/ Testes unitários, fixtures, e outros tipos de testes. Mais informações em Testing Rails Applications.
tmp/ Arquivos temporários (como cache e arquivos pid).
vendor/ Diretório com todos os códigos de terceiros. Em uma típica aplicação Rails inclui vendored gems.
.gitignore Este arquivo diz ao Git quais arquivos (ou padrões) devem ser ignorados. Acesse GitHub - Ignoring files para mais informações sobre arquivos ignorados.
.ruby-version Este arquivo contém a versão padrão do Ruby.

4 Olá, Rails!

Para começar vamos colocar um texto na tela rapidamente. Para fazer isso, você precisa que seu servidor de aplicação Rails esteja em execução.

4.1 Inicializando o Servidor Web

Você já tem uma aplicação Rails funcional. Para vê-la você deve iniciar um servidor web em sua máquina de desenvolvimento. Você pode fazer isso executando o seguinte comando no diretório blog:

$ bin/rails server

Se você está usando Windows, deve executar os scripts do diretório bin para o interpretador do Ruby: ruby bin\rails server.

A compressão de assets JavaScript requer que você tenha um executor disponível em seu sistema operacional. Na ausência de um executor você verá um erro de execjs durante a compressão dos assets. Geralmente o macOS e o Windows possuem um executor JavaScript instalado por padrão. therubyrhino é o executor recomendado para usuários de JRuby e vem no Gemfile por padrão em aplicações geradas com JRuby. Você pode avaliar todos executores em ExecJS.

A execução do comando irá iniciar o Puma, um servidor web distribuído com o Rails por padrão. Para ver sua aplicação em execução, abra um navegador e navegue para http://localhost:3000. Você deve ver a página padrão com informações do Rails:

Captura de tela escrito Yay! Você está no Rails(em inglês)

Quando você deseja interromper a execução do servidor Web, pressione Ctrl+C na janela do terminal em que o servidor está sendo executado. No ambiente de desenvolvimento, o Rails geralmente não requer que você reinicie o servidor; mudanças em arquivos são automaticamente interpretadas pelo servidor.

A página de "Yay! Você está no Rails! (Yay! You're on Rails!)" é o smoke test (teste de sanidade) para uma nova aplicação Rails: garante que o seu software esteja configurado corretamente, o suficiente para gerar uma página.

4.2 Diga "Olá", Rails

Para que o Rails diga "Olá", você precisa criar no mínimo uma rota (route), um controller com uma action e uma view. A route mapeia uma requisição para uma action de um controller. A action do controller faz todo o trabalho necessário para lidar com a requisição, e prepara qualquer dado para a view. A view mostra o dado no formato que você quiser.

Em termos de implementação: Rotas são regras escritas em um DSL (domain-specific language) em Ruby. Controllers são classes Ruby, e seus métodos públicos são as actions. E as views são templates, geralmente escritos numa mistura de Ruby e HTML.

Vamos começar adicionando uma rota ao nosso arquivo de rotas, config/routes.rb, no topo do bloco Rails.application.routes.draw:

Rails.application.routes.draw do
  get "/articles", to: "articles#index"

  # Para mais detalhes da DSL disponível para esse arquivo, veja https://guides.rubyonrails.org/routing.html
end

A rota acima declara que as requisições de GET / articles são mapeadas para o action index do ArticlesController.

Para criar o ArticlesController e sua action index, vamos executar o controlador gerador (com a opção --skip-routes porque já temos um rota apropriada):

$ bin/rails generate controller Articles index --skip-routes

O Rails vai gerar vários arquivos para você:

create  app/controllers/articles_controller.rb
invoke  erb
create    app/views/articles
create    app/views/articles/index.html.erb
invoke  test_unit
create    test/controllers/articles_controller_test.rb
invoke  helper
create    app/helpers/articles_helper.rb
invoke    test_unit
invoke  assets
invoke    scss
create      app/assets/stylesheets/articles.scss

O mais importante desses é o arquivo controller, app/controllers/articles_controller.rb. Vamos dar uma olhada nele:

class ArticlesController < ApplicationController
  def index
  end
end

A action index está vazia. Quando uma action não renderiza explicitamente uma view (ou de outra forma acionar uma resposta HTTP), o Rails irá renderizar automaticamente uma view que corresponde ao nome do controller e action. Convenção sobre Configuração! As views estão localizadas no diretório app/views. Portanto, a action index renderizará app/views/articles/index.html.erb por padrão.

Vamos abrir o arquivo app/views/articles/index.html.erb, e substituir todo código existente por:

<h1>Olá, Rails!</h1>

Se você parou anteriormente o servidor web para executar o gerador do controller, reinicie-o com bin/rails server. Agora visite http://localhost:3000/articles e veja nosso texto exibido!

4.3 Configuração da Página Inicial da Aplicação

No momento, http://localhost:3000 ainda exibe "Yay! Você está no Rails!". Vamos mostrar nosso "Olá, Rails!" texto em http://localhost:3000 também. Para fazer isso, vamos adicionar uma rota que mapeia o caminho raiz (root path) da nossa aplicação para o controller e action apropriados.

Vamos abrir o arquivo config/routes.rb, e adicionar o a rota root no começo do bloco Rails.application.routes.draw:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
end

Agora podemos ver a mensagem "Olá, Rails!", quando visitamos http://localhost:3000 confirmando que a route root também mapeia para a action index de ArticlesController.

Para mais informações sobre roteamento, consulte Roteamento do Rails de Fora para Dentro.

5 Auto carregamento

Aplicações Rails não usam require para carregar o código da aplicação.

Você deve ter notado que ArticlesController herda de ApplicationController, mas app/controllers/articles_controller.rb não tem nada como

require "application_controller" # NÃO FAÇA ISSO.

As classes e módulos da aplicação estão disponíveis em todos os lugares, você não precisa e não deve carregar nada em app com require. Esse recurso é chamado de autoloading e você pode aprender mais sobre ele em Autoloading and Reloading Constants.

Você só precisa de chamadas require para dois casos de uso:

  • Para carregar arquivos no diretório lib.
  • Para carregar dependências de gem que tenham require: false no Gemfile.

6 MVC e Você

Até agora, discutimos rotas, controllers, actions e views. Todas essas são peças típicas de uma aplicação web que segue o padrão MVC (Model-View-Controller).

MVC é um padrão de projeto que divide as responsabilidades de uma aplicação para facilitar nosso entendimento. O Rails segue esse padrão de projeto por convenção.

Já que temos um controller e uma view para trabalhar, agora vamos gerar a próxima peça: o model.

6.1 Gerando um Model

Um Model é uma classe Ruby utilizada para representar dados. Além disso, os models podem interagir com o banco de dados da aplicação através de um recurso do Rails chamado Active Record.

Para definir um model, utilizaremos um gerador de models:

$ bin/rails generate model Article title:string body:text

Os nomes dos models são no singular, pois um model instanciado representa um único registro de dados. Para ajudar a lembrar esta convenção, pense em como você chamaria o construtor do model: queremos escrever Article.new(...), não Articles.new(...).

O comando utilizando o gerador criará vários arquivos:

invoke  active_record
create    db/migrate/<timestamp>_create_articles.rb
create    app/models/article.rb
invoke    test_unit
create      test/models/article_test.rb
create      test/fixtures/articles.yml

Os dois arquivos em que vamos nos concentrar são o arquivo da migration (db/migrate/<timestamp>_create_articles.rb) e o arquivo do model (app/models/article.rb).

6.2 Migrações de Banco de Dados

As Migrations são utilizadas para alterar a estrutura do banco de dados de uma aplicação. Em aplicações Rails, as migrations são escritas em Ruby para que possam ser independentes do banco de dados.

Vamos dar uma olhada no conteúdo do nosso novo arquivo de migration:

class CreateArticles < ActiveRecord::Migration[7.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :body

      t.timestamps
    end
  end
end

A chamada para create_table especifica como a tabela articles deve ser construída. Por padrão, o método create_table adiciona uma coluna id como chave primária de auto incremento. Portanto, o primeiro registro na tabela terá um id de valor 1, o próximo registro terá um id de valor 2 e assim por diante.

Dentro do bloco de create_table, duas colunas são definidas: title e body. Elas foram adicionadas pelo gerador, pois incluímos a instrução no nosso comando (bin/rails generate model Article title:string body:text).

Na última linha do bloco há uma chamada para t.timestamps. Este método define duas colunas adicionais chamadas created_at e updated_at. Como veremos mais pra frente, o Rails gerenciará isso para nós, definindo os valores quando criamos ou atualizamos um objeto model.

Vamos executar a nossa migration com o seguinte comando:

$ bin/rails db:migrate

O comando exibirá o resultado do processamento indicando que a tabela foi criada:

==  CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0018s
==  CreateArticles: migrated (0.0018s) ==========================

Para saber mais sobre migrations, consulte Active Record Migrations.

Agora podemos interagir com a tabela utilizando o nosso model.

6.3 Utilizando um Model para Interagir com o Banco de Dados

Para brincar um pouco com o nosso model, vamos utilizar um recurso do Rails chamado console. O console é um ambiente de codificação interativo como o irb, mas que também carrega automaticamente o Rails e o código da nossa aplicação.

Vamos iniciar o console com o comando:

$ bin/rails console

Você deve visualizar um prompt irb:

Loading development environment (Rails 7.0.0)
irb(main):001:0>

Neste prompt, podemos inicializar um novo objeto Article:

irb> article = Article.new(title: "Hello Rails", body: "I am on Rails!")

É importante notar que apenas inicializamos este objeto. O objeto não é salvo no banco de dados e no momento está disponível apenas no console. Para salvar o objeto no banco de dados, devemos chamar save:

irb> article.save
(0.1ms)  begin transaction
Article Create (0.4ms)  INSERT INTO "articles" ("title", "body", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["title", "Hello Rails"], ["body", "I am on Rails!"], ["created_at", "2020-01-18 23:47:30.734416"], ["updated_at", "2020-01-18 23:47:30.734416"]]
(0.9ms)  commit transaction
=> true

A saída acima mostra uma query INSERT INTO "articles" ... de banco de dados. Isso indica que o artigo foi inserido em nossa tabela. Se dermos uma olhada no objeto article novamente, vemos que algo interessante aconteceu:

irb> article
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

Os atributos id, created_at e updated_at agora estão definidos. O Rails fez isso por nós quando salvamos o objeto.

Quando quisermos buscar este artigo no banco de dados, podemos chamar find no model e passar o id como argumento:

irb> Article.find(1)
=> #<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">

E quando quisermos obter todos os artigos do banco de dados, podemos chamar all no model:

irb> Article.all
=> #<ActiveRecord::Relation [#<Article id: 1, title: "Hello Rails", body: "I am on Rails!", created_at: "2020-01-18 23:47:30", updated_at: "2020-01-18 23:47:30">]>

Esté método retorna um objeto ActiveRecord::Relation, que você pode considerar como um array superpotente.

Para saber mais sobre models, consulte o Básico do Active Record e Interface de Consulta do Active Record.

Os models são a peça final do quebra-cabeça MVC. A seguir, conectaremos todas as peças.

6.4 Exibindo uma Lista de Artigos

Vamos voltar ao nosso controller em app/controllers/articles_controller.rb e alterar a action index para buscar todos os artigos do banco de dados:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

As variáveis de instância do controller podem ser acessadas pela view. Isso significa que podemos referenciar @articles em app/views/articles/index.html.erb. Vamos abrir esse arquivo e substituir seu conteúdo por:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= article.title %>
    </li>
  <% end %>
</ul>

O código acima é uma mistura de HTML e ERB. ERB é um sistema de template que avalia código Ruby embarcado em um documento. Aqui, podemos ver dois tipos de tags ERB: <% %> e <%= %>. A tag <% %> significa "avaliar o código Ruby incluso". A tag <%= %> significa "avaliar o código Ruby incluso e retornar o valor de saída". Qualquer coisa que possa ser escrita em um programa normal em Ruby pode ir dentro dessas tags ERB, embora geralmente seja melhor manter o conteúdo das tags ERB de forma curta para facilitar a leitura.

Já que não queremos gerar o valor retornado por @articles.each, vamos colocar esse código em <% %>. Porém, uma vez que queremos exibir o valor retornado em article.title (para cada artigo), incluímos esse códido em <%= %>.

Nós podemos visualizar o resultado final visitando http://localhost:3000 (lembre-se de que bin/rails server deve estar em execução!). Aqui estão as etapas do que acontece quando fazemos isso:

  1. O navegador faz uma requisição (request): GET http://localhost:3000.
  2. Nossa aplicação Rails recebe essa requisição.
  3. O roteador do Rails mapeia a rota raiz para a action index de ArticlesController.
  4. A action indexutiliza o model Article para buscar todos os artigos no banco de dados.
  5. O Rails renderiza automaticamente a view app/views/articles/index.html.erb.
  6. O código ERB na view é avaliado para gerar código HTML.
  7. O servidor envia uma resposta (response) de volta ao navegador contendo o HTML.

Conectamos todas as peças do MVC e temos nossa primeira action no controller! A seguir, passaremos para a segunda action.

7 Operações CRUD

Quase todas as aplicações web abrangem operações CRUD (Create, Read, Update e Delete), traduzidos como criação, consulta, atualização e destruição de dados. Você pode até descobrir que a maior parte do trabalho que a sua aplicação faz é o CRUD. O Rails reconhece isso e fornece muitos recursos para ajudar a simplificar o código na hora de fazer o CRUD.

Vamos começar a explorar esses recursos adicionando mais funcionalidades à nossa aplicação.

7.1 Exibindo um Único Artigo

Atualmente, temos uma view que lista todos os artigos em nosso banco de dados. Vamos adicionar uma nova view que exibe o título (title) e o corpo (body) de um único artigo.

Começamos adicionando uma nova rota que mapeia para uma nova action do controller (que adicionaremos a seguir). Abra o arquivo config/routes.rb e insira a última rota exibida aqui:

Rails.application.routes.draw do
  root "articles#index"

  get "/articles", to: "articles#index"
  get "/articles/:id", to: "articles#show"
end

A nova rota é outra rota do tipo get, mas tem algo extra no seu caminho (path): :id. Isso denomina um parâmetro de rota. Um parâmetro de rota captura um pedaço do caminho da requisição e coloca esse valor no Hash params, que pode ser acessado pela action do controller. Por exemplo, ao lidar com uma requisição como GET http://localhost:3000/articles/1, 1 seria capturado como o valor para id, que seria então acessível em params[:id] na action show de ArticlesController.

Vamos adicionar a action show agora, abaixo da action index em app/controllers/articles_controller.rb:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end
end

A action show chama Article.find (mencionado anteriormente) com o ID capturado pelo parâmetro de rota. O artigo retornado é armazenado na variável de instância @article, portanto, pode ser acessado pela view. Por padrão, a action show vai renderizar app/views/articles/show.html.erb.

Vamos criar app/views/articles/show.html.erb, com o seguinte conteúdo:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

Agora podemos ver o artigo quando visitarmos http://localhost:3000/articles/1!

Para finalizar, vamos adicionar uma maneira mais prática para chegar à página de um artigo. Iremos vincular o título de cada artigo em app/views/articles/index.html.erb para sua página:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="/articles/<%= article.id %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

7.2 Roteamento de Resources (recursos)

Até agora, nós vimos o "R" (Read, consulta) do CRUD. Iremos eventualmente cobrir o "C" (Create, criação), "U" (Update, atualização) e o "D" (Delete, destruição). Como você deve ter imaginado, faremos isso adicionando novas rotas, actions no controller e views que funcionam em conjunto para realizar as operações CRUD em uma entidade. Chamamos essa entidade de resource (recurso). Por exemplo, em nossa aplicação, diríamos que um artigo é um recurso.

O Rails fornece um método de rotas chamado resources que mapeia todas as rotas convencionais para uma coleção de recursos, como artigos. Portanto, antes de prosseguir para as seções "C", "U" e "D", vamos substituir as duas rotas get em config/routes.rb por resources:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

Nós podemos inspecionar quais rotas estão mapeadas executando o comando bin/rails routes:

$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
        root GET    /                            articles#index
    articles GET    /articles(.:format)          articles#index
 new_article GET    /articles/new(.:format)      articles#new
     article GET    /articles/:id(.:format)      articles#show
             POST   /articles(.:format)          articles#create
edit_article GET    /articles/:id/edit(.:format) articles#edit
             PATCH  /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy

O método resources também configura URL e métodos auxiliares (helper) de caminhos que podemos utilizar para evitar que nosso código dependa de uma configuração de rota específica. Os valores na coluna "Prefix" acima, mais um sufixo _url ou _path formam os nomes desses helpers. Por exemplo, o helper article_path retorna "/articles/#{article.id}" quando recebe um artigo. Podemos utilizá-lo para organizar nossos links em app/views/articles/index.html.erb:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <a href="<%= article_path(article) %>">
        <%= article.title %>
      </a>
    </li>
  <% end %>
</ul>

No entanto, daremos um passo adiante utilizando o helper link_to. O helper link_to renderiza um link com seu primeiro argumento como o texto do link e seu segundo argumento como o destino do link. Se passarmos um objeto model como segundo argumento, o link_to chamará o helper de caminho apropriado para converter o objeto em um caminho. Por exemplo, se passarmos um artigo, o link_to chamará o article_path. Portanto, app/views/articles/index.html.erb se torna:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

Muito bom!

Para aprender mais sobre roteamento, consulte Rotas do Rails de Fora pra Dentro.

7.3 Criando um Novo Artigo

Agora, seguimos para o "C" (Create, criação) do CRUD. Normalmente, em aplicações web, a criação de um novo recurso é um processo de várias etapas. Primeiro, o usuário solicita um formulário para preencher. Em seguida, o usuário envia o formulário. Se não houver erros, o recurso será criado e algum tipo de confirmação será exibido. Caso contrário, o formulário é exibido novamente com mensagens de erros e o processo é repetido.

Em uma aplicação Rails, esses passos não convencionalmente tratados pelas actions new e create do controller. Vamos implementar essas actions em app/controllers/articles_controller.rb, abaixo da action show:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(title: "...", body: "...")

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end
end

A action new instancia um novo artigo, mas não o salva no banco de dados. Este artigo será utilizado na view ao construirmos o formulário. Por padrão, a action new renderizará app/views/articles/new.html.erb, que criaremos a seguir.

A action create instancia um novo artigo com os valores para o título e corpo e tenta salvá-lo no banco de dados. Se o artigo for salvo com sucesso, a action redireciona o navegador para a página do artigo em "http://localhost:3000/articles/#{@article.id}". Caso contrário, a action exibe novamente o formulário renderizando a view app/views/articles/new.html.erb com um código de status 4XX para que a aplicação funcione bem com a Turbo. O título e o corpo aqui são valores fictícios. Depois de criarmos o formulário, vamos voltar no controller e alterá-los.

redirect_to fará com que o navegador faça uma nova requisição, enquanto render renderiza a view especificada para a requisição atual. É importante utilizar o redirect_to após alterar o banco de dados ou o estado da aplicação. Caso contrário, se o usuário atualizar a página, o navegador fará a mesma requisição e a mutação será repetida.

7.3.1 Utilizando um Construtor de Formulário (Form Builder)

Utilizaremos uma funcionalidade do Rails chamada form builder (construtor de formulário) para criar nosso formulário. Utilizando um construtor de formulário, podemos escrever uma quantidade mínima de código para gerar um formulário que está totalmente configurado e segue as convenções do Rails.

Vamor criar a view app/views/articles/new.html.erb com o seguinte conteúdo:

<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

O método auxiliar form_with instancia um construtor de formulário. No bloco form_with chamamos métodos como label e text_field no construtor para gerar os elementos apropriados de um formulário.

O resultado de saída da nossa chamada form_with será parecido com:

<form action="/articles" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="...">

  <div>
    <label for="article_title">Title</label><br>
    <input type="text" name="article[title]" id="article_title">
  </div>

  <div>
    <label for="article_body">Body</label><br>
    <textarea name="article[body]" id="article_body"></textarea>
  </div>

  <div>
    <input type="submit" name="commit" value="Create Article" data-disable-with="Create Article">
  </div>
</form>

Para saber mais sobre os construtores de formulários, consulte Action View Form Helpers.

7.3.2 Utilizando Strong Parameters (Parâmetros Fortes)

Os dados do formulário enviados são colocados no Hash params, junto com os parâmetros de rota capturados. Assim, a action create pode acessar o título enviado via params[:article][:title] e o corpo enviado via params[:article][:body]. Poderíamos passar esses valores individualmente para Article.new, mas isso seria longo demais e possivelmente sujeito a erros. E ficaria pior a medida que adicionamos mais campos.

Em vez disso, passaremos um único Hash que contém os valores. No entanto, ainda devemos especificar quais valores são permitidos nesse Hash, caso contrário, um usuário mal intencionado pode enviar campos extras no formulário e sobrescrever dados privados. Na verdade, se passarmos o Hash params[:article] não filtrado diretamente para Article.new, o Rails lançará um ForbiddenAttributesError para nos alertar sobre o problema. Portanto, utilizaremos um recurso do Rails chamado Strong Parameters (Parâmetros Fortes) para filtrar params. Pense nisso como tipagem forte para params.

Vamos adicionar um método privado na parte inferior de app/controllers/articles_controller.rb chamado article_params que filtra o params. E vamos alterar o método create para utilizá-lo:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

Para saber mais sobre Strong Parameters, consulte Action Controller Overview § Parâmetros Fortes.

7.3.3 Validações e Exibição de Mensagens de Erros

Como vimos, a criação de um recurso é um processo de várias etapas. Lidar com a entrada inválida do usuário é outra etapa desse processo. O Rails fornece um recurso chamado validações para nos ajudar a lidar com entradas inválidas do usuário. As validações são regras que são verificadas antes de um objeto model ser salvo. Se alguma das validações falhar, o objeto não será salvo e as mensagens de erros apropriadas serão adicionadas ao atributo errors do objeto model.

Vamos adicionar algumas validações ao nosso model em app/models/article.rb:

class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

A primeira validação declara que um valor title deve estar presente. Como title é uma string, isso significa que o valor title deve conter pelo menos um caractere diferente de espaço em branco.

A segunda validação declara que um valor body também deve estar presente. Além disso, declara que o valor body deve ter pelo menos 10 caracteres.

Você pode estar se perguntando onde os atributos title e body são definidos. O Active Record define automaticamente os atributos do model para cada coluna da tabela, então você não precisa declarar esses atributos em seu arquivo model.

Com nossas validações no lugar, vamos modificar app/views/articles/new.html.erb para exibir quaisquer mensagens de erro para title e body:

<h1>New Article</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% @article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% @article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

O método full_messages_for retorna um array de mensagens de erro amigáveis para um atributo especificado. Se não houver erros para esse atributo, o array ficará vazio.

Para entender como tudo isso funciona junto, vamos dar uma olhada nas actions de new e create do controller:

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

Quando visitamos http://localhost:3000/articles/new, a solicitação GET /articles/new é mapeada para a action new. A action new não tenta salvar o @article. Portanto, as validações não são verificadas e não haverá mensagens de erro.

Quando enviamos o formulário, a solicitação POST /articles é mapeada para a action create. A action create tenta salvar o @article. Portanto, as validações são verificadas. Se alguma validação falhar, o @article não será salvo e a view app/views/articles/new.html.erb será renderizada com as mensagens de erro com um código de status 4XX para que a aplicação funcione bem com a Turbo.

Para saber mais sobre validações, consulte Validações do Active Record. Para saber mais sobre as mensagens de erro de validação, consulte Validações do Active Record § Trabalhando com Erros de Validação.

7.3.4 Finalizando

Agora podemos criar um artigo visitando http://localhost:3000/articles/new. Para finalizar, vamos criar um link para essa página na parte inferior da view app/views/articles/index.html.erb:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

7.4 Atualizando um Artigo

Nós cobrimos o "CR" do CRUD. Agora, vamos passar para o "U" (Update, atualização). Atualizar um recurso é muito semelhante a criar um recurso. Ambos são processos de várias etapas. Primeiro, o usuário solicita um formulário para editar os dados. Se não houver erros, o recurso será atualizado. Caso contrário, o formulário é exibido novamente com mensagens de erro e o processo é repetido.

Essas etapas são convencionalmente tratadas pelas actions edit e update de um controller. Vamos adicionar uma implementação típica dessas actions em app/controllers/articles_controller.rb, abaixo da action create:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

Observe como as actions edit e update se assemelham às actions new e create.

A action edit busca o artigo do banco de dados e o armazena em @article para que possa ser utilizado ao construir o formulário. Por padrão, a action edit renderizará app/views/articles/edit.html.erb.

A action update busca novamente o artigo do banco de dados e tenta atualizá-lo com os dados filtrados do formulário enviado por article_params. Se nenhuma validação falhar e a atualização for bem-sucedida, a action redireciona o navegador para a página do artigo. Caso contrário, a action exibe novamente o formulário com mensagens de erro, renderizando app/views/articles/edit.html.erb com um código de status 4XX para que a aplicação funcione bem com a Turbo.

7.4.1 Utilizando Partials para Compartilhar Código de View

Nosso formulário edit terá a mesma aparência que o nosso formulário new. Até o código será o mesmo, graças ao construtor de formulários do Rails e ao roteamento de recursos. O construtor de formulários configura automaticamente o formulário para fazer o tipo apropriado de requisição, baseando-se sobre se o objeto do model foi salvo anteriormente.

Como o código será o mesmo, vamos separá-lo em uma view compartilhada chamada partial. Vamos criar a view app/views/articles/_form.html.erb com o seguinte conteúdo:

<%= form_with model: article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

O código acima é igual ao nosso formulário em app/views/articles/new.html.erb, exceto que todas as ocorrências de @article foram substituídas por article. Como partials são códigos compartilhados, a melhor prática é que elas não dependam de variáveis de instância específicas definidas por uma action do controller. Em vez disso, passaremos o artigo para a partial como uma variável local.

Vamos atualizar app/views/articles/new.html.erb para utilizar a partial via render:

<h1>New Article</h1>

<%= render "form", article: @article %>

O nome do arquivo da partial deve ser prefixado com um sublinhado, por exemplo, _form.html.erb. Mas ao renderizar, ela é referenciada sem o sublinhado, por exemplo, render form.

E agora vamos criar uma view app/views/articles/edit.html.erb muito semelhante:

<h1>Edit Article</h1>

<%= render "form", article: @article %>

Para saber mais sobre partials, consulte Layouts e Renderização no Rails § Usando Partials.

7.4.2 Finalizando

Agora podemos atualizar um artigo visitando sua página de edição, por exemplo, http://localhost:3000/articles/1/edit. Para finalizar, vamos criar um link para a página de edição na parte inferior de app/views/articles/show.html.erb:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
</ul>

7.5 Deletando um Artigo

Finalmente chegamos do "D" (Delete , destruição) do CRUD. Deletar um recurso é um processo mais simples do que criar ou atualizar. Requer apenas uma rota e uma action do controller. E nosso roteamento de recursos (resources :articles) já fornece a rota que mapeia as requisições DELETE /articles/:id para a action destroy de ArticlesController.

Então, vamos adicionar uma típica action destroy em app/controllers/articles_controller.rb, abaixo da action update:

class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to root_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

A action destroy busca o artigo do banco de dados e chama o método destroy do artigo. Em seguida, redireciona o navegador para o caminho raiz (root path).

Nós optamos por redirecionar para o caminho raiz porque esse é o nosso principal ponto de acesso para os artigos. Mas, em outras circunstâncias, você pode optar por redirecionar para, por exemplo, articles_path

Agora vamos adicionar um link na parte inferior de app/views/articles/show.html.erb para que possamos deletar um artigo de sua própria página:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

Nó código acima, estamos passando algumas opções adicionais para o link_to. A opção method: :delete faz com que o link faça uma requisição DELETE em vez de uma requisição GET. A opção data: { confirm: "Are you sure?" } faz com que uma caixa de diálogo de confirmação apareça quando o link é clicado. Se o usuário cancelar a caixa de diálogo, a requisição será abortada. Ambas as opções são alimentadas por um recurso do Rails chamado JavaScript Discreto (UJS). O arquivo JavaScript que implementa esses comportamentos é incluído por padrão em novas aplicações Rails.

Para saber mais sobre Javascript Discreto (unobtrusive), consulte Trabalhando com JavaScript no Rails.

E é isso! Agora podemos listar, exibir, criar, atualizar e deletar artigos! Incrível!

8 Adicionando um Segundo Model

É hora de adicionar um segundo model à aplicação. O segundo model vai lidar com comentários em artigos.

8.1 Gerando um Model

Nós veremos o mesmo generator que usamos antes quando criamos o model Article (artigo, inglês). Desta vez vamos criar um model Comment (comentário) que contém a referência para um artigo. Rode esse comando no seu terminal:

$ bin/rails generate model Comment commenter:string body:text article:references

Este comando vai gerar quatro arquivos:

Arquivo Propósito
db/migrate/20140120201010_create_comments.rb Migration para criar a tabela de comentários no seu banco de dados (o nome incluirá um timestamp diferente)
app/models/comment.rb O model Comment
test/models/comment_test.rb Aparelhagem de testes para o model de comentário
test/fixtures/comments.yml Exemplo de comentários para uso em testes

Primeiro, veja o arquivo app/models/comment.rb:

class Comment < ApplicationRecord
  belongs_to :article
end

Isso é muito semelhante ao model Article que vimos antes. A diferença está na linha belongs_to : article, o que configura uma associação no Active Record. Você vai aprender um pouco sobre associações na próxima seção deste guia.

A palavra-chave (:references) usada no comando shell é um tipo especial de dado para models. Ela cria uma nova coluna na tabela do banco de dados com o nome fornecido ao model anexada a um _id que contém um valor do tipo integer. Para compreender melhor, analise o arquivo db/schema.rb depois de rodar a migration.

Além do model, o Rails também gerou a migration para criar a tabela correspondente no banco de dados:

class CreateComments < ActiveRecord::Migration[7.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, null: false, foreign_key: true

      t.timestamps
    end
  end
end

A linha t.references cria uma coluna com valores do tipo integer chamada article_id, um índice para ela e uma restrição de chave estrangeira (foreign key) que aponta para a coluna id da tabela articles. Vá em frente e rode a migration:

$ bin/rails db:migrate

O Rails é inteligente o suficiente para executar somente as migrações que ainda não foram rodadas no banco de dados atual, assim neste caso você verá:

==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

8.2 Associando Models

Associações do Active Record permitem declarar facilmente a relação entre dois models. No caso de comentários e artigos, você poderia descrever a relação da seguinte maneira:

  • Cada comentário pertece a um artigo.
  • Um artigo pode possuir muitos comentários.

De fato, essa sintaxe é muito similar à utilizada pelo Rails para declarar essa associação. Você já viu a linha de código dentro do model Comment (app/models/comment.rb) que faz com que cada comentário pertença a um Artigo:

class Comment < ApplicationRecord
  belongs_to :article
end

Você vai precisar editar o arquivo app/models/article.rb para adicionar o outro lado da associação:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

Estas duas declarações habilitam uma boa parte de comportamento automático. Por exemplo, se você possui uma instância da variável @article que contém um artigo, você pode recuperar todos os comentários pertencentes àquele artigo na forma de um array usando @article.comments.

Para mais informações sobre associações do Active Record, veja o guia Associações no Active Record.

8.3 Adicionando a Rota para Comentários

Da mesma forma que o controller articles, nós vamos precisar adicionar a rota para que o Rails saiba para onde queremos navegar para encontrar comments. Abra o arquivo config/routes.rb novamente e o edite da seguinte maneira:

Rails.application.routes.draw do
  root "articles#index"

  resources :articles do
    resources :comments
  end
end

Isso cria comments como um recurso aninhado (nested resource) dentro de article. Essa é outra parte do processo para recuperar as relações hierárquicas que existem entre artigos e comentários.

Para mais informações sobre rotas, veja o guia Roteamento no Rails

8.4 Gerando um Controller

Com o model em mãos, você pode voltar sua atenção para a criação do controller correspondente. Mais uma vez, você vai usar o generator usado anteriormente:

$ bin/rails generate controller Comments

Isso cria quatro arquivos e um diretório vazio:

Arquivo/Diretório Propósito
app/controllers/comments_controller.rb O controller de comentários
app/views/comments/ Views do controller são armazenadas aqui
test/controllers/comments_controller_test.rb O teste para o controller
app/helpers/comments_helper.rb Arquivo de helpers da view
app/assets/stylesheets/comments.scss Cascading style sheet (CSS) para o controller

Como em qualquer blog, nossos leitores vão criar seus comentários diretamente depois de lerem o artigo e, uma vez que adicionarem o comentário, serão enviados de volta para a página show do artigo para verem o comentário agora listado. Por essa razão, nosso CommentsController está aqui para fornecer um método que cria comentários e deleta comentários spam quando chegarem.

Então, primeiro nós vamos ligar o show template para Artigos (app/views/articles/show.html.erb) para que possamos criar um novo comentários:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

Isso adiciona na página show do Article um formulário que cria um novo comentário chamando a action create no CommentsController. O form_with aqui usa um array que vai construir uma rota aninhada, como /articles/1/comments.

Vamos ligar a action create em app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

Você verá um pouco mais de complexidade aqui do que no controller para artigos. Esse é o efeito colateral do aninhamento que você configurou. Cada requisição para um comentário deve lembrar o artigo ao qual o comentário está anexado, para que a chamada inicial do método find do model Article encontre o artigo em questão.

Além disso, o código aproveita-se de alguns métodos disponíveis para uma associação. Nós usamos o método create em @article.comments para criar e salvar um comentário. Isso vai automaticamente conectar o comentário para que ele pertença àquele artigo em particular.

Uma vez que temos um novo comentário, nós enviamos o usuário de volta ao artigo original usando o helper article_path(@article). Como já vimos anteriormente, isso chama a action show do ArticlesController que por sua vez renderiza o template show.html.erb. É aqui que queremos que o comentário apareça, então vamos adicionar isso ao arquivo app/views/articles/show.html.erb.

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

Agora podemos adicionar artigos e comentários ao seu blog e mostrá-los nos lugares certos.

Article with Comments

9 Refatorando

Agora que nossos artigos e comentários funcionam, dê uma olhada no template app/views/articles/show.html.erb. Ele está ficando longo e esquisito. Nós podemos usar partials (views parciais) para melhorá-lo.

9.1 Renderizando Coleções de Partials

Primeiramente, nós vamos criar uma partial para extrair a exibição de todos os comentários para o artigo. Crie o arquivo app/views/comments/_comment.html.erb e insira o código a seguir:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

Então você pode mudar app/views/articles/show.html.erb para o seguinte código:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

Isso fará com que a partial seja renderizada em app/views/comments/_comment.html.erb uma vez para cada comentário na coleção @article.comments. Como o método render itera sobre a coleção @article.comments, ele designa cada comentário para uma variável local nomeada como a partial, nesse caso comment, que então fica disponível para ser exibida na partial.

9.2 Renderizando um Formulário com Partial

Agora vamos mover aquela nova seção de comentários para sua própria partial. Novamente, crie o arquivo app/views/comments/_form.html.erb contendo:

<%= form_with model: [ @article, @article.comments.build ] do |form| %>
  <p>
    <%= form.label :commenter %><br>
    <%= form.text_field :commenter %>
  </p>
  <p>
    <%= form.label :body %><br>
    <%= form.text_area :body %>
  </p>
  <p>
    <%= form.submit %>
  </p>
<% end %>

Então deixe o arquivo app/views/articles/show.html.erb assim:

<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article),
                  method: :delete,
                  data: { confirm: "Are you sure?" } %></li>
</ul>

<h2>Comments</h2>
<%= render @article.comments %>

<h2>Add a comment:</h2>
<%= render 'comments/form' %>

O segundo render apenas define o template de partial que queremos renderizar, comments/form. O Rails é inteligente o suficiente para entender a barra nessa string e perceber que você quer renderizar o arquivo _form.html.erb no diretório app/views/comments.

O objeto @article está disponível para todas as partials renderizadas na view porque o definimos como uma variável de instância.

9.3 Usando Concerns

Concerns são uma forma de tornar grandes controllers ou models mais fáceis de entender e gerenciar. Isso também tem a vantagem de ser reutilizável quando vários models (ou controllers) compartilham as mesmas preocupações. As concerns são implementadas usando módulos (module) que contêm métodos que representam uma fatia bem definida da funcionalidade pela qual um model ou controller é responsável. Em outras linguagens, os módulos costumam ser conhecidos como mixins.

Você pode usar as concerns em seu controller ou model da mesma forma que usaria qualquer módulo. Quando você criou sua aplicação pela primeira vez com rails new blog, duas pastas foram criadas dentro de app/ junto com o resto:

app/controllers/concerns
app/models/concerns

Um determinado artigo do blog pode ter vários status - por exemplo, pode ser visível para todos (ou seja, public), ou visível apenas para o autor (ou seja, private). Também pode estar oculto para todos, mas ainda pode ser recuperado (ou seja, archived). Os comentários também podem estar ocultos ou visíveis. Isso pode ser representado usando uma coluna status em cada um dos models.

Primeiro, vamos executar as seguintes migrations para adicionar status a Articles e Comments:

$ bin/rails generate migration AddStatusToArticles status:string
$ bin/rails generate migration AddStatusToComments status:string

E a seguir, vamos atualizar o banco de dados com as migrations geradas:

$ bin/rails db:migrate

para saber mais sobre migrations, consulte Migrations do Active Record.

Também temos que permitir a chave :status como parte do strong parameters, em app/controllers/articles_controller.rb:

  private
    def article_params
      params.require(:article).permit(:title, :body, :status)
    end

e em app/controllers/comments_controller.rb:

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end

Dentro do model article, após executar uma migration para adicionar uma coluna status usando o comando bin/rails db:migrate, você adicionaria:

class Article < ApplicationRecord
  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

e no model Comment:

class Comment < ApplicationRecord
  belongs_to :article

  VALID_STATUSES = ['public', 'private', 'archived']

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

Então, em nossa action index (app/views/articles/index.html.erb), usaríamos o método archived? Para evitar a exibição de qualquer artigo que está arquivado:

<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

Da mesma forma, em nossa views parcial de comentários (app/views/comments/_comment.html.erb) usaríamos o método archived? para evitar a exibição de qualquer comentário arquivado:

<% unless comment.archived? %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

No entanto, se você olhar novamente para nossos models agora, pode ver que a lógica está duplicada. Se, no futuro, aumentarmos a funcionalidade do nosso blog - para incluir mensagens privadas, por exemplo - podemos ver a duplicação de lógica mais uma vez. É aqui que as concerns são úteis.

Uma concerns é responsável apenas por um subconjunto específico da responsabilidade do model; os métodos na nossa concern estarão todos relacionados à visibilidade de um model. Vamos chamar nossa nova concern (módulo) de Visible. Podemos criar um novo arquivo dentro de app/models/concerns chamadovisible.rb, e armazenar todos os métodos de status que foram duplicados nos models.

app/models/concerns/visible.rb

module Visible
  def archived?
    status == 'archived'
  end
end

Podemos adicionar nossa validação de status à concern, mas isso é um pouco mais complexo, pois as validações são métodos chamados no nível da classe. O ActiveSupport::Concern (API Guide) nos dá uma maneira mais simples de incluí-los:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  def archived?
    status == 'archived'
  end
end

Agora, podemos remover a lógica duplicada de cada model e, em vez disso, incluir nosso novo módulo Visible:

Em app/models/article.rb:

class Article < ApplicationRecord
  include Visible

  has_many :comments

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

e em app/models/comment.rb:

class Comment < ApplicationRecord
  include Visible

  belongs_to :article
end

Os métodos de classe também podem ser adicionados às concerns. Se quisermos que uma contagem de artigos públicos ou comentários sejam exibidos em nossa página principal, podemos adicionar um método de classe a Visible da seguinte maneira:

module Visible
  extend ActiveSupport::Concern

  VALID_STATUSES = ['public', 'private', 'archived']

  included do
    validates :status, inclusion: { in: VALID_STATUSES }
  end

  class_methods do
    def public_count
      where(status: 'public').count
    end
  end

  def archived?
    status == 'archived'
  end
end

Então, na view, você pode chamá-lo como qualquer método de classe:

<h1>Articles</h1>

Our blog has <%= Article.public_count %> articles and counting!

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
      <li>
        <%= link_to article.title, article %>
      </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

Para finalizar, adicionaremos uma caixa de seleção aos formulários e permitiremos que o usuário selecione o status ao criar um novo artigo ou postar um novo comentário. Também podemos especificar o status padrão como public. Em app/views/articles/_form.html.erb, podemos adicionar:

<div>
  <%= form.label :status %><br>
  <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</div>

e em app/views/comments/_form.html.erb:

<p>
  <%= form.label :status %><br>
  <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
</p>

10 Deletando Comentários

Outra importante feature de um blog é excluir comentários de spam. Para fazer isto, nós precisamos implementar um link de alguma view e uma action destroy no CommentsController.

Primeiro, vamos adicionar o link delete na partial app/views/comments/_comment.html.erb:

<p>
  <strong>Autor do comentário:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comentário:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Destruir comentário', [comment.article, comment],
              method: :delete,
              data: { confirm: "Are you sure?" } %>
</p>

Clicar neste novo link "Destruir comentário" será disparado um DELETE /articles/:article_id/comments/:id ao nosso CommentsController, que pode ser usar isso para encontrar o comentário que queremos excluir, então vamos adicionar uma ação destroy ao nosso controller (app/controllers/comments_controller.rb):

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body, :status)
    end
end

A action destroy vai encontrar o artigo que estamos vendo, localizar o comentário na collection @article.comments, removê-lo do seu banco de dados e nos enviar de volta para a action show do artigo.

10.1 Excluindo objetos associados

Se você excluir um artigo, os comentários (comments) associados também precisarão ser excluídos, caso contrário, eles simplesmente ocupariam espaço no banco de dados. O Rails permite que você use a opção dependent de uma associação para conseguir isso. Modifique o Modelo de artigo (article), app/models/article.rb, da seguinte forma:

class Article < ApplicationRecord
  include Visible

  has_many :comments, dependent: :destroy

  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

11 Segurança

11.1 Autenticação Básica

Se fosse fosse publicar o seu blog online, qualquer um poderia adicionar, editar e deletar seus artigos ou comentários.

O Rails disponibiliza um sistema de autenticação HTTP que funcionará tranquilamente nesta situação.

No ArticlesController nós precisamos que tenha um meio de bloquear o acesso à várias ações se uma pessoa não estiver autenticada. Aqui podemos usar o método http_basic_authenticate_with, que permite o acesso para a ação requisitada se o método deixar.

Para usar o sistema de autenticação, nós especificamos no topo do nosso ArticlesController em app/controllers/articles_controller.rb. No nosso caso, nós queremos que o usuário esteja autenticado em todas as ações, exceto index e show, então nós colocamos isso:

class ArticlesController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # snippet for brevity

Nós também queremos autorizar somente usuários autenticados a deletar comentários, então em CommentsController (app/controllers/comments_controller.rb) nós colocamos:

class CommentsController < ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    # ...
  end

  # snippet for brevity

Agora se você tentar criar um novo artigo, você deverá preencher um formulário de autenticação:

Formulário de Autenticação

Outros métodos de autenticação estão disponíveis para aplicações Rails. Dois add-ons de autenticação populares para Rails são o Devise e o Authlogic entre outros.

11.2 Outras Considerações de Segurança

Segurança, especialmente em aplicações web, é uma area ampla e detalhada. O tópico de segurança aplicações Rails é coberto com mais detalhes em Guia de Segurança Ruby on Rails.

12 O que vem depois?

Agora que você criou sua primeira aplicação Rails, sinta-se à vontade para atualizar e experimentar por conta própria.

Lembre-se, você não precisa fazer tudo sem ajuda. Se você precisa de assistência para começar a desenvolver com Rails, sinta-se à vontade para consultar estes recursos:

13 Dicas de Configuração

O caminho mais fácil para se trabalhar com o Rails é armazenar todos os dados externos como UTF-8. Se não fizer assim, as bibliotecas Ruby e o Rails vão, na maioria das vezes, conseguir converter seus dados nativos em UTF-8, porém não é sempre que isso funciona corretamente, então é melhor que você assegure que todos seus dados externos estão em UTF-8.

Caso tenha cometido um erro nessa parte, o sintoma mais comum é o aparecimento de um diamante preto com um ponto de interrogação dentro no seu navegador. Outro sintoma comum é o aparecimento de caracteres como "ü" ao invés de "ü". O Rails executa um número de passos internos para mitigar causas comuns desses problemas que possam ser detectadas e corrigidas automaticamente. Porém, caso você possua dados externos que não estão armazenados como UTF-8, eles poderão ocasionalmente resultar em problemas que não podem ser detectados e nem resolvidos de forma automática pelo Rails.

Duas fontes muito comuns de dados que não estão em UTF-8 são:

  • Seu editor de texto: A maioria dos editores de texto (como o TextMate), salvam os arquivos em UTF-8 de forma padrão. Caso o seu editor de texto não salve, isso pode resultar em caracteres especiais inseridos por você nos seus templates (como por exemplo: é) aparecerem no navegador como um diamante com um ponto de interrogação dentro. Isso também se aplica aos seus arquivos de tradução i18n. Muitos editores que não salvam em UTF-8 por padrão (como algumas versões do Dreamweaver) oferecem uma forma de alterar o padrão para UTF-8. Faça isso.
  • Seu banco de dados: o Rails converte seus dados do banco de dados em UTF-8 de forma padrão. Porém, se seu banco de dados não está utilizando UTF-8 internamente, pode ser que não consiga armazenar todos os caracteres que seus usuários insiram. Por exemplo, se seu banco de dados está utilizando Latin-1 internamente, e seu usuário inserir um caractere russo, hebraico ou japonês, os dados serão perdidos para sempre assim que entrarem no banco de dados. Se possível, utilize UTF-8 como padrão de armazenamento para o seu banco de dados.

Feedback

Você é incentivado a ajudar a melhorar a qualidade deste guia.

Por favor, contribua caso veja quaisquer erros, inclusive erros de digitação. Para começar, você pode ler nossa sessão de contribuindo com a documentação.

Você também pode encontrar conteúdo incompleto ou coisas que não estão atualizadas. Por favor, adicione qualquer documentação em falta na main do Rails. Certifique-se de checar o Edge Guides (en-US) primeiro para verificar se o problema já foi resolvido ou não no branch main. Verifique as Diretrizes do Guia Ruby on Rails para estilo e convenções.

Se, por qualquer motivo, você encontrar algo para consertar, mas não conseguir consertá-lo, por favor abra uma issue no nosso Guia.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem vinda na lista de discussão rubyonrails-docs e nas issues do Guia em português.


dark theme icon