Rails - O problemas das migrations com mesmo time-stamp
Ultimamente tenho trabalhado muito em sistemas mas nos últimos dias voltei a trabalhar com websites menores e sites em flash mantidos por admin em Rails. E em um dos plugins que utilizo para dar o ponta-pé inicial nos meus admins preciso criar várias migrations através de um generator no Rails.
O problema é que ao utilizar um generator para criar várias migrations ele irá gerar todas com o mesmo timestamp. Resultando em algo como:
20090227123152_create_product_categories.rb 20090227123152_create_product_images.rb 20090227123152_create_products.rb
E ao executar rake db:migrate teremos um problema.
Este problema já é conhecido porém não é um bug do framework e pode ser resolvido com alterações de arquitetura e consolidando as migrations. Acontece que em casos como o meu não podemos fazer muita coisa além de arrumar o nome das migrations na mão, pois não justifica mudar a arquitetura.
Talvez, futuramente eu migre este plugin para o sistema de engines do Rails 2.3, mas por enquanto preciso continuar com meus generators e para resolver este problema vamos precisar entender mais afundo o que o Rails faz para que o sistema de generators funcione.
Entendendo o problema melhor. Por exemplo, tenho um generator que cria a parte de catálogo de produtos no website. Um catálogo de produtos envolve o model de produtos, categorias de produtos e imagens de cada produto… ou seja, na minha abordagem, 3 tabelas.
Como já expliquei em um post anterior, os generators do Rails utilizam uma instância da classe Manifest para saber quais ações devem ser executadas, por isso precisamos criar um arquivo com o nome do generator e a classe dele para então implementar o método manifest. Como abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class ProductsGenerator < Rails::Generator::Base def manifest record do |m| # ... TODO AS OUTRAS ACOES DO MEU GENERATOR ... # Migrations m.migration_template "migrate/create_product_categories.rb", "db/migrate", :migration_file_name => "create_product_categories" m.migration_template "migrate/create_product_images.rb", "db/migrate", :migration_file_name => "create_product_images" m.migration_template "migrate/create_products.rb", "db/migrate", :migration_file_name => "create_products" end end end |
O que acontece por baixo dos panos é que o Rails vai chamar o método record que recebe um bloco e este bloco vai criar uma instância da classe Rails::Generator::Manifest com todas as ações criadas dentro do bloco. Acontece que Manifest não implementa nenhuma das ações, então através do method_missing ele vai repassar estas ações para a classe Rails::Generator::Commands::Base que será utilizada para estender as classes Rails::Generator::Commands::Create e Rails::Generator::Commands::Destroy, que são responsáveis por executar todas as ações que chamamos dentro do método manifest quando rodamos script/generate e script/destroy .
Então uma solução seria implementar uma ação que seja executada depois de rodar as ações de criação das migrations e alterar o timestamp (existem várias outras formas, mas esta solução foi a que considerei mais simples). Mas vale lembrar que as ações do manifest são executadas ao contrário quando chamamos script/destroy, então teremos um erro se apenas criarmos uma chamada de função que faça esta alteração de nomes no bloco record do manifest, então ela deve ser realmente uma ação que pertença a classe Rails::Generator::Commands::Base.
Desta forma podemos definir como executa-la no Create( script/generate ) e no Destroy( script/destroy ). A solução seria a utilizar a capacidade do Ruby para abrir as classes e implementar algo como abaixo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
class Rails::Generator::Commands::Base def after_generate(*args) ; end end class Rails::Generator::Commands::Create def after_generate(*args) path = "#{Rails.root}/db/migrate/" inc = 0 args.each do |m| # evitando variaveis com mesma referencia migration = Dir.entries(path).find_all {|g| g =~ /[0-9]*_#{m}.rb/ }.first new_migration = Dir.entries(path).find_all {|g| g =~ /[0-9]*_#{m}.rb/ }.first si = migration.index("_") - 1 # second char index new_migration[si..si] = (migration[si..si].to_i + inc).to_s File.rename(path+migration, path+new_migration) inc += 1 end end end class ProductsGenerator < Rails::Generator::Base def manifest record do |m| # ... TODO AS OUTRAS ACOES DO MEU GENERATOR ... # Migrations m.migration_template "migrate/create_product_categories.rb", "db/migrate", :migration_file_name => "create_product_categories" m.migration_template "migrate/create_product_images.rb", "db/migrate", :migration_file_name => "create_product_images" m.migration_template "migrate/create_products.rb", "db/migrate", :migration_file_name => "create_products" m.after_generate "create_product_categories", "create_product_images","create_products" m.readme "INSTALL" end end |
Desta forma temos uma implementação do método after_generate vázio em Base que será usado na classe Destroy, porém temos uma implementação do método que faz as alterações dos nomes das migrations em Create.
Esta foi a abordagem que encontrei para resolver o problema, se alguem tiver alguma outra sugestão seja bem vindo.
Comentário
CATEGORIAS
HomeDesign
SEO
Empreendimento
Cifras
Ruby e Rails
Flex
Photoshop
Flash
XHTML/CSS
JavaScript
Variados
Database
Firefox
Projetos
3D
Projetos
TextMate
Smalltalk
Mac
Livros
ARQUIVO
07/2010 (2)06/2010 (4)
05/2010 (4)
04/2010 (4)
03/2010 (5)
02/2010 (7)
01/2009 (4)
12/2009 (7)
11/2009 (4)
10/2009 (10)
09/2009 (7)
08/2009 (6)
07/2009 (12)
06/2009 (5)
05/2009 (6)
04/2009 (9)
03/2009 (14)
02/2009 (18)
01/2009 (14)
12/2008 (20)
11/2008 (18)
10/2008 (9)
09/2008 (12)
08/2008 (6)
07/2008 (12)
06/2008 (10)
05/2008 (15)
04/2008 (19)




PomoDo e Pomodoro Technique
comentado por Luis
Flash SlideShow
comentado por Gilmar
50% do software é design
comentado por Daniel Lopes
Vetores Grátis - VectorLab Pack
comentado por louis vuitton
Texturas legais
comentado por thomas sabo kommt