O acrônimo CRUD (Create, Read, Update e Delete) é algo que todo iniciante em linguagens de programação ou frameworks necessita saber, pois significa nada menos que as operações básicas para a criação/manutenção de cadastros.
O nosso primeiro passo é a parte de criação (C do CRUD). Para realizar todos os passos vamos desenvolver usando as metodologias de BDD/TDD, que irá nos possibilitar um código estável e mais fácil de manter.
Criando um cadastro
Nessa primeira história vamos criar um resource (recurso) que representa um produto, é somente para fins didáticos, o nome é o que menos interessa. Para o produto vamos ter dois atributos que são necessários: nome e descrição. O nosso primeiro passo é criar a nossa feature de spec, em spec/features
(crie esse diretorios se não existirem), crie o diretório products e um arquivo com o nome de creating_products_spec.rb
.
Uma dica, sempre use a nomenclatura em inglês em sua aplicação Rails, garanto que você evitará muitas dores de cabeça, pois o Rails se torna muito mais amigável na língua do tio Obama.
No arquivo criado vamos especificar o que desejamos que o nosso cadastro faça. Abaixo está o código da especificação:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Execute o comando rspec spec/features/products/creating_products_spec.rb
. Esse comando executará a nossa feature de teste e que apresentará o seguinte erro:
1 2 3 4 5 6 |
|
O teste falhará pois não temos o link “Novo Produto” na rota “/”, que é a nosso root path(página inicial). Então antes de seguir, vamos criar o link na página inicial. Coloque o seguinte código dentro do arquivo _navigation_links.html.erb
, que está na pasta layouts de nossas views (app/views/layouts/_navigation_links.html.erb
):
1
|
|
Edite a página de layout para possuir esse link:
1
|
|
Rode os testes novamente e ocorrera um erro pois não definimos uma rota para os nossos produtos:
1 2 3 |
|
Adicione a rota no arquivo config/routes.rb
:
1
|
|
Rode os testes novamente e acontecerá um nova quebra na nossa feature. Dessa vez é devido que a nossa suite de testes até conseguiu encontrar a rota, mas não encontrou nenhum controller que condize-se com o resource procurado. Veja o erro:
1 2 3 |
|
Essa ficou fácil. Crie o controller, em app/controllers/products_controller.rb
. Nesse arquivo declare um classe ruby com o nome de ProductsController
extendendo de ApplicationController
:
1 2 3 |
|
Rode novamente o teste. Problema resolvido, mas outro surgiu. Agora o rspec não conseguiu encontrar uma action no controller, que refletisse com a operação de novo cadastro. Veja:
1 2 3 |
|
Ao controller criado instantes atrás, adicione a action new:
1 2 3 4 |
|
Se rodarmos novamente os testes, nossa ação é encontrada, porém o template erb não existe, ocasionando outra quebra:
1 2 3 |
|
Devemos criar o nosso template na camada de visão do projeto. Então na pasta app/views
, crie uma pasta chamada products e nela um arquivo com o nome new.html.erb
. Feito isso rode o teste mais uma vez. Agora temos um problema diferente de todos até agora. O Capybara na tentativa de preencher os campos do formulário (que ainda não existe) acaba gerando um novo erro:
1 2 3 |
|
Como podemos ver, o Capybara não conseguiu encontrar o elemento com o field “Nome”. Isso é lógico, nós não criamos ainda o formulário com os campos. Porém antes teremos que criar uma instância de Product em nossa controller e passar a nossa view para que ela possua os campos necessários:
1 2 3 |
|
A constante Product é o nosso model, que deve ser criado em app/models/product.rb. Mas antes de sair criando o arquivo, vamos acelerar as coisas usando um gerador de código do Rails.
1
|
|
Esse generator (gerador) nos poupa um tempo criando uma série de arquivos, como o arquivo de migração do banco de dados com os campos necessários, nosso model extendendo de ActiveRecord que contém um infinidade de funcionalidades para trabalhar com nossos dados e arquivos de testes unitários.
Um Model deve ser capaz de prover o acesso a camada de dados e é por isso que ele utiliza o Active Record. O Model também provê um local para a lógica de negócio, bem como, fazer validações e associações entre modelos.
Entre os arquivos gerados, está o arquivo de migration. Migration é um mecanismo eficiente de manter um controle de versão de nosso banco de dados, onde podemos evoluir e retrocer nosso schema quando necessário, sem trabalho adicional. Nosso arquivo de migração está é gerado dentro da pasta db/migrate e o nome leva em considereção o timestamp do momento de geração, para nunca ter o mesmo nome e o Rails conseguir controlar o historico de migrações. Nosso arquivo db/migrate/[data]_create_product.rb apresenta o seguite código Ruby:
1 2 3 4 5 6 7 8 9 10 |
|
Devemos rodar o comando abaixo para ter o nosso banco de dados atualizado:
1
|
|
O esquema de nomenclatura é interessante, o Rails irá ter o model com o nome product (singular), no entanto a tabela no banco de dados será products(plural).
Com nosso model product criado e o nosso banco com a tabela necessária, vamos voltar ao formulário. Execute o teste novamente:
1
|
|
E veremos que nada mudou desde a ultima execução, porém agora temos nosso banco de dados pronto, e um objeto do tipo Product que dará os atributos necessários para montar nosso formulário de cadastro. Abra o arquivo app/views/products/new.html.erb e adicione o código abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Formulário criado, devemos rodar nosso teste novamente. Fazendo isso veremos que nesse ponto, o que ocorre é que o formulário é preenchido, porém ao ser submitido, nenhuma ação para criar (create) é encontrado no nosso controller:
1 2 3 |
|
Então para resolver isso devemos criar o nosso método create dentro do ProductContoller:
1 2 3 4 5 6 7 8 9 10 11 |
|
Também defina um método privado para permitir que nossos parametros sejam lidos, sem serem bloqueados pelo mecanismo de Strong Parameters do Rails 4:
1 2 3 4 |
|
Como vamos usar flash messages do Rails, é necessário em nosso application.html.erb definir um local para
fazer a exibição das mensagens. Crie um diretório denominado shared
dentro da pasta views. Nessa nova pasta
crie o arquivo _messages.html.erb
. Adicione o seguinte conteúdo:
1 2 3 4 5 6 7 8 |
|
Com o partial criado, adicione a chamada de renderização no arquivo de layout, logo acima da
instrução yield
:
1 2 3 4 |
|
Rode seu teste e ele ira dizer que não encontrou a action show. Isso quer dizer que o nosso cadastro está sendo salvo, porém não temos uma ação para fazer a exibição de cadastro. Adicione a ação show ao seu ProductController:
1 2 3 |
|
Se rodarmos os testes vamos ter um erro por que o template correspondente a action, não pode ser encontrada, crie um o seguinte arquivo app/views/products/show.html.erb
, e adicione o código abaixo:
1 2 |
|
Rodandos os testes, nossa feature vai passar. Se quiser testar manualmente, inicie o servidor com o comando rails s
e realize um cadastro.
Adicionando validações aos nossos campos
Como o nosso cadastro criado e funcionando, devemos adicionar algumas validações
para os nossos campos. Ao campo nome
vamos verificar se ele foi informado,
se atende o tamanho minímo de 3 caracteres e o máximo de 50 e também, o nome deve
ser único. Já para o campo descrição
verificáremos se foi informado e se possue
pelo menos 15 carácteres de texto. Nessa etapa, vamos usar testes unitários e testes
de integração. Os testes unitários vão garantir que as validações estão sendo exigidas
e o teste integrado deverá validar a operação de cadastro completo.
Testando unitáriamente nosso model
Vamos iniciar testando no nosso model Product
se algum valor a propriedade name foi
informado, assim impediremos que o nosso campo tenha um valor em branco. Vamos usar
a validação de tamanho para impedir o não preenchimento do campo. Adicione o seguinte código de ao arquivo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
Rode o comando rspec spec/models/product_spec.rb
e o nosso teste deverá falhar,
pois estamos esperando que algo errado aconteça com o uso dos métodos should_not be_valid
,
mas como não definimos as validações no nosso model, nada de errado ocorre e o nosso teste
falha. Adicione o código abaixo e rode o comando em seguida, nosso testes deve passar:
1 2 3 |
|
A validação do nome está quase completa, para finalizar adicione ao teste á baixo do último bloco describe:
1 2 3 4 5 6 7 8 9 |
|
Usamos o método dup quando queremos duplicar um módelo. Aqui queremos que o nome que já exista, não seja duplicado, e é isso que o teste nos assegura. Ao model adicione:
1
|
|
E rode o teste novamente. Tudo verde, o campo nome está validado, agora vamos
validar o campo description
. Ao teste insira abaixo do ultimo bloco de describe
o seguinte código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
E ao nosso modelo insira as validações:
1
|
|
Mas que beleza! Nosso modelo está validando nossos dados e temos a garantia disso com os testes unitários, hora de testar integrado e ajustar nossas mensagens.
Testando de maneira integrada
Antes de continuar a implementação, vamos refatorar a nossa spec feature spec/features/products/creating_products_spec.rb
. Vamos adicionar um bloco before
,
e nele vamos colocar o que estará repetindo nos outros cenários, nossa feature deve ser
semelhante a isso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Rode a spec com o comando rspec spec/features/products/creating_products_spec.rb
, seu primeiro
cenário deverá falhar. Agora estamos validando nossos campos, e a nossa descrição não está
no tamanho correto, corrija, colocando uma descrição com pelo menos 15 caracteres, rode o teste
novamente e tudo estará correto.
Vamos agora focar nos cenários de teste negativo. Primeiro para valores do campo nome. Crie um novo cenário abaixo do último criado, como o seguinte código:
1 2 3 4 5 6 7 |
|
Se rodar o teste, irá falhar, pois essa mensagem não está sendo exibida. Vamos criar
um arquivo, na pasta shared de nossas views, com o nome de errors.html.erb
(app/views/shared/errors.html.erb). Será um partial
, que irá exibir todos os
erros de validação.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Também deve-se incluir esse arquivo no formulário de cadastro:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Podemos rodar nosso teste novamente, e teremos ainda algum erro, mas estamos quase.
Precisamos ajustar para que nossos atributos (name e description) sejam reconhecidos
como nome e descrição. Basta mudar no nosso arquivo de tradução em config/locales/pt-BR/pt-BR.yml
1 2 3 4 5 6 7 |
|
E mude a configuração padrão, para reconhecer a tradução bem como torna-la default para o nosso sistema:
1 2 3 4 5 6 7 8 9 |
|
E também baixe a tradução das mensagens. Salve no diretorio config/locales/pt-BR
com
o nome de rails.pt-BR.yml. Baixe desse repositório:
https://raw.githubusercontent.com/svenfuchs/rails-i18n/master/rails/locale/pt-BR.yml
Dessa maneira podemos traduzir tudo que o active record gera automáticamente, é uma maneira muito flexível de se traduzir o nosso sistema. Rode novamente os testes e dessa vez, nossa spec passou. Hora de refatorar. Agora que colocamos a tradução dos campos do active record, não precisamos mais a tradução que está fixa nos campos do formulário de cadastro. Remova a tradução:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Complete nossa spec com teste para descrição:
1 2 3 4 5 6 7 |
|
Poderíamos colocar mais testes, mas não é esse o intuíto do post (Fica de incentivo para você).
O código fonte pode ser baixado aqui: https://github.com/marceloboth/crud-rspec. Baixando o código
faça checkout para o branch criando_cadastro: git chechout criando_cadastro
.