edge
Mais em rubyonrails.org: Mais Ruby on Rails

Começando com o Rails

Este guia aborda como começar e executar o Ruby on Rails.

Após ler este guia, você saberá:

1 Suposições do Guia

Este guia é projetado para iniciantes que desejam começar a criar um aplicativo Rails do zero. Ele não assume que você tenha qualquer experiência prévia com o Rails.

Rails é um framework de desenvolvimento de aplicativos web que roda na linguagem de programação Ruby. Se você não tem experiência prévia com Ruby, encontrará uma curva de aprendizado muito íngreme mergulhando diretamente no Rails. Existem várias listas selecionadas de recursos online para aprender Ruby:

Esteja ciente de que alguns recursos, embora ainda excelentes, cobrem versões mais antigas de Ruby e podem não incluir algumas sintaxes que você verá no desenvolvimento do dia a dia com o Rails.

2 O que é o Rails?

Rails é um framework de desenvolvimento de aplicativos web escrito na linguagem de programação Ruby. Ele foi projetado para facilitar a programação de aplicativos web, fazendo suposições sobre o que cada desenvolvedor precisa para começar. Ele permite que você escreva menos código enquanto realiza mais do que muitas outras linguagens e frameworks. Desenvolvedores experientes do Rails também relatam que ele torna o desenvolvimento de aplicativos web mais divertido.

Rails é um software opinativo. Ele assume que existe uma "melhor" maneira de fazer as coisas e é projetado para encorajar essa maneira - e em alguns casos desencorajar alternativas. Se você aprender "O Jeito Rails", provavelmente descobrirá um aumento tremendo na produtividade. Se você persistir em trazer hábitos antigos de outras linguagens para o desenvolvimento do Rails e tentar usar padrões que aprendeu em outros lugares, pode ter uma experiência menos feliz.

A filosofia do Rails inclui dois princípios orientadores principais:

  • Não se Repita: DRY é um princípio de desenvolvimento de software que afirma que "Cada pedaço de conhecimento deve ter uma única, inequívoca, autoritativa representação dentro de um sistema". Ao não escrever as mesmas informações repetidamente, nosso código é mais fácil de manter, mais extensível e menos propenso a erros.
  • Convenção sobre Configuração: O Rails tem opiniões sobre a melhor maneira de fazer muitas coisas em um aplicativo web e usa essas convenções como padrão, em vez de exigir que você especifique minúcias por meio de arquivos de configuração intermináveis.

3 Criando um Novo Projeto Rails

A melhor maneira de ler este guia é segui-lo passo a passo. Todos os passos são essenciais para executar este exemplo de aplicativo e nenhum código ou etapa adicional é necessário.

Ao seguir este guia, você criará um projeto Rails chamado blog, um weblog (diário online) muito simples. Antes de começar a construir o aplicativo, você precisa garantir que tenha o Rails instalado.

NOTA: Os exemplos abaixo usam o símbolo $ para representar o prompt do terminal em um sistema operacional semelhante ao UNIX, embora possa ter sido personalizado para aparecer de forma diferente. Se você estiver usando o Windows, seu prompt será parecido com C:\source_code>.

3.1 Instalando o Rails

Antes de instalar o Rails, verifique se o seu sistema possui os pré-requisitos corretos instalados. Estes incluem:

  • Ruby
  • SQLite3

3.1.1 Instalando o Ruby

Abra um prompt de linha de comando. No macOS, abra o Terminal.app; no Windows, escolha "Executar" no menu Iniciar e digite cmd.exe. Quaisquer comandos precedidos por um sinal de dólar $ devem ser executados na linha de comando. Verifique se você tem uma versão atual do Ruby instalada:

$ ruby --version
ruby 2.7.0

O Rails requer a versão 2.7.0 ou posterior do Ruby. É preferível usar a versão mais recente do Ruby. Se o número da versão retornada for menor que esse número (como 2.3.7 ou 1.8.7), você precisará instalar uma nova cópia do Ruby.

Para instalar o Rails no Windows, você precisará primeiro instalar o Ruby Installer.

Para obter mais métodos de instalação para a maioria dos sistemas operacionais, dê uma olhada em ruby-lang.org.

3.1.2 Instalando o SQLite3

Você também precisará de uma instalação do banco de dados SQLite3. Muitos sistemas operacionais semelhantes ao UNIX populares já possuem uma versão aceitável do SQLite3. Outros podem encontrar instruções de instalação no site do SQLite3. Verifique se está corretamente instalado e no seu PATH de carregamento:

$ sqlite3 --version

O programa deve exibir sua versão.

3.1.3 Instalando o Rails

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

$ gem install rails

Para verificar se tudo foi instalado corretamente, você deve ser capaz de executar o seguinte em um novo terminal:

$ rails --version

Se ele exibir algo como "Rails 7.0.0", você está pronto para continuar.

3.2 Criando a Aplicação Blog

O Rails vem com uma série de scripts chamados geradores que são projetados para facilitar sua vida de desenvolvimento, criando tudo o que é necessário para começar a trabalhar em uma tarefa específica. Um deles é o gerador de nova aplicação, que fornecerá a base de uma nova aplicação Rails para que você não precise escrevê-la.

Para usar este gerador, abra um terminal, navegue até um diretório onde você tenha permissão para criar arquivos e execute:

$ rails new blog

Isso criará uma aplicação Rails chamada Blog em um diretório blog e instalará as dependências de gemas que já estão mencionadas no Gemfile usando bundle install.

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

Depois de criar a aplicação blog, mude para a pasta dela:

$ cd blog

O diretório blog terá vários arquivos e pastas gerados que compõem a estrutura de uma aplicação Rails. A maior parte do trabalho neste tutorial acontecerá na pasta app, mas aqui está uma visão geral básica da função de cada um dos arquivos e pastas que o Rails cria por padrão:

