Comment travailler avec les branches Git et les migrations Rails


131

Je travaille sur une application rails avec pas mal de branches git et beaucoup d'entre elles incluent des migrations db. Nous essayons d'être prudents, mais parfois un morceau de code dans master demande une colonne qui a été supprimée / renommée dans une autre branche.

  1. Quelle serait une bonne solution pour "coupler" des branches git avec des états DB?

  2. Quels seraient réellement ces «états»?

    Nous ne pouvons pas simplement dupliquer une base de données si sa taille est de quelques Go.

  3. Et que devrait-il se passer avec les fusions?

  4. La solution se traduirait-elle également par des bases de données noSQL?

    Nous utilisons actuellement MySQL, mongodb et redis


EDIT: On dirait que j'ai oublié de mentionner un point très important, je ne suis intéressé que par l' environnement de développement mais avec de grandes bases de données (quelques Go de taille).


Que faites-vous pour avoir un environnement exécutant votre branche master dont la base de données peut être modifiée par d'autres branches? Je ne comprends pas quel est votre flux de travail ni pourquoi vous pensez que vous devez synchroniser les branches avec des bases de données particulières.
Jonah

3
Disons que nous avons une table dans notre base de données avec les clients (nom, email, téléphone) et dans une branche nous divisons l'une des colonnes (nom -> prénom + nom). Tant que nous ne fusionnons pas la branche avec le maître, le maître et toutes les autres branches basées sur lui échoueront.
Kostas

Réponses:


64

Lorsque vous ajoutez une nouvelle migration dans une branche, exécutez rake db:migrateet validez à la fois la migration et db/schema.rb

Si vous faites cela, en développement, vous pourrez passer à une autre branche qui a un ensemble différent de migrations et simplement exécuter rake db:schema:load.

Notez que cela recréera toute la base de données et que les données existantes seront perdues .

Vous ne voudrez probablement exécuter la production qu'à partir d'une branche avec laquelle vous êtes très prudent, donc ces étapes ne s'appliquent pas ici (exécutez simplement rake db:migratecomme d'habitude). Mais en développement, recréer la base de données à partir du schéma ne devrait pas être un problème, ce qui rake db:schema:loadfera l'affaire.


5
Je pense que cela ne résoudra que le problème de schéma, les données seront perdues à chaque migration vers le bas pour ne plus jamais être revues. Serait-ce une bonne idée de sauvegarder une sorte de db-data-patch qui est enregistré lors du déplacement d'une branche et un autre qui est chargé lors du déplacement dans une autre branche? Les correctifs ne doivent contenir que les données qui seraient perdues en descendant (migrations).
Kostas

4
Si vous souhaitez charger des données, utilisez db/seeds.rb Il ne devrait pas être trop dévastateur de détruire votre base de données de développement si vous y configurez des données de départ raisonnables.
Andy Lindeman

pas besoin de bombarder quoi que ce soit. voir ma solution ci-dessous. Sachez simplement que vous aurez de nombreuses instances et que lorsque vous changez de branche, les données ne sont pas là. C'est tout à fait correct si vous développez avec des tests.
Adam Dymitruk le

Merci Andy, cette réponse aussi ma question. Et convenez d'utiliser db/seeds.rbpour ripopuler les données de base de données
perdues

Pour les grosses applications compliquées où vous devez reproduire localement des bogues réels, il est absolument impossible d'utiliser un fichier de départ, vous avez besoin des données réelles de la production (ou de la mise en scène). Et la restauration d'une base de données peut prendre un certain temps, donc non ce n'est pas une bonne solution pour mon cas.
Joel_Blum

21

Si vous avez une grande base de données que vous ne pouvez pas facilement reproduire, je vous recommande d'utiliser les outils de migration normaux. Si vous voulez un processus simple, voici ce que je vous recommande:

  • Avant de changer de branche, rollback ( rake db:rollback) à l'état avant le point de branchement. Ensuite, après avoir changé de branche, exécutez db:migrate. C'est mathématiquement correct, et tant que vous écrivez des downscripts, cela fonctionnera.
  • Si vous oubliez de le faire avant de changer de branche, en général, vous pouvez en toute sécurité revenir en arrière, revenir en arrière et basculer à nouveau, donc je pense qu'en tant que flux de travail, c'est faisable.
  • Si vous avez des dépendances entre les migrations dans différentes branches ... eh bien, vous devrez réfléchir sérieusement.

2
Vous devez garder à l'esprit que toutes les migrations ne sont pas réversibles, cela dit, la première étape suggérée n'est pas garantie de réussir. Je pense que dans l'environnement de développement, une bonne idée serait d'utiliser rake db:schema:loadet rake db:seedcomme @noodl l'avait dit.
pisaruk

