nov 17 2008

Tradução: Refatorando seu código legado - Parte 1: No início houve…

Este artigo é uma tradução do artigo Refactoring your legacy code - Part One: In the beginning there was…, caso você encontre erros de português, concordância, tem algum comentário ou agradecimento, FAÇA! É como um amigo meu sempre fala, se você ver alguma coisa errada, conserte!

Lars Jankowfsky é desenvolvedor PHP e participou da International PHP Conference 2008. Ele possui o Frontalaufpral onde ele fala sobre PHP, Agile Development e outros assuntos.

Deixa de papo e vamos a tradução!

Refatorando seu código legado - Parte Um: No início houve…

Como prometido, Tomas e eu iremos procurar mostrar uma visão sobre o que você precisa considerar quando você planejar refatorar suas velhas (legadas) aplicações. Não será um guia detalhado nem muito ordenado. Será mais ou menos nossas experiências e pensamentos nos últimos anos.

Se você precisa de uma introdução mais detalhada (incluindo a nível de código) você deve participar de nosso workshop na Internation PHP Conference 2008. Eu irei ministrar o workshop juntamente com dois dos melhores programadores PHP (Johann Peter Hartmann e Thorsten Rinne), você pode esperar muito mais detalhes.

Com certeza eu irei publicar a apresentação após o IPC - mas não antes ;)

Agora - Refatorando, hmm… Refatorar é, por definição, o meio de modificar (limpando) sem alterar o comportamento. Mas como você pode ter certeza que não está mudando o comportamento se você não está fazendo testes? Isto significa - você só pode refatorar um código se ele possui testes. E é aqui que começa o problema - onde está os testes?

E lá vamos nós. Vamos assumir que você possui um grande projeto - que cresceu durante anos. E agora você está na sorte que seu chefe concorda com você sobre a necessidade dos testes. Mas como iniciar? O time não possui experiência escrevendo testes. Pior ainda, nem sabem o que é Test Driven Development.

Você precisará investir um tempo no time para que eles aprendam como escrever testes. E você experimentará que conhecer e compreender são duas coisas diferentes. Na minha opinião, somente uma coisa pode ser comparada. A diferença entre programação procedural e orientada a objetos. Estranho exemplo? Não.

Deixe-me explicar. É muito fácil pegar um livro e aprender um pouco sobre classes, objetos, etc. Mas somente depois de você usar por um tempo é que você entenderá profundamente o conceito de OOP e buscar o bom senso. O mesmo é para o TDD - escrever testes é mais ou menos questão de minutos. Ou digamos horas se for o seu primeiro teste. Mas entender por quê os testes são importantes e como usá-los. Isto precisa de tempo. E você terá que investir esse tempo.

Depois de eu ter empurrado o time dentro desta direção ( e ei! - isto não foi tão fácil, como muitos desenvolvedores tendem a ser mais conservadores), eles fizeram os testes - simplesmente por quê o chefe disse isso. Mas somente poucas semanas depois, um desenvolvedor disse-me “Ei! Testes são legais! Eu encontrei um bug, eu nunca teria encontrado antes”. Este é o ponto que você precisa para o seu time.

Um pouco mais de teoria. Mas o que dizer dos testes? Por onde começar? Essa resposta é fácil. Você precisa começar com o mais sórdido, mais sujo, e maior arquivo que você pode achar no seu projeto. Eu sei, Eu sei. Eles querem começar com os arquivos mais fáceis, onde os testes são feitos rapidamente e você ver algum progresso. Os desenvolvedores realmente terão medo deste arquivo. Mas o que vai acontecer se você iniciar pelos fáceis? Simples - você verá algum progresso e terá a sensação que as coisas estão tudo bem. Seu time não entenderá os testes completamente - e eles deixam as partes mais sórdidas do código para o final. Você tem que ter forças para resolver os demônios logo no começo. Isso vai demorar um pouco. Mas então você pode ter certeza que o restante vai ser um bom e delicioso pedaço de bolo. De outro modo, você irá mover o parte do risco para o fim do período de refatoração -  e isto não é uma boa idéia.

Você poderia pensar “Esse cara é louco, onde está o problema em escrever testes…?” Ei! Nós estamos falando de velho código crescido ( == espaguete ). Este código é massivamente interconectado. Variáveis globais, classes mistas, selvagens chamadas entre módulos e objetos, etc. É muito difícil escrever testes para isto.