Arquivo/Pasta Propósito
app/ Contém os controladores, modelos, visualizações, ajudantes, correios, canais, trabalhos e ativos da sua aplicação. Você vai se concentrar nesta pasta pelo resto deste guia.
bin/ Contém o script rails que inicia sua aplicação e pode conter outros scripts que você usa para configurar, atualizar, implantar ou executar sua aplicação.
config/ Contém a configuração das rotas, banco de dados e mais da sua aplicação. Isso é abordado em mais detalhes em Configurando Aplicações Rails.
config.ru Configuração do Rack para servidores baseados em Rack usados para iniciar a aplicação. Para mais informações sobre o Rack, consulte o site do Rack.
db/ Contém o esquema do banco de dados atual, bem como as migrações do banco de dados.
Gemfile
Gemfile.lock
Esses arquivos permitem que você especifique quais dependências de gemas são necessárias para sua aplicação Rails. Esses arquivos são usados pelo gem Bundler. Para mais informações sobre o Bundler, consulte o site do Bundler.
lib/ Módulos estendidos para sua aplicação.
log/ Arquivos de log da aplicação.
public/ Contém arquivos estáticos e ativos compilados. Quando sua aplicação está em execução, este diretório será exposto como está.
Rakefile Este arquivo localiza e carrega tarefas que podem ser executadas a partir da linha de comando. As definições de tarefas são definidas em todos os componentes do Rails. Em vez de alterar o Rakefile, você deve adicionar suas próprias tarefas adicionando arquivos ao diretório lib/tasks da sua aplicação.
README.md Este é um manual de instruções breve para sua aplicação. Você deve editar este arquivo para informar aos outros o que sua aplicação faz, como configurá-la, e assim por diante.
storage/ Arquivos de Active Storage para o serviço de disco. Isso é abordado em Visão Geral do Active Storage.
test/ Testes unitários, fixtures e outros aparatos de teste. Isso é abordado em Testando Aplicações Rails.
tmp/ Arquivos temporários (como cache e arquivos pid).
vendor/ Um lugar para todo o código de terceiros. Em uma aplicação Rails típica, isso inclui gemas vendidas.
.gitattributes Este arquivo define metadados para caminhos específicos em um repositório git. Esses metadados podem ser usados pelo git e outras ferramentas para aprimorar seu comportamento. Consulte a documentação do gitattributes para obter mais informações.
.gitignore Este arquivo informa ao git quais arquivos (ou padrões) ele deve ignorar. Consulte GitHub - Ignorando arquivos para obter mais informações sobre a exclusão de arquivos.
.ruby-version Este arquivo contém a versão padrão do Ruby.

4 Olá, Rails!

Para começar, vamos colocar algum texto na tela rapidamente. Para fazer isso, você precisa iniciar o servidor da sua aplicação Rails.

4.1 Iniciando o Servidor Web

Na verdade, você já tem uma aplicação Rails funcional. Para vê-la, você precisa 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

DICA: Se você estiver usando o Windows, você precisa passar os scripts da pasta bin diretamente para o interpretador Ruby, por exemplo, ruby bin\rails server.

DICA: A compressão de ativos JavaScript requer que você tenha um tempo de execução JavaScript disponível em seu sistema; na ausência de um tempo de execução, você verá um erro execjs durante a compressão de ativos. Geralmente, macOS e Windows já vêm com um tempo de execução JavaScript instalado. therubyrhino é o tempo de execução recomendado para usuários do JRuby e é adicionado por padrão ao Gemfile em aplicativos gerados com o JRuby. Você pode investigar todos os tempos de execução suportados em ExecJS.

Isso iniciará o Puma, um servidor web distribuído com o Rails por padrão. Para ver sua aplicação em ação, abra uma janela do navegador e navegue até http://localhost:3000. Você deverá ver a página de informações padrão do Rails:

Captura de tela da página inicial do Rails

Quando você quiser parar o servidor web, pressione Ctrl+C na janela do terminal onde ele está sendo executado. No ambiente de desenvolvimento, o Rails geralmente não requer que você reinicie o servidor; as alterações que você fizer nos arquivos serão automaticamente capturadas pelo servidor.

A página inicial do Rails é o "teste de fumaça" para um novo aplicativo Rails: ela garante que você tenha configurado corretamente seu software para servir uma página.

4.2 Diga "Olá", Rails

Para fazer o Rails dizer "Olá", você precisa criar, no mínimo, uma rota, um controlador com uma ação e uma visão. Uma rota mapeia uma solicitação para uma ação do controlador. Uma ação do controlador executa o trabalho necessário para lidar com a solicitação e prepara quaisquer dados para a visão. Uma visão exibe os dados em um formato desejado.

Em termos de implementação: Rotas são regras escritas em uma DSL (Linguagem Específica de Domínio) Ruby. Controladores são classes Ruby, e seus métodos públicos são ações. E as visões são modelos, geralmente escritos em uma mistura de HTML e Ruby.

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

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

  # Para obter detalhes sobre a DSL disponível neste arquivo, consulte https://guides.rubyonrails.org/routing.html
end

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

Para criar o ArticlesController e sua ação index, executaremos o gerador de controladores (com a opção --skip-routes porque já temos uma rota apropriada):

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

O Rails criará 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

O mais importante deles é o arquivo do controlador, app/controllers/articles_controller.rb. Vamos dar uma olhada nele:

class ArticlesController < ApplicationController
  def index
  end
end

A ação index está vazia. Quando uma ação não renderiza explicitamente uma visão (ou de outra forma aciona uma resposta HTTP), o Rails renderizará automaticamente uma visão que corresponda ao nome do controlador e da ação. Convenção sobre Configuração! As visões estão localizadas no diretório app/views. Portanto, a ação index renderizará app/views/articles/index.html.erb por padrão.

