Models e Migrations mais espertos

Posted on July 15, 2007

Da série “Plugins que uso ;)”: Sexy Migrations, Validation Reflection e Validate Attributes.


Sexy Migrations

Nome sugestivo? :) Talvez seu chefe não goste… (renomeie), mas a satisfação é garantida (ops!).

Por exemplo, sem Sexy Migrations:

    create_table :funcionarios do |t|
      t.column :nome, :string
      t.column :email, :string
      t.column :nascimento, :date, :null => true
      t.column :empresa_id, :integer
      t.column :created_at
      t.column :updated_at
    end

Com Sexy Migrations:

    create_table :funcionarios do |t|
      string :nome, :email
      date :nascimento, :null => true
      fkey :empresa
      timestamps
    end

Gostou? Acesse o ‘post’ do projeto. :D

Vaditation Reflection

Que tal ter informações sobre as validações de um model? Para que? Para utilizar nos testes ou nos formulários. Como? É só instalar o Validation Reflection e usar:

Funcionario.reflect_on_all_validations
Funcionario.reflect_on_validations_for(:nome)

Isto retornará um vetor de MacroReflections , com informações sobre as validações do Model(ou de apenas um atributo). MacroReflections também são os mesmos utilizados para armazenar informações sobre as agregações e associações.

Exemplo:

Funcionario.reflect_on_validations_for(:nome).detect do |v|
   v.macro == "validates_uniqueness_of" 
end.options[:scope]

O Validation Reflection também possui três métodos que define algumas validações baseadas no metadados da tabela:

validates_presence_of_mandatory_content_columns # v*_presence_of para atributos :null => false
validates_lengths_of_string_attributes #v*_size_of para strings
validates_all_associated # v*_associated para todas as associações
E se não fosse suficiente, ainda adiciona no class das tags dos campos dos atributos informações sobre as validações, fazendo text_field(‘funcionario’,’nome’) obrigatório com limite 30 retornar:
<input id="funcionario_nome" name="funcionario[nome]" class="mandatory  validate_maxlength_30" size="30" type="text" />

Validate Attributes

Com o Validate Attributes podemos chamar o velho valid? para um ou mais atributos. Exemplo:
@funcionario.validate_attributes(:only => ["nome", "email"])

Interessante para testes, wizards e validações com ajax.


Espero que (alguém) tenha gostado! :) E até as próximas dicas! :D

Flexperimentando com o Rails/WebOrb

Posted on July 08, 2007

Tirei um tempinho para testar o Flex e sua integração com o Rails utilizando o plugin WebOrb. Segue o passo a passo que realizei, que deve servir para quem quer iniciar e não sabe como. Boa leitura! :D

Antes de começar, aqui vai uma imagem do resultado: Demo Flex Usuários

Utilizei o Flex2 SDK e o plugin 9 do Flash no Windows. Como só utilizaremos o SDK no final, pode deixar baixando enquanto está implementando. :)

Crie o projeto:
rails demo_flex
Instale o plugin WebOrb (lembre de executar na pasta raiz do projeto):
ruby script/plugin install http://themidnightcoders.net:8089/svn/weborb

Para verificar se tudo ocorreu bem já pode iniciar o servidor (ruby script/server ) e acessar: http://localhost:3000/examples/main.html ou http://localhost:3000/examples/example.html.

Entre os arquivos e pastas adicionados pela instalação do plugin temos:
  • A pasta app/services onde implementamos os serviços disponíveis para serem acessados via RPC. Nela encontramos alguns exemplos.
  • O arquivo config/WEB-INF/flex/remoting-config.xml onde declaramos os serviços implementados.
  • A pasta public/examples onde temos o código dos exemplos.

Partindo para a criação do nosso próprio exemplo: listar registros de uma tabela de usuários, crie seu database e edite o config/database.yml de acordo.

Caso esteja usando MySQL, segue uma ajuda:
create database demo_flex_dev;
grant all on demo_flex_dev.* to 'demo_flex'@'localhost' identified by '123';
flush privileges;
E no database.yml:
development:
  adapter: mysql
  database: demo_flex_dev
  username: demo_flex
  password: 123
  host: localhost

Depois:

ruby script/generate model Usuario login:string senha:string nome:string
rake db:migrate
Para verificar se até agora está tudo ok e aproveitar para adicionar alguns dados:
ruby script/console
Usuario.create!(:login => 'roberto', :senha => '123', :nome => 'Roberto Soares')
Agora crie o arquivo app/services/UsuarioService.rb:
require 'weborb/context'
require 'rbconfig'

class UsuarioService
  def list
    Usuario.find(:all)
  end
end
E declare-o no arquivo config\WEB-INF\flex\remoting-config.xml:
    <destination id="UsuarioService">
        <properties>
            <source>UsuarioService</source>
        </properties>
    </destination>

Copie o arquivo public/examples/example.html para public/examples/usuarios.html substituindo todos ‘example’ por ‘usuarios’ para ganhar tempo. :)