Na verdade, enquanto estiver escrevendo testes você notará que precisa refatorar o código. Você simplesmente tem que refatorar. Pois de outra forma você não pode escrever os testes. Ou - vamos ser honesto - você pode. Se você escrever mocks e stubs que é o triplo do tamanho do seu código. Isto é exatamente o que desenvolvedores sem experiência com TDD irão fazer. Se você ver isso - chutem eles! E em seguida novamente, por que a sensação é muito boa ;).

Cada teste leva ao refatoramento. Como o nó górdio (Vejam no wikipedia) você começará a puxar o worm, e irá puxar, puxar e puxar. E após algum tempo a refatoração estará pronta e o primeiro teste pode ser escrito. Ok, só estava brincando. Mas há alguma verdade nesta afirmação. Você precisa refatorar o código primeiro - então escrever o teste. Sem grandes stubs.

E isto será trabalhoso - especialmente no início, onde tudo está interconectado. E algumas dependencias precisam ser abordadas e resolvidas. Primeiro. Separe o modelo da visão. Na visão, deixe sem lógica - somente I/O. E então teste o modelo. Algumas pessoas até mesmo testam as visões com testes unitários. Eu não. Eu prefiro testar o modelo, com pequenas visões sem lógica e deixar o resto para os testes com selenium.

O sórdido. Leva tempo. Muito trabalho. Mas acredite em mim. Não há outra forma.

Durante a leitura, você pode ficar com a idéia de que você vai fugir com os testes de aceitação. (selenium). Você acha que isso é um bom negócio? A idéia é sensacional. Se você escrever testes do selenium para toda a aplicação - então refatorar - e você não quebrar nenhum teste, você pode ter certeza que você não alterou qualquer funcionalidade. Boa ideia? Não vai funcionar. Desculpe

Lembre-se. Eu estou falando de uma grande aplicação que cresceu. Ela terá toneladas de funcionalidades. Isto significa centenas ou milhares de testes. E estes testes podem durar muito. Eu sei disso. Eu fui até esta estrada - e ela é uma estrada de mão uníca. Nós terminamos com um teste completo rodando por 10 horas. Isso não é desenvolvimento guiado a testes. Para fazer isso que você precisa instantaneamente - instantaneamente - de feedback. Ninguém pode trabalhar assim.

Contínua na parte dois.

Essa foi a primeira parte do artigo. Mais uma vez, espero que vocês gostem. Qualquer dúvida entre em contato! Não deixe de me passar seu feedback.


nov 12 2008

Tradução: Rails Database Migrations - Parte IV

Esta é a última parte da tradução do guia sobre Rails Database Migrations. Espero que vocês gostem, agora irei partir para tradução de mais guias :) Mais uma vez, dúvidas, erros de português, tradução com sentido diferente, por favor, me comuniquem!! Podem me mandar email (caironoleto NOT SPAM at gmail dot com ), pelo twitter (@caironoleto) ou pela seção de contato ou pelo Gtalk, msn, skype, enfim, me comuniquem!!

Rails Database Migration - Parte I

Rails Database Migration - Parte II

Rails Database Migration - Parte III

6. Armazenando esquemas e você

6.1 Quais são os arquivos do esquema?

Migrações, poderosas como são, não são fontes autorizada para o esquema do seu banco de dados. Esse papel cabe ao schema.rb ou ao um arquivo SQL gerado pelo Active Record através da análise do banco de dados. Este arquivo não foi projetado para ser editado, ele é uma representação do estado atual do banco de dados.

Não é necessário (e isto é um erro propenso) para implantar uma nova instancia de uma aplicação repetindo o histórico inteiro da migração. É mais simples e rápido apenas carregar dentro do banco de dados a descrição do esquema atual.

Por exemplo, esta é a forma de como o banco de dados de testes é criado: o banco de dados atual de desenvolvimento é excluído (tanto para o schema.rb ou para development.sql) e então carregado dentro do banco de dados de teste.

Arquivos de esquema também são uteis se deseja olhar rapidamente quais atributos o objeto do Active Record possui. Estas informações não está no código do modelo e frequentemente está espalhado pelas várias migrações mas está resumido no arquivo de esquema. O plugin annotate_models, adiciona automaticamente cada (e atualiza) um dos comentários no início de cada modelo resumindo o esquema, que pode ser de seu interesse.