Vamos abrir app/views/articles/index.html.erb e substituir seu conteúdo por:

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

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

4.3 Definindo a Página Inicial do Aplicativo

No momento, http://localhost:3000 ainda exibe uma página com o logotipo do Ruby on Rails. Vamos exibir nosso texto "Olá, Rails!" em http://localhost:3000 também. Para fazer isso, adicionaremos uma rota que mapeia o caminho raiz de nosso aplicativo para o controlador e a ação apropriados.

Vamos abrir config/routes.rb e adicionar a seguinte rota root no início do bloco Rails.application.routes.draw:

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

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

Agora podemos ver nosso texto "Olá, Rails!" quando visitamos http://localhost:3000, confirmando que a rota root também está mapeada para a ação index do ArticlesController.

DICA: Para saber mais sobre roteamento, consulte Roteamento do Rails de fora para dentro.

5 Carregamento Automático

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

Você pode ter percebido que ArticlesController herda de ApplicationController, mas app/controllers/articles_controller.rb não tem algo como

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

Classes e módulos do aplicativo estão disponíveis em todos os lugares, você não precisa e não deve carregar nada em app com require. Essa funcionalidade é chamada de carregamento automático, e você pode aprender mais sobre ela em Carregamento Automático e Recarregamento de Constantes. Você só precisa de chamadas require para dois casos de uso:

  • Para carregar arquivos no diretório lib.
  • Para carregar dependências de gemas que têm require: false no Gemfile.

6 MVC e Você

Até agora, discutimos rotas, controladores, ações e visualizações. Todos esses são elementos típicos de uma aplicação web que segue o padrão MVC (Model-View-Controller). MVC é um padrão de design que divide as responsabilidades de uma aplicação para torná-la mais fácil de entender. O Rails segue esse padrão de design por convenção.

Como temos um controlador e uma visualização para trabalhar, vamos gerar a próxima parte: um modelo.

6.1 Gerando um Modelo

Um modelo é uma classe Ruby que é usada para representar dados. Além disso, os modelos podem interagir com o banco de dados da aplicação por meio de um recurso do Rails chamado Active Record.

Para definir um modelo, usaremos o gerador de modelos:

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

NOTA: Os nomes dos modelos são singulares, porque um modelo instanciado representa um único registro de dados. Para ajudar a lembrar dessa convenção, pense em como você chamaria o construtor do modelo: queremos escrever Article.new(...), não Articles.new(...).

Isso 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 de migração (db/migrate/<timestamp>_create_articles.rb) e o arquivo do modelo (app/models/article.rb).

6.2 Migrações de Banco de Dados

Migrações são usadas para alterar a estrutura do banco de dados de uma aplicação. Em aplicações Rails, as migrações 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 migração:

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 autoincrementada. Portanto, o primeiro registro na tabela terá um id de 1, o próximo registro terá um id de 2 e assim por diante.

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

Na última linha do bloco, há uma chamada para t.timestamps. Esse método define duas colunas adicionais chamadas created_at e updated_at. Como veremos, o Rails irá gerenciar essas colunas para nós, definindo os valores quando criamos ou atualizamos um objeto de modelo.

Vamos executar nossa migração com o seguinte comando:

$ bin/rails db:migrate

O comando exibirá uma saída indicando que a tabela foi criada:

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

DICA: Para saber mais sobre migrações, consulte Migrações do Active Record.

Agora podemos interagir com a tabela usando nosso modelo.

6.3 Usando um Modelo para Interagir com o Banco de Dados

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

Vamos iniciar o console com o seguinte comando:

$ bin/rails console

Você verá um prompt irb como este:

Carregando ambiente de desenvolvimento (Rails 7.0.0)
irb(main):001:0>

Neste prompt, podemos inicializar um novo objeto Article:

irb> article = Article.new(title: "Hello Rails", body: "Estou no Rails!")

É importante observar que apenas inicializamos este objeto. Este objeto não foi salvo no banco de dados. Ele está disponível apenas no console no momento. 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", "Estou no 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 consulta de banco de dados INSERT INTO "articles" .... Isso indica que o artigo foi inserido em nossa tabela. E se olharmos para o objeto article novamente, veremos algo interessante aconteceu:

irb> article
=> #<Article id: 1, title: "Hello Rails", body: "Estou no 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 do objeto agora estão definidos. O Rails fez isso por nós quando salvamos o objeto.

Quando queremos buscar este artigo no banco de dados, podemos chamar o método find no modelo 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 queremos buscar todos os artigos do banco de dados, podemos chamar o método all no modelo:

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">]>

Este método retorna um objeto ActiveRecord::Relation, que pode ser pensado como um array super poderoso.

DICA: Para aprender mais sobre modelos, consulte Active Record Basics e Active Record Query Interface.

Modelos são a peça final do quebra-cabeça do MVC. Em seguida, vamos conectar todas as peças juntas.

6.4 Mostrando uma lista de artigos

Vamos voltar ao nosso controlador em app/controllers/articles_controller.rb e alterar a ação 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 controlador podem ser acessadas pela visão. Isso significa que podemos fazer referência a @articles em app/views/articles/index.html.erb. Vamos abrir esse arquivo e substituir seu conteúdo por:

<h1>Artigos</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 templates que avalia código Ruby embutido em um documento. Aqui, podemos ver dois tipos de tags ERB: <% %> e <%= %>. A tag <% %> significa "avalie o código Ruby contido". A tag <%= %> significa "avalie o código Ruby contido e exiba o valor que ele retorna". Qualquer coisa que você possa escrever em um programa Ruby regular pode ser colocada dentro dessas tags ERB, embora seja geralmente melhor manter o conteúdo das tags ERB curto, para melhor legibilidade.