Para finalizar crie o arquivo public/examples/usuarios.mxml que pode ser baixado aqui.
<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
    creationComplete="setUp();loadList();">

    <mx:Script>
    <![CDATA[    
        import mx.rpc.remoting.RemoteObject;
        import mx.controls.Alert;
        import mx.rpc.events.ResultEvent;
        import mx.rpc.events.FaultEvent;     

        private var remoteObject:RemoteObject;

        public function setUp():void
        {
            //objeto que representa o nosso servico
            remoteObject = new RemoteObject();
            remoteObject.destination = "UsuarioService";

            //em caso de sucesso do método list do servico,
            //será chamado o método onList recebendo o resultado
            remoteObject.list.addEventListener("result", onList);

            //em caso de falha será chamado o onFault
            remoteObject.addEventListener("fault", onFault);        
        }

        public function loadList():void
        {
            //aqui é chamado o list do UsuarioService
            remoteObject.list();               
        }

        public function onList(event:ResultEvent):void
        {
            var usuarios:Array = event.result as Array;

            //atualizando o DataGrid
            listDataGrid.dataProvider = usuarios;
        }

        public function onFault (event:FaultEvent):void 
        {
            Alert.show(event.fault.faultString, 'Error');
        }

    ]]>
    </mx:Script>

    <mx:TitleWindow title="Exemplo" layout="vertical" height="100%" width="100%">

            <mx:Button label="Atualizar Lista" click="loadList()"/>

            <mx:DataGrid id="listDataGrid" width="100%" height="100%">
                <mx:columns>
                    <mx:DataGridColumn dataField="login" headerText="Login"/>
                    <mx:DataGridColumn dataField="senha" headerText="Senha"/>
                    <mx:DataGridColumn dataField="nome" headerText="Nome"/>
                </mx:columns>
            </mx:DataGrid>

    </mx:TitleWindow>

</mx:Application>
Para terminar, compile o usuarios.mxml usando o mxmlc do Flex2SDK:
endereco_do_sdk\bin\mxmlc --compiler.services ../../config/WEB-INF/flex/services-config.xml -compiler.context-root ../../config/WEB-INF/flex usuarios.xml

Dica: coloque a pasta bin do FlexSDK no PATH do sistema. ;)

Pronto! É só rodar o servidor ( ruby script/server ) e acessar http://localhost:3000/examples/usuarios.html.

Download do projeto completo do exemplo(1,09MB).

Gostou? Vá dar uma olhada em outras fontes: http://blog.egenial.com.br #olá instrutor :D http://flexonrails.net http://flexiblerails.com #primeiro livro FlexOnRails http://flex.org http://livedocs.adobe.com/flex/2/langref/ #doc das classes do flex

Na próxima testarei com o FoRc que não utiliza RemoteObject, mas sim HTTPService.

t+!

RSpec: Behaviour-Driven Development On Ruby

Posted on April 06, 2007

Escrevi um pouco apressado, estarei viajando daqui a pouco e como já tinha uma parte no ‘draft’, resolvi publicar logo. :)

O código apresentado nos exemplos está utilizando os métodos da versão do RSpec 0.9, que foi lançado ontem na lista de usuários e o site do projeto ainda apresenta a versão 0.8.2(até o momento), porém nada complicado de “converter”.

Então, aqui vamos. :)

Pequena Introdução

Muito, e há muito tempo, tem se falado de Desenvolvimento orientado a Testes, ou TDD(Test-Driven Development). Metodologias, como a Extreme Programming, que fazem sua utilização, além de frameworks e ferramentas para as mais diversas linguagens. “Mas será que estamos fazendo corretamente? TDD é realmente sobre testes?”. Foram alguns dos questionamentos que levaram a Dave Astels e Dan North a criarem outra alternativa: BDD, Behaviour Driven Development, ou Desenvolvimento orientado a Comportamento. Notaram que experientes praticantes de TDD chegavam a um ponto que percebiam que não estavam escrevendo testes e sim definindo comportamentos. Tendo isso em mente, desenvolveram frameworks que permitissem um melhor foco nessas definições: JBehave e RSpec(tema deste post).


Instalando


gem install rspec

Possui algumas dependências como o rcov e o rake, acesse a documentação para outras.

Estrutura básica


#no "describe" descrevemos o que estamos especificando no momento
describe Carteira do 

    #este método é chamado antes de cada "it", pode ser utilizado para indicar um estado dos objetos envolvidos e mocks.
    setup do
        ...
    end

    #nos métodos "it" é onde realmente trabalhamos
    it "deve ter um método para entrada $" do
        ...
    end

    it "não deve estar vazia" {
        ...
    }

end

#podemos definir mais de um describe por arquivo.
describe Carteira, " (validações)" do #este describe resultaria em: "Carteira (validações)" 
    ...
end

Should e Expectations


O RSpec adiciona os métodos should(‘devia’) e should_not(‘não devia’) à Object, tornando disponível para qualquer objeto de sua aplicação (durantes os testes :) ):
#exemplos
carteira.should_not ...
post.should_not ...
post.save!.should ...