6.2 Formas de armazenar o esquema

Existe duas formas de armazenar o esquema. Uma é setar em config/environment.rb atribuindo o config.active_record.schema_format, que pode ser :sql ou :ruby.

Se :ruby é selecionada então o esquema será armazenado em db/schema.rb. Se você olhar este arquivo você verá que não encontrará um pouco mais do que uma grande migração:

ActiveRecord::Schema.define(:version => 20080906171750) do
  create_table “authors”, :force => true do |t|
    t.string   “name”
    t.datetime “created_at”
    t.datetime “updated_at”
  end

  create_table “products”, :force => true do |t|
    t.string   “name”
    t.text     “description”
    t.datetime “created_at”
    t.datetime “updated_at”
    t.string   “part_number”
  end
end

De muitas formas é exatamente isso. Este arquivo é criado pela examinação do banco de dados e expressado em estruturas usando create_table, add_index e assim por diante. Por causa da independência do banco de dados ele deve carregar dentro de qualquer banco de dados que o Active Record suporta. Isso poderia ser muito útil se você quiser distribuir a aplicação para rodar em vários banco de dados.

Existe porém uma desvantagem: schema.rb não expressa itens específicos de banco de dados como constraints de chaves estrangeiras, triggers ou stored procedures. Enquanto na migração você pode executar SQL customizadas, o esquema armazenado não pode reconstituir essas atribuições do banco de dados. Se você usar recursos como este, então você deve atribuir o esquema para :sql.

Em vez de usar o esquema de armazenamento do Active Record a estrutura será armazenada usando uma ferramenta específica do banco de dados (pela tarefa rake db:structure:dump) dentro de db/#\{RAILS_ENV\}_structure.sql. Por exemplo para o PostgreSQL a utilidade pg_dump é usada e para o MySQL este arquivo irá conter a saída de SHOW CREATE TABLE para as várias tabelas. Carregando este esquema é uma simples questão de executar as declarações SQL contida dentro.

Por definição irá fazer uma copia perfeita da estrutura do banco de dados mas isso vai impedir o carregamento do esquema dentro de outros banco de dados que não seja um dos utilizados para criá-lo.

6.3 Armazenamento de esquema e controle de código

Por causa da autoridade do código do seu esquema de banco de dados, é altamente recomendado que você verifique dentro de seu controle de código.

7. Active Record e Integridade Referencial

O Active Record é a maneira inteligente de permanecer em seus modelos, não no banco de dados. Alguns recursos como triggers ou constraints de chaves estrangeiras, que empurra alguma inteligência de volta para os banco de dados não são muito usados.

Validações como validates_uniqueness_of é uma forma de seus modelos podem valer a integridade dos dados. A opção :dependent em associações adiciona aos modelos automaticamente destruir os objetos filhos que seus pais são destruídos. Como tudo que funciona a nível de aplicação, estes não podem garantir integridade referencial e algumas pessoas aumentam com constraints de chaves estrangeiras.

Apesar de o Active Record não fornecer todas as ferramentas para trabalhar diretamente com todos os recursos, o método execute pode ser usado para executar SQL arbitrárias. Há também uma série de plugins como redhillonrails que adicionam suporte a chaves estrangeiras para o Active Record (incluindo suporte para destruir chaves estrangeiras no schema.rb)


nov 4 2008

Tradução: Rails Database Migrations - Parte III

Esta é a terceira parte da tradução do artigo Rails Database Migrations. Mais uma vez, antes de desfrutarem da leitura, quero dizer-lhes que se encontrar erros de português ou a tradução com sentido diferente, por favor, comuniquem-me! Avisem-me por email, twitter (@caironoleto), ou qualquer mensageiro!! :P

Rails Database Migrations - Parte I

Rails Database Migrations - Parte II

4. Rodando migrações

Rails fornece um conjunto de tarefas rake para trabalhar com as migrações, que se resume em rodar alguns conjuntos de migrações. A tarefa rake mais relatada que você provavelmente usará é db:migrate. Na sua forma mais básica certamente rodará o método up para todas as migrações que ainda não foram rodadas. Se não existir migrações ele sai.

Se você especificar uma migração, o Active Record irá rodar as migrações requeridas (up ou down) até que ela tenha chegado nessa versão específica. A versão é o prefixo numérico do nome de uma migração. Por exemplo para migrar até a versão 20080906120000 execute

