Come lavorare con le migrazioni di rami e Rails Git


131

Sto lavorando su un'app di rotaie con alcuni rami git e molti di questi includono migrazioni db. Cerchiamo di stare attenti ma a volte un pezzo di codice nel master richiede una colonna che è stata rimossa / rinominata in un altro ramo.

  1. Quale sarebbe una buona soluzione per "accoppiare" i rami git con gli stati DB?

  2. Quali sarebbero questi "stati" in realtà?

    Non possiamo semplicemente duplicare un database se ha dimensioni di pochi GB.

  3. E cosa dovrebbe accadere con le fusioni?

  4. La soluzione si tradurrebbe anche in database noSQL?

    Attualmente utilizziamo MySQL, mongodb e redis


EDIT: Sembra che mi sia dimenticato di menzionare un punto molto importante, sono interessato solo all'ambiente di sviluppo ma con database di grandi dimensioni (pochi GB di dimensioni).


Cosa stai facendo di avere un ambiente che esegue il tuo ramo principale il cui database può essere modificato da altri rami? Non capisco quale sia il tuo flusso di lavoro o perché pensi di dover sincronizzare i rami con determinati database.
Giona

3
Supponiamo di avere una tabella nel nostro database con i client (nome, email, telefono) e in un ramo abbiamo diviso una delle colonne (nome -> nome_primo + nome_ultimo). Fino a quando non uniremo il ramo con il master, il master e tutti gli altri rami basati su di esso falliranno.
Kostas,

Risposte:


64

Quando si aggiunge una nuova migrazione in qualsiasi ramo, eseguire rake db:migratee confermare sia la migrazione che db/schema.rb

Se lo fai, in fase di sviluppo, sarai in grado di passare a un altro ramo che ha un diverso set di migrazioni ed eseguire semplicemente rake db:schema:load.

Si noti che ciò ricrea l'intero database e i dati esistenti andranno persi .

Probabilmente vorrai solo eseguire la produzione da un ramo con cui stai molto attento, quindi questi passaggi non si applicano lì (esegui rake db:migratecome al solito lì). Ma nello sviluppo, non dovrebbe essere un grosso problema ricreare il database dallo schema, che è ciò rake db:schema:loadche farà.


5
Penso che questo risolverà solo il problema dello schema, i dati andranno persi ad ogni migrazione verso il basso per non essere mai più visti. Sarebbe una buona idea salvare una sorta di patch db-data che viene salvata quando ci si sposta da un ramo e un altro che viene caricato quando si sposta in un altro ramo? Le patch dovrebbero contenere solo i dati che andrebbero persi durante la discesa (migrazioni).
Kostas,

4
Se si desidera caricare i dati, utilizzare db/seeds.rb Non dovrebbe essere troppo devastante aggiungere il proprio DB di sviluppo se si impostano lì dei dati seed ragionevoli.
Andy Lindeman,

non c'è bisogno di nuke nulla. vedi la mia soluzione di seguito. Basta essere consapevoli del fatto che si avranno molte istanze e quando si cambia ramo, i dati non sono lì. Questo va benissimo se stai sviluppando con i test.
Adam Dymitruk, il

Grazie Andy, questa risposta è anche la mia domanda. E accetta di utilizzare db/seeds.rbper ripopolare i dati di db persi
pastullo

Per le app più grandi e complicate in cui è necessario riprodurre localmente i bug della vita reale, è assolutamente impossibile utilizzare un file seed, sono necessari i dati reali dalla produzione (o dalla stadiazione). E il ripristino di un database può richiedere parecchio tempo, quindi no, questa non è una buona soluzione per il mio caso.
Joel_Blum,

21

Se si dispone di un database di grandi dimensioni che non è possibile riprodurre facilmente, si consiglia di utilizzare i normali strumenti di migrazione. Se vuoi un processo semplice, questo è quello che consiglierei:

  • Prima di cambiare ramo, ripristinare (rake db:rollback eseguire il ) allo stato prima del punto di diramazione. Quindi, dopo aver cambiato i rami, corri db:migrate. Questo è matematicamente corretto, e fintanto che scriverai degli downscript, funzionerà.
  • Se ti dimentichi di farlo prima di cambiare ramo, in generale puoi tornare indietro, rollback e cambiare in modo sicuro, quindi penso che sia un flusso di lavoro, è fattibile.
  • Se hai dipendenze tra le migrazioni in diversi rami ... beh, dovrai pensare intensamente.

2
Devi tenere presente che non tutte le migrazioni sono reversibili, detto che il primo passaggio suggerito non è garantito per avere successo. Penso che in ambiente di sviluppo sarebbe una buona idea usare rake db:schema:loade rake db:seedcome aveva detto @noodl.
pisaruk,

@pisaruk So che hai risposto a questa domanda sei anni fa, ma leggendo sono curioso di sapere quale sarebbe un esempio di migrazione non reversibile. Sto facendo fatica a immaginare una situazione. Immagino che la più semplice sarebbe una colonna rilasciata contenente un mucchio di dati, ma questo potrebbe essere "invertito" per avere una colonna vuota o una colonna con un valore predefinito. Stavi pensando ad altri casi?
Luke Griffiths,

1
Penso che tu ti sia risposto da solo! Sì, una colonna rilasciata è un buon esempio. O una migrazione distruttiva dei dati.
ndp,

13

Ecco uno script che ho scritto per passare da una filiale all'altra che contiene diverse migrazioni:

https://gist.github.com/4076864

Non risolverà tutti i problemi che hai citato, ma dato un nome di ramo sarà:

  1. Esegui il rollback di eventuali migrazioni sul tuo ramo corrente che non esistono sul ramo dato
  2. Annulla qualsiasi modifica al file db / schema.rb
  3. Dai un'occhiata al ramo indicato
  4. Esegui eventuali nuove migrazioni esistenti nel ramo specificato
  5. Aggiorna il tuo database di test

Mi ritrovo a farlo manualmente tutto il tempo sul nostro progetto, quindi ho pensato che sarebbe stato bello automatizzare il processo.


1
Questo script fa esattamente quello che voglio fare, mi piacerebbe vederlo messo in un gancio di checkout automatico.
Brysgo,

1
Appena arrivato
brysgo,

Nella tua sceneggiatura, intendevi davvero dire git checkout db/schema.rbo intendevi git checkout -- db/schema.rb? (ovvero con trattini doppi)
user664833

1
Bene sì ... Non sapevo dei doppi trattini in quel momento. Ma il comando funzionerà allo stesso modo a meno che tu non abbia un ramo chiamato db/schema.rb. :)
Jon Lemmon,

Il comando git_rails evoluto di @ brysgo ( github.com/brysgo/git-rails ) funziona alla grande. Grazie a te Jon :)
Zia Ul Rehman Mughal,

7

Database separato per ciascun ramo

È l'unico modo per volare.

Aggiornamento del 16 ottobre 2017

Sono tornato su questo dopo un po 'di tempo e ho apportato alcuni miglioramenti:

  • Ho aggiunto un'altra attività di rake dello spazio dei nomi per creare un ramo e clonare il database in un colpo solo, con bundle exec rake git:branch.
  • Mi rendo conto ora che la clonazione dal master non è sempre ciò che si desidera fare, quindi ho reso più esplicito che l' db:clone_from_branchattività richiede una SOURCE_BRANCHe una TARGET_BRANCHvariabile di ambiente. Quando git:branchlo si utilizza utilizzerà automaticamente il ramo corrente comeSOURCE_BRANCH .
  • Refactoring e semplificazione.

config/database.yml

E per semplificarti, ecco come aggiorni il tuo database.ymlfile per determinare dinamicamente il nome del database in base al ramo corrente.

<% 
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

Ecco un'attività Rake per clonare facilmente il tuo database da un ramo all'altro. Questo richiede variabili ae SOURCE_BRANCHa TARGET_BRANCHambiente. Basato sul compito di @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

Questa attività creerà un ramo git al di fuori del ramo corrente (master o in altro modo), verificarlo e clonare il database del ramo corrente nel database del nuovo ramo. È una buona AF.

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

Ora, tutto ciò che devi fare è correre bundle exec git:branch, inserire il nome del nuovo ramo e iniziare a uccidere gli zombi.


4

Forse dovresti prendere questo come suggerimento che il tuo database di sviluppo è troppo grande? Se è possibile utilizzare db / seeds.rb e un set di dati più piccolo per lo sviluppo, il problema può essere facilmente risolto utilizzando schema.rb e seeds.rb dal ramo corrente.

Ciò presuppone che la tua domanda riguardi lo sviluppo; Non riesco a immaginare perché avresti bisogno di cambiare regolarmente rami nella produzione.


Non sapevo db/seeds.rb, ci darò un'occhiata.
Kostas,

3

Ero alle prese con lo stesso problema. Ecco la mia soluzione:

  1. Assicurarsi che sia schema.rb sia tutte le migrazioni siano archiviate da tutti gli sviluppatori.

  2. Dovrebbe esserci una persona / macchina per le distribuzioni in produzione. Chiamiamo questa macchina come la macchina di unione. Quando le modifiche vengono trasferite nella macchina di unione, l'unione automatica per schema.rb non riuscirà. Senza problemi. Basta sostituire il contenuto con qualunque fosse il contenuto precedente per schema.rb (puoi metterne da parte una copia o ottenerla da Github se la usi ...).

  3. Ecco il passo importante. Le migrazioni da tutti gli sviluppatori saranno ora disponibili nella cartella db / migrate. Vai avanti ed esegui bundle exec rake db: migrate. Porterà il database sulla macchina di unione alla pari con tutte le modifiche. Rigenera anche schema.rb.

  4. Impegnare e inviare le modifiche a tutti i repository (telecomandi e singoli, che sono anche telecomandi). Dovresti aver finito!


3

Questo è quello che ho fatto e non sono sicuro di aver coperto tutte le basi:

In fase di sviluppo (usando postgresql):

  • sql_dump nome_db> tmp / branch1.sql
  • git checkout branch2
  • dropdb nome_db
  • createb nome_db
  • psql nome_db <tmp / branch2.sql # (dal precedente switch di succursale)

Questo è molto più veloce delle utility rake su un database con circa 50K record.

Per la produzione, mantenere il ramo principale come sacrosanto e tutte le migrazioni sono registrate, shema.rb è stato correttamente unito. Passa attraverso la procedura di aggiornamento standard.


Per dimensioni di database abbastanza piccole e averlo fatto in background quando si controlla un ramo sembra una soluzione molto bella.
Kostas,

2

Si desidera preservare un "ambiente db" per ramo. Osserva lo script smudge / clean per indicare diverse istanze. Se esaurisci le istanze db, fai in modo che lo script esegua il roll off di un'istanza temporanea, quindi quando passi a un nuovo ramo, è già lì e deve solo essere rinominato dallo script. Gli aggiornamenti del DB devono essere eseguiti appena prima di eseguire i test.

Spero che questo ti aiuti.


Questa soluzione è valida solo per i rami "temporanei". Ad esempio, se abbiamo un "bordo" di ramo dove testiamo tutti i tipi di cose folli (probabilmente con altri sotto-rami) e poi li uniamo di tanto in tanto al master, i 2 database andranno alla deriva (i loro dati no essere lo stesso).
Kostas,

Questa soluzione è buona per l'esatto contrario. Questa è un'ottima soluzione se si esegue la versione dello script della versione del database.
Adam Dymitruk, il

2

Sperimento totalmente la pita che stai avendo qui. A mio avviso, il vero problema è che tutti i rami non hanno il codice per ripristinare determinati rami. Sono nel mondo del django, quindi non conosco bene il rake. Sto giocando con l'idea che le migrazioni vivono nel loro repository che non si ramifica (git-submodule, di cui ho recentemente appreso). In questo modo tutti i rami hanno tutte le migrazioni. La parte appiccicosa è assicurarsi che ogni ramo sia limitato alle sole migrazioni a cui tengono. Fare / tenere traccia di ciò manualmente sarebbe una pita e soggetta a errori. Ma nessuno degli strumenti di migrazione è stato creato per questo. Questo è il punto in cui sono senza una via da seguire.


Questa è una bella idea ma cosa succede quando un ramo rinomina una colonna? Il resto dei rami guarderà un tavolo rotto.
Kostas,

um - questa è la parte appiccicosa - quale ramo si preoccupa di quali migrazioni. quindi puoi "sincronizzare" e sa "ripristinare questa migrazione" in modo che la colonna torni indietro.
JohnO,

1

Vorrei suggerire una delle due opzioni:

opzione 1

  1. Inserisci i tuoi dati seeds.rb . Una buona opzione è quella di creare i dati del seme tramite gemma FactoryGirl / Fabrication. In questo modo puoi garantire che i dati siano sincronizzati con il codice se assumiamo che le fabbriche vengano aggiornate insieme all'aggiunta / rimozione di colonne.
  2. Dopo il passaggio da un ramo all'altro, eseguire rake db:reset, che elimina / crea / esegue il seeding effettivo del database.

opzione 2

Mantenere manualmente gli stati del database eseguendo sempre rake db:rollback/ rake db:migrateprima / dopo un controllo di succursale. L'avvertenza è che tutte le migrazioni devono essere reversibili, altrimenti non funzionerà.


0

Su ambiente di sviluppo:

Dovresti collaborare rake db:migrate:redoper verificare se lo script è reversibile, ma tieni presente che dovrebbe sempre avere un rapporto seed.rbcon la popolazione di dati.

Se lavori con git, seed.rb dovrebbe essere modificato con una modifica della migrazione e l'esecuzione di db:migrate:redo for the begining (carica i dati per un nuovo sviluppo su un'altra macchina o nuovo database)

A parte "scambio", con i tuoi metodi su e giù il tuo codice sarà sempre uno scenario di copertura per il "cambiamento" in questo momento e quando inizia da zero.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.