Como trabalhar com ramificações do Git e migrações do Rails

131

Estou trabalhando em um aplicativo de trilhos com algumas ramificações git e muitas delas incluem migrações de banco de dados. Tentamos ser cuidadosos, mas ocasionalmente algum pedaço de código no master solicita uma coluna que foi removida / renomeada em outro ramo.

  1. Qual seria uma boa solução para "unir" ramificações git com estados de banco de dados?

  2. Quais seriam esses "estados" realmente?

    Não podemos apenas duplicar um banco de dados se tiver alguns GBs de tamanho.

  3. E o que deve acontecer com as mesclagens?

  4. A solução também se traduziria em bancos de dados noSQL?

    Atualmente, usamos MySQL, mongodb e redis


Edição: Parece que eu esqueci de mencionar um ponto muito importante, estou interessado apenas no ambiente de desenvolvimento, mas com grandes bancos de dados (alguns GBs de tamanho).

Kostas
fonte
O que você está fazendo para ter um ambiente executando sua ramificação mestre cujo banco de dados pode ser modificado por outras ramificações? Não entendo qual é o seu fluxo de trabalho ou por que você acha que precisa manter as ramificações sincronizadas com bancos de dados específicos.
Jonah
3
Digamos que temos uma tabela em nosso banco de dados com clientes (nome, email, telefone) e em uma ramificação dividimos uma das colunas (nome -> primeiro_nome + sobrenome). Até mesclar o ramo com o mestre, o mestre e todos os outros ramos com base nele falharão.
Kostas

Respostas:

64

Ao adicionar uma nova migração em qualquer ramificação, execute rake db:migratee confirme a migração e db/schema.rb

Se você fizer isso, no desenvolvimento, poderá mudar para outro ramo que possui um conjunto diferente de migrações e simplesmente executar rake db:schema:load.

Observe que isso recriará o banco de dados inteiro e os dados existentes serão perdidos .

Provavelmente, você só quer executar a produção de um ramo com o qual tenha muito cuidado, para que essas etapas não se apliquem lá (apenas execute rake db:migratecomo de costume). Mas no desenvolvimento, não deve ser grande coisa recriar o banco de dados a partir do esquema, que é o que rake db:schema:loadfará.

Andy Lindeman
fonte
5
Eu acho que isso só resolverá o problema do esquema, os dados serão perdidos a cada migração para baixo e nunca mais serão vistos. Seria uma boa idéia salvar algum tipo de patch de dados de banco de dados que seja salvo ao sair de um ramo e outro que seja carregado ao passar para outro ramo? Os patches devem conter apenas os dados que seriam perdidos no caminho (migrações).
Kostas
4
Se você deseja carregar dados, use db/seeds.rb Não deve ser muito devastador destruir o seu banco de dados de desenvolvimento se você configurar alguns dados de sementes razoáveis ​​lá.
Andy Lindeman
não há necessidade de destruir nada. veja minha solução abaixo. Esteja ciente de que você terá muitas instâncias e quando você alterna ramificações, os dados não estão lá. Isso é totalmente bom se você estiver desenvolvendo com testes.
Adam Dymitruk
Obrigado Andy, esta resposta também é minha pergunta. E concorda em usar db/seeds.rbpara ripopulating os dados db perdidos
pastullo
Para aplicativos grandes e complicados, nos quais você precisa reproduzir erros da vida real localmente, é absolutamente impossível usar um arquivo de sementes, você precisa dos dados reais da produção (ou preparação). E a restauração de um banco de dados pode demorar um pouco, portanto, essa não é uma boa solução para o meu caso.
Joel_Blum 10/01/19
21

Se você possui um banco de dados grande que não pode ser reproduzido facilmente, recomendo o uso das ferramentas normais de migração. Se você deseja um processo simples, é isso que eu recomendaria:

  • Antes de alternar ramificações, faça rollback ( rake db:rollback) para o estado anterior ao ponto de ramificação. Depois de alternar ramificações, execute db:migrate. Isso é matematicamente correto e, desde que você escreva downscripts, ele funcionará.
  • Se você esquecer de fazer isso antes de alternar entre ramificações, em geral, você poderá alternar, reverter e alternar com segurança novamente, portanto, como fluxo de trabalho, é viável.
  • Se você tiver dependências entre migrações em diferentes ramos ... bem, terá que pensar muito.
