Aprender a lidar com operações básicas de manipulação das informações armazenadas em um banco de dados é um dos primeiros passos para a construção de aplicações mais complexas. O Rails oferece muitos recursos interessantes para o desenvolvimento de aplicações web, como as migrações e os geradores de estruturas. Durante esse artigo o leitor notará o quanto esse framework facilita as tarefas da fase de implementação, tornando o desenvolvimento menos cansativo. Vamos construir com as ferramentas do Rails uma aplicação que gerencie tarefas pessoais. Cada tarefa terá um nome, descrição data de início, data de previsão da conclusão e um status.

Criando o projeto

Para iniciar o desenvolvimento da aplicação esteja certo que tenha as ferramentas necessárias instaladas no seu computador. Os exemplos deste artigo utilizam o Ruby na sua versão 2.1.5 e o framework Rails na versão 4.2.4. Para verificar se um computador dispõe destes basta digitar os comandos apresentados na Listagem 1, no terminal ou prompt de comando. Ambos devem retornar as versões da linguagem Ruby e do framework Rails, respectivamente.

Listagem 1. Teste das versões do Ruby e Rails.

ruby --version
  rails --version

Caso algum erro informe que esses comandos não estão disponíveis será necessário seguir os passos para instalação dos mesmos. Esses passos diferem, dependendo do sistema operacional que esteja sendo usado. O site da linguagem ruby e do framework rails (vide seção Links) apresentam de modo detalhado como esses podem ser baixados e configurados para vários sistemas operacionais. Neste artigo partiremos do princípio que ambos estão instalados na sua máquina.

Depois dessa checagem já é possível criar a estrutura da aplicação. Isso pode ser feito digitando o comando a seguir:

 ~/RailsProjects$ rails new tarefas-app

Note que a sintaxe desse comando é rails new [nome_do_aplicativo], e deve ser executado dentro do diretório onde se deseja armazenar os fontes do projeto. No caso do exemplo, o diretório escolhido foi ~/RailsProjects.

A Listagem 2 mostra alguns trechos do resultado da criação do projeto: perceba que muitos diretórios e arquivos são criados.

Listagem 2. Trecho da saída do comando rails new.


create
…
create  Gemfile
create  app
create  app/controllers/
create  app/controllers/application_controller.rb
create  app/views/layouts/application.html.erb
...
create  app/models/
…
run  bundle install

Neste momento os diretórios que nos interessam são app/controllers, app/views e app/models, pois eles armazenam os códigos que vamos implementar controladores, páginas e modelos, respectivamente.

CRUD de Tarefas

Vamos começar o processo de desenvolvimento propriamente dito com as operações de CRUD (Criar, Recuperar, Atualizar e Deletar) para o modelo que representará as Tarefas. Para isso utilizaremos o recurso de geradores do rails, conforme o código a seguir:

 ~/RailsProjects/tarefas-app$ rails generate scaffold Tarefa nome:string descricao:string inicio:date previsao:date status:string

Veja que esse comando gera o scaffold, ou seja, toda a estrutura para o CRUD de tarefas com seus atributos nome, descrição, início, previsão e status, bem como os seus respectivos tipos e o diagrama de classes. Note que esse comando deve ser executado dentro do diretório do projeto criado anteriormente.

As linhas mais importantes da saída desse comando podem ser vistas na Listagem 3.

Listagem 3. Saída do comando rails generate.


invoke  active_record
create    app/models/tarefa.rb
create    db/migrate/20160109230654_create_tarefas.rb
…
route    resources :tarefas
invoke   scaffold_controller
create    app/controllers/tarefas_controller.rb
invoke    erb
create      app/views/tarefas
create      app/views/tarefas/index.html.erb
create      app/views/tarefas/edit.html.erb
create      app/views/tarefas/show.html.erb
create      app/views/tarefas/new.html.erb
create      app/views/tarefas/_form.html.erb

Perceba que o início dessa saída diz que um arquivo app/models/tarefa.rb foi criado: este é a classe modelo que representará uma tabela no banco de dados.

O código da Listagem 4 representa o conteúdo do arquivo, que contém apenas uma classe que estende ActiveRecord::Base.

