sábado, 10 de dezembro de 2011

GRUB error 17

Estava formatando as minhas partições, utilizando o Live CD do Ubuntu 11.04 e o GParted, quando fui reiniciar a máquina me deparei com o chato erro do GRUB error 17, alguns de vocês já devem ter resolvido isso, mas como nunca tinha formatado partições fui atrás do erro, achei este link falando do assunto e consegui adaptar para minha realidade.
O erro acontece quando as partições não estão organizadas e o GRUB precisava ficar na partição do Linux, coisa que não acontecia, então fiz o seguinte:

Antes de começar, TENHA CUIDADO.

Entrei fazendo boot novamente pelo CD, abri o terminal. Virei root.

sudo -i

Listei as partições com:

fdisk -l

A minha saída foi:

Disk /dev/sda: 160.0 GB, 160041885696 bytes
255 heads, 63 sectors/track, 19457 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x4dc5fddf
 
Device       Boot   Start         End        Blocks         Id     System
/dev/sda1             1               10           80293+       de    Dell Utility
/dev/sda2   *        11             1926       15383099    7      HPFS/NTFS
/dev/sda3             18184       19457     10233405    db   CP/M / CTOS / ...
/dev/sda4             1926         18183     130590720  5     Extended
/dev/sda5             1926         17566     125628416  83   Linux
/dev/sda6            17566        18183     4960256      82   Linux swap / Solaris

Partition table entries are not in disk order

No meu caso o GRUB precisa estar em /dev/sda5, sabendo disso montei minha partição com:

mount /dev/sad5 /mnt
mount -t proc none /mnt/proc
mount -o bind /dev /mnt/dev

Depois que o GRUB descobriu meus drivers, faço o chroot
 
chroot /mnt /bin/bash

Assim que acabar essa parte, abra o grub:  

grub

Encontre quais os parâmetros que você deve passar no comando root com o find abaixo:

find /boot/grub/stage1

No meu caso a saída foi (hd0,4), passe ele no root:

root (hd0,4)

Por fim rode o setup e feche:

setup (hd0)
quit

Reinicie a máquina e estará pronto. 

domingo, 19 de junho de 2011

Devise x Authlogic

Aviso aos navegantes que essa é a minha opinião, aconteceu comigo e só estou passando minha experiência para outros devels como eu, então não me julguem, posso estar falando alguma merda, mas pelo que conversei com alguns colegas eles tem uma opinião parecida.

Devise é uma ferramenta FODA, mas tem que saber muito bem que tipo de projeto irá utiliza-la. Se for um projeto rápido que você não quer perder tempo nele, vai ser pequeno, é feijão com arroz, utilize o Devise por que já está pronto. Não perca tempo reinventando a roda ela está pronta, utilize.

Se o seu projeto tiver que ter uma personalização maior, por que o cliente pediu algo diferente, usa o Authlogic. Com o Devise poderá ter dor de cabeça dependendo do que o cliente pedir, fora que pode acontecer dele não fazer tudo que você espera, ou ele fazer demais. Considero que o Devise como muitas outras ferramentas foi feita com um propósito e ele foi atingido com sucesso, mas para a sua utilização ele tem que se encaixar no seu modelo de negócio, já que varia de empresa para empresa. Não quer dizer que você esteja fazendo errado, e sim está fazendo da forma que acredita ser a correta.

Estou dizendo isso por que tive dois projetos que erramos na escolha pelo Devise que fazia muito mais do que precisávamos, depois tivemos que retirar e colocar o Authlogic. Hoje temos uma gem feita com o sistema de Engines do Rails 3.10.beta que com uma linha no terminal instala tudo que precisamos e configura, ela é específica para os nossos interesses por isso a utilização do Devise foi descartada por completo, mas não quer dizer para você não possa utilizar. Planejamento é muito importante para não ter dor de cabeça em uma troca mais tarde. Nosso caso trocamos, sendo que tínhamos um projeto em produção então imagine o trabalho que foi, tivemos que mexer em migração, banco de dados, modelo, nossa da calafrio até de lembrar.

Ps.: Demorei pra postar algo por causa da famosa faculdade, já pensei em alguns posts maneiros para fazer.

Até a próxima!!

domingo, 22 de maio de 2011

Quando usar Beta?

Essa é uma perguntinha chata que deve ecoar na cabeça de muito desenvolvedor e gerente de projeto. Eu tenho uma pequena experiência com os dois lados, já desenvolvi com ferramentas estáveis e também com ferramentas betas.