ndp
fonte
2
Você deve ter em mente que nem todas as migrações são reversíveis, ou seja, não é garantido que o primeiro passo sugerido seja bem-sucedido. Eu acho que no ambiente de desenvolvimento uma boa idéia seria usar rake db:schema:loade rake db:seedcomo @noodl havia dito.
pisaruk
@pisaruk Eu sei que você respondeu isso há seis anos, mas lendo estou curioso sobre o que seria um exemplo de migração não reversível. Estou tendo dificuldade em imaginar uma situação. Eu acho que o mais simples seria uma coluna descartada contendo um monte de dados, mas isso poderia ser "revertido" para ter uma coluna vazia ou uma coluna com algum valor padrão. Você estava pensando em outros casos?
Luke Griffiths
1
Eu acho que você respondeu sua própria pergunta! Sim, uma coluna descartada é um bom exemplo. Ou uma migração de dados destrutiva.
ndp
13

Aqui está um script que eu escrevi para alternar entre ramificações que contêm diferentes migrações:

https://gist.github.com/4076864

Ele não resolverá todos os problemas que você mencionou, mas, com um nome de filial, ele irá:

  1. Retroceda todas as migrações em sua ramificação atual que não existam na ramificação especificada
  2. Descartar quaisquer alterações no arquivo db / schema.rb
  3. Confira o ramo fornecido
  4. Execute quaisquer novas migrações existentes na ramificação especificada
  5. Atualize seu banco de dados de teste

Eu me vejo fazendo isso manualmente o tempo todo em nosso projeto, então achei que seria legal automatizar o processo.

Jon Lemmon
fonte
1
Esse script faz exatamente o que eu quero fazer, adoraria vê-lo em um gancho de pagamento automático.
Brysgo
1
Isto apenas dentro, eu bifurcada sua essência e fez dela um gancho post-checkout: gist.github.com/brysgo/9980344
brysgo
No seu roteiro, você realmente quis dizer git checkout db/schema.rbou quis dizer git checkout -- db/schema.rb? (ou seja, com traços duplos)
user664833
1
Bem, sim ... eu não sabia sobre traços duplos na época. Mas o comando funcionará da mesma forma, a menos que você tenha um ramo chamado db/schema.rb. :)
Jon Lemmon
O comando evoluído git_rails do @ brysgo ( github.com/brysgo/git-rails ) funciona muito bem. Obrigado a você Jon :)
Zia Ul Rehman Mughal
7

Banco de dados separado para cada filial

É a única maneira de voar.

Atualização 16 de outubro de 2017

Voltei a isso depois de algum tempo e fiz algumas melhorias:

  • Adicionei outra tarefa de varredura de namespace para criar uma ramificação e clonar o banco de dados de uma só vez bundle exec rake git:branch.
  • Percebo agora que a clonagem do mestre não é sempre o que você quer fazer assim que a fez mais explícito que a db:clone_from_branchtarefa leva um SOURCE_BRANCHe uma TARGET_BRANCHvariável de ambiente. Ao usá- git:branchlo, usará automaticamente o ramo atual como o SOURCE_BRANCH.
  • Refatoração e simplificação.

config/database.yml

E, para facilitar as coisas, veja como você atualiza seu database.ymlarquivo para determinar dinamicamente o nome do banco de dados com base na ramificação atual.

<% 
database_prefix = 'your_app_name'
environments    = %W( development test ) 
current_branch  = `git status | head -1`.to_s.gsub('On branch ','').chomp
%>

defaults: &defaults
  pool: 5
  adapter: mysql2
  encoding: utf8
  reconnect: false
  username: root
  password:
  host: localhost

<% environments.each do |environment| %>  

<%= environment %>:
  <<: *defaults
  database: <%= [ database_prefix, current_branch, environment ].join('_') %>
<% end %>

lib/tasks/db.rake

Aqui está uma tarefa do Rake para clonar facilmente seu banco de dados de um ramo para outro. Isso leva a SOURCE_BRANCHe a TARGET_BRANCHvariáveis ​​de ambiente. Baseado na tarefa de @spalladino .

namespace :db do

  desc "Clones database from another branch as specified by `SOURCE_BRANCH` and `TARGET_BRANCH` env params."
  task :clone_from_branch do

    abort "You need to provide a SOURCE_BRANCH to clone from as an environment variable." if ENV['SOURCE_BRANCH'].blank?
    abort "You need to provide a TARGET_BRANCH to clone to as an environment variable."   if ENV['TARGET_BRANCH'].blank?

    database_configuration = Rails.configuration.database_configuration[Rails.env]
    current_database_name = database_configuration["database"]

    source_db = current_database_name.sub(CURRENT_BRANCH, ENV['SOURCE_BRANCH'])
    target_db = current_database_name.sub(CURRENT_BRANCH, ENV['TARGET_BRANCH'])

    mysql_opts =  "-u #{database_configuration['username']} "
    mysql_opts << "--password=\"#{database_configuration['password']}\" " if database_configuration['password'].presence

    `mysqlshow #{mysql_opts} | grep "#{source_db}"`
    raise "Source database #{source_db} not found" if $?.to_i != 0

    `mysqlshow #{mysql_opts} | grep "#{target_db}"`
    raise "Target database #{target_db} already exists" if $?.to_i == 0

    puts "Creating empty database #{target_db}"
    `mysql #{mysql_opts} -e "CREATE DATABASE #{target_db}"`

    puts "Copying #{source_db} into #{target_db}"
    `mysqldump #{mysql_opts} #{source_db} | mysql #{mysql_opts} #{target_db}`

  end

end

lib/tasks/git.rake

Essa tarefa criará uma ramificação git fora da ramificação atual (master ou outra), faça check-out e clonará o banco de dados da ramificação atual no novo banco de dados da ramificação. É AF liso.

namespace :git do

  desc "Create a branch off the current branch and clone the current branch's database."
  task :branch do 
    print 'New Branch Name: '
    new_branch_name = STDIN.gets.strip 

    CURRENT_BRANCH = `git status | head -1`.to_s.gsub('On branch ','').chomp

    say "Creating new branch and checking it out..."
    sh "git co -b #{new_branch_name}"

    say "Cloning database from #{CURRENT_BRANCH}..."

    ENV['SOURCE_BRANCH'] = CURRENT_BRANCH # Set source to be the current branch for clone_from_branch task.
    ENV['TARGET_BRANCH'] = new_branch_name
    Rake::Task['db:clone_from_branch'].invoke

    say "All done!"
  end

end

Agora, tudo o que você precisa fazer é executar bundle exec git:branch, inserir o novo nome do ramo e começar a matar zumbis.

Joshua Pinter
fonte
4

Talvez você deva considerar isso como uma dica de que seu banco de dados de desenvolvimento é muito grande? Se você pode usar db / seeds.rb e um conjunto de dados menor para desenvolvimento, seu problema pode ser facilmente resolvido usando schema.rb e seeds.rb da ramificação atual.

Isso pressupõe que sua pergunta esteja relacionada ao desenvolvimento; Não consigo imaginar por que você precisaria alternar regularmente as ramificações na produção.

noodl
fonte
Eu não sabia db/seeds.rb, vou dar uma olhada nisso.
Kostas
3

Eu estava lutando com o mesmo problema. Aqui está a minha solução:

  1. Verifique se o schema.rb e todas as migrações foram registradas por todos os desenvolvedores.

  2. Deve haver uma pessoa / máquina para implantações na produção. Vamos chamar esta máquina como a máquina de mesclagem. Quando as alterações são puxadas para a máquina de mesclagem, a mesclagem automática para schema.rb falhará. Sem problemas. Apenas substitua o conteúdo pelo conteúdo anterior para schema.rb (você pode deixar uma cópia de lado ou obtê-la no github se você a usar ...).

  3. Aqui está o passo importante. As migrações de todos os desenvolvedores agora estarão disponíveis na pasta db / migrate. Vá em frente e execute o bundle exec rake db: migrate. Ele trará o banco de dados na máquina de mesclagem a par de todas as alterações. Ele também irá gerar o schema.rb.

  4. Confirme e envie as alterações para todos os repositórios (controles remotos e indivíduos, que também são remotos). Você deveria estar pronto!

Tabrez
fonte
3

Foi isso que fiz e não tenho certeza de ter coberto todas as bases:

Em desenvolvimento (usando o postgresql):

  • sql_dump db_name> tmp / branch1.sql
  • git checkout branch2
  • dropdb db_name
  • createdb db_name
  • psql db_name <tmp / branch2.sql # (da opção de ramificação anterior)

Isso é muito mais rápido que os utilitários de rake em um banco de dados com cerca de 50 mil registros.

Para produção, mantenha a ramificação principal como sacrossanto e todas as migrações são registradas, shema.rb mescladas corretamente. Siga seu procedimento de atualização padrão.

Paul Carmody
fonte
Para tamanhos de banco de dados suficientemente pequenos, e fazer isso em segundo plano ao verificar uma ramificação, parece uma solução muito boa.
Kostas
2

Você deseja preservar um "ambiente de banco de dados" por filial. Observe o script smudge / clean para apontar para diferentes instâncias. Se você ficar sem instâncias de banco de dados, faça com que o script desative uma instância temporária; portanto, quando você alterna para uma nova ramificação, ela já existe e precisa ser renomeada pelo script. As atualizações do banco de dados devem ser executadas antes de você executar seus testes.

Espero que isto ajude.

Adam Dymitruk
fonte
Essa solução é boa apenas para ramificações "temporárias". Por exemplo, se tivermos uma "borda" de ramificação, se testarmos todos os tipos de coisas malucas (provavelmente com outras sub-ramificações) e depois a mesclarmos ao mestre de tempos em tempos, os dois bancos de dados se afastarão (seus dados não serão ser o mesmo).
Kostas
Esta solução é boa para o exato oposto. Essa é uma solução muito boa se você fizer a versão do script de versão do banco de dados.
Adam Dymitruk
2

Eu experimento totalmente a pita que você está tendo aqui. Na minha opinião, o problema real é que nem todos os ramos têm o código para reverter certos ramos. Estou no mundo do django, então não conheço muito bem o rake. Estou brincando com a idéia de que as migrações vivem em seu próprio repositório que não é ramificado (submódulo git, sobre o qual aprendi recentemente). Dessa forma, todos os ramos têm todas as migrações. A parte complicada é garantir que cada filial seja restrita apenas às migrações de que eles se preocupam. Fazer / acompanhar manualmente isso seria uma pita e propenso a erros. Mas nenhuma das ferramentas de migração foi criada para isso. Esse é o ponto em que estou sem um caminho a seguir.

JohnO
fonte
É uma boa idéia, mas o que acontece quando um ramo renomeia uma coluna? O restante dos galhos estará olhando para uma mesa quebrada.
Kostas
hum - essa é a parte complicada - qual ramo se preocupa com quais migrações. para que você possa "sincronizar" e saber "reverter essa migração", para que a coluna volte.
precisa saber é o seguinte
1

Eu sugeriria uma das duas opções:

Opção 1

  1. Coloque seus dados seeds.rb. Uma boa opção é criar seus dados de sementes via gem FactoryGirl / Fabrication. Dessa forma, você pode garantir que os dados estejam sincronizados com o código, se considerarmos que as fábricas são atualizadas juntamente com a adição / remoção de colunas.
  2. Depois de alternar de um ramo para outro, execute rake db:reset, que efetivamente descarta / cria / semeia o banco de dados.

opção 2

Mantenha manualmente os estados do banco de dados executando sempre rake db:rollback/ rake db:migrateantes / após um check-out da filial. A ressalva é que todas as suas migrações precisam ser reversíveis, caso contrário, isso não funcionará.

Alexander
fonte
0

No ambiente de desenvolvimento:

Você deve trabalhar rake db:migrate:redopara testar se seu script é reversível, mas lembre-se sempre de ter um seed.rbcom a população de dados.

Se você trabalha com o git, o seed.rb deve ser alterado com uma alteração de migração e a execução do db:migrate:redoinício (carregue os dados para um novo desenvolvimento em outra máquina ou novo banco de dados)

Além da 'mudança', com os métodos de subida e descida, seu código sempre cobre cenários para a "mudança" neste momento e quando começa do zero.

Daniel Antonio Nuñez Carhuayo
fonte