Jak pracować z gałęziami Git i migracjami Railsów

133

Pracuję nad aplikacją railsową z kilkoma gałęziami git i wiele z nich zawiera migracje db. Staramy się być ostrożni, ale czasami jakiś fragment kodu w module głównym prosi o kolumnę, która została usunięta / zmieniona w innej gałęzi.

  1. Jakie byłoby fajne rozwiązanie „parowania” gałęzi git ze stanami DB?

  2. Czym właściwie byłyby te „stany”?

    Nie możemy po prostu powielić bazy danych, jeśli ma ona kilka GB.

  3. A co powinno się stać z połączeniami?

  4. Czy rozwiązanie przełożyłoby się również na bazy danych noSQL?

    Obecnie używamy MySQL, mongodb i redis


EDYCJA: Wygląda na to, że zapomniałem wspomnieć o bardzo ważnym punkcie, interesuje mnie tylko środowisko programistyczne, ale z dużymi bazami danych (kilka GB).

Kostas
źródło
Co robisz, że masz środowisko, w którym działa gałąź główna, której baza danych może być modyfikowana przez inne gałęzie? Nie rozumiem, jaki jest Twój przepływ pracy ani dlaczego uważasz, że musisz synchronizować gałęzie z określonymi bazami danych.
Jonah
3
Powiedzmy, że mamy w naszej bazie tabelę z klientami (imię i nazwisko, email, telefon) iw oddziale podzieliliśmy jedną z kolumn (nazwa -> imię + nazwisko). Dopóki nie połączymy gałęzi z wzorcem, master i wszystkie inne gałęzie na nim oparte nie będą działać.
Kostas,

Odpowiedzi:

66

Dodając nową migrację w dowolnej gałęzi, uruchom rake db:migratei zatwierdź zarówno migrację, jak i db/schema.rb

Jeśli to zrobisz, w fazie rozwoju będziesz mógł przełączyć się na inną gałąź, która ma inny zestaw migracji i po prostu uruchomić rake db:schema:load.

Zwróć uwagę, że spowoduje to odtworzenie całej bazy danych, a istniejące dane zostaną utracone .

Prawdopodobnie będziesz chciał uruchomić produkcję tylko z jednej gałęzi, z którą jesteś bardzo ostrożny, więc te kroki nie mają zastosowania tam (po prostu uruchom rake db:migratetam jak zwykle). Jednak w fazie rozwoju odtworzenie bazy danych ze schematu nie powinno być wielkim problemem, co jest tym rake db:schema:load, co zrobi.

Andy Lindeman
źródło
5
Myślę, że to tylko rozwiąże problem ze schematem, dane zostaną utracone przy każdej migracji w dół i nigdy więcej nie będą widoczne. Czy nie byłoby dobrym pomysłem zapisanie jakiejś poprawki db-data, która jest zapisywana podczas przenoszenia się z gałęzi i innej, która jest ładowana podczas przenoszenia do innej gałęzi? Poprawki powinny zawierać tylko te dane, które zostałyby utracone podczas upadku (migracje).
Kostas,
4
Jeśli chcesz załadować dane, użyj db/seeds.rb Nie powinno być zbyt niszczycielskie, aby niszczyć twoją deweloperską bazę danych, jeśli ustawisz tam jakieś rozsądne dane początkowe.
Andy Lindeman,
nie ma potrzeby niczego bombardować. zobacz moje rozwiązanie poniżej. Pamiętaj tylko, że będziesz mieć wiele instancji, a kiedy zmieniasz gałęzie, danych tam nie ma. Jest to całkowicie w porządku, jeśli tworzysz za pomocą testów.
Adam Dymitruk
Dziękuję Andy, to też odpowiedź na moje pytanie. I zgódź się na użycie db/seeds.rbdo ripopulacji utraconych danych db
pastullo
W przypadku dużych, skomplikowanych aplikacji, w których trzeba lokalnie odtwarzać błędy z prawdziwego życia, użycie pliku seed jest absolutnie niemożliwe, potrzebujesz prawdziwych danych z produkcji (lub przemieszczania). Przywracanie bazy danych może zająć trochę czasu, więc nie, to nie jest dobre rozwiązanie w moim przypadku.
Joel_Blum,
22