rake db:migrate VERSION=20080906120000

Se a versão for maior do que a versão corrente (ou seja, está migrando para cima) irá rodar o método up de em todas as migrações acima e incluindo a versão 20080906120000, se a migração for para baixo, então será executado os métodos down de todas as migrações para baixo até, mas não incluindo, 20080906120000.

4.1 Reversão

Uma tarefa comum é regressar a última migração, por exemplo, se você cometeu um engano e deseja corrigi-lo. Ao invés de monitorar o método down com a migração anterior, você pode rodar

rake db:rollback

Isso irá rodar o método down da migração mais recente. Se você precisa se desfazer de várias migrações, você pode fornecer o parâmetro STEP:

rake db:rollback STEP=3

irá rodar o método down das 3 últimas migrações.

A tarefa db:migrate:redo é um atalho para fazer uma reversão e a migração de volta. Assim como na tarefa db:rollback você pode usar o parâmetro STEP se você precisar voltar em mais de uma versão, por exemplo

rake db:migrate:redo STEP=3

Nenhuma dessas tarefas Rake fazem qualquer coisa que você não poderia fazer com db:migrate, são simplesmente mais convenientes, desde que você não precise especificar explicitamente de uma migração para outra.

Finalmente, a tarefa db:reset irá destruir sua base de dados, recria-la e carregar o schema atual dentro dela.

4.2 Especificando uma migração

Se você precisa especificar uma migração para cima ou para baixo, as tarefas db:migrate:up e db:migrate:down irão fazer isso. Basta especificar a versão apropriada e a migração correspondente e terá seu método up ou down invocado, por exemplo

rake db:migrate:up VERSION=20080906120000

irá rodar o método up da migração 20080906120000. Estas tarefas checa se a migração já tenha sido executada, se por exemplo db:migrate:up VERSION=20080906120000 não irá fazer nada se o Active Record acreditar que 20080906120000 já tenha sido executada.

4.3 Sendo comunicativo

Por padrão, as migrações falam exatamente o que elas estão fazendo e o tempo de duração. Uma migração criando uma tabela e adicionando um index produz uma saída como esta

== 20080906170109 CreateProducts: migrating ===================================
– create_table(:products)
   -> 0.0021s
– add_index(:products, :name)
   -> 0.0026s
== 20080906170109 CreateProducts: migrated (0.0059s) ==========================

Vários método fornecem para você o controle tudo isto:

  • suppress_messages suprime qualquer mensagem gerada pelo bloco
  • say saída de texto (o segundo argumento controle se é recortado ou não)
  • say_with_time saída de texto com o tempo utilizado pelos blocos. Se o bloco retornar um inteiro, assume-se que este é o número de linhas afetadas.

Por exemplo, esta migração

class CreateProducts < ActiveRecord::Migration
  def self.up
    suppress_messages do
      create_table :products do |t|
        t.string :name
        t.text :description
        t.timestamps
      end
    end
    say “Created a table”
    suppress_messages {add_index :products, :name}
    say “and an index!”, true
    say_with_time ‘Waiting for a while’ do
      sleep 10
      250
    end
  end

  def self.down
    drop_table :products
  end
end

gera a seguinte saída

== 20080906170109 CreateProducts: migrating ===================================
– Created a table
   -> and an index!
– Waiting for a while
   -> 10.0001s
   -> 250 rows
== 20080906170109 CreateProducts: migrated (10.0097s) =========================

Se você quiser que o Active Record mantenha-se em silêncio, então execute rake db:migrate VERBOSE=false irá suprimir qualquer saída.

5. Usando Models nas suas migrações

Ao criar ou atualizar dados na sua migração, muitas vezes, é tentador utilizar um de seus models. Afinal eles existem para fornecer acesso fácil nos dados subjacentes. Isto pode ser feito mas um certo cuidado devem ser observados.

Considere por exemplo a migração que usa o modelo Product para atualizar a linha na tabela correspondente. Alice depois atualiza o modelo Product, adicionando uma nova coluna e uma validação. Bobs volta do feriado, atualiza o código e roda as migrações pendentes com rake db:migrate, incluindo o modelo que é utilizado para o Product. Quando o código é atualizado e só então o modelo Product possui a atualização adicionada pela Alice. O banco de dados entretanto não é atualizado e assim não possui a coluna e então gerará um erro, por que a validação para esta coluna ainda não existe.