@pisaruk Je sais que vous avez répondu à cela il y a six ans, mais en lisant, je suis curieux de savoir ce que serait un exemple de migration irréversible. J'ai du mal à imaginer une situation. Je suppose que le plus simple serait une colonne supprimée contenant un tas de données, mais cela pourrait être «inversé» pour avoir une colonne vide ou une colonne avec une valeur par défaut. Pensiez-vous à d'autres cas?
Luke Griffiths

1
Je pense que vous avez répondu à votre propre question! Oui, une colonne supprimée est un bon exemple. Ou une migration de données destructrice.
ndp

13

Voici un script que j'ai écrit pour basculer entre les branches contenant différentes migrations:

https://gist.github.com/4076864

Cela ne résoudra pas tous les problèmes que vous avez mentionnés, mais avec un nom de branche, cela:

  1. Annulez toutes les migrations sur votre branche actuelle qui n'existent pas sur la branche donnée
  2. Ignorer toutes les modifications apportées au fichier db / schema.rb
  3. Découvrez la branche donnée
  4. Exécutez toutes les nouvelles migrations existantes dans la branche donnée
  5. Mettez à jour votre base de données de test

Je me retrouve à faire cela manuellement tout le temps sur notre projet, alors j'ai pensé que ce serait bien d'automatiser le processus.


1
Ce script fait exactement ce que je veux faire, j'aimerais le voir mis dans un crochet de paiement automatique.
brysgo

1
Ceci juste dedans, j'ai bifurqué votre essence et en ai fait un crochet après le paiement: gist.github.com/brysgo/9980344
brysgo

Dans votre scénario, vouliez-vous vraiment dire git checkout db/schema.rbou vouliez-vous dire git checkout -- db/schema.rb? (c'est-à-dire avec des tirets doubles)
user664833

1
Eh bien oui ... je ne connaissais pas les doubles tirets à l'époque. Mais la commande fonctionnera de la même manière sauf si vous avez une branche appelée db/schema.rb. :)
Jon Lemmon

La commande évoluée git_rails de @ brysgo ( github.com/brysgo/git-rails ) fonctionne très bien. Merci à vous Jon :)
Zia Ul Rehman Mughal

7

Base de données distincte pour chaque succursale

C'est la seule façon de voler.

Mise à jour 16 octobre 2017

J'y suis revenu après un certain temps et j'ai apporté quelques améliorations:

  • J'ai ajouté une autre tâche de râteau d'espace de noms pour créer une branche et cloner la base de données d'un seul coup, avec bundle exec rake git:branch.
  • Je me rends compte maintenant que le clonage à partir de master n'est pas toujours ce que vous voulez faire, alors j'ai précisé que la db:clone_from_branchtâche prend une variable d'environnement SOURCE_BRANCHet une TARGET_BRANCH. Lors de son utilisation, git:branchil utilisera automatiquement la branche actuelle comme fichier SOURCE_BRANCH.
  • Refactoring et simplification.

config/database.yml

Et pour vous faciliter la tâche, voici comment mettre à jour votre database.ymlfichier pour déterminer dynamiquement le nom de la base de données en fonction de la branche actuelle.

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

Voici une tâche Rake pour cloner facilement votre base de données d'une branche à une autre. Cela prend une SOURCE_BRANCHet une TARGET_BRANCHvariables d'environnement. Basé sur la tâche 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

Cette tâche créera une branche git hors de la branche actuelle (maître ou autre), la récupérera et clonera la base de données de la branche actuelle dans la base de données de la nouvelle branche. C'est AF lisse.

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

Maintenant, tout ce que vous avez à faire est de courir bundle exec git:branch, entrez le nouveau nom de branche et commencez à tuer des zombies.


4

Peut-être devriez-vous prendre cela comme un indice que votre base de données de développement est trop grande? Si vous pouvez utiliser db / seedss.rb et un ensemble de données plus petit pour le développement, votre problème peut être facilement résolu en utilisant schema.rb et seeds.rb de la branche actuelle.

Cela suppose que votre question concerne le développement; Je ne peux pas imaginer pourquoi vous auriez besoin de changer régulièrement de branche en production.


Je ne savais pas db/seeds.rb, je vais y jeter un coup d'oeil.
Kostas

3