Jeśli masz dużą bazę danych, której nie możesz łatwo odtworzyć, polecam użycie zwykłych narzędzi do migracji. Jeśli chcesz prostego procesu, polecam:

  • Przed przełączeniem gałęzi, cofnij ( rake db:rollback) do stanu przed punktem rozgałęzienia. Następnie po zmianie gałęzi biegnij db:migrate. Jest to matematycznie poprawne i tak długo, jak piszesz downskrypty, będzie działać.
  • Jeśli zapomnisz o tym zrobić przed przełączeniem gałęzi, ogólnie możesz bezpiecznie przełączyć się z powrotem, wycofać i przełączyć ponownie, więc myślę, że jest to wykonalne.
  • Jeśli masz zależności między migracjami w różnych gałęziach ... cóż, będziesz musiał się mocno zastanowić.
ndp
źródło
2
Należy pamiętać, że nie wszystkie migracje są odwracalne, to znaczy, że pierwszy sugerowany krok nie gwarantuje sukcesu. Myślę, że w środowisku programistycznym dobrym pomysłem byłoby użycie rake db:schema:loadi rake db:seedjak powiedział @noodl.
pisaruk
@pisaruk Wiem, że odpowiedziałeś na to sześć lat temu, ale czytając jestem ciekawy, jaki byłby przykład nieodwracalnej migracji. Trudno mi sobie wyobrazić sytuację. Wydaje mi się, że najprostsza byłaby porzucona kolumna zawierająca zbiór danych, ale można to „odwrócić”, aby uzyskać pustą kolumnę lub kolumnę z pewną wartością domyślną. Czy myślałeś o innych przypadkach?
Luke Griffiths
1
Myślę, że odpowiedziałeś na własne pytanie! Tak, upuszczona kolumna jest dobrym przykładem. Albo destrukcyjna migracja danych.
ndp
13

Oto skrypt, który napisałem do przełączania się między gałęziami, które zawierają różne migracje:

https://gist.github.com/4076864

Nie rozwiąże wszystkich problemów, o których wspomniałeś, ale biorąc pod uwagę nazwę gałęzi, będzie:

  1. Wycofaj wszystkie migracje w Twojej bieżącej gałęzi, które nie istnieją w danej gałęzi
  2. Odrzuć wszelkie zmiany w pliku db / schema.rb
  3. Sprawdź dany oddział
  4. Uruchom wszelkie nowe migracje istniejące w danej gałęzi
  5. Zaktualizuj swoją testową bazę danych

Często robię to ręcznie w naszym projekcie, więc pomyślałem, że fajnie byłoby zautomatyzować ten proces.

Jon Lemmon
źródło
1
Ten skrypt robi dokładnie to, co chcę, chciałbym zobaczyć, jak jest umieszczony w automatycznym haku do kasy.
brysgo
1
Właśnie to, rozwidliłem twoje sedno i zrobiłem z niego haczyk po kasie: gist.github.com/brysgo/9980344
brysgo
W swoim scenariuszu naprawdę chciałeś powiedzieć, git checkout db/schema.rbczy miałeś na myśli git checkout -- db/schema.rb? (tj. z podwójnymi myślnikami)
user664833
1
Cóż, tak ... nie wiedziałem wtedy o podwójnych myślnikach. Ale polecenie będzie działać tak samo, chyba że masz wywołaną gałąź db/schema.rb. :)
Jon Lemmon
Rozwinięte polecenie git_rails @ brysgo ( github.com/brysgo/git-rails ) działa świetnie. Dziękuję Jon :)
Zia Ul Rehman Mughal
7

Oddzielna baza danych dla każdego oddziału

To jedyny sposób na latanie.

Aktualizacja z 16 października 2017 r

