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:
- Site Oficial da Linguagem de Programação Ruby (Em inglês)
- Lista de Livros Grátis de Programação (Em inglês)
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
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 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. |
.gitattributes | Este arquivo define metadados para locais específicos do repositório git. Estes metadados podem ser usados pelo e git e outras ferramentas para melhorar seu comportamento. Veja a documentação de gitattributes para mais informações. |
.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:
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 inicialização do 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
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 a página de inicialização do 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
noGemfile
.
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:
- O navegador faz uma requisição (request):
GET http://localhost:3000
. - Nossa aplicação Rails recebe essa requisição.
- O roteador do Rails mapeia a rota raiz para a action
index
deArticlesController
. - A action
index
utiliza o modelArticle
para buscar todos os artigos no banco de dados. - O Rails renderiza automaticamente a view
app/views/articles/index.html.erb
. - O código ERB na view é avaliado para gerar código HTML.
- 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 422 Unprocessable Entity.
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.
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
.
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, status: :see_other
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) com o código de status 303 See Other.
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), data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %></li>
</ul>
No código acima, nós usamos a opção data
para definir os atributos HTML
data-turbo-method
e data-turbo-confirm
no link "Destroy". Ambos estes
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="Are you sure?"
fará 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 solicitação será
abortada.
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 três 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 |
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), 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 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), data: {
turbo_method: :delete,
turbo_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.
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), data: {
turbo_method: :delete,
turbo_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), data: {
turbo_method: :delete,
turbo_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
No exemplo abaixo, implementaremos um novo recurso para nosso blog que se beneficiaria do uso de uma concern. Então, vamos criar uma concern e refatorar o código para usá-lo, tornando o código mais DRY e sustentável.
Um 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
:
<% unless comment.archived? %>
<p>
<strong>Commenter:</strong>
<%= comment.commenter %>
</p>
<p>
<strong>Comment:</strong>
<%= comment.body %>
</p>
<p>
<%= link_to "Destruir comentário", [comment.article, comment], data: {
turbo_method: :delete,
turbo_confirm: "Are you sure?"
} %>
</p>
<% end %>
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), status: :see_other
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:
Depois de inserir o nome de usuário e a senha corretos, você permanecerá autenticado até que um nome de usuário e senha diferentes sejam necessários ou o navegador seja fechado.
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 forum oficial do Ruby on Rails e nas issues do Guia em português.