So arbeiten Sie mit Git-Zweigen und Rails-Migrationen

131

Ich arbeite an einer Rails-App mit einigen Git-Zweigen, von denen viele DB-Migrationen enthalten. Wir versuchen vorsichtig zu sein, aber gelegentlich fragt ein Teil des Codes im Master nach einer Spalte, die in einem anderen Zweig entfernt / umbenannt wurde.

  1. Was wäre eine gute Lösung, um Git-Zweige mit DB-Zuständen zu "koppeln"?

  2. Was wären diese "Staaten" eigentlich?

    Wir können eine Datenbank nicht einfach duplizieren, wenn sie einige GB groß ist.

  3. Und was soll mit Zusammenführungen passieren?

  4. Würde die Lösung auch in noSQL-Datenbanken übersetzt?

    Wir verwenden derzeit MySQL, Mongodb und Redis


EDIT: Sieht so aus, als hätte ich vergessen, einen sehr wichtigen Punkt zu erwähnen. Ich interessiere mich nur für die Entwicklungsumgebung, aber für große Datenbanken (einige GB groß).

Kostas
quelle
Was tun Sie, wenn in einer Umgebung Ihr Hauptzweig ausgeführt wird, dessen Datenbank von anderen Zweigen geändert werden kann? Ich verstehe nicht, was Ihr Workflow ist oder warum Sie denken, dass Sie Zweige mit bestimmten Datenbanken synchronisieren müssen.
Jonah
3
Angenommen, wir haben eine Tabelle mit Clients (Name, E-Mail, Telefon) in unserer Datenbank und in einem Zweig teilen wir eine der Spalten (Name -> Vorname + Nachname). Bis wir den Zweig mit dem Master zusammenführen, schlagen der Master und alle anderen darauf basierenden Zweige fehl.
Kostas

Antworten:

64

Wenn Sie in einem Zweig eine neue Migration hinzufügen, führen Sie rake db:migratedie Migration und aus db/schema.rb

Wenn Sie dies tun, können Sie in der Entwicklung zu einem anderen Zweig mit anderen Migrationen wechseln und einfach ausführen rake db:schema:load.

Beachten Sie, dass dadurch die gesamte Datenbank neu erstellt wird und vorhandene Daten verloren gehen .

Sie möchten wahrscheinlich nur die Produktion von einem Zweig aus ausführen, mit dem Sie sehr vorsichtig sind, daher gelten diese Schritte dort nicht (nur dort rake db:migratewie gewohnt ausführen ). In der Entwicklung sollte es jedoch keine große Sache sein, die Datenbank aus dem Schema neu zu erstellen rake db:schema:load.

Andy Lindeman
quelle
5
Ich denke, dies wird nur das Schema-Problem lösen, die Daten gehen bei jeder Abwärtsmigration verloren und werden nie wieder gesehen. Wäre es eine gute Idee, eine Art DB-Daten-Patch zu speichern, der beim Verlassen eines Zweigs gespeichert wird, und einen anderen, der beim Umzug in einen anderen Zweig geladen wird? Die Patches sollten nur die Daten enthalten, die auf dem Weg nach unten verloren gehen würden (Migrationen).
Kostas
4
Wenn Sie Daten laden möchten, verwenden db/seeds.rb Sie Es sollte nicht zu verheerend sein, Ihre Entwicklungsdatenbank zu zerstören, wenn Sie dort einige vernünftige Startdaten einrichten.
Andy Lindeman
keine Notwendigkeit, etwas zu zerstören. siehe meine Lösung unten. Beachten Sie jedoch, dass Sie viele Instanzen haben und wenn Sie die Zweige wechseln, sind die Daten nicht vorhanden. Dies ist völlig in Ordnung, wenn Sie mit Tests entwickeln.
Adam Dymitruk
Danke Andy, diese Antwort auch meine Frage. Und vereinbaren db/seeds.rbSie, die verlorenen Datenbankdaten für die Ripopulation zu verwenden
Pastullo
Für große, komplizierte Apps, bei denen Sie echte Fehler lokal reproduzieren müssen, ist es absolut unmöglich, eine Seeds-Datei zu verwenden. Sie benötigen die realen Daten aus der Produktion (oder dem Staging). Das Wiederherstellen einer Datenbank kann eine Weile dauern. Nein, dies ist keine gute Lösung für meinen Fall.
Joel_Blum
21