Frequentemente eu preciso atualizar as linhas no banco de dados sem escrever SQL pelas minhas mãos. Eu não estou usando qualquer especialidade do modelo. Um padrão para isso é definir uma cópia do modelo dentro da própria migração, por exemplo

class AddPartNumberToProducts < ActiveRecord::Migration
  class Product < ActiveRecord::Base
  end

  def self.up
    
  end

  def self.down
    
  end
end

A migração possuirá uma própria copia do modelo Product e não mais precisará saber sobre o modelo Product definido na própria aplicação.

5.1 Lidando com as mudanças no modelo

Por razões de performance, as informações sobre as colunas de um modelo é cacheada. Por exemplo, se você adicionar a coluna na tabela e tentar usar o modelo correspondente para inserir uma nova linha, ele pode tentar usar as informações antigas. Você pode forçar o Active Record para reler a informação da coluna com o método reset_column_information, por exemplo

class AddPartNumberToProducts < ActiveRecord::Migration
  class Product < ActiveRecord::Base
  end

  def self.up
    add_column :product, :part_number, :string
    Product.reset_column_information
    
  end

  def self.down
    
  end
end

Continuação: Rails Database Migrations - Parte IV


out 20 2008

Tradução: Rails database Migrations - Parte II

Esta é a segunda parte da tradução do artigo Rails database Migrations. Mais uma vez, antes de desfrutarem da leitura, quero dizer-lhes que se encontrar erros de português ou a tradução com sentido diferente, por favor, comuniquem-me! Avisem-me por email, twitter (@caironoleto), ou qualquer mensageiro!! :P

Rails Database Migrations - Parte I

2.0 Criando migrações

2.1 Criando um Modelo

Um Modelo e os geradores de scaffold criará migrações apropriadas para criação de um novo Modelo. Esta migração contém instruções prontas para criação de uma tabela relevante. Se você mostrar pro Rails as colunas que você precisa então as declarações já estarão criadas. Por exemplo, rodando ruby script/generate model Product name:string description:text gerará uma migração como esta

class CreateProducts < ActiveRecord::Migration
  def self.up
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end

  def self.down
    drop_table :products
  end
end

Você pode adicionar quantos nomes/tipo de colunas como você deseja. Por padrão t.timestamps (Que criam as colunas updated_at e created_at que serão populadas automáticamentes pelo Rails) podem ser adicionadas para você.

2.2 Criando uma migração autônoma

Se você criar uma migração para outros propósitos (Por exemplo, adicionar uma coluna numa tabela já existente) então você pode usar o gerador de migrações:

ruby script/generate migration AddPartNumberToProducts

Criará uma migração vazia mas já apropriada com o nome da migração:

class AddPartNumberToProducts < ActiveRecord::Migration
  def self.up
  end

  def self.down
  end
end

Se o nome da migração é na forma AddXXXtoYYY ou RemoveXXXtoYYY e for seguida de uma lista de nomes de colunas e seus tipos então a migração será criada contendo declarações apropriadas para adicionar e remover colunas.

ruby script/generate migration AddPartNumberToProducts part_number:string

Gerará:

class AddPartNumberToProducts < ActiveRecord::Migration
  def self.up
    add_column :products, :part_number, :string
  end

  def self.down
    remove_column :products, :part_number
  end
end

Similarmente:

ruby script/generate migration RemovePartNumberFromProducts part_number:string

gerará

class RemovePartNumberFromProducts < ActiveRecord::Migration
  def self.up
    remove_column :products, :part_number
  end

  def self.down
    add_column :products, :part_number, :string
  end
end

E você não está limitado a gerar magicamente apenas uma coluna, por exemplo

ruby script/generate migration AddDetailsToProducts part_number:string price:decimal

gerará

class AddDetailsToProducts < ActiveRecord::Migration
  def self.up
    add_column :products, :part_number, :string
    add_column :products, :price, :decimal
  end

  def self.down
    remove_column :products, :price
    remove_column :products, :part_number
  end
end

E sempre o que foi gerado é apenas um ponto de partida, você pode adicionar ou remover a partir dele o que você quiser, como você achar melhor.

3. Escrevendo uma migração