E também define um conjunto de Expectations(‘expectativas’) para que possamos definir os comportamentos esperados.
#exemplos
be_...(...)
have(...).(...)
raise_error(...)
‘Juntando’:
carteira.should_not be_a_kind_of(Carro)
#carteira não devia ser do tipo Carro

post.should_not have(3).comentarios
#post não deve ter 3 comentários

post.save!.should raise_error
#post.save! devia causar erro

A leitura flui melhor do que ‘assert aquilo’, não? :) Veja outros Expectations.

Exemplo


Como exemplo vamos definir o comportamento de Carteira.

Primeiro criamos o “esqueleto”:
#arquivo carteira_spec.rb
require 'carteira' #vamos criar mais abaixo
describe Carteira do

    setup do
        @carteira = Carteira.new
    end
end

Agora vamos para o primeiro comportamento:

  • o valor inicial de dinheiro deve ser 0. :|

it "o valor inicial de dinheiro deve ser 0 :|" do
    @carteira.dinheiro.should == 0
    @carteira.dinheiro.should eql?(0) #alternativa 1
    @carteira.dinheiro.should be_zero #alternativa 2        
end

Três formas de (d)escrever. Na última o “be_zero” na verdade está utilizando o método “zero?” de dinheiro(Fixnum), servindo para qualquer ‘predicate’ (método terminado em ’?’). Ex: be_include (Array:include?), be_empty(String:empty?).

Mais dois comportamentos:
  • o método ganho(x) deve incrementar o dinheiro em x. :)
  • o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :(

it "o método ganho(x) deve incrementar o dinheiro em x. :)" do
    @carteira.ganho(3)
    @carteira.dinheiro.should == 3 #outra forma! =]        
end

it "o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :(" do
    lambda{ @carteira.gasto(3) }.should raise_error(SemGranaError)

    @carteira.ganho(3)
    @carteira.gasto(3).should == 0        
end

Fácil, não? Agora vamos criar nossa Carteira, para podermos executar os testes(?):

#arquivo carteira.rb
class Carteira

    attr_accessor :dinheiro

    def initialize
    end

    def ganho(dinheiro)
    end

    def gasto(dinheiro)
    end

end
Para executar: spec carteira_spec.rb. Como saída teremos:
FFF

1)
NoMethodError in 'Carteira o valor inicial de dinheiro deve ser 0 :|'
undefined method `dinheiro' for #<Carteira:0xb7c51270>
./carteira_spec.rb:10:

2)
NoMethodError in 'Carteira o método ganho(x) deve incrementar o dinheiro em x. :)'
undefined method `dinheiro' for #<Carteira:0xb7c3b484>
./carteira_spec.rb:17:

3)
NameError in 'Carteira o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :('
uninitialized constant SemGranaError
./carteira_spec.rb:21:

Finished in 0.009035 seconds

3 examples, 3 failures

Cada ‘F’ no início representa uma falha e cada tópico descreve uma falha. É interessante que tudo falhe na primeira vez, para sabermos que funcionou depois que implementamos (oras!).

Implementando a primeira especificação:
    def initialize
        @dinheiro = 0
    end
Executando(spec carteira_spec.rb), temos:
.FF

1)
'Carteira o método ganho(x) deve incrementar o dinheiro em x. :)' FAILED
expected 3, got 0 (using ==)
./carteira_spec.rb:17:

2)
NameError in 'Carteira o método gasto(x) deve decrementar o dinheiro em x, lançar erro SemGranaError caso o valor for se tornar negativo. :('
uninitialized constant SemGranaError
./carteira_spec.rb:21:

Finished in 0.008884 seconds

3 examples, 2 failures

“E cadê o que eu fiz?”. Virou um ’.’, aquele pontinho quer dizer que funcionou.

Finalizando implementando o resto:
#arquivo carteira.rb
class Carteira

    attr_accessor :dinheiro

    def initialize
        @dinheiro = 0
    end

    def ganho(dinheiro)
        @dinheiro = @dinheiro + dinheiro
    end

    def gasto(dinheiro)
        raise SemGranaError if (@dinheiro - dinheiro) < 0
        @dinheiro = @dinheiro - dinheiro
    end

end

class SemGranaError < StandardError

end
Saída:
...

Finished in 0.007725 seconds

3 examples, 0 failures

Experimente outras opções de formatação, para olhar as opções execute: spec – -help

Fim!

Gostou? Se sim, no próximo projeto utilize o RSpec, ou alguma ferramenta BDD ou TDD, seu aplicativo ficará saudável, não tem mais desculpa. (Não preparei nada para a alternativa ‘não’ :D ).

Já estou preparando posts sobre mock e rspec_on_rails.

Comporte-se e até a próxima.

pdf.write "Hello World"

Posted on April 03, 2007
gem install pdf-writer -y
rails recibo
cd recibo
ruby script/plugin install svn://rubyforge.org//var/svn/railspdfplugin/railspdf/
script/generate controller recibos
echo "pdf.text 'Hello, World', :font_size => 72, :justification => :center " > app/views/recibos/print.rpdf
ruby script/server

Pronto, agora é só jogar em um .sh, rodar e abrir o navegador :D .