Listagem 4. Conteúdo do arquivo app/models/tarefa.rb.

class Tarefa < ActiveRecord::Base
End

Ainda de acordo com a saída da Listagem 3 vemos a criação do arquivo 20160109230654_create_tarefas.rb no diretório db/migrate. Esse arquivo é responsável pela criação da tabela que armazenará as tarefas no banco de dados. O conteúdo dele deve ser parecido com o exibido na Listagem 5.

Listagem 5. Conteúdo do arquivo [...]_create_tarefas.rb.


  class CreateTarefas < ActiveRecord::Migration
    
    def change
   
      create_table :tarefas do |t|
        t.string :nome
        t.string :descricao
        t.date :inicio
        t.date :previsao
        t.string :status
   
        t.timestamps null: false
   
      end
   
    end
   
  end

O arquivo define uma classe chamada CreateTarefas, que estende as funcionalidades de ActiveRecord::Migration e sobrescreve o método change. Esse método usa a instrução create_table para criar a tabela tarefas passando o nome de cada coluna com os seus tipos. Há também uma linha t.timestamps, que na verdade cria duas colunas: uma para armazenar a data de criação da tupla ou registro, e outro para guardar a data da última alteração do objeto. Para efetuar a criação da tabela que armazenará as tarefas digite o comando a seguir:

~/RailsProjects/tarefas-app$ rake db:migrate

A Listagem 6 apresenta a saída referente a execução da migração CreateTarefas, que confirma a criação de uma tabela chamada tarefas.

Listagem 6. Saída do comando rake db:migrate.


  == 20160109230654 CreateTarefas: migrating========
  -- create_table(:tarefas) -> 0.0019s
  == 20160109230654 CreateTarefas: migrated (0.0021s)===

Outro arquivo criado de acordo com a saída da Listagem 3 é o app/controllers/tarefas_controller.rb, que funciona como controlador. Ele recebe as requisições vindas das páginas da aplicação e define (ou controla) que ação do modelo será executada. Alguns dos métodos definidos nesta classe estarão associados, cada um a uma página da aplicação, como é o caso dos métodos index, show e new.

A função do método index é simplesmente buscar na base de dados uma lista de tarefas. Ele faz isso usando o método all, disponível para objetos que estendem ActiveRecord::Base, como é o caso da classe Tarefa. O resultado da chamada desse método é armazenado em uma variável de instância (as variáveis de instância iniciam com “@”), que fica disponível na camada de visão, ou seja, nas páginas do sistema. Veja na Listagem 7 a definição desse método.

Listagem 7. Código do método index.


  def index
      @tarefas = Tarefa.all
  End

O método index é automaticamente chamado quando a página index.html.erb, localizada na pasta app/views/tarefas, é acessada. Essa página apenas faz uso da variável @tarefas definida no método index do controlador para criar uma lista ou tabela de tarefas, conforme mostra a Listagem 8. Esse código é gerado pelo framework para essa página. Perceba que a página contém tags <%...%>, que contém código ruby usado para construir a lista de modo dinâmico. Um dos usos dessa tag é para chamar o método each na variável @tarefas, a fim de percorrer a lista de tarefas e apresentá-las na tabela. Perceba também que a página usa os helpers link_to para construir hiperlinks para as páginas de cadastro (new), exibição (show) e edição (edit).

Listagem 8. Código da página index.html.erb.


  <p id="notice"><%= notice %></p>
   
  <h1>Listing Tarefas</h1>
   
  <table>
    <thead>
      <tr>
        <th>Nome</th>
        <th>Descricao</th>
        <th>Inicio</th>
        <th>Previsao</th>
        <th>Status</th>
        <th colspan="3"></th>
      </tr>
    </thead>
   
    <tbody>
      <% @tarefas.each do |tarefa| %>
        <tr>
          <td><%= tarefa.nome %></td>
          <td><%= tarefa.descricao %></td>
          <td><%= tarefa.inicio %></td>
          <td><%= tarefa.previsao %></td>
          <td><%= tarefa.status %></td>
          <td><%= link_to 'Show', tarefa %></td>
          <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td>
       <td><%= link_to 'Destroy', tarefa, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
   
  <br>
   
  <%= link_to 'New Tarefa', new_tarefa_path %>