Po jakimś czasie wróciłem do tego i wprowadziłem kilka poprawek:

  • Dodałem kolejne zadanie rake'u przestrzeni nazw, aby utworzyć gałąź i sklonować bazę danych za jednym zamachem, za pomocą bundle exec rake git:branch.
  • Zdaję sobie sprawę, że teraz klonowanie od pana nie zawsze jest to, co chcesz zrobić, więc zrobiłem to bardziej wyraźne, że db:clone_from_branchzadanie trwa SOURCE_BRANCHi jest TARGET_BRANCHzmienną środowiskową. Podczas korzystania z git:branchniego automatycznie użyje bieżącej gałęzi jako pliku SOURCE_BRANCH.
  • Refaktoryzacja i uproszczenie.

config/database.yml

Aby ci to ułatwić, oto jak aktualizujesz database.ymlplik, aby dynamicznie określać nazwę bazy danych na podstawie bieżącej gałęzi.

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

Oto zadanie Rake, aby łatwo sklonować bazę danych z jednej gałęzi do drugiej. To wymaga a SOURCE_BRANCHi TARGET_BRANCHzmiennych środowiskowych. Na podstawie zadania @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

To zadanie spowoduje utworzenie gałęzi git z bieżącej gałęzi (głównej lub innej), sprawdzenie jej i sklonowanie bazy danych bieżącej gałęzi do bazy danych nowej gałęzi. To zręczny 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

Teraz wszystko, co musisz zrobić, to uruchomić bundle exec git:branch, wpisać nazwę nowej gałęzi i zacząć zabijać zombie.

Joshua Pinter
źródło
4

Może powinieneś potraktować to jako wskazówkę, że twoja programistyczna baza danych jest zbyt duża? Jeśli możesz użyć bazy danych db / seeds.rb i mniejszego zestawu danych do programowania, wówczas problem można łatwo rozwiązać za pomocą schematów schema.rb i seeds.rb z bieżącej gałęzi.

To zakłada, że ​​twoje pytanie dotyczy rozwoju; Nie mogę sobie wyobrazić, dlaczego musiałbyś regularnie zmieniać gałęzie w produkcji.

makaron
źródło
Nie wiedziałem db/seeds.rb, przyjrzę się temu.
Kostas,
3

Zmagałem się z tym samym problemem. Oto moje rozwiązanie:

  1. Upewnij się, że zarówno plik schema.rb, jak i wszystkie migracje są wpisywane przez wszystkich programistów.

  2. Powinna istnieć jedna osoba / maszyna do wdrożeń do produkcji. Nazwijmy tę maszynę maszyną scalającą. Po ściągnięciu zmian do maszyny scalającej automatyczne scalanie pliku schema.rb nie powiedzie się. Nie ma problemów. Po prostu zastąp zawartość tym, czym była poprzednia zawartość schema.rb (możesz odłożyć kopię na bok lub pobrać ją z github, jeśli jej używasz ...).

  3. Oto ważny krok. Migracje od wszystkich programistów będą teraz dostępne w folderze db / migrate. Śmiało i uruchom pakiet exec rake db: migrate. Spowoduje to, że baza danych na maszynie scalającej będzie na równi ze wszystkimi zmianami. Spowoduje również zregenerowanie pliku schema.rb.

  4. Zatwierdź i wypuść zmiany do wszystkich repozytoriów (pilotów i osób, które również są pilotami). Powinno być gotowe!

Tabrez
źródło
3

Oto, co zrobiłem i nie jestem do końca pewien, czy omówiłem wszystkie podstawy:

W opracowaniu (przy użyciu postgresql):

  • zrzut_sql nazwa_db> tmp / branch1.sql
  • git checkout branch2
  • dropdb nazwa_db
  • createdb nazwa_db
  • psql nazwa_db <tmp / branch2.sql # (z przełącznika poprzedniego oddziału)

Jest to o wiele szybsze niż narzędzia rake w bazie danych zawierającej około 50 000 rekordów.

Na potrzeby produkcji utrzymuj gałąź główną jako świętą, a wszystkie migracje są rejestrowane, a shema.rb prawidłowo scalony. Przejdź przez standardową procedurę aktualizacji.

Paul Carmody
źródło
Dla wystarczająco małych rozmiarów bazy danych i zrobienie tego w tle podczas sprawdzania gałęzi wygląda bardzo fajnie.
Kostas
2