Como não queremos exibir o valor retornado por @articles.each, envolvemos esse código em <% %>. Mas, como queremos exibir o valor retornado por article.title (para cada artigo), envolvemos esse código em <%= %>.

Podemos ver o resultado final visitando http://localhost:3000. (Lembre-se de que bin/rails server deve estar em execução!) Aqui está o que acontece quando fazemos isso:

  1. O navegador faz uma solicitação: GET http://localhost:3000.
  2. Nosso aplicativo Rails recebe essa solicitação.
  3. O roteador do Rails mapeia a rota raiz para a ação index de ArticlesController.
  4. A ação index usa o modelo Article para buscar todos os artigos no banco de dados.
  5. O Rails renderiza automaticamente a visão app/views/articles/index.html.erb.
  6. O código ERB na visão é avaliado para gerar HTML.
  7. O servidor envia uma resposta contendo o HTML de volta para o navegador.

Conectamos todas as peças do MVC e temos nossa primeira ação do controlador! Em seguida, passaremos para a segunda ação.

7 CRUDit Onde o CRUDit é Devido

Quase todas as aplicações web envolvem operações CRUD (Create, Read, Update e Delete). Você pode até descobrir que a maioria do trabalho que sua aplicação faz é CRUD. O Rails reconhece isso e fornece muitos recursos para ajudar a simplificar o código que faz o CRUD.

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

7.1 Mostrando um único artigo

Atualmente, temos uma visão que lista todos os artigos em nosso banco de dados. Vamos adicionar uma nova visão que mostra o título e o corpo de um único artigo.

Começamos adicionando uma nova rota que mapeia para uma nova ação do controlador (que adicionaremos em seguida). Abra config/routes.rb e insira a última rota mostrada 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 get, mas tem algo extra em seu caminho: :id. Isso designa um parâmetro de rota. Um parâmetro de rota captura um segmento do caminho da solicitação e coloca esse valor no params Hash, que é acessível pela ação do controlador. Por exemplo, ao lidar com uma solicitação como GET http://localhost:3000/articles/1, 1 seria capturado como o valor para :id, que então seria acessível como params[:id] na ação show de ArticlesController. Vamos adicionar agora a ação show, abaixo da ação 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 ação show chama Article.find (mencionado anteriormente) com o ID capturado pelo parâmetro da rota. O artigo retornado é armazenado na variável de instância @article, tornando-o acessível pela view. Por padrão, a ação show 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 visitamos http://localhost:3000/articles/1!

Para finalizar, vamos adicionar uma forma conveniente de acessar a página de um artigo. Vamos linkar o título de cada artigo em app/views/articles/index.html.erb para a sua página:

<h1>Artigos</h1>

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

7.2 Rotas de Recursos

Até agora, cobrimos o "R" (Read) do CRUD. Eventualmente, cobriremos o "C" (Create), "U" (Update) e "D" (Delete). Como você pode ter imaginado, faremos isso adicionando novas rotas, ações de controlador e views. Sempre que temos uma combinação dessas rotas, ações de controlador e views que trabalham juntas para realizar operações CRUD em uma entidade, chamamos essa entidade de 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. Então, antes de prosseguirmos 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

Podemos verificar 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 métodos auxiliares de URL e path que podemos usar 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 dado um artigo. Podemos usá-lo para limpar nossos links em app/views/articles/index.html.erb:

<h1>Artigos</h1>

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

No entanto, vamos levar isso um passo adiante usando 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 de modelo como segundo argumento, o link_to chamará o helper de path apropriado para converter o objeto em um path. Por exemplo, se passarmos um artigo, o link_to chamará article_path. Então, app/views/articles/index.html.erb se torna:

<h1>Artigos</h1>

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

Legal!

DICA: Para aprender mais sobre roteamento, consulte Rails Routing from the Outside In.

7.3 Criando um Novo Artigo

Agora passamos para o "C" (Create) do CRUD. Tipicamente, em aplicações web, criar 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 é criado e alguma forma de confirmação é exibida. Caso contrário, o formulário é exibido novamente com mensagens de erro, e o processo é repetido.

Em uma aplicação Rails, essas etapas são convencionalmente tratadas pelas ações new e create de um controlador. Vamos adicionar uma implementação típica dessas ações em app/controllers/articles_controller.rb, abaixo da ação 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 ação new instancia um novo artigo, mas não o salva. Este artigo será usado na view ao construir o formulário. Por padrão, a ação new renderizará app/views/articles/new.html.erb, que criaremos em seguida. A ação create instancia um novo artigo com valores para o título e o corpo e tenta salvá-lo. Se o artigo for salvo com sucesso, a ação redireciona o navegador para a página do artigo em "http://localhost:3000/articles/#{@article.id}". Caso contrário, a ação exibe novamente o formulário renderizando app/views/articles/new.html.erb com o código de status 422 Unprocessable Entity. O título e o corpo aqui são valores fictícios. Depois de criar o formulário, voltaremos e alteraremos esses valores.

NOTA: redirect_to fará com que o navegador faça uma nova solicitação, enquanto render renderiza a visualização especificada para a solicitação atual. É importante usar redirect_to após modificar 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 solicitação e a mutação será repetida.

7.3.1 Usando um Construtor de Formulários

Vamos usar um recurso do Rails chamado construtor de formulários para criar nosso formulário. Usando um construtor de formulários, podemos escrever uma quantidade mínima de código para gerar um formulário totalmente configurado e que segue as convenções do Rails.

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

<h1>Novo Artigo</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ários. No bloco form_with, chamamos métodos como label e text_field no construtor de formulários para gerar os elementos de formulário apropriados.

A saída resultante da chamada form_with será parecida com esta:

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

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

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

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

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

7.3.2 Usando Strong Parameters