Meu exemplo de ferramenta estável foi de outubro de 2009 até dezembro de 2010, com o Ruby 1.8.7 e o Rails 2.3.5, aonde trabalho. Fizemos coisas bem legais com essas ferramentas, porém no meio do processo vimos a chegada do Rails3, que em caso de atualizar para nova versão iria modificar muita coisa interna e não valeria apena. Fora que as ferramentas que usavamos estavam migrando rapidamente para a nova versão do Rails3 e assim estagnando nossa ferramenta. Basicamente esse é o mal de usar o estável, suas aplicações podem ficar com código defasado muito rápido pelas mudanças constantes das ferramentas que você geralmente utiliza.

Com ferramenta Beta uso desde dezembro de 2010, uso o Ruby estável 1.9.2, mas uso o Beta do Rails o 3.1.0.beta, usava já antes dele virar "--pre". A vantagem disso? Uso funcionalidades que a maioria das pessoas só vai usar em 6 mêses ou mais, modificações realmente necessárias como as Engines que foram colocadas à pouco tempo. Só que existe um preço, se tiver alguma dificuldade para fazer algo, não tem muita documentação existente, vai ter que saber procurar, em listas, foruns, posts que o pessoal do Core Team participa. Outra dificuldade é que outras Gems que seu projeto depende podem não ser compatíveis com o Framework, quer dizer que você vai ter que meter a mão para ajudar a desenvolver. Esses pequenos contratempos fazem que a conclusão do projeto aumente.

E agora? Quando usar Beta? Não sei, depende do propósito, de tempo, equipe, são muitas variáveis para pensar antes de fazer a escolha. O meu pensamento hoje em dia está mais da seguinte forma:
Se for uma ferramenta que me ajude, uso Beta, mais tempo para trabalhar já que provavelmente irá ter muita coisa para fazer. Tendo o cuidado certo não vai ficar ultrapassada.
Se for um site simples, uso estável, sem demora usando gems estáveis que irão me ajudar na conclusão rápida do projeto.
Basicamente o que vai fazer você usar estável ou Beta é o tempo que irá ter que fazer aquela tarefa e que grau de complexidade ele pode vir a ter.
Espero ter ajudado, até a próxima!!!

quinta-feira, 28 de abril de 2011

Instalando Nginx e Unicorn no Rails 3