Chcesz zachować „środowisko db” dla każdej gałęzi. Spójrz na rozmazany / czysty skrypt, aby wskazać różne wystąpienia. Jeśli zabraknie instancji bazy danych, poproś skrypt o wyodrębnienie instancji tymczasowej, aby po przełączeniu się do nowej gałęzi już ona tam była i wystarczy zmienić jej nazwę przez skrypt. Aktualizacje bazy danych powinny być uruchamiane tuż przed wykonaniem testów.

Mam nadzieję że to pomoże.

Adam Dymitruk
źródło
To rozwiązanie jest dobre tylko dla oddziałów „tymczasowych”. Na przykład, jeśli mamy "krawędź" gałęzi, w której testujemy wszelkiego rodzaju szalone rzeczy (prawdopodobnie z innymi podgałęziami), a następnie łączymy je od czasu do czasu do mastera, 2 bazy danych będą się od siebie oddalać (ich dane nie będą być takim samym).
Kostas,
To rozwiązanie jest dobre dla dokładnie odwrotnego przypadku. Jest to bardzo dobre rozwiązanie, jeśli używasz wersji skryptu wersji bazy danych.
Adam Dymitruk
2

Całkowicie doświadczam pity, którą tu masz. Kiedy o tym myślę, prawdziwym problemem jest to, że wszystkie gałęzie nie mają kodu do wycofania niektórych gałęzi. Jestem w świecie django, więc nie znam dobrze rake'u. Bawię się pomysłem, że migracje żyją we własnym repozytorium, które nie jest rozgałęzione (git-submodule, o którym niedawno się dowiedziałem). W ten sposób wszystkie gałęzie mają wszystkie migracje. Lepką częścią jest upewnienie się, że każda gałąź jest ograniczona tylko do migracji, na których im zależy. Wykonywanie / śledzenie tego ręcznie byłoby pita i podatne na błędy. Ale żadne z narzędzi do migracji nie jest do tego stworzone. To jest punkt, w którym nie mam wyjścia.

JohnO
źródło
To fajny pomysł, ale co się dzieje, gdy gałąź zmienia nazwę kolumny? Reszta gałęzi będzie patrzeć na zepsuty stół.
Kostas,
um - czyli ta lepka część - która gałąź dba o to, które migracje. więc możesz przejść do „synchronizacji” i wie, „cofnąć tę migrację”, aby kolumna się cofnęła.
JohnO
1

Proponuję jedną z dwóch opcji:

opcja 1

  1. Wprowadź swoje dane seeds.rb . Fajną opcją jest utworzenie danych nasion za pośrednictwem klejnotu FactoryGirl / Fabrication. W ten sposób możesz zagwarantować, że dane są zsynchronizowane z kodem, jeśli założymy, że fabryki są aktualizowane wraz z dodawaniem / usuwaniem kolumn.
  2. Po przełączeniu się z jednej gałęzi do drugiej, uruchom rake db:reset, który efektywnie upuszcza / tworzy / zasysa bazę danych.

Opcja 2

Ręcznie utrzymuj stany bazy danych, zawsze uruchamiając rake db:rollback/ rake db:migrateprzed / po sprawdzeniu oddziału. Zastrzeżenie jest takie, że wszystkie migracje muszą być odwracalne, w przeciwnym razie to nie zadziała.

Aleksandra
źródło
0

W środowisku programistycznym:

Powinieneś popracować, rake db:migrate:redoaby sprawdzić, czy twój skrypt jest odwracalny, ale pamiętaj, że zawsze powinieneś mieć seed.rbz populacją danych.

Jeśli pracujesz z git, seed.rb powinien zostać zmieniony wraz ze zmianą migracji i wykonaniem db:migrate:redo na początek (załaduj dane do nowego rozwoju na innym komputerze lub nowej bazie danych)

Oprócz „zmiany”, z twoimi metodami w górę iw dół, twój kod zawsze będzie obejmował scenariusze „zmiany” w tym momencie i gdy zaczniesz od zera.

Daniel Antonio Nuñez Carhuayo
źródło