Os dados do formulário enviados são colocados no Hash params, juntamente com os parâmetros de rota capturados. Assim, a ação create pode acessar o título enviado através de params[:article][:title] e o corpo enviado através de params[:article][:body]. Poderíamos passar esses valores individualmente para Article.new, mas isso seria verboso e possivelmente propenso a erros. E pioraria à medida que adicionássemos mais campos.

Em vez disso, passaremos um único Hash que contém os valores. No entanto, ainda precisamos especificar quais valores são permitidos nesse Hash. Caso contrário, um usuário mal-intencionado poderia enviar campos de formulário extras 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, usaremos um recurso do Rails chamado Strong Parameters para filtrar params. Pense nisso como tipagem forte para params.

Vamos adicionar um método privado no final de app/controllers/articles_controller.rb chamado article_params que filtra params. E vamos alterar o create para usá-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

DICA: Para saber mais sobre Strong Parameters, consulte Action Controller Overview § Strong Parameters.

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

Como vimos, criar um recurso é um processo de várias etapas. Lidar com uma entrada de usuário inválida é outra etapa desse processo. O Rails fornece um recurso chamado validações para nos ajudar a lidar com uma entrada de usuário inválida. As validações são regras que são verificadas antes que um objeto do modelo seja salvo. Se alguma das verificações falhar, o salvamento será interrompido e mensagens de erro apropriadas serão adicionadas ao atributo errors do objeto do modelo.

Vamos adicionar algumas validações ao nosso modelo 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 de title deve estar presente. Como title é uma string, isso significa que o valor de title deve conter pelo menos um caractere que não seja espaço em branco.

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

NOTA: Você pode estar se perguntando onde os atributos title e body são definidos. O Active Record define automaticamente os atributos do modelo para cada coluna da tabela, então você não precisa declarar esses atributos em seu arquivo de modelo. Com nossas validações em vigor, vamos modificar app/views/articles/new.html.erb para exibir quaisquer mensagens de erro para title e body:

<h1>Novo Artigo</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 estará vazio.

Para entender como tudo isso funciona em conjunto, vamos dar mais uma olhada nas ações do controlador new e create:

  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 requisição GET /articles/new é mapeada para a ação new. A ação new não tenta salvar @article. Portanto, as validações não são verificadas e não haverá mensagens de erro.

Quando enviamos o formulário, a requisição POST /articles é mapeada para a ação create. A ação create tenta salvar @article. Portanto, as validações são verificadas. Se alguma validação falhar, @article não será salvo e app/views/articles/new.html.erb será renderizado com mensagens de erro.

DICA: Para aprender mais sobre validações, consulte Active Record Validations. Para aprender mais sobre mensagens de erro de validação, consulte Active Record Validations § Trabalhando com Erros de Validação.

7.3.4 Finalizando

Agora podemos criar um artigo visitando http://localhost:3000/articles/new. Para finalizar, vamos adicionar um link para essa página no final de app/views/articles/index.html.erb:

<h1>Artigos</h1>

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

<%= link_to "Novo Artigo", new_article_path %>

7.4 Atualizando um Artigo

Já cobrimos o "CR" do CRUD. Agora vamos passar para o "U" (Atualização). Atualizar um recurso é muito semelhante a criar um recurso. Ambos são processos de vários passos. Primeiro, o usuário solicita um formulário para editar os dados. Em seguida, o usuário envia o formulário. Se não houver erros, o recurso é atualizado. Caso contrário, o formulário é exibido novamente com mensagens de erro e o processo é repetido.

Essas etapas são convencionalmente tratadas pelas ações edit e update de um controlador. Vamos adicionar uma implementação típica dessas ações em app/controllers/articles_controller.rb, abaixo da ação 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 ações edit e update se assemelham às ações new e create.

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

A ação update busca (re-)o artigo no banco de dados e tenta atualizá-lo com os dados do formulário enviados filtrados por article_params. Se nenhuma validação falhar e a atualização for bem-sucedida, a ação redireciona o navegador para a página do artigo. Caso contrário, a ação exibe novamente o formulário - com mensagens de erro - renderizando app/views/articles/edit.html.erb.

7.4.1 Usando Partials para Compartilhar Código de Visualização

Nosso formulário edit será igual ao nosso formulário new. Até mesmo 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, com base em se o objeto do modelo foi salvo anteriormente.

Como o código será o mesmo, vamos extrair isso para uma visualização compartilhada chamada partial. Vamos criar 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 é o mesmo que o nosso formulário em app/views/articles/new.html.erb, exceto que todas as ocorrências de @article foram substituídas por article. Como os parciais são códigos compartilhados, é uma boa prática que eles não dependam de variáveis de instância específicas definidas por uma ação do controlador. Em vez disso, passaremos o artigo para o parcial como uma variável local.

Vamos atualizar app/views/articles/new.html.erb para usar o parcial através do render:

<h1>Novo Artigo</h1>

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

NOTA: O nome do arquivo de um parcial deve ser prefixado com um sublinhado, por exemplo, _form.html.erb. Mas ao renderizar, ele é referenciado sem o sublinhado, por exemplo, render "form".

E agora, vamos criar um app/views/articles/edit.html.erb muito semelhante:

<h1>Editar Artigo</h1>

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

DICA: Para aprender mais sobre parciais, consulte Layouts e Renderização no Rails § Usando Parciais.

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 adicionar um link para a página de edição no final de app/views/articles/show.html.erb:

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

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

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

7.5 Excluindo um Artigo

Finalmente, chegamos ao "D" (Delete) do CRUD. Excluir um recurso é um processo mais simples do que criar ou atualizar. Ele só requer uma rota e uma ação do controlador. E nosso roteamento de recursos (resources :articles) já fornece a rota, que mapeia as solicitações DELETE /articles/:id para a ação destroy do ArticlesController.

Então, vamos adicionar uma ação destroy típica ao app/controllers/articles_controller.rb, abaixo da ação 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, status: :see_other
  end

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