Wenn Sie eine große Datenbank haben, die Sie nicht ohne weiteres reproduzieren können, würde ich die Verwendung der normalen Migrationstools empfehlen. Wenn Sie einen einfachen Prozess wünschen, würde ich Folgendes empfehlen:

  • Setzen Sie rollback ( rake db:rollback) vor dem Verzweigen in den Zustand vor dem Verzweigungspunkt. Führen Sie dann nach dem Wechseln der Zweige aus db:migrate. Dies ist mathematisch korrekt und solange Sie downSkripte schreiben , funktioniert es.
  • Wenn Sie dies vergessen, bevor Sie die Zweige wechseln, können Sie im Allgemeinen sicher zurück-, zurücksetzen und wieder wechseln. Ich denke, als Workflow ist dies machbar.
  • Wenn Sie Abhängigkeiten zwischen Migrationen in verschiedenen Zweigen haben, müssen Sie gründlich überlegen.
ndp
quelle
2
Sie müssen bedenken, dass nicht alle Migrationen reversibel sind. Der erste vorgeschlagene Schritt ist jedoch nicht garantiert erfolgreich. Ich denke, dass in der Entwicklungsumgebung eine gute Idee wäre, rake db:schema:loadund rake db:seedwie @noodl gesagt hatte.
Pisaruk
@pisaruk Ich weiß, dass Sie dies vor sechs Jahren beantwortet haben, aber beim Lesen bin ich gespannt, was ein Beispiel für eine nicht reversible Migration wäre. Es fällt mir schwer, mir eine Situation vorzustellen. Ich denke, die einfachste wäre eine abgelegte Spalte, die eine Reihe von Daten enthält, aber dies könnte "umgekehrt" werden, um eine leere Spalte oder eine Spalte mit einem Standardwert zu haben. Haben Sie an andere Fälle gedacht?
Luke Griffiths
1
Ich denke du hast deine eigene Frage beantwortet! Ja, eine abgelegte Spalte ist ein gutes Beispiel. Oder eine destruktive Datenmigration.
ndp
13

Hier ist ein Skript, das ich zum Wechseln zwischen Zweigen mit unterschiedlichen Migrationen geschrieben habe:

https://gist.github.com/4076864

Es werden nicht alle von Ihnen erwähnten Probleme gelöst, aber mit einem Zweigstellennamen wird es:

  1. Setzen Sie alle Migrationen in Ihrem aktuellen Zweig zurück, die in dem angegebenen Zweig nicht vorhanden sind
  2. Verwerfen Sie alle Änderungen an der Datei db / schema.rb
  3. Überprüfen Sie den angegebenen Zweig
  4. Führen Sie alle neuen Migrationen aus, die in der angegebenen Verzweigung vorhanden sind
  5. Aktualisieren Sie Ihre Testdatenbank

Ich mache das die ganze Zeit manuell in unserem Projekt, daher dachte ich, es wäre schön, den Prozess zu automatisieren.

Jon Lemmon
quelle
1
Dieses Skript macht genau das, was ich tun möchte. Ich würde es gerne in einem automatischen Checkout-Hook sehen.
Brysgo
1
Dies nur in, ich gabelte Ihren Kern und machte es zu einem Post-Checkout-Haken: gist.github.com/brysgo/9980344
Brysgo
Wollten Sie in Ihrem Drehbuch wirklich sagen git checkout db/schema.rboder meinten Sie git checkout -- db/schema.rb? (dh mit doppelten Strichen)
user664833
1
Na ja ... ich wusste damals noch nichts über Doppelstriche. Der Befehl funktioniert jedoch genauso, es sei denn, Sie haben einen Zweig namens db/schema.rb. :)
Jon Lemmon
@ brysgos weiterentwickelter Befehl git_rails ( github.com/brysgo/git-rails ) funktioniert hervorragend. Dank dir Jon :)
Zia Ul Rehman Mughal
7

Separate Datenbank für jeden Zweig

Es ist der einzige Weg zu fliegen.

Update 16. Oktober 2017

Ich bin nach einiger Zeit darauf zurückgekommen und habe einige Verbesserungen vorgenommen:

  • Ich habe eine weitere Namespace-Rake-Task hinzugefügt, um einen Zweig zu erstellen und die Datenbank auf einen Schlag zu klonen bundle exec rake git:branch.
  • Mir ist jetzt klar, dass das Klonen vom Master nicht immer das ist, was Sie tun möchten. Deshalb habe ich expliziter gemacht, dass die db:clone_from_branchAufgabe eine SOURCE_BRANCHund eine TARGET_BRANCHUmgebungsvariable enthält. Bei Verwendung git:branchwird automatisch der aktuelle Zweig als verwendet SOURCE_BRANCH.
  • Refactoring und Vereinfachung.

