Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Ruby e o deep copy

Uma quase alucinada busca de uma forma de fazer deep copy em Ruby, apenas por preguiça de copiar, e pra não ferir o orgulho e repetir código.

Descrição

Eu queria testar se uma chave de minha base de dados realmente estava como chave única.

O código estava nesse ponto, e por orgulho e preguiça, eu não queria copiar e colar a atribuição de valores:


test "Não pode salvar produtos com descrição existente" do
    produto = Produto.new()
    produto.unidade = 'UN'
    produto.preco = 1.25
    produto.categoria = 'beb'
    produto.descricao = "Descrição supimpa"

    mesmo_produto = produto # isso não vai dar certo...
    produto.save
    assert_not mesmo_produto.save, "Salvou duas vezes produto com mesma descrição"
end

Aquele mesmo_produto = produto não fez o que eu queria, mas eu já estava desconfiando. Afinal, eu apenas dei um apelido para um objeto existente. Logo pensei em Deep Copy, mas após algumas leituras, desisti. Teria que usar uma gem para isso, ou fazer a cópia na unha.

Encontrei em algum lugar (realmente perdi a referência e nem me lembro como foi que encontrei) essa dica:


test "Não pode salvar produtos com descrição existente" do
    produto = Produto.new()
    produto.unidade = 'UN'
    produto.preco = 1.25
    produto.categoria = 'beb'
    produto.descricao = "Descrição supimpa"

    mesmo_produto = Produto.new(produto.attributes)
    produto.save
    assert_not mesmo_produto.save, "Salvou duas vezes produto com mesma descrição"
end

Ainda não gostei. Mas o que é que tem nesse produto.attributes? Eu, um quase hacker, fui pro console da minha aplicação fuçar nas coisas:


$ rails c
Running via Spring preloader in process 6543
Loading development environment (Rails 5.0.0.1)
2.3.0 :001 > p = Produto.new
 => #<Produto id: nil, descricao: nil, categoria: nil, preco: nil, unidade: nil, created_at: nil, updated_at: nil>
2.3.0 :002 > p.attributes
 => {"id"=>nil, "descricao"=>nil, "categoria"=>nil, "preco"=>nil, "unidade"=>nil, "created_at"=>nil, "updated_at"=>nil}
2.3.0 :003 >

Próxima tentativa, mais ao meu gosto e experiência com Python:


test "Não pode salvar produtos com descrição existente" do
    salva_produto = Proc.new(|mensagem| {
        p = Produto.new({
            "id" => nil,
            "descricao" => "Descrição supimpa",
            "categoria" => "beb",
            "preco" => 1.25,
            "unidade" => "UN",
            "created_at" => nil,
            "updated_at" => nil,
        }

        assert p.save, mensagem
    })

    # salva primeiro, tem que dar certo.
    salva_produto "Não salvou produto inexistente"

    # salva segundo, tem que dar erro.
    salva_produto "Salvou duas vezes produto com mesma descrição"
end

E se eu criar um proc?


test "Não pode salvar produtos com descrição existente" do
    salva_produto = proc {
        Produto.new({
            "id" => nil,
            "descricao" => "Descrição supimpa",
            "categoria" => "beb",
            "preco" => 1.25,
            "unidade" => "UN",
            "created_at" => nil,
            "updated_at" => nil,
        }).save
    }

    # salva primeiro, tem que dar certo.
    assert salva_produto.call, "Não salvou produto inexistente"

    # salva segundo, tem que dar erro.
    assert_not salva_produto.call, "Salvou duas vezes produto com mesma descrição"
end

Agora sim, voltou a ter cara de teste e é só bater o olho pra entender o que está acontecendo.