O método show tem como objetivo recuperar uma tarefa procurando-a na base de dados pelo seu identificador. Note que o código gerado pelo Rails para este método está vazio porque ele usa a estrutura before_action para recuperar o objeto tarefa “antes que” os métodos show, edit, update, e destroy sejam executados. Veja isso na Listagem 9.

Listagem 9. Métodos set_tarefa e show.

before_action :set_tarefa, only: [:show, :edit, :update, :destroy]
   
  def set_tarefa
     @tarefa = Tarefa.find(params[:id])
  end
   
  def show
  end

A função show também é executada quando a página app/views/tarefas/show.html.erb é carregada. Essa página usa o objeto @tarefa do controlador para exibir os valores para os atributos do objeto. Veja o código dessa página na Listagem 10.

Listagem 10. Código para a página show.html.erb.


  <p id="notice"><%= notice %></p>
   
  <p>
    <strong>Nome:</strong>
    <%= @tarefa.nome %>
  </p>
   
  <p>
    <strong>Descricao:</strong>
    <%= @tarefa.descricao %>
  </p>
   
  <p>
    <strong>Inicio:</strong>
    <%= @tarefa.inicio %>
  </p>
   
  <p>
    <strong>Previsao:</strong>
    <%= @tarefa.previsao %>
  </p>
   
  <p>
    <strong>Status:</strong>
    <%= @tarefa.status %>
  </p>
   
  <%= link_to 'Edit', edit_tarefa_path(@tarefa) %> |
  <%= link_to 'Back', tarefas_path %>

O método new apenas instancia um novo objeto para que este seja posteriormente persistido no banco de dados, como mostra a Listagem 11.

Listagem 11. Código do método new.


  def new
    @tarefa = Tarefa.new
  End

A página associada ao método new é a new.html.erb, que também foi gerada na pasta app/views/tarefas. O código para esse arquivo pode ser visto na Listagem 12. Note que o formulário está associado ao objeto @tarefa, instanciado no método new, e cada campo contido na página é associado com um atributo do modelo, como: nome, descrição, inicio, previsão e status. Perceba também que há, logo no início do arquivo, uma sessão para apresentar erros de validação.

Listagem 12. Código para a página new.html.erb.


  <%= form_for(@tarefa) do |f| %>
    <% if @tarefa.errors.any? %>
      <div id="error_explanation">
        <h2><%= pluralize(@tarefa.errors.count, "error") %> prohibited this tarefa from being saved:</h2>
   
        <ul>
        <% @tarefa.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
        </ul>
      </div>
    <% end %>
   
    <div class="field">
      <%= f.label :nome %><br>
      <%= f.text_field :nome %>
    </div>
    <div class="field">
      <%= f.label :descricao %><br>
      <%= f.text_field :descricao %>
    </div>
    <div class="field">
      <%= f.label :inicio %><br>
      <%= f.date_select :inicio %>
    </div>
    <div class="field">
      <%= f.label :previsao %><br>
      <%= f.date_select :previsao %>
    </div>
    <div class="field">
      <%= f.label :status %><br>
      <%= f.text_field :status %>
    </div>
    <div class="actions">
      <%= f.submit %>
    </div>
  <% end %>

Já o método destroy não tem uma página associada e a sua função é apenas procurar a tarefa com o identificador passado e excluí-la do banco de dados. Esse método é mostrado na Listagem 13. Lembrando que o método set_tarefa é chamado antes da execução do destroy.

Listagem 13. Código do método destroy.


  before_action :set_tarefa, only: [:show, :edit, :update, :destroy]
   
  def set_tarefa
    @tarefa = Tarefa.find(params[:id])
  end
   
   
  def destroy
    @tarefa.destroy
  End

Os métodos create e update também não possuem páginas associadas: eles apenas recebem os parâmetros vindos da requisição e persistem ou atualizam os objetos no banco de dados. O código desses métodos deve ser semelhante ao apresentado na Listagem 14.