Nginx e Unicorn

    O Nginx com o Unicorn é uma ótima solução para colocar seu projeto em produção. É bem fácil a instalação deles. Eu utilizo essa combinação na minha máquina para testar meus projetos que desenvolvo. Quer saber mais sobre o que os dois fazem ? Vai pesquisar no Google, eu vou mostrar só a implementação básica.

        sudo apt-get install nginx
   
    Para poder usar o Nginx e o Apache sem ter que desligar um deles é só mudar a porta padrão do Nginx. Abra o arquivo default do Nginx para mudar a porta.
   
        sudo nano /etc/nginx/sites-available/default
   
    Comente a linha:
   
        #listen   [::]:80 default ipv6only=on; ## listen for ipv6
   
    Modifique a porta, eu coloco 8080:
   
        listen   8080; ## listen for ipv4
   
    Crie um arquivo nesta mesma página com um nome que identifique o site que está fazendo.
   
        sudo nano sua_app
       
    O meu arquivo está assim:

        upstream app_server {
            # fail_timeout=0 means we always retry an upstream even if it failed
            # to return a good HTTP response (in case the Unicorn master nukes a
            # single worker for timing out).

            # for UNIX domain socket setups:
            server unix:/tmp/.sock fail_timeout=0;

            # for TCP setups, point these to your backend servers
            # server 192.168.0.7:8080 fail_timeout=0;
            # server 192.168.0.8:8080 fail_timeout=0;
            # server 192.168.0.9:8080 fail_timeout=0;
          }

          server {
            # enable one of the following if you're on Linux or FreeBSD
            listen 8080 default deferred; # for Linux
            # listen 80 default accept_filter=httpready; # for FreeBSD

            client_max_body_size 4G;
            server_name sua_app.local;
           
            # ~2 seconds is often enough for most folks to parse HTML/CSS and
            # retrieve needed images/icons/frames, connections are cheap in
            # nginx so increasing this is generally safe...
            keepalive_timeout 5;

            # path for static files
            root /home/seu_usuario/seu_caminho/sua_app/public;

            # Prefer to serve static files directly from nginx to avoid unnecessary
            # data copies from the application server.
            #
            # try_files directive appeared in in nginx 0.7.27 and has stabilized
            # over time.  Older versions of nginx (e.g. 0.6.x) requires
            # "if (!-f $request_filename)" which was less efficient:
            # http://bogomips.org/unicorn.git/tree/examples/nginx.conf?id=v3.3.1#n127
            try_files $uri/index.html $uri.html $uri @app;

            location @app {
              # an HTTP header important enough to have its own Wikipedia entry:
              #   http://en.wikipedia.org/wiki/X-Forwarded-For
              proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

              # enable this if and only if you use HTTPS, this helps Rack
              # set the proper protocol for doing redirects:
              # proxy_set_header X-Forwarded-Proto https;

              # pass the Host: header from the client right along so redirects
              # can be set properly within the Rack application
              proxy_set_header Host $http_host;

              # we don't want nginx trying to do something clever with
              # redirects, we set the Host: header above already.
              proxy_redirect off;

              # set "proxy_buffering off" *only* for Rainbows! when doing
              # Comet/long-poll stuff.  It's also safe to set if you're
              # using only serving fast clients with Unicorn + nginx.
              # Otherwise you _want_ nginx to buffer responses to slow
              # clients, really.
              # proxy_buffering off;

              proxy_pass http://app_server;
            }

            # Rails error pages
            error_page 500 502 503 504 /500.html;
            location = /500.html {
              root /home/seu_usuario/seu_caminho/sua_app/public;
            }
          }


    Os pontos importantes desse arquivo são:
       
        # para definir a porta.
        listen 8080 default deferred;
       
        # definição do host chamado do navegador.
        server_name sua_app.local;
       
        # caminho estático da sua app.
        root /home/seu_usuario/seu_caminho/sua_app/public;
       
    Crie um link simbólico em sites-enabled:
   
        cd /etc/nginx/sites-enabled/
        sudo ln -s /etc/nginx/sites-available/sua_app sua_app
       
    Defina o nome da sua app no arquivo hosts:
       
        sudo nano /etc/hosts
   
    Dentro do arquivo:
   
        127.0.0.1   sua_app.local
   
    Start no nginx:
       
        sudo /etc/init.d/nginx start
       
    Teste o endereço com http://sua_app.local:8080/, vai dar uma mensagem. Vamos instalar o Unicorn. No arquivo Gemfile de sua app, adicione:
       
        gem 'unicorn', '3.5.0'
       
     Depois rode o 'bundle install' no terminal. Em 'config/' crie um arquivo 'unicorn.rb' e coloque o conteúdo abaixo conforme sua máquina:
    
        # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
        # documentation.
        worker_processes 2
        # Help ensure your application will always spawn in the symlinked
        # "current" directory that Capistrano sets up.
        working_directory "/home/deployer/myapp.com/current"
        # listen on both a Unix domain socket and a TCP port,
        # we use a shorter backlog for quicker failover when busy
        listen "/tmp/myapp.com.sock", :backlog => 64
        listen 8080, :tcp_nopush => true
        # nuke workers after 30 seconds instead of 60 seconds (the default)
        timeout 30
        # feel free to point this anywhere accessible on the filesystem
        user 'deployer', 'staff'
        shared_path = "/home/deployer/myapp.com/shared"
        pid "#{shared_path}/pids/unicorn.pid"
        stderr_path "#{shared_path}/log/unicorn.stderr.log"
        stdout_path "#{shared_path}/log/unicorn.stdout.log"
       
     Esse é o arquivo default dele encontrado aqui. Para colocar o unicorn em produção é só rodar essa linha no terminal dentro da sua app:
    
        unicorn_rails -c config/unicorn.rb -E production -D
       
     Para matar ele é só rodar:
    
        kill -quit `cat tmp/pids/unicorn.pid`
       
     É só isso, bem tranquilo é rápido, se tiver alguma dúvida comenta que eu ajudo no que puder.
     Até a próxima!!

terça-feira, 19 de abril de 2011

