sexta-feira, 18 de março de 2011

Ruby on Rails do zero, Teste de model com Rspec

  Continuando nosso blog(github.com/vagnerzampieri/zblog), irei de Rspec hoje para validar nossos models.
 
  Rspec:
 
  Vamos fazer então nossos primeiros teste, algo bem simples, vai ser teste de validação de modelo. Certifique que as gems abaixo estão no seu Gemfile:
 
  gem 'rspec'
  gem 'rspec-rails'
 
  Se não existirem, coloque e faça um "bundle install" no terminal. Ainda no terminal instale o rspec no seu projeto fazendo um:
 
  rails g rspec:install
 
  Ele criará isso:
 
  create  .rspec
  create  spec
  create  spec/spec_helper.rb
 
  Entre na pasta "spec" que ele criou, crie uma pasta chamada "models", dentro dela crie o arquivo "post_spec.rb", e adicione o conteúdo abaixo:
 
  require 'spec_helper'

  describe Post do
    it "test"
  end
 
  Primeiro eu dou um "require 'spec_helper'" para ele chamar o arquivo de mesmo nome que está na pasta "spec" para que eu possa utilizar o Rspec. Eu vou testar comportamento e não método, vou usar BDD, testar comportamento e verificar se a ação que o usuário efetuou está de acordo. Para você ver alguma saída rode no terminal:
 
  rake spec:models
 
  Vocé irá ver uma saída em amarelo, e um contador avisando que existe 1 exemplo, e ele está pendente. Isso é só um teste para ver se o Rspec está funcionando da forma certa. A primeira coisa que testaremos é se o Post pode ser criado sem título, sempre tenha em mente, "red", "green", "refactory".
 
  RED, vamos ver nossa ideia quebrar de propósito com esse exemplo abaixo:
 
  describe Post do
    it "should be not created without title" do
      post = Post.new(:title => "", :body => "Texto qualquer em body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
      post.should be_valid
    end
  end
 
  Saída:
 
  F

  Failures:

    1) Post should be not created without title
       Failure/Error: post.should be_valid
         expected valid? to return true, got false
       # ./spec/models/post_spec.rb:6:in `block (2 levels) in <top (required)>'

  Finished in 0.04851 seconds
  1 example, 1 failure
  rake aborted!
  ruby -S bundle exec rspec ./spec/models/post_spec.rb failed
 
  Pra quem não conhece Rspec, eu descrevou uma situação quem não pode acontecer no "it", mas faço ao contrário no final "post.should be_valid", eu digo que ele "deve validar". Se você ler a saída vai ver o que estou dizendo. Para nossa espectativa funcionar troque o "should", por "should_not" e irá ver que a saída será GREEN. Faça o mesmo teste para "body", já que são os únicos que validamos no post passado em "app/models/post.rb".
 
  describe Post do
    it "should be not created without title" do
      post = Post.new(:title => "", :body => "Text for body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
      post.should_not be_valid
    end
   
    it "should be not created without body" do
      post = Post.new(:title => "Title", :body => "", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
      post.should be_valid
    end
  end
 
  Ocorreu o mesmo RED de antes, faça ele ficar GREEN e vamos agora para o REFACTORY. Esse código está meio repetitivo, vamos deixar ele mais elegante:
 
  describe Post do
    before(:each) do
      @post = Post.new(:title => "Title", :body => "Text for body", :publication => Time.now.strftime("%Y-%m-%d %H:%M:%S"), :enabled => 1)
    end
   
    it "should be not created without title" do
      @post.title = nil
      @post.should_not be_valid
    end
   
    it "should be not created without body" do
      @post.body = nil
      @post.should_not be_valid
    end
  end
 
  Eu criei um "before(:each)" que criará um post, dentro de cada "it" eu passo o valor que eu quero testar como "nil", irá dar o efeito que quero. Crio um último "it" para todos os dados passem.
 
  it 'should be created with all requirements' do
    @post.should be_valid
  end
 
  Agora façam os mesmos tipos de teste com User, depois conferem se ficou igual ao que eu fiz. Lembre que o Devise faz validação de campos e que você só precisa testar os campos que exigem validação. E também não existe um certo, e sim o que você acha necessário.
 
  describe User do
    before(:each) do
      @user = User.new(:name => 'User Test', :login => 'usertest', :email => 'test@test.com', :password => 'senha123', :password_confirmation => 'senha123' )
    end
   
    it 'should be not created without name' do
      @user.name = nil
      @user.should_not be_valid
    end
   
    it 'should be not created without login' do
      @user.login = nil
      @user.should_not be_valid
    end

    it 'should be not created without email' do
      @user.email = nil
      @user.should_not be_valid
    end
   
    it 'should be not created without password' do
      @user.password = nil
      @user.should_not be_valid
    end
   
    it 'should be not created if password and password_confirmation not equal' do
      @user.password = 'senha123'
      @user.password_confirmation = 'senha'
      @user.password.should_not == @user.password_confirmation
    end
    
    it 'should be created with all requirements' do
      @user.should be_valid
    end 
  end
 
  O único comportamento diferente é o penúltimo que testa se a senha do usuário está diferente.
 
  Vamos voltar a fazer modificações no Post. Temos agora dois layouts simulando a "home" de um site e "admin", mas nossa rota não indica que "posts" faz parte de um admin, então vá no arquivo routes e modifique o path da rota, e depois é só carregar a página, veja como vai ficar abaixo.
 
  resources :posts, :path => 'admin/posts'
 
  Vamos adicionar autor na nossa tabela posts, já que todo post precisa de um autor, e esse autor será o usuário que estiver armazenado na nossa sessão. Primeiro crie um migração nova que adicionará o campo "author_id" na tabela "posts", será um relacionamento de um autor para muitos posts, e somente um post para um usuário. Crie a migração desta forma:
 
  rails g migration add_field_author_id_to_posts
 
  Abra o arquivo que foi criado e faça as modificações abaixo:
 
  class AddFieldAuthorIdToPosts < ActiveRecord::Migration
    def self.up
      add_column :posts, :author_id, :integer
      add_index :posts, :author_id
    end

    def self.down
      remove_index :posts, :author_id
      remove_column :posts, :author_id
    end
  end
 
  Está adicionando uma nova coluna e um novo índice na tabela posts. Agora abra o model "user" e adicione a linha abaixo, na primeira linha depois da classe ter sido criada.
 
  has_many :posts, :foreign_key => "author_id"
 
  Está dizendo que um usuário terá muitos posts, e sua chave estrangeira é "author_id". Agora no model "post" adicione a linha abaixo, na primeira linha depois da classe ter sido criada.
 
  belongs_to :author, :class_name => "User", :foreign_key => "author_id"
 
  Está dizendo que a chave estrangeira "author_id" pertence a classe "User". Como sempre o post será do usuário logado ao sistema que é um autor, a associação de autor e post não será interferida neste momento. Abra o controller "posts" e faça o código abaixo no método create.
 
  def create
    @post = Post.new params[:post]
    @post.author_id = current_user.id
   
    if @post.save
      flash[:notice] = 'Post was successfully created.'
      respond_with @post
    else
      render :action => :new
    end
  end
 
  Você não viu errado, é só adicionar essa linha mesmo, antes de salvar eu adiciono o id do usuário atual no campo "author_id", esse "current_user" é um método do Devise, retorna um hash com os dados do usuário. Nas views "index" e "show" adicione a saída.
 
  index:
 
  <th>Author</th>
 
  <td><%= post.author.name unless post.author.nil? %></td>
 
  show:
 
  <p>
    <b>Author:</b>
    <%= @post.author.name unless @post.author.nil? %>
  </p>
 
  Para quem não conhece esse "unless" é uma negação, só vai executar se author não for nulo.
 
  Chega por hoje, o próximo post deve ser de Rspec, mas teste de controller.
  Até a próxima!!
 

segunda-feira, 14 de março de 2011

Otimizando seu controller

  Hoje vou voltar um pouco para o scaffold de post que fiz, vou implementar coisas novas lá, deixar ele com uma "cara" melhor. O que já foi feito:
    - Scaffold de Post feito com a gem Simple Form.
    - Um Controller Home que vai simular uma página do site.
    - Implementação básica do Devise.
    - Gravatar.
  
  O projeto está aqui(github.com/vagnerzampieri/zblog).
 
  Primeiro vamos definir visualmente o que seria a capa do site da administração do site, o scaffold post simula o começo de uma administração, então ele precisa ter um layout modificado. Então vamos criar um layout específico para ele.
  Entre 'app/views/layouts/' e crie um arquivo chamado 'admin.html.erb', e copie o código abaixo:
 
  <!DOCTYPE html>
  <html>
    <head>
      <title>Blog</title>
      <%= stylesheet_link_tag :all %>
      <%= javascript_include_tag :defaults %>
      <%= csrf_meta_tag %>
    </head>
    <body>
      <container>
        <p class="notice"><%= notice %></p>
        <p class="alert"><%= alert %></p>

        <%= yield %>
      </div>
    </container>
  </html>
 
  Se já teve algum contato com HTML5 vai notar que é um layout do mesmo, o scaffold do rails já faz isso, o que tem de diferente que eu coloquei foi a tag "<container>". No 'app/views/layouts/application.html.erb' coloque esse mesmo HTMl, mas substitua o "<content>". Se você estiver se perguntando o que é "<%= yield %>" é aonde todas as telas que você criar irá entrar. Agora vamos diferenciar o layout no CSS.
 
  Em 'public/stylesheets/scaffold.css, ajuste a primeira linha para isso:
 
  body { background-color: #000; color: #000; }
 
  Depois adicione isto:
 
  th, tr, td {
    border: solid 1px black;
  }
 
  container, content {
    display:block;
  }
 
  container {
    width: 75%;
    margin: 0 auto;
    background-color: #ccc;
    padding: 20px 40px;
    border: solid 1px black;
    margin-top: 20px;
  }

  content {
    width: 75%;
    margin: 0 auto;
    background-color: #493233;
    padding: 20px 40px;
    border: solid 1px black;
    margin-top: 20px;
  }
 
  Agora no controller de 'app/controllers/posts_controller.rb', adicione na primeira linha depois que entrar dentro da classe:
 
  layout 'admin'
 
  Ele vai definir o layout que a página vai usar, o padrão é "application" mas eu quero o admin para "posts", então vá olhar como ficou a "home" e "posts". Eu não sou designer e uso layout escuro para não incomodar minha vista.
 
  Se não existir conteúdo em posts, adicione alguns, depois veja como ele está listando. Entre no código da view em "app/views/posts/index.html.erb" e no controller 'app/controllers/posts_controller.rb'. O que vou fazer agora é explicar código, otimizar e trazer para a realidade de Rails3.
 
  No método "index" faremos algumas modificações, esse método gera uma saída para a view "index.html.erb":
 
  def index
    @posts = Post.all

    respond_to do |format|
      format.html
      format.xml  { render :xml => @posts }
    end
  end
 
  No Ruby a última linha de um método, ou um bloco é seu retorno, o método respond_to é um bloco que gera duas saídas, uma em html e a outra em xml. O método "index" gera uma lista de todos os posts, essa lista é pega de sua tabela "posts", ela é acessada através da consulta "Post.all", que será atribuída para uma variável chamada "@posts", que é uma variável de instância, ela irá trazer em forma de array todos os posts. O "Post" é o model relativo a tabela posts. Vamos deixar esse código com uma cara de Rails3.
 
  Primeiro, no controller adicione a linha abaixo, logo depois de "layout 'admin'":
 
  respond_to :html, :xml, :js
 
  Depois modifique nosso método index para:
 
  def index
    @posts = Post.order "publication DESC"

    respond_with @posts
  end
 
  Esse novo "respond_to" vai gerar os mesmos formatos que antes mais o formato "js"(javascript), será utilizado depois quando fizermos saídas com ajax, é só passar os parâmetros que quiser. Na primeira linha do método eu modifiquei para que retorne todos os posts em ordem decrescente por data. E o "respond_with" é meu complemento do método "respond_to", eu passo como parâmetro a variável de instância "@posts". Se estiver achando desconfortável a falta de parênteses quando chamo um método, é só adicionar, o espaço simples substitue os parênteses.
 
  def index
    @posts = Post.where(:enabled => 1)

    respond_with(@posts)
  end
 
  Em 'app/views/posts/index.html.erb', note a nossa variável de instância "@posts", é passado um método "each" que irá acessar o array desta linha, e gerar a saída uma por uma de todos os conteúdos de dentro dele, através do seu bloco.
   
  Se colocou em "body" texto suficiente, notou que ficou estranho o layout, vamos formatar isso usando o método "truncate".
 
  <td><%= truncate post.body, :length => 20 %></td>
 
  Estou passando como parâmetros, o campo "body" e um tamanho de caracteres. Vou formatar a data também:
 
  <td><%= post.publication.strftime "%Y-%m-%d %H:%M:%S" %></td>
 
  Nos métodos, "show", "new", "edit" e "destroy", faça a modificação do "respond_with".
 
  Agora vamos otimizar o método create, por enquanto ele está assim:
 
  def create
    @post = Post.new params[:post]

    respond_to do |format|
      if @post.save
        format.html { redirect_to(@post, :notice => 'Post was successfully created.') }
        format.xml  { render :xml => @post, :status => :created, :location => @post }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @post.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  Ele pega os dados vindos do parâmetro "params[:post]", que vem do formulário que está em "app/views/posts/_form.html.erb". Se os parâmetros estiverem certos, ele salva e avisa que foi criado com sucesso, senão ele se matem na página que está. Vamos otimizar esse código. Outra coisa que irão notar é que eu coloquei :new e não "new", simples explicação, da primeira forma eu crio um símbolo e da segunda é uma string. Usando símbolos, o consumo de memória pode ser drasticamente reduzido, uma vez que você estará usando o mesmo objeto, sem necessidade de crias várias cópias, quando estiver em um projeto grande essas pequenas diferenças fazem diferença no tempo total de processamento.
 
  def create
    @post = Post.new params[:post]
   
    if @post.save
      flash[:notice] = 'Post was successfully created.'
      respond_with @post
    else
      render :action => :new 
    end
  end
 
  A ideia foi a mesma que já tinha usado, eu diminuo o código quando utilizo o "respond_with". Veja como vai ficar o "update".
 
  def update
    @post = Post.find params[:id]

    if @post.update_attributes params[:post]
      flash[:notice] = 'Post was successfully updated.'
      respond_with @post
    else
      render :action => :edit
    end
  end
 
  O método "update" pega o id do post em questão, armazena na variável @post, depois atualiza ele passando o parâmetro "params[:post]" que vem do formulário. Para você o "else" funcionar valide a presença dos campos "name" e "body", por exemplo. Faça em "app/models/post.rb"
 
  validates_presence_of :title, :body
 
  Para não deixar esse post grande vou encerrar aqui e falar sobre Rspec no próximo, iremos validar o modelo post.
 
  Até a próxima!!

quinta-feira, 10 de março de 2011

Ruby on Rails do zero, Devise

Continuação do post "Ruby on Rails do zero" no repositório github.com/vagnerzampieri/zblog tem o projeto que estamos fazendo aqui.

Devise:
  Agora vamos ver como o Devise funciona, se você for dar uma olhada no post anterior irá notar que já baixamos o Devise, mas ainda não instalamos ele. A linha abaixo é a linha que fica no arquivo Gemfile que fica na raiz principal da sua aplicação.
 
  gem 'devise', '1.1.rc2'
 
  O Devise é um autenticador, através dele você poderá criar usuário, logar, recuperar senha, sessão de usuário aberta pelo tempo que desejar, criptografa senha, e muito mais. É uma ferramenta muito completa e muito usada no mundo Rails e não é difícil de usar, o básico dela qualquer nível de programador pode usar, até o menos experiente. Esse Devise que estou usando provavelmente já deve existir um mais recente, então use um mais recente, não acredito que vai quebrar a aplicação ou algo assim, mas se estiver com receio utilize essa versão mesmo.
 
  Vamos dizer que não fizemos download do Devise, se estiver usando RVM coloque aquela linha que dei um pouco acima e coloque ela em Gemfile e faça um "bundle install". Note que ela será baixada para o seu projeto.
 
  Agora vamos instalar o Devise:
 
  rails g devise:install
 
  Irá instalar dois arquivos na sua aplicação, o mais importante é o arquivo de configuração dele 'config/initializers/devise.rb'. É importante dar uma olhada nele, ele já é bem explicado, quando eu for fazer alguma alteração nele irei explicar o por que, mas saiba que ele existe e é muito útil. Outra coisa legal que ele instala é em 'config/locales/devise.en.yml' para dar a saída na língua que você quiser.
 
  O Devise além de te criar os arquivos vai mostrar uma saída que  pedirá que você faça 3 passos. O primeiro é configirar uma url default, isso é só para quando for desenvolvimento. A segunda é para configurar uma rota padrão, esse passo foi feito no post passado, eu cheguei a criar um controller home e coloquei a rota da forma que pede o arquivo, o Devise pede esse passo por que ele usa a rota "root" para os seus redirecionamentos. O terceiro passo são alertas para o Devise mostrar para o usuário se a ação feita foi correspondida da forma esperada ou se ocorreu algum erro. Faça os 3 passos na sua aplicação, se estiver seguindo o post passado o segundo não precisará fazer, por que já está feito.
 
  Ao usar o Devise é habilitado um gerador, então vamos utilizar ele e ver a mágica dele.
 
  rails g devise User
 
  As importantes coisas que ele faz eu vou listar abaixo:
 
  create    app/models/user.rb
  create    db/migrate/20110307130914_devise_create_users.rb
  route  devise_for :users
 
  Utilizando o model User que você colocou como opção ele cria uma série de arquivos com o nome do seu modelo, mas antes de falar sobre o que ele criou vamos logo adicionar as views do Devise na sua aplicação.
 
  rails g devise:views 
 
  Vou começar pelo model, 'app/models/user.rb':
 
  class User < ActiveRecord::Base
    # Include default devise modules. Others available are:
    # :token_authenticatable, :confirmable, :lockable and :timeoutable
    devise :database_authenticatable, :registerable,
           :recoverable, :rememberable, :trackable, :validatable

    # Setup accessible (or protected) attributes for your model
    attr_accessible :email, :password, :password_confirmation, :remember_me
  end
 
  O Devise inclui alguns modulos que serão usados por ele. Na linha que tem 'attr_accessible' é para habilitar os campos que você for querer utilizar quando criar a tabela 'users', se colocar um campo novo e não adicionar nesta linha do model não vai funcionar. Vamos deixar usuário um pouco mais interessante, coloque como abaixo:
 
  attr_accessible :name, :login, :enabled, :url_image, :email, :password, :password_confirmation, :remember_me
 
  Eu adicionei 'name', 'login', 'enabled', 'url_image', os dois primeiros ta na cara. O 'enabled' é para se eu quiser ativar, ou desativar um usuário, eu não vou precisar excluir ele de vez, é só desativar. O 'url_image' é para se o usuário não quiser usar a opção do Gravatar.
 
  Agora no arquivo 'db/migrate/20110307130914_devise_create_users.rb', ele vai ficar assim com as modificações.
 
  class DeviseCreateUsers < ActiveRecord::Migration
    def self.up
      create_table(:users) do |t|
        t.string :name
        t.string :login
        t.boolean :enabled, :default => true
        t.string :url_image
        t.database_authenticatable :null => false
        t.recoverable
        t.rememberable
        t.trackable

        # t.confirmable
        # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
        # t.token_authenticatable


        t.timestamps
      end

      add_index :users, :name
      add_index :users, :login, :unique => true
      add_index :users, :enabled
      add_index :users, :url_image
      add_index :users, :email,                :unique => true
      add_index :users, :reset_password_token, :unique => true
      # add_index :users, :confirmation_token,   :unique => true
      # add_index :users, :unlock_token,         :unique => true
    end

    def self.down
      drop_table :users
    end
  end
 
  Se você comparar linha por linha, irá notar que eu adicionaei os 4 campos e adicionei os index deles. Antes de rodar o 'rake db:migrate', vamos nas views do Devise e colocar os campos que agente adicionou.
 
  Em 'app/views/devise/registrations/new.html.erb' e 'app/views/devise/registrations/edit.html.erb' ficará assim:
 
  <h2>Sign up</h2>

  <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %>
    <%= devise_error_messages! %>
   
    <p><%= f.label :name %><br />
    <%= f.text_field :name %></p>
   
    <p><%= f.label :email %><br />
    <%= f.text_field :email %></p>

    <p><%= f.label :login %><br />
    <%= f.text_field :login %></p>

    <p><%= f.label :password %><br />
    <%= f.password_field :password %></p>

    <p><%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation %></p>
   
    <p><%= f.label :enabled %><br />
    <%= f.check_box :enabled %></p>
   
    <p><%= f.label :url_image %><br />
    <%= f.text_field :url_image %></p>

    <p><%= f.submit "Sign up" %></p>
  <% end %>

  <%= render :partial => "devise/shared/links" %>
 
  Para você não ficar maluco por que não viu saída de nada até agora, roda 'rake db:create' e 'rake db:migrate' no terminal e vai em 'http://localhost:3000/users/sign_up', mas eu quero duas coisas, uma quero acessar essas rotas através da minha home que criei e quero mudar essas rotas. Primeiro vamos para 'app/home/index'.
 
  <div id="user_nav">
    <% if user_signed_in? %>
      Logado como <%= current_user.email %>. Não é você?
      <%= link_to 'Sair', destroy_user_session_path, :method => :delete %>
      </br>
      <%= link_to 'Post', posts_path %>
    <% else %>
      <%= link_to 'Cadastrar', new_user_registration_path %> ou <%= link_to 'Logar', new_user_session_path %>
    <% end %> 
  </div>
 
  Eu adicionei o código acima, ma o que ele faz, é bem simples. Se o usuário estiver logado ele cai no 'if', que vai mostrar qual email do usuário logado, dar um link para ele destruir a sessão atual dele e um link para 'Post', senão dará duas opções, uma se cadastrar e a outra se logar. Lembre o Devise só faz a autenticação, autorização tem outras formas, se colocar 'posts' na url o Devise não vai te impedir de ir para página. Crie um usuário qualquer. Ao criar irá notar que ele já se loga automaticamente, e mostra uma mensagem que foi logado com sucesso.
 
  Vá na sua tabela users e veja como esse registro de usuário foi criado. Notará que a senha por exemplo está criptografada em 'encrypted_password', que o Devise conta quantas vezes o seu usuário se conectou ao sistema em 'sign_in_count', entre outras informações.
 
  Outra coisa, o model user não tem validação ainda, só para os campos que o Devise criou, então vamos colocar uma validação simples, de pressença, se existe ou não dado naquele campo.
  
  Em 'app/models/user.rb':
 
  validates_presence_of :name, :login
 
  Tente criar um usuário agora sem nome e login para ver o que acontece. Erro, agora tem uma pequena validação, mais a frente vou usar RSpec para testar as nossas validações de formulários.
 
  Se você fez testes se logando viu que a opção é de logar é com email, mas se você não quiser usar email ? Em 'config/initializers/devise.rb' tem uma linha comentada:
 
  config.authentication_keys = [ :email ]
 
  Teste colocando login:
 
  config.authentication_keys = [ :login ]
 
  Em 'app/views/devise/sessions' troque o campo email por login. Vamos configurar um Range de quantos dígitos poderá ter em um password:
 
  config.password_length = 4..20 # Coloquei de 4 à 20 para ilustração.
 
  Leia com atenção as inúmeras codificações que poderá fazer em termos de sessão de usuário. Se quiser configurar a rota para ficar de uma maniera que vai agradar mais você, faça assim:
 
  Em 'config/routes.rb':
 
  devise_for :users, :path => 'usuarios', :path_names => {:sign_in => 'logar', :sign_out => 'sair', :sign_up => 'cadastrar'}
 
  Para encerrar o post de hoje vamos usar o Gravatar. Eu ainda não falei de tudo que da pra fazer com o Devise, quando eu precisar fazer mais coisas nele vou escrever aqui.
 
  O Gravatar é bem simples, coloque a linha abaixo no seu helper, 'app/helpers/application_helper.rb':
 
  def avatar_url(user)
    if user.url_image.present?
      user.url_image
    else
      gravatar_id = Digest::MD5.hexdigest(user.email.downcase)
      "http://gravatar.com/avatar/#{gravatar_id}.png?s=48"
    end
  end
 
  E em 'app/views/home/index.html', chame o helper passando o usuario atual como parâmetro:
 
  Logado como <%= avatar_url(current_user) %> <%= current_user.email %>. Não é você?
 
  O código do método é bem simples, primeiro ele verifica se existe alguma url de imagem no campo 'url_image' que você colocou anteriormente, senão ele vai utilizar o email do usuário como um id para buscar a imagem que está armazenada no Gravatar, o parâmetro 's=48' no final é para definir o tamanho que será dimensionada a imagem.
 
  Chega por hoje, eu decidi fazer logo a autenticação por um simples motivo, todo site tem usuários que tem permissões específicas, áreas que só ele pode entrar, então é geralmente a parte em que se trabalha mais em um site. No próximo post vamos voltar a mexer no scaffold que fizemos de post.
 
  Até a próxima!!

quinta-feira, 3 de março de 2011

Ruby on Rails do zero

No decorrer dos meus posts vou trabalhar sempre na mesma aplicação que começarei neste post, vou disponibilizar ela no Github.
Essa aplicação terá de tudo que um site tem normalmente, Devise, Simple Form, Sphinx, Acts As Taggable On, Gravatar, Autocomplete, Ajax, Rspec, etc.. Vou ajudar a criar tarefas que otimizem o seu tempo, como testar suas aplicações, como fazer um bom código. Muita coisa que vou escrever aqui vocês poderam encontrar fazendo uma busca no Google, mas a diferença é que vai estar tudo em um só lugar e explicado em vários posts para facilitar sua vida, fora que como disse antes, a aplicação ficará no Github para o seu auxílio.

Vou utilizar o Ruby 1.9.2, e estou usando RVM.

Crie uma aplicação nova:

rails new zblog -d mysql

Vá na pasta public e renomeie o arquivo index.

Abra o arquivo Gemfile e adicione as linhas abaixo:

gem 'aws-s3'
gem 'warden', '0.10.7'
gem 'bcrypt-ruby', '2.1.2'
gem 'devise', '1.1.rc2'
gem 'rspec'
gem 'rspec-rails'
gem 'simple_form', '1.3.1'

Irei falar de cada um com o tempo. Vamos configurar o Simple Form para você poder fazer os scaffolds já utilizando ele, primeiro instale.

rails g simple_form:install

Arquivos gerados abaixo:

create  config/initializers/simple_form.rb
create  config/locales/simple_form.en.yml
create  lib/templates/erb/scaffold/_form.html.erb

Crie um controller home que será a nossa futura capa:

rails g controller home

Vá em config/routes e coloque:

root :to => "home#index"

Vamos gerar um scaffold de Post para você ver a mágica do Simple Form.

rails g scaffold Post title:string body:text publication:datetime enabled:boolean

Em posts/_form.html.erb:

<%= simple_form_for(@post) do |f| %>
  <%= f.error_notification %>

  <div class="inputs">
    <%= f.input :title %>
    <%= f.input :body %>
    <%= f.input :publication %>
    <%= f.input :enabled %>
  </div>

  <div class="actions">
    <%= f.button :submit %>
  </div>
<% end %>

Se está acostomado com os scaffolds do Rails, você vai logo ver a diferença. Primeiro é adicionado um helper chamado simple_form_for, que é nosso formulário, os inputs criados criam um html automático que você teria que fazer, isso diminui e muito o código a ser colocado no seu arquivo.

Adicione o CSS abaixo para dar uma melhorada no layout:


.simple_form label {
  float: left;
  width: 100px;
  text-align: right;
  margin: 2px 10px;
}

.simple_form div.input {
  margin-bottom: 10px;
}

.simple_form div.boolean, .simple_form input[type='submit'] {
  margin-left: 120px;
}

.simple_form div.boolean label, .simple_form label.collection_radio {
  float: none;
  margin: 0;
}

.simple_form label.collection_radio {
  margin-right: 10px;
  margin-left: 2px;
}

.simple_form .error {
  clear: left;
  margin-left: 120px;
  font-size: 12px;
  color: #D00;
  display: block;
}

.simple_form .hint {
  clear: left;
  margin-left: 120px;
  font-size: 12px;
  color: #555;
  display: block;
  font-style: italic;
}

Depois disso faça alguns teste utilizando o Simple Form, como adicionar um "hint" no input "title":

<%= f.input :title, :hint => "title" %>

também teste validação no seu model, e veja como fica o layout:

class Post < ActiveRecord::Base
  validates_presence_of :title, :body
end

Existe um arquivo em config/initializers/simple_form.rb que você poderá configurar e um arquivo em config/locales/simple_form.en.yml, para tradução, se criar um arquivo simple_form.pt-br.yml, irá poder traduzir os inputs gerados pelo Simple Form. Mas se for traduzir tem que especificar em config/application.rb, qual será a língua padrão:

config.i18n.default_locale = 'pt-br'

Para não alongar muito o post e deixar cansativo, vou encerrar ele por aqui, no próximo vou mostrar o Devise e o Gravatar. Vou mostrar mais sobre o Simple Form com o tempo, por enquanto paro por aqui. Essa aplicação já está no Github(github.com/vagnerzampieri/zblog), você irá notar que tem código a mais do que foi mostrado aqui, mas nós chegaremos lá.
Até a pŕoxima.