Listagem 14. Código para os métodos create e update.


  def tarefa_params
     params.require(:tarefa).permit(:nome, :descricao, :inicio, :previsao, :status)
  end
   
  def create
      @tarefa = Tarefa.new(tarefa_params)
        if @tarefa.save
          redirect_to 'show', notice: 'Tarefa was successfully created.' }
        else
          render :new, notice: 'Error.'
        end
  end
   
    def update
    
        if @tarefa.update(tarefa_params)
          redirect_to 'show', notice: 'Tarefa was successfully updated.'
        else
          render :edit, notice 'Error.'
        end
    end

Caso você deseje ver o que foi feito até agora em funcionamento, vá até o terminal ou prompt de comando e, dentro do diretório do seu projeto, digite o seguinte código:

 ~/RailsProjects/tarefas-app$ rails server

A saída será a mesma apresentada na Listagem 15.

Listagem 15. Código para iniciar o servidor da aplicação.

=> Booting WEBrick
  => Rails 4.2.4 application starting in development on http://localhost:3000
  => Run `rails server -h` for more startup options
  => Ctrl-C to shutdown server
  [2016-01-13 11:56:48] INFO  WEBrick 1.3.1
  [2016-01-13 11:56:48] INFO  ruby 2.1.5 (2014-11-13) [x86_64-linux-gnu]
  [2016-01-13 11:56:48] INFO  WEBrick::HTTPServer#start: pid=4039 port=3000

Como pode ser visto na saída, esse comando inicia o a aplicação que fica disponível por meio da URL localhost:3000. Após iniciar o servidor acesse a o endereço localhost:3000/tarefas. Vejas nas Figuras 1 a 3 algumas das páginas que o framework criou para a aplicação.

Página de
listagem de tarefas

Figura 1. Página de listagem de tarefas.

Página de
criação de nova tarefa

Figura 2. Página de criação de nova tarefa.

Página para
exibição dos detalhes de uma tarefa

Figura 3. Página para exibição dos detalhes de uma tarefa.

Formatando os campos tipo Data

Como pode ser observado na Figura 1, o formato das datas de início e previsão de conclusão das tarefas ficou um pouco confuso. Isso porque o Rails construiu a pasta de listagem deixando o formato das datas no padrão próprio (ano-mês-dia). É possível alterar esse formato por meio do uso do método strftime, que recebe como parâmetro uma string com padrão que a data deve ter. A Listagem 16 mostra as estruturas mais comuns que podem ser usadas para formatar um atributo do tipo data.

Listagem 16. Estruturas mais usadas para formatar datas.


  %H – recupera a hora no formato 24
  %l – recupera a hora no formato 12
  %Y – recupera o ano
  %m – recupera o mês
  %d – recupera o dia´

A partir dessas instruções já é possível formatar as datas de início e previsão para a classe Tarefa. Veja na Listagem 17 como ficaria a página de listagem de tarefas formatando as datas no estilo dia/mês/ano. Perceba no código que os atributos inicio e previsao agora fazem uma chamada ao método strftime. A Figura 4 mostra o resultado dessa modificação na listagem de tarefas.

Listagem 17. Código para a página de listagem de tarefas formatando datas.


  <p id="notice"><%= notice %></p>
   
  <h1>Listing Tarefas</h1>
   
  <table>
    <thead>
      <tr>
        <th>Nome</th>
        <th>Descricao</th>
        <th>Inicio</th>
        <th>Previsao</th>
        <th>Status</th>
        <th colspan="3"></th>
      </tr>
    </thead>
   
    <tbody>
      <% @tarefas.each do |tarefa| %>
        <tr>
          <td><%= tarefa.nome %></td>
          <td><%= tarefa.descricao %></td>
          <td><%= tarefa.inicio.strftime("%m/%d/%Y") %></td>
          <td><%= tarefa.previsao.strftime("%m/%d/%Y") %></td>
          <td><%= tarefa.status %></td>
          <td><%= link_to 'Show', tarefa %></td>
          <td><%= link_to 'Edit', edit_tarefa_path(tarefa) %></td>
          <td><%= link_to 'Destroy', tarefa, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
   
  <br>
   
  <%= link_to 'New Tarefa', new_tarefa_path %>

Página de
listagem com datas formatadas