Uma vez que você criou uma migração usando um dos geradores, é a hora de trabalhar!

3.1 Criando uma tabela

create_table será um dos métodos mais usados. Tipicamente usada assim

create_table :products do |t|
  t.string :name
end

criará uma tabela products com uma coluna chamada name (e como discutido anteriormente, implicitamente criará uma coluna id).

O objeto “renderizado” (yielded) no bloco permite você criar colunas na tabela. Existe duas formas de se fazer isso. A primeira é algo assim

create_table :products do |t|
  t.column :name, :string, :null => false
end

a segunda forma, que é chamada de migração “sexy”, elimina redundância dos métodos. Em vez de string, integer, etc os métodos são criados a partir do tipo da coluna, onde os parâmetros subseqüentes são idênticos.

Por padrão create_table criará uma chave primária chamada id. Você pode alterar o nome da chave primária com a opção :primary_key (Não esqueça de atualizar o modelo correspondente) ou se você não precisar de uma chave primária (por exemplo HABTM (has and belongs to many) join table) você pode passar :id ⇒ false. Se você precisa passar alguma informação específica, você pode passar um fragmento SQL na opção :options. Por exemplo:

create_table :products, :options => “ENGINE=BLACKHOLE” do |t|
  t.string :name, :null => false
end

Irá anexar ENGINE=BLACKHOLE na sql usada para criar a tabela (Quando se usa MySQL por padrão é usado “ENGINE=InnoDB”.

Os tipos que o Active Record suporta são :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.

Eles vão ser mapeados apropriadamente para cada banco de dados, por exemplo com MySQL :string é mapeada para VARCHAR(255). Você pode criar colunas e tipos não suportados pelo Active Record usando uma sintaxe não sexy, por exemplo:

create_table :products do |t|
  t.column :name, ‘polygon’, :null => false
end

Esta forma, no entanto, dificulta a portabilidade para outros banco de dados.

3.2 Mudando tabelas

O primo mais próximo de create_table é change_table. Usado para alterar tabelas existentes, é similarmente usada como o create_table mas o objeto “rendenrizado” (yielded) para o bloco conhece mais truques. Por exemplo

change_table :products do |t|
  t.remove :description, :name
  t.string :part_number
  t.index :part_number
  t.rename :upccode, :upc_code
end

remove a coluna description e name, adiciona a coluna part_number e adiciona um index nesta mesma coluna. E por ultimo altera o nome da coluna upccode. É o mesmo que fazer

remove_column :products, :description
remove_column :products, :name
add_column :products, :part_number, :string
add_index :products, :part_number
rename_column :products, :upccode, :upc_code

Você não deve manter repetindo o nome da tabela e de todos os grupos de declarações relatados para modificar uma tabela em particular. Em uma transformação individual os nomes são curtos, por exemplo remove_column torna-se remove e add_index torna-se index.

3.3 Helpers especiais

O Active Record provê alguns atalhos para as funcionalidades mais comuns. Por exemplo, é muito comum adicionar as colunas created_at e updated_at e o método que faz exatamente isso é:

create_table :products do |t|
  t.timestamps
end

Criará uma tabela products com essas duas colunas

change_table :products do |t|
  t.timestamps
end

Adiciona essas duas colunas a uma tabela existente.

Outro helper é chamado de references (Também disponível como belongs_to ). Na sua forma mais simples adiciona alguma habilidade de reutilização.

create_table :products do |t|
  t.references :category
end

então criará a coluna category_id com o tipo apropriado. Note que você deve passar o nome do modelo e não da coluna. O Active Record adicionará o sufixo _id para você. Se você tiver uma associação belongs_to polimórfica então references irá adicionar as colunas referidas

create_table :products do |t|
  t.references :attachment, :polymorphic => {:default => ‘Photo’}
end

irá adicionar a coluna attachment_id e a coluna attachment_type com o valor padrão Photo.

Se os helpers providos pelo Active Record não for suficiente, você pode utilizar a função execute para executar SQL arbitrárias.

Para mais detalhes e exemplos de métodos individuais dê uma olhada na documentação da API, em particular a documentação para ActiveRecord::ConnectionAdapters::SchemaStatements (na qual provê os métodos disponíveis nos métodos up e down), ActiveRecord::ConnectionAdapters::TableDefinition (na qual provê os métodos disponíveis para o objeto “rendenrizado” (yielded) por create_table) e o ActiveRecord::ConnectionAdapters::Table (na qual provê os métodos disponíveis para o objeto “rendenrizado” (yielded) por change_table).

3.4 Escrevendo seu método down

O método down da sua migração deve reverter as transformações concluídas pelo método up. Em outras palavras, o banco de dados deve se manter inalterado se você fizer um up seguido de um down. Por exemplo, se você criar uma tabela no método up você deverá excluí-la no método down. Deve ser sábio e precisamente inverso ao método up. Por exemplo

class ExampleMigration < ActiveRecord::Migration

  def self.up
    create_table :products do |t|
      t.references :category
    end
    #add a foreign key
    execute “ALTER TABLE products ADD CONSTRAINT fk_products_categories FOREIGN KEY (category_id) REFERENCES categories(id)”

    add_column :users, :home_page_url, :string

    rename_column :users, :email, :email_address
  end

  def self.down
    rename_column :users, :email_address, :email
    remove_column :users, :home_page_url
    execute “ALTER TABLE products DROP FOREIGN KEY fk_products_categories”
    drop_table :products
  end
end

as vezes a sua migração pode fazer algumas coisas irreversíveis, por exemplo quando você destrói alguns dados. Em casos como esse onde você não pode reverter uma migração, você pode lançar IrreversibleMigration para o seu método down. Se alguém tentar reverter a sua migração uma mensagem será mostrada falando que ela não será completa.

E aqui finaliza a segunda parte do artigo.

Continuação: Rails Database Migrations - Parte III

Continuação: Rails Database Migrations - Parte IV

Até a próxima!


set 23 2008

Tradução: Rails database Migrations - Parte I

Olá, este é o primeiro de quatro partes desse artigo sobre as Migrations do Rails. Antes de se desfrutarem da leitura quero dizer-lhes que se encontrar erros de português ou a tradução com sentido diferente, por favor, comuniquem-me! Avisem-me por email, twitter (@caironoleto), ou qualquer mensageiro!! :P

Migrações em Banco de Dados em Rails

Migrações é a forma conveniente de você alterar seu banco de dados de uma maneira organizada e estruturada. Você poderia editar fragmentos de SQL na mão mas você teria a responsabilidade de comunicar aos outros desenvolvedores que eles precisam ir lá e executá-los. Você também necessita acompanhar as mudanças na máquina de produção na próxima vez que você for fazer deploy. O Active Record marca as migrações que já foram executadas e tudo que você precisa fazer é atualizar seu código e rodar rake db:migrate. O Active Record irá trabalhar para que suas migrações sejam executadas.

Migrações é a forma de você descrever essas transformações usando Ruby. A grande coisa disso tudo (como muito das funcionalidades do Active Record) é a independência do banco de dados: você não precisa se preocupar com mais nenhuma sintaxe para CREATE TABLE, ou que você se preocupe sobre variações de SELECT * (você pode abstrair os requisitos específicos de banco de dados SQL). Por exemplo, você poderia usar SQLite 3 no desenvolvimento, mas MySQL em produção.

Você aprenderá tudo sobre migrações incluindo:

  • Os geradores que você pode usar para criá-los
  • Os métodos que o Active Record provê para manipular seu banco de dados
  • As tarefas Rake que você pode manipular
  • Como eles são relativo ao schema.rb

1. Anatomia de uma migração

Antes de eu mergulhar nos detalhes de uma migração, aqui estão alguns exemplos curtos de coisas que você pode fazer:

class CreateProducts < ActiveRecord::Migration
  def self.up
    create_table :products do |t|
      t.string :name
      t.text :description

      t.timestamps
    end
  end

  def self.down
    drop_table :products
  end
end

A migração adiciona uma tabela chamada products com uma coluna string chamada name e uma coluna text chamada description. Uma chave primária chamada id também será adicionada, no entanto por padrão não precisamos pedir isso. As colunas timestamps created_at e updated_at que o Active Record preenche automaticamente também são adicionadas. Revertendo essa migração simplesmente remove a tabela.

Migrações não tem limite para alterar o esquema. Você pode usar para corrigir dados errados no banco de dados ou popular novos campos:

class AddReceiveNewsletterToUsers < ActiveRecord::Migration
  def self.up
    change_table :users do |t|
      t.boolean :receive_newsletter, :default => false
    end
    User.update_all ["receive_newsletter = ?", true]
  end

  def self.down
    remove_column :users, :receive_newsletter
  end
end

Esta migração adiciona a coluna receive_newsletter para a tabela users. Nós queremos que o padrão seja falso para novos usuários, mas para os usuários existentes nos consideramos que eles já fizeram a sua opção, então nos usamos o modelo User para setar a bandeira para true para usuários existentes.

1.1 Migrações são classes

A migração é uma subclasse de ActiveRecord::Migration que implementa dois métodos: up (para realizar as transformações exigidas) e down (reverte o que foi feito).

O Active Record prover métodos para executar as tarefas comuns na definição dos dados independente do banco de dados (Você verá mais detalhes mais tarde):

  • create_table
  • change_table
  • drop_table
  • add_column
  • remove_column
  • change_column
  • rename_column
  • add_index
  • remove_index

Se você precisa executar tarefas específicas para seu banco de dados (por exemplo, criar uma chave estrangeira) então a função execute permite executar SQL arbitrarias. As migrações é apenas uma classe regular em Ruby, então você não esta limitado a apenas essas funções. Por exemplo, após adicionar uma coluna, você pode escrever código para setar o valor dessa coluna para dados existentes (se necessário usando seus modelos).

1.2 O que está no nome

Migrações sao armazenadas em arquivos em db/migrate, uma para cada classe de migração. O nome dos arquivos é na forma de YYYYMMDDHHMMSS_create_products.rb, ou seja, uma hora UTC identificando a migração seguida de um sublinhado e seguido do nome da migração. O Nome da classe de migração deve bater com a ultima parte do arquivo. Por exemplo, 20080906120000_create_products.rb deveria definir CreateProducts e 20080906120001_add_details_to_products.rb deveria definer AddDetailsToProducts. Se você acha que necessita mudar o nome do arquivo, então você deve atualizar o nome da classe dentro do arquivo ou então o Rails ira queixar-se que não existe a classe.

Internamente Rails usa apenas o numero da migração (data) para identificá-lo. Antes do Rails 2.1 os números das migrações eram iniciados em 1 e apenas incrementado cada vez que era gerado uma nova migração. Com múltiplos desenvolvedores era mais fácil haver colisões, necessitando você voltar e reordena-los. Você pode reverter para o esquema da velha numeração setando config.active_record.timestamped_migrations para false no environment.rb.

A combinação do tempo e o registro que permite executar as migrações foram a forma como o Rails fez para manipular as situações comuns que ocorrem com múltiplos desenvolvedores.

Por exemplo Alice adicionou a migração 20080906120000 e 20080906123000 e Bob adicionou 20080906124500 e rodou. Alice finaliza suas mudanças e checa nas suas migrações e Bob puxa as mais recentes atualizações. Rails sabe que ele não rodou as duas migrações de Alice assim rake db:migrate irá executá-los (Apesar que a migração do Bob seja executada uma hora mais tarde), e similarmente fazendo regreção da migração não teria executado esses dois métodos.

Claro que isso não substitui a comunicação dentro da equipe, por exemplo, na migração da Alice ela removeu uma tabela que Bob assumiu a existência na sua migração, então claro que um problema irá acontecer.

1.3 Mudando migrações

Ocasionalmente você comete um erro enquanto escrevia a migração. Se você já tiver executado a migração, então você não pode simplesmente editar e executar novamente a migração: Rails acha que a migração já foi executada, então ele não fará nada quando você rodar rake db:migrate. Você deve voltar a migração (por exemplo, com rake db:rollback), editar sua migração e rodar rake db:migrate para a correta versão.

No geral, editar migrações existentes não é uma boa idéia: você criará trabalho extra para você mesmo e para seus parceiros de trabalho e causar um maior problema se a versão da migração for rodada em uma máquina de produção. Em vez disso, você deve escrever uma nova migração para realizar as alterações que você solicita. Editando uma recém migração gerada e que ainda não foi commitada para o controle de versão (ou que geralmente não foi propagada além da sua máquina de desenvolvimento) é relativamente inofensivo. Apenas use com bom senso.

Continuação: Rails Database Migrations - Parte II

Continuação: Rails Database Migrations - Parte III

Continuação: Rails Database Migrations - Parte IV

E aqui finaliza a primeira parte deste artigo.

Até a próxima!