web-dev-qa-db-fra.com

Comment travailler avec les branches Git et les migrations Rails

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é dans une autre branche.

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

  2. Que 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 dans les 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 environnement de développement mais avec de grandes bases de données (quelques Go en taille).

124
Kostas

Lorsque vous ajoutez une nouvelle migration dans une branche, exécutez rake db:migrate et valider la migration etdb/schema.rb

Si vous faites cela, en cours de 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 la base de données entière et les données existantes seront perdues.

Vous voudrez probablement exécuter la production à partir d'une seule branche avec laquelle vous faites très attention, donc ces étapes ne s'appliquent pas là (exécutez simplement rake db:migrate comme d'habitude là-bas). Mais en développement, il ne devrait pas être très important de recréer la base de données à partir du schéma, ce qui est rake db:schema:load ça ira.

61
Andy Lindeman

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

  • Avant de changer de branche, annulez (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 scripts down, cela fonctionnera.
  • Si vous oubliez de le faire avant de changer de branche, en général, vous pouvez revenir en arrière, restaurer et basculer en toute sécurité, donc je pense qu'en tant que flux de travail, c'est faisable.
  • Si vous avez des dépendances entre migrations dans différentes branches ... eh bien, vous devrez bien réfléchir.
19
ndp

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

https://Gist.github.com/4076864

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

  1. Annulez toutes les migrations sur votre branche actuelle qui n'existent pas sur la branche donnée
  2. Ignorez 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 à le faire manuellement tout le temps sur notre projet, alors j'ai pensé que ce serait bien d'automatiser le processus.

13
Jon Lemmon

Base de données distincte pour chaque branche

C'est le seul moyen de voler.

Mise à jour du 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 rake 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, donc j'ai rendu plus explicite que le db:clone_from_branch la tâche prend un SOURCE_BRANCH et un TARGET_BRANCH variable d'environnement. Lors de l'utilisation de git:branch il utilisera automatiquement la branche courante comme SOURCE_BRANCH.
  • Refactorisation et simplification.

config/database.yml

Et pour vous faciliter la tâche, voici comment mettre à jour votre database.yml fichier 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 un SOURCE_BRANCH et un TARGET_BRANCH Variables 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 (principale ou autre), la vérifiera et clonera la base de données de la branche actuelle dans la nouvelle 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 devez faire est d'exécuter bundle exec git:branch, entrez le nouveau nom de la branche et commencez à tuer les zombies.

5
Joshua Pinter

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/seeds.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.

4
noodl

Je me débattais 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 la machine de fusion. Lorsque les modifications sont extraites de la machine de fusion, la fusion automatique pour 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 auprès de 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 le bundle exec rake db: migrate. Il apportera la base de données sur la machine de fusion à égalité avec toutes les modifications. Il régénérera également schema.rb.

  4. Validez et transférez les modifications vers tous les référentiels (télécommandes et individus, qui sont également des télécommandes). Vous devriez avoir terminé!

3
Tabrez

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 db_name> tmp/branch1.sql
  • git checkout branch2
  • dropdb db_name
  • createdb db_name
  • psql nom_base <tmp/branch2.sql # (à partir du commutateur de branche précédent)

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

Pour la production, conservez la branche principale en tant que sacro-saint et toutes les migrations sont archivées, shema.rb correctement fusionné. Suivez votre procédure de mise à niveau standard.

3
Paul Carmody

J'expérimente 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 Django, donc je ne sais pas si bien ratisser. Je joue avec l'idée que les migrations vivent dans leur propre référentiel qui ne soit pas ramifié (git -submodule, dont j'ai récemment entendu parler.) De cette façon, toutes les branches ont toutes les migrations. La partie collante consiste à s'assurer que chaque branche est limitée aux seules migrations qui leur tiennent à cœur. sujettes à erreur. Mais aucun des outils de migration n'est conçu pour cela. C'est le point où je suis sans voie à suivre.

2
JohnO

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

J'espère que cela t'aides.

2
Adam Dymitruk

Je suggérerais l'une des deux options:

Option 1

  1. Mettez vos données dans seeds.rb. Une bonne option consiste à créer vos données de semences via FactoryGirl/Fabrication gem. 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 être passé d'une branche à une autre, exécutez rake db:reset, 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:migrate avant/après un paiement en succursale. La mise en garde est que toutes vos migrations doivent être réversibles, sinon cela ne fonctionnera pas.

1
Alex Popov

Sur l'environnement de développement:

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

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

Hormis ´change´, avec vos méthodes up et down, votre code sera toujours des scénarios de couverture pour le "changement" en ce moment et quand il partira de zéro.