Figura 4. Página de listagem com datas formatadas.

Validando atributos

Outro problema que a aplicação apresenta é que é possível persistir tarefas no banco de dados sem informar atributos essenciais, como o nome e a descrição. É possível usar os recursos de validação do Rails para evitar que isso aconteça. A seguir temos um breve resumo:

  • validates_presence_of – campos marcados com essa instrução são obrigatório;
  • validates_length_of – valida o tamanho mínimo e máximo de um atributo;
  • validates_uniqueness_of – valida que um atributo seja único, como por exemplo o CPF;
  • validates_numericality_of – obriga que o campo contenha apenas números.

É possível ainda agrupar várias validações para um mesmo atributo, bem como personalizar mensagens de erro. Veja a seguir um exemplo de agrupamento de validações para um atributo cpf:

validates :cpf, length: {in: 11, message: 'O campo cpf deve conter 11 dígitos'}, uniqueness: {message: 'Esse cpf já está cadastrado'}

Veja agora na Listagem 18 como ficaria a classe Tarefa utilizando esses recursos para validar os seus atributos nome, descrição e status.

Listagem 18. Classe Tarefa validando atributos.

class Tarefa < ActiveRecord::Base
    validates_presence_of :nome, message: 'deve ser preenchido'
    validates_length_of :nome, maximum: 100, message: 'deve ter até 100 caracteres'
    validates_presence_of :descricao, message: 'deve ser preenchido'
    validates_length_of :descricao, maximum: 255, message: 'deve ter até 255 caracteres'
    validates_presence_of :status, message: 'deve ser preenchido'
  end

Essas instruções de validação podem ser escritas também de modo agrupado, tendo o mesmo efeito e com um código mais enxuto. Veja na Listagem 19 como ficaria a classe Tarefa agrupando as validações.

Listagem 19. Classe Tarefa com validações agrupadas.

class Tarefa < ActiveRecord::Base
    validates :nome, presence: {message: 'deve ser preenchido'},
                     length: {maximum: 100, message: 'dever ter até 100 caracteres'}
    validates :descricao, presence: {message: 'deve ser preenchido'},
                          length: {maximum: 255, message: 'dever ter até 100 caracteres'}
    validates :status, presence: {message: 'deve ser preenchido'}
  end

Após essas modificações, quando o usuário tentar adicionar uma nova tarefa sem obedecer aos critérios de validação receberá algumas mensagens de erro, como pode ser visto na Figura 5.

Erros de
validação na criação de uma tarefa

Figura 5. Erros de validação na criação de uma tarefa.

Configurando página inicial

Outro ponto que merece destaque é que assim que a aplicação é iniciada com o comando rails server e a acessamos pelo endereço localhost:3000, uma página padrão do framework é apresentada, com algumas instruções dos primeiros passos para construir uma aplicação. Vamos adicionar uma configuração para que a página inicial seja a listagem de tarefas. Essa modificação deve ser feita no arquivo routes.rb, localizado na pasta config, apenas por adicionar a instrução root.

Veja na Listagem 20 como ficaria o código desse arquivo após configurar a lista de tarefas como página inicial. Perceba que o nome do controlador e do método a serem executados foi passado para a instrução root e eles estão separados pelo sinal de “#”.

Listagem 20. Configurando página inicial.

Rails.application.routes.draw do
    root 'tarefas#index'
    resources :tarefas
  end

Percebe-se que utilizando os recursos do Rails é possível criar uma aplicação bastante funcional em pouco tempo: isso acontece pelo fato do framework oferecer recursos que automatizam tarefas repetitivas, como criação de páginas HTML e operações de consultas a banco de dados. Também fica claro como pode ser facilmente personalizado alguns detalhes da visão, como foi o caso para formatar os atributos do tipo data. Pode-se facilmente utilizar algumas bibliotecas CSS’s conhecidas (bootstrap, foundation entre outras) para melhorar a aparência da aplicação.

Espero que tenho gostado!

Referências

Rails Guides
http://guides.rubyonrails.org/form_helpers.html
http://guides.rubyonrails.org/action_controller_overview.html
http://guides.rubyonrails.org/active_record_validations.html

API Ruby On Rails
http://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html