A ação destroy busca o artigo no banco de dados e chama destroy nele. Em seguida, redireciona o navegador para a rota raiz com o código de status 303 See Other.

Escolhemos redirecionar para a rota raiz porque esse é 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 no final de app/views/articles/show.html.erb para que possamos excluir um artigo de sua própria página:

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

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

<ul>
  <li><%= link_to "Editar", edit_article_path(@article) %></li>
  <li><%= link_to "Excluir", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "Tem certeza?"
                  } %></li>
</ul>

No código acima, usamos a opção data para definir os atributos HTML data-turbo-method e data-turbo-confirm do link "Excluir". Ambos os atributos se conectam ao Turbo, que é incluído por padrão em novas aplicações Rails. data-turbo-method="delete" fará com que o link faça uma solicitação DELETE em vez de uma solicitação GET. data-turbo-confirm="Tem certeza?" fará com que um diálogo de confirmação apareça quando o link for clicado. Se o usuário cancelar o diálogo, a solicitação será cancelada.

E é isso! Agora podemos listar, mostrar, criar, atualizar e excluir artigos! InCRUDível!

8 Adicionando um Segundo Modelo

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

8.1 Gerando um Modelo

Vamos ver o mesmo gerador que usamos antes ao criar o modelo Article. Desta vez, vamos criar um modelo Comment para armazenar uma referência a um artigo. Execute este comando no seu terminal:

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

Este comando irá gerar quatro arquivos:

Arquivo Propósito
db/migrate/20140120201010_create_comments.rb Migração para criar a tabela de comentários no seu banco de dados (seu nome incluirá um timestamp diferente)
app/models/comment.rb O modelo Comment
test/models/comment_test.rb Conjunto de testes para o modelo Comment
test/fixtures/comments.yml Comentários de exemplo para uso nos testes

Primeiro, dê uma olhada em app/models/comment.rb:

class Comment < ApplicationRecord
  belongs_to :article
end

Isso é muito semelhante ao modelo Article que você viu anteriormente. A diferença é a linha belongs_to :article, que configura uma associação do Active Record. Você aprenderá um pouco sobre associações na próxima seção deste guia. A palavra-chave (:references) usada no comando shell é um tipo de dado especial para modelos. Ele cria uma nova coluna na tabela do banco de dados com o nome do modelo fornecido seguido de um _id que pode armazenar valores inteiros. Para entender melhor, analise o arquivo db/schema.rb após executar a migração.

Além do modelo, o Rails também criou uma migração 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 inteira chamada article_id, um índice para ela e uma restrição de chave estrangeira que aponta para a coluna id da tabela articles. Vá em frente e execute a migração:

$ bin/rails db:migrate

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

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

8.2 Associando Modelos

As associações do Active Record permitem declarar facilmente o relacionamento entre dois modelos. No caso de comentários e artigos, você poderia escrever as relações desta forma:

  • Cada comentário pertence a um artigo.
  • Um artigo pode ter muitos comentários.

Na verdade, isso é muito próximo da sintaxe que o Rails usa para declarar essa associação. Você já viu a linha de código dentro do modelo 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ê precisará editar 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

Essas duas declarações permitem um bom comportamento automático. Por exemplo, se você tiver uma variável de instância @article contendo um artigo, você pode recuperar todos os comentários pertencentes a esse artigo como um array usando @article.comments.

DICA: Para obter mais informações sobre as associações do Active Record, consulte o Guia de Associações do Active Record.

8.3 Adicionando uma Rota para Comentários

Assim como o controlador articles, precisaremos adicionar uma rota para que o Rails saiba para onde queremos navegar para ver os comments. Abra o arquivo config/routes.rb novamente e edite-o da seguinte forma:

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

  resources :articles do
    resources :comments
  end
end

Isso cria comments como um recurso aninhado dentro de articles. Isso é outra parte da captura do relacionamento hierárquico que existe entre artigos e comentários.

DICA: Para obter mais informações sobre roteamento, consulte o Guia de Roteamento do Rails.

8.4 Gerando um Controlador

Com o modelo em mãos, você pode voltar sua atenção para a criação de um controlador correspondente. Novamente, usaremos o mesmo gerador que usamos antes:

$ bin/rails generate controller Comments

Isso cria três arquivos e um diretório vazio:

Arquivo/Diretório Propósito
app/controllers/comments_controller.rb O controlador Comments
app/views/comments/ As visualizações do controlador são armazenadas aqui
test/controllers/comments_controller_test.rb O teste para o controlador
app/helpers/comments_helper.rb Um arquivo de helper de visualização

Assim como em qualquer blog, nossos leitores criarão seus comentários diretamente após ler o artigo e, depois de adicionar seu comentário, serão enviados de volta para a página de exibição do artigo para ver seu comentário listado. Por causa disso, nosso CommentsController está lá para fornecer um método para criar comentários e excluir comentários de spam quando eles chegam.

Então, primeiro, vamos conectar o template de exibição do Artigo (app/views/articles/show.html.erb) para nos permitir fazer um novo comentário:

<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), data: {
                    turbo_method: :delete,
                    turbo_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 um formulário na página de exibição do Article que cria um novo comentário chamando a ação create do CommentsController. A chamada form_with aqui usa um array, que criará uma rota aninhada, como /articles/1/comments. Vamos conectar o create no 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 controlador para artigos. Isso é um efeito colateral do aninhamento que você configurou. Cada solicitação de um comentário precisa acompanhar o artigo ao qual o comentário está anexado, portanto, a chamada inicial ao método find do modelo Article para obter o artigo em questão.

Além disso, o código aproveita alguns dos métodos disponíveis para uma associação. Usamos o método create em @article.comments para criar e salvar o comentário. Isso vinculará automaticamente o comentário para que ele pertença a esse artigo específico.

Depois de criar o novo comentário, enviamos o usuário de volta ao artigo original usando o auxiliar article_path(@article). Como já vimos, isso chama a ação show do ArticlesController, que por sua vez renderiza o modelo show.html.erb. É aqui que queremos que o comentário seja exibido, então vamos adicioná-lo ao app/views/articles/show.html.erb.

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

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

<ul>
  <li><%= link_to "Editar", edit_article_path(@article) %></li>
  <li><%= link_to "Excluir", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "Tem certeza?"
                  } %></li>