J'étais aux prises avec le même problème. Voici ma solution:

  1. Assurez-vous que schema.rb et toutes les migrations sont archivés par tous les développeurs.

  2. Il doit y avoir une personne / machine pour les déploiements en production. Appelons cette machine comme la machine de fusion. Lorsque les modifications sont extraites de la machine de fusion, la fusion automatique de schema.rb échoue. Pas d'issues. Remplacez simplement le contenu par le contenu précédent de schema.rb (vous pouvez mettre une copie de côté ou l'obtenir depuis github si vous l'utilisez ...).

  3. Voici l'étape importante. Les migrations de tous les développeurs seront désormais disponibles dans le dossier db / migrate. Allez-y et exécutez bundle exec rake db: migrate. Cela mettra la base de données sur la machine de fusion au pair avec tous les changements. Il régénérera également schema.rb.

  4. Validez et transmettez les modifications à tous les référentiels (télécommandes et individus, qui sont également des télécommandes). Tu devrais avoir fini!


3

C'est ce que j'ai fait et je ne suis pas sûr d'avoir couvert toutes les bases:

En développement (en utilisant postgresql):

  • sql_dump nom_base> tmp / branch1.sql
  • git checkout branch2
  • dropdb nom_base
  • createdb nom_base
  • psql nom_base <tmp / branch2.sql # (du commutateur de branche précédent)

C'est beaucoup plus rapide que les utilitaires de rake sur une base de données avec environ 50K enregistrements.

Pour la production, maintenez la branche master comme sacro-sainte et toutes les migrations sont archivées, shema.rb correctement fusionné. Suivez votre procédure de mise à niveau standard.


Pour des bases de données suffisamment petites et faire cela en arrière-plan lors de l'extraction d'une branche, cela semble être une très bonne solution.
Kostas

2

Vous souhaitez conserver un "environnement db" par branche. Regardez le script smudge / clean pour pointer vers différentes instances. Si vous manquez d'instances de base de données, faites tourner le script d'une instance temporaire afin que lorsque vous passez à une nouvelle branche, elle est déjà là et doit simplement être renommée par le script. Les mises à jour de la base de données doivent s'exécuter juste avant d'exécuter vos tests.

J'espère que cela t'aides.


Cette solution n'est valable que pour les succursales «temporaires». Par exemple, si nous avons une branche "edge" où nous testons toutes sortes de trucs dingues (probablement avec d'autres sous-branches) puis la fusionnons avec le master de temps en temps, les 2 bases de données vont se séparer (leurs données ne être le même).
Kostas

Cette solution est bonne pour l'exact opposé. C'est une très bonne solution si vous versez votre script de version de base de données.
Adam Dymitruk le

2

Je vis totalement le pita que vous avez ici. En y réfléchissant, le vrai problème est que toutes les branches n'ont pas le code pour restaurer certaines branches. Je suis dans le monde du django, donc je ne connais pas bien le rake. Je joue avec l'idée que les migrations vivent dans leur propre repo qui ne se ramifie pas (git-submodule, dont j'ai récemment entendu parler). De cette façon, toutes les branches ont toutes les migrations. La partie collante est de s'assurer que chaque branche est limitée aux seules migrations qui les intéressent. Faire / garder une trace de cela manuellement serait un pita et sujet à l'erreur. Mais aucun des outils de migration n'est conçu pour cela. C’est à ce stade que je suis sans issue.


C'est une bonne idée mais que se passe-t-il lorsqu'une branche renomme une colonne? Les autres branches regarderont une table cassée.
Kostas

euh - c'est la partie collante - quelle branche se soucie de quelles migrations. donc vous pouvez aller "synchroniser" et il sait, "annuler cette migration" afin que la colonne revienne.
JohnO

1

Je suggérerais l'une des deux options suivantes:

Option 1

  1. Mettez vos données dedans seeds.rb. Une bonne option consiste à créer vos données de départ via la gemme FactoryGirl / Fabrication. De cette façon, vous pouvez garantir que les données sont synchronisées avec le code si nous supposons que les usines sont mises à jour avec l'ajout / la suppression de colonnes.
  2. Après avoir basculé d'une branche à une autre, exécutez rake db:reset, ce qui supprime / crée / amorce efficacement la base de données.

Option 2

Gérez manuellement les états de la base de données en exécutant toujours rake db:rollback/ rake db:migrateavant / après une extraction de branche. La mise en garde est que toutes vos migrations doivent être réversibles, sinon cela ne fonctionnera pas.


0

Sur l'environnement de développement:

Vous devriez travailler avec rake db:migrate:redopour tester si votre script est réversible, mais gardez à l'esprit que vous devriez toujours avoir un seed.rbavec la population de données.

Si vous travaillez avec git, votre seed.rb devrait être modifié avec un changement de migration, et l'exécution de db:migrate:redopour le début (charger les données pour un nouveau développement sur une autre machine ou une nouvelle base de données)

Hormis le «changement», avec les vôtres méthodes de haut en bas, votre code sera toujours des scénarios de couverture pour le «changement» en ce moment et lorsque vous partez de zéro.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.