Ajax no Rails 3

    Era para eu falar neste post sobre teste de controller com RSpec, mas estava fazendo alguns testes com Ajax, então aproveitei o momento para falar de Ajax. Desculpe a demora por novos posts, estava em período de entrega de trabalhos e provas na faculdade, não deu tempo de preparar nada. Todos os códigos que faço aqui estão no Github(github.com/vagnerzampieri/zblog).
    Pegue aquele CRUD que fizemos sobre Post, ele vai ser modificado e dará lugar a documentos com extensão '.js.erb'. A ideia é que todas as ações fiquem na index, sendo assim, ficará um carregamento mais rápido e uma página mais dinâmica.
    Primeiro coloque a gem 'jquery-rails' no arquivo Gemfile da sua aplicação. A versão que estou usando está com o jquery 1.5.
        gem 'jquery-rails', '0.2.7'
    Logo em seguida, vá em um terminal e rode o 'bundle install'. Agora rode no terminal 'rails generate jquery:install', ele irá substtuir o prototype pelo jquery.
    Primeiro, vamos começar modificando a criação de post. Ele vai ficar da seguinte forma, ao clicar no link 'New' irá carregar abaixo um formulário, ao ser preenchido e enviado irá aparecer na lista o item que foi criado.
    Na index, vou modificar a tabela e colocar um id, esse id irá servir depois para identificar aonde irá adicionar na lista o post que for criado.
        posts/index.html.erb:
       
        <table id="posts">
       
    Crie um partial que irá virar uma lista dinâmica, já que será muito utilizada no ajax:
        posts/_post.html.erb:
       
        <tr id="post_<%= post.id %>">
          <td><%= post.title %></td>
          <td><%= truncate post.body, :length => 20 %></td>
          <td><%= post.publication.strftime "%Y-%m-%d %H:%M:%S" %></td>
          <td><%= post.enabled %></td>
          <td><%= link_to 'Show', post, :remote => true %></td>
          <td><%= link_to 'Edit', edit_post_path(post), :remote => true %></td>
          <td><%= link_to 'Destroy', post, :confirm => 'Are you sure?', :method => :delete, :remote => true %></td>
        </tr>
       
    Note que coloquei um id dinâmico no 'tr', e nos liks adicionei ':remote => true' é ele que irá fazer a mágica no Ajax, coloque ele também no 'form'.
        <%= simple_form_for(@post, :remote => true) do |f| %>
   
    Na index coloque um render para esse partial.
        posts/index.html.erb:
       
        <% @posts.each do |post| %> 
          <%= render post%>
        <% end %>
       
    Adicione também o link 'New' um pouco abaixo:
        <%= link_to 'New', '/posts/new', :remote => true %>
       
    E uma div onde será chamado o formulário:
        <div id="form">
        </div>
       
    Crie um arquivo 'new.js.erb' e dentro dele coloque o código abaixo:
   
        $('#form').empty();
        $("#form").append("<%= escape_javascript(render(:partial => "form"))%>");
       
    Esse código é bem simples, a primeira linha vai deixar vazio a div 'form', por um simples motivo, se você não colocar essa linha sempre que clicar em um botão ele vai criar um novo elemento em vez de excluir o antigo. A segunda linha chama o partial do 'form'. Crie um arquivo 'create.js.erb' e adicione o código abaixo:
   
        $('#posts').append('<%= escape_javascript(render(@post)) %>');
        $("#new_post")[0].reset();
       
    A primeira linha chama o partial 'post' que nós criamos e adiciona mais um elemento na lista. A segunda limpa o formulário para poder ocorrer mais um cadastro. Para finalisar, melhore o controller assim:
        def create
          @post = Post.new params[:post]

          flash[:notice] = 'Post was successfully created.' if @post.save
          respond_with @post
        end
    
    Agora o 'Show', mais acima irá notar que o link para o 'Show' já fi criado. Adicione a div abaixo na index, ela será o guia que utilizaremos para a aparição do show.
       
        <div id="show"> 
        </div>
       
    Crie um arquivo 'show.js.erb' e adicione o código abaixo:
       
        $('#show').empty();
        $('#show').append('<h2>Show post</h2><p><b>Title: </b><%= @post.title %></p><p><b>Body: </b><%= @post.body %></p><p><b>Publication: </b><%= @post.publication.strftime "%Y-%m-%d %H:%M:%S" %></p><p><b>Enabled: </b><%= @post.enabled %></p>');
   
    Como acontece no 'New' a primeira linha é para limpar se existir algo antes. A segunda irá exibir o item ao ser clicado, o append vai exibir tudo que estiver dentro dele. O próximo é o destroy, é o mais simples de todos, crie o arquivo 'destroy.js.erb' e adicione:
   
        $('#post_<%= @post.id %>').remove();
       
    Vai remover a linha exata que você destruir. O último é editar, é bem parecido que o criar, crie os arquivos 'edit.js.erb' e 'update.js.erb':
        posts/edit.js.erb:
       
        $('#form').empty();
        $("#form").append("<%= escape_javascript(render(:partial => "form"))%>")
       
    Mesma explicação do 'New'.
   
        posts/update.js.erb:
       
        $("#post_<%= @post.id %>").replaceWith("<%= escape_javascript(render(:partial => @post)) %>");
        <% @post = Post.new %>
        $(".simple_form").replaceWith("<%= escape_javascript(render(:partial => "form"))%>")
       
    Primeira linha, vai adicionar o elemento criado na lista. Segundo e terceira linha, vai resetar a variável de instância para ao editar o formulário, se modifique de edição para criação. Deixe o método update no controller da forma abaixo:
   
        def update
          @post = Post.find params[:id]

          flash[:notice] = 'Post was successfully updated.' if @post.update_attributes params[:post]
          respond_with @post
        end
       
    Pode apagar agora os arquivos abaixo, as suas funcionalidades não serão mais necessárias já que foram migradas.
        new.html.erb
        edit.html.erb
        show.html.erb
   
    Chega por hoje, ainda não pensei em um próximo post, talvez um banco de imagem com mais brincadeiras com Ajax.
    Até a próxima!!

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