</ul>

<h2>Comentários</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Comentarista:</strong>
    <%= comment.commenter %>
  </p>

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

<h2>Adicionar um comentário:</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 você pode adicionar artigos e comentários ao seu blog e vê-los aparecer nos lugares certos.

Artigo com Comentários

9 Refatoração

Agora que temos artigos e comentários funcionando, dê uma olhada no modelo app/views/articles/show.html.erb. Ele está ficando longo e desajeitado. Podemos usar parciais para deixá-lo mais limpo.

9.1 Renderizando Coleções de Parciais

Primeiro, vamos criar um parcial para os comentários e extrair a exibição de todos os comentários do artigo. Crie o arquivo app/views/comments/_comment.html.erb e coloque o seguinte nele:

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

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

Em seguida, você pode alterar o arquivo app/views/articles/show.html.erb para ficar assim:

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

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

<ul>
  <li><%= link_to "Editar", edit_article_path(@article) %></li>
  <li><%= link_to "Excluir", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "Tem certeza?"
                  } %></li>
</ul>

<h2>Comentários</h2>
<%= render @article.comments %>

<h2>Adicionar um comentário:</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 o parcial será renderizado em app/views/comments/_comment.html.erb uma vez para cada comentário na coleção @article.comments. Conforme o método render itera sobre a coleção @article.comments, ele atribui cada comentário a uma variável local com o mesmo nome do parcial, neste caso comment, que fica disponível no parcial para exibição.

9.2 Renderizando um Formulário Parcial

Vamos também mover a seção de novo comentário para seu próprio parcial. Novamente, você cria um 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 %>

Em seguida, você faz o arquivo app/views/articles/show.html.erb ficar assim:

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

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

<ul>
  <li><%= link_to "Editar", edit_article_path(@article) %></li>
  <li><%= link_to "Excluir", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "Tem certeza?"
                  } %></li>
</ul>

<h2>Comentários</h2>
<%= render @article.comments %>

<h2>Adicionar um comentário:</h2>
<%= render 'comments/form' %>

A segunda renderização apenas define o modelo parcial que queremos renderizar, comments/form. O Rails é inteligente o suficiente para identificar a barra inclinada na string e perceber que você deseja renderizar o arquivo _form.html.erb no diretório app/views/comments.

O objeto @article está disponível para qualquer parcial renderizado na visualização porque o definimos como uma variável de instância.

9.3 Usando Concerns

Concerns são uma maneira de tornar controladores ou modelos grandes mais fáceis de entender e gerenciar. Isso também tem a vantagem de reutilização quando vários modelos (ou controladores) compartilham as mesmas preocupações. Os concerns são implementados usando módulos que contêm métodos que representam uma parte bem definida da funcionalidade pela qual um modelo ou controlador é responsável. Em outras linguagens, os módulos são frequentemente conhecidos como mixins. Você pode usar concerns no seu controller ou model da mesma forma que usaria qualquer módulo. Quando você criou seu aplicativo com rails new blog, duas pastas foram criadas dentro de app/, juntamente com o restante:

app/controllers/concerns
app/models/concerns

No exemplo abaixo, implementaremos um novo recurso para nosso blog que se beneficiaria do uso de um concern. Em seguida, criaremos um concern e refatoraremos o código para usá-lo, tornando o código mais DRY e fácil de manter.

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

Primeiro, execute as seguintes migrações para adicionar status a Articles e Comments:

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

Em seguida, atualize o banco de dados com as migrações geradas:

$ bin/rails db:migrate

Para escolher o status para os artigos e comentários existentes, você pode adicionar um valor padrão aos arquivos de migração gerados, adicionando a opção default: "public" e executar as migrações novamente. Você também pode chamar no console do rails Article.update_all(status: "public") e Comment.update_all(status: "public").

DICA: Para saber mais sobre migrações, consulte Active Record Migrations.

Também precisamos permitir a chave :status como parte do strong parameter, 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 modelo article, após executar uma migração 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 modelo Comment:

class Comment < ApplicationRecord
  belongs_to :article

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

  validates :status, inclusion: { in: VALID_STATUSES }

  def archived?
    status == 'archived'
  end
end

Em seguida, em nosso template de ação index (app/views/articles/index.html.erb), usaríamos o método archived? para evitar exibir qualquer artigo que esteja 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 visualização parcial de comentários (app/views/comments/_comment.html.erb), usaríamos o método archived? para evitar exibir qualquer comentário que esteja 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 modelos agora, verá que a lógica está duplicada. Se no futuro aumentarmos a funcionalidade do nosso blog - para incluir mensagens privadas, por exemplo - podemos nos encontrar duplicando a lógica novamente. É aqui que os concerns são úteis.

Um concern é responsável apenas por um subconjunto focado da responsabilidade do modelo; os métodos em nosso concern estarão todos relacionados à visibilidade de um modelo. Vamos chamar nosso novo concern (módulo) de Visible. Podemos criar um novo arquivo dentro de app/models/concerns chamado visible.rb e armazenar todos os métodos de status que foram duplicados nos modelos.

app/models/concerns/visible.rb

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

Podemos adicionar nossa validação de status ao 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í-las:

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 modelo 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

Métodos de classe também podem ser adicionados a preocupações. Se quisermos exibir a contagem de artigos públicos ou comentários em nossa página principal, podemos adicionar um método de classe a Visible da seguinte forma:

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 visualização, você pode chamá-lo como qualquer método de classe:

<h1>Artigos</h1>

Nosso blog tem <%= Article.public_count %> artigos e contando!

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

<%= link_to "Novo Artigo", 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 Excluindo Comentários

Outra funcionalidade importante de um blog é a capacidade de excluir comentários de spam. Para fazer isso, precisamos implementar um link de algum tipo na visualização e uma ação destroy no CommentsController.

Então, primeiro, vamos adicionar o link de exclusão na parcial app/views/comments/_comment.html.erb:

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

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

  <p>
    <%= link_to "Excluir Comentário", [comment.article, comment], data: {
                  turbo_method: :delete,
                  turbo_confirm: "Tem certeza?"
                } %>
  </p>
<% end %>

Clicar neste novo link "Excluir Comentário" enviará uma solicitação DELETE /articles/:article_id/comments/:id para o nosso CommentsController, que pode então usar isso para encontrar o comentário que queremos excluir, então vamos adicionar uma ação destroy ao nosso controlador (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), status: :see_other
  end

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

A ação destroy encontrará o artigo que estamos visualizando, localizará o comentário dentro da coleção @article.comments e, em seguida, o removerá do banco de dados e nos enviará de volta para a ação de exibição do artigo.

10.1 Excluindo Objetos Associados

Se você excluir um artigo, seus comentários associados também precisarão ser excluídos, caso contrário, eles 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 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 você publicar seu blog online, qualquer pessoa poderá adicionar, editar e excluir artigos ou excluir comentários.

O Rails fornece um sistema de autenticação HTTP que funcionará bem nessa situação.

No ArticlesController, precisamos ter uma maneira de bloquear o acesso às várias ações se a pessoa não estiver autenticada. Aqui podemos usar o método http_basic_authenticate_with do Rails, que permite o acesso à ação solicitada se esse método permitir.

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

class ArticlesController < ApplicationController

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

  def index
    @articles = Article.all
  end

  # trecho para brevidade

Também queremos permitir apenas usuários autenticados a excluir comentários, então no CommentsController (app/controllers/comments_controller.rb) escrevemos:

class CommentsController < ApplicationController

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

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

  # trecho para brevidade

Agora, se você tentar criar um novo artigo, será recebido com um desafio básico de autenticação HTTP:

Desafio Básico de Autenticação HTTP

Após inserir o nome de usuário e senha corretos, você permanecerá autenticado até que um nome de usuário e senha diferentes sejam solicitados ou o navegador seja fechado. Outros métodos de autenticação estão disponíveis para aplicações Rails. Dois complementos populares de autenticação para Rails são o motor do Devise e a gema Authlogic, juntamente com outros.

11.2 Outras Considerações de Segurança

A segurança, especialmente em aplicações web, é uma área ampla e detalhada. A segurança em sua aplicação Rails é abordada com mais profundidade no Guia de Segurança do Ruby on Rails.

12 O que vem a seguir?

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

Lembre-se de que você não precisa fazer tudo sem ajuda. Se precisar de assistência para começar e executar o Rails, consulte os recursos de suporte a seguir:

13 Configurações problemáticas

A maneira mais fácil de trabalhar com o Rails é armazenar todos os dados externos como UTF-8. Se você não fizer isso, as bibliotecas Ruby e o Rails geralmente conseguirão converter seus dados nativos em UTF-8, mas isso nem sempre funciona de forma confiável, então é melhor garantir que todos os dados externos sejam UTF-8.

Se você cometeu um erro nessa área, o sintoma mais comum é um diamante preto com um ponto de interrogação dentro aparecendo no navegador. Outro sintoma comum é a aparência de caracteres como "ü" em vez de "ü". O Rails toma várias medidas internas para mitigar as causas comuns desses problemas que podem ser detectadas e corrigidas automaticamente. No entanto, se você tiver dados externos que não estão armazenados como UTF-8, isso pode resultar ocasionalmente nesses tipos de problemas que não podem ser detectados e corrigidos automaticamente pelo Rails.

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

  • Seu editor de texto: A maioria dos editores de texto (como o TextMate) é configurada para salvar arquivos como UTF-8. Se o seu editor de texto não estiver, isso pode resultar em caracteres especiais que você insere em seus modelos (como é) aparecerem como um diamante com um ponto de interrogação dentro no navegador. Isso também se aplica aos seus arquivos de tradução i18n. A maioria dos editores que não têm UTF-8 como padrão (como algumas versões do Dreamweaver) oferece uma maneira de alterar o padrão para UTF-8. Faça isso.
  • Seu banco de dados: O Rails converte dados do seu banco de dados em UTF-8 por padrão na fronteira. No entanto, se o seu banco de dados não estiver usando UTF-8 internamente, ele pode não ser capaz de armazenar todos os caracteres que seus usuários inserem. Por exemplo, se o seu banco de dados estiver usando Latin-1 internamente e o 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, use UTF-8 como armazenamento interno do seu banco de dados.

Feedback

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

Por favor, contribua se encontrar algum erro de digitação ou factual. Para começar, você pode ler nossa contribuição à documentação seção.

Você também pode encontrar conteúdo incompleto ou desatualizado. Por favor, adicione qualquer documentação ausente para o principal. Certifique-se de verificar Guias Edge primeiro para verificar se os problemas já foram corrigidos ou não no branch principal. Verifique as Diretrizes dos Guias do Ruby on Rails para estilo e convenções.

Se por algum motivo você encontrar algo para corrigir, mas não puder corrigi-lo você mesmo, por favor abra uma issue.

E por último, mas não menos importante, qualquer tipo de discussão sobre a documentação do Ruby on Rails é muito bem-vinda no Fórum oficial do Ruby on Rails.