config/database.yml

Um es Ihnen einfacher zu machen, aktualisieren Sie Ihre database.ymlDatei wie folgt, um den Datenbanknamen basierend auf dem aktuellen Zweig dynamisch zu ermitteln.

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

Hier ist eine Rake-Aufgabe, mit der Sie Ihre Datenbank einfach von einem Zweig in einen anderen klonen können. Dies erfordert eine SOURCE_BRANCHund eine TARGET_BRANCHUmgebungsvariable. Basierend auf der Aufgabe von @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

Diese Aufgabe erstellt einen Git-Zweig aus dem aktuellen Zweig (Master oder auf andere Weise), checkt ihn aus und klont die Datenbank des aktuellen Zweigs in die Datenbank des neuen Zweigs. Es ist glatter 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

Jetzt müssen Sie nur noch rennen bundle exec git:branch, den neuen Filialnamen eingeben und Zombies töten.

Joshua Pinter
quelle
4

Vielleicht sollten Sie dies als Hinweis darauf nehmen, dass Ihre Entwicklungsdatenbank zu groß ist? Wenn Sie db / seeds.rb und einen kleineren Datensatz für die Entwicklung verwenden können, kann Ihr Problem mithilfe von schema.rb und seeds.rb aus dem aktuellen Zweig leicht gelöst werden.

Dies setzt voraus, dass sich Ihre Frage auf die Entwicklung bezieht. Ich kann mir nicht vorstellen, warum Sie regelmäßig die Filialen in der Produktion wechseln müssten.

Nudel
quelle
Ich wusste es nicht db/seeds.rb, ich werde es mir ansehen.
Kostas
3

Ich hatte mit dem gleichen Problem zu kämpfen. Hier ist meine Lösung:

  1. Stellen Sie sicher, dass sowohl schema.rb als auch alle Migrationen von allen Entwicklern eingecheckt werden.

  2. Es sollte eine Person / Maschine für die Bereitstellung in der Produktion vorhanden sein. Nennen wir diese Maschine die Merge-Maschine. Wenn die Änderungen auf den Zusammenführungscomputer übertragen werden, schlägt die automatische Zusammenführung für schema.rb fehl. Keine Probleme. Ersetzen Sie einfach den Inhalt durch den vorherigen Inhalt für schema.rb (Sie können eine Kopie beiseite legen oder von github herunterladen, wenn Sie sie verwenden ...).

  3. Hier ist der wichtige Schritt. Die Migrationen aller Entwickler sind jetzt im Ordner db / migrate verfügbar. Fahren Sie fort und führen Sie bundle exec rake db: migrate aus. Dadurch wird die Datenbank auf dem Zusammenführungscomputer mit allen Änderungen gleichgesetzt. Außerdem wird schema.rb neu generiert.

  4. Übernehmen Sie die Änderungen und übertragen Sie sie auf alle Repositorys (Fernbedienungen und Einzelpersonen, die ebenfalls Fernbedienungen sind). Du solltest fertig sein!

Tabrez
quelle
3

Dies ist, was ich getan habe und ich bin nicht ganz sicher, ob ich alle Grundlagen abgedeckt habe:

In der Entwicklung (mit postgresql):

  • sql_dump Datenbankname> tmp / branch1.sql
  • Git Checkout Branch2
  • dropdb db_name
  • createdb db_name
  • psql db_name <tmp / branch2.sql # (vom vorherigen Verzweigungsschalter)

Dies ist viel schneller als die Rake-Dienstprogramme in einer Datenbank mit etwa 50.000 Datensätzen.

Behalten Sie für die Produktion den Hauptzweig als sakrosankt bei, und alle Migrationen werden eingecheckt und shema.rb ordnungsgemäß zusammengeführt. Führen Sie Ihr Standard-Upgrade-Verfahren durch.

Paul Carmody
quelle
Bei ausreichend kleinen Datenbankgrößen und wenn dies beim Auschecken eines Zweigs im Hintergrund erfolgt, ist dies eine sehr gute Lösung.
Kostas
2

Sie möchten eine "Datenbankumgebung" pro Zweig beibehalten. Schauen Sie sich das Skript smudge / clean an, um auf verschiedene Instanzen zu verweisen. Wenn Ihnen die Datenbankinstanzen ausgehen, lassen Sie das Skript von einer temporären Instanz herunterfahren. Wenn Sie also zu einem neuen Zweig wechseln, ist dieser bereits vorhanden und muss nur vom Skript umbenannt werden. DB-Updates sollten unmittelbar vor der Ausführung Ihrer Tests ausgeführt werden.

Hoffe das hilft.

Adam Dymitruk
quelle
Diese Lösung eignet sich nur für "temporäre" Filialen. Wenn wir zum Beispiel einen Zweig "Rand" haben, wo wir alle Arten von verrückten Sachen testen (wahrscheinlich mit anderen Unterzweigen) und ihn dann von Zeit zu Zeit mit dem Master zusammenführen, werden die 2 Datenbanken auseinander driften (ihre Daten werden nicht gleich sein).
Kostas
Diese Lösung ist genau das Gegenteil. Dies ist eine sehr gute Lösung, wenn Sie Ihr Datenbankversionsskript versionieren.
Adam Dymitruk
2

Ich erlebe die Pita, die du hier hast. Wenn ich darüber nachdenke, ist das eigentliche Problem, dass nicht alle Zweige den Code haben, um bestimmte Zweige zurückzusetzen. Ich bin in der Django-Welt, also weiß ich nicht, wie gut ich harke. Ich spiele mit der Idee, dass die Migrationen in einem eigenen Repo leben, das nicht verzweigt wird (Git-Submodul, von dem ich kürzlich erfahren habe). Auf diese Weise haben alle Zweige alle Migrationen. Der klebrige Teil stellt sicher, dass jeder Zweig nur auf die Migrationen beschränkt ist, die ihn interessieren. Dies manuell zu tun / zu verfolgen wäre eine Pita und fehleranfällig. Keines der Migrationstools ist dafür ausgelegt. Das ist der Punkt, an dem ich keinen Weg nach vorne habe.

JohnO
quelle
Dies ist eine gute Idee, aber was passiert, wenn ein Zweig eine Spalte umbenennt? Der Rest der Zweige wird auf einen kaputten Tisch schauen.
Kostas
ähm - das ist der klebrige Teil - welcher Zweig kümmert sich um welche Migrationen. Sie können also "synchronisieren" und wissen, "diese Migration zurücksetzen", damit die Spalte zurückgeht.
JohnO
1

Ich würde eine von zwei Optionen vorschlagen:

Option 1

  1. Geben Sie Ihre Daten ein seeds.rb. Eine gute Möglichkeit ist, Ihre Saatgutdaten über FactoryGirl / Fabrication Gem zu erstellen. Auf diese Weise können Sie sicherstellen, dass die Daten mit dem Code synchron sind, wenn wir davon ausgehen, dass die Fabriken zusammen mit dem Hinzufügen / Entfernen von Spalten aktualisiert werden.
  2. Führen Sie nach dem Wechsel von einem Zweig zu einem anderen rake db:resetdie Datenbank aus, wodurch die Datenbank effektiv gelöscht / erstellt / gesetzt wird.

Option 2

Verwalten Sie den Status der Datenbank manuell, indem Sie immer rake db:rollback/ rake db:migratevor / nach einer Verzweigungsprüfung ausführen. Die Einschränkung ist, dass alle Ihre Migrationen reversibel sein müssen, sonst funktioniert dies nicht.

Alexander
quelle
0

Zur Entwicklungsumgebung:

Sie sollten mit arbeiten, um rake db:migrate:redozu testen, ob Ihr Skript reversibel ist, aber denken Sie daran, dass Sie immer eine Datenpopulation haben sollten seed.rb.

Wenn Sie mit git arbeiten, sollte Ihre seed.rb mit einer Migrationsänderung und der Ausführung von geändert werden db:migrate:redo Anfang an (Laden Sie die Daten für eine neue Entwicklung auf einen anderen Computer oder eine neue Datenbank).

Abgesehen von "Ändern" sind Ihre Codes mit Ihren Auf- und Ab-Methoden immer Deckungsszenarien für die "Änderung" in diesem Moment und wenn Sie bei Null beginnen.

Daniel Antonio Nuñez Carhuayo
quelle