Zaktualizuj jedną kolumnę do wartości innej w migracji Rails

80

Mam tabelę w aplikacji Rails z setkami tysięcy rekordów i mają one tylko created_atznacznik czasu. Dodam możliwość edycji tych rekordów, więc chcę dodać updated_atsygnaturę czasową do tabeli. Podczas migracji, aby dodać kolumnę, chcę zaktualizować wszystkie wiersze, aby nowy updated_atpasował do starego created_at, ponieważ jest to domyślne dla nowo utworzonych wierszy w Railsach. Mógłbym zrobić find(:all)i iterować przez rekordy, ale zajęłoby to godziny ze względu na rozmiar tabeli. To, co naprawdę chcę zrobić, to:

UPDATE table_name SET updated_at = created_at;

Czy istnieje lepszy sposób na zrobienie tego w migracji Railsów przy użyciu ActiveRecord zamiast wykonywania surowego SQL?

jrdioko
źródło

Odpowiedzi:

136

Stworzyłbym migrację

rails g migration set_updated_at_values

aw środku napisz coś takiego:

class SetUpdatedAt < ActiveRecord::Migration
  def self.up
    Yourmodel.update_all("updated_at=created_at")
  end

  def self.down
  end
end

W ten sposób osiągniesz dwie rzeczy

  • jest to powtarzalny proces, który jest wykonywany przy każdym możliwym wdrożeniu (w razie potrzeby)
  • to jest wydajne. Nie mogę wymyślić bardziej rubinowego rozwiązania (tak wydajnego).

Uwaga: możesz również uruchomić surowy sql wewnątrz migracji, jeśli zapytanie stanie się zbyt trudne do napisania przy użyciu activerecord. Po prostu napisz:

Yourmodel.connection.execute("update your_models set ... <complicated query> ...")
nathanvda
źródło
+1 - ostatnio musiałem zrobić dokładnie to i użyłem SQL na ActiveRecord. Jest tak szybki, jak to tylko możliwe.
Peter Brown
40
Yourmodel.update_all 'update_at=created_at'jest ładniej, nie? Działa też na zakres.
Marc-André Lafortune
Zgodnie z przewodnikiem po Railsach : „schemat bazy danych powinien pozostać niezmieniony, jeśli wykonasz a, uppo którym nastąpi a down . Więc rozważ def changetylko zamiast tego.
EliadL
1
@EliadL kilka uwag: 1) nie zmieniamy schematu, tylko zawartość bazy danych. I 2) w czasie, gdy pisano tę odpowiedź, changemetoda jeszcze nie istniała, ale w tym przypadku nadal wolę używać wyrażenia jawnego upi downbyć bardziej wyraźnym (jeśli chcesz kontrolować, co downnależy zrobić).
nathanvda
20

Możesz użyć update_all, który działa bardzo podobnie do surowego SQL. To wszystkie opcje, które masz.

Swoją drogą osobiście nie przywiązuję tak dużej wagi do migracji. Czasami surowy SQL jest naprawdę najlepszym rozwiązaniem. Zwykle kod migracji nie jest ponownie używany. To jest jednorazowa akcja, więc nie przejmuję się czystością kodu.

Greg Dan
źródło
2
To zależy od Twoich potrzeb związanych z wdrożeniem. Bardzo lubię korzystać z migracji, ponieważ pozwalają na powtarzalne wdrażanie na istniejących platformach i dają ten sam efekt. Mamy kilka etapów wdrażania: dev, test, qa / akceptacja, platforma proof of concept (do testowania klientów), platforma produkcyjna: musimy mieć możliwość bezbłędnej migracji istniejących danych do nowo wdrożonej wersji. Dodanie kolumny i upewnienie się, że dane są prawidłowe, w naszym przypadku NIE jest jednorazową czynnością.
nathanvda,
Piszę o używaniu update_allwewnątrz pliku migracyjnego :-) Można również wykonać surowy SQL w pliku migracyjnym. Jednak update_alljest trochę bardziej elegancki. Oba będą działać dokładnie tak samo.
Greg Dan,
Zwykle dobrym pomysłem jest zadeklarowanie modelu podczas migracji, ponieważ zapobiegnie to problemom, jeśli oryginalny model zostanie później przedefiniowany. Właśnie znalazłem ten artykuł, który wyjaśnia wszystko całkiem ładnie: skomplikowane-simplicity.com/2010/05/…
François Beausoleil
Nie update_allmam pojęcia, jak ustawić wartość kolumny na inną, zgodnie z żądaniem PO. Proszę zademonstrować.
nathanvda
14

Jak napisał gregdan, możesz użyć update_all. Możesz zrobić coś takiego:

Model.where(...).update_all('updated_at = created_at')

Pierwsza część to typowy zestaw warunków. Ostatnia część mówi, jak wykonać zadania. Spowoduje to wyświetlenie UPDATEinstrukcji, przynajmniej w Rails 4.

Martin Streicher
źródło
To w 4.2 generuje SET'posts'.'email' = 'options', opcje to dosłowny ciąg
lulalala
Potwierdzenie tej wskazówki też nie działa dla mnie. Nie jestem pewien, czy to całkowicie błędne rozwiązanie. Don't donwvote @martin answer
woto
Oto wynik z konsoli Railsów:User.update_all('updated_at = created_at') SQL (0.4ms) UPDATE "users" SET updated_at = created_at
Martin Streicher,
2

Możesz bezpośrednio uruchomić następujące polecenie do swojego rails console ActiveRecord::Base.connection.execute("UPDATE TABLE_NAME SET COL2 = COL1")

Na przykład: Chcę zaktualizować SKU mojej tabeli elementów o zdalny_id tabel elementów. polecenie będzie następujące:
ActiveRecord::Base.connection.execute("UPDATE items SET sku = remote_id")

Sarwan Kumar
źródło
W rzeczywistości jest to zdecydowanie najbardziej bezpieczny sposób w historii, ponieważ w przyszłości (gdy niektórzy będą uruchamiać migracje, model Yourmodelmożna już usunąć. Staraj się unikać używania modeli w migracjach).
Foton
0

Jest to ogólny sposób rozwiązywania problemów, bez konieczności pisania zapytań, ponieważ zapytania są narażone na ryzyko.

  class Demo < ActiveRecord::Migration
    def change
     add_column :events, :time_zone, :string
     Test.all.each do |p|
       p.update_attributes(time_zone: p.check.last.time_zone)
     end
     remove_column :sessions, :time_zone
    end
  end
Wilson Varghese
źródło
-4

Jako operacja jednorazowa zrobiłbym to po prostu w rails console. Czy to naprawdę zajmie godziny? Może jeśli są miliony rekordów…

records = ModelName.all; records do |r|; r.update_attributes(:updated_at => r.created_at); r.save!; end;`
ghoppe
źródło
Zasadniczo tego próbowałem najpierw, ale ponieważ istnieją setki tysięcy rekordów, które wymagają zmiany, zajmie to godziny (dni?).
jrdioko
Kiedy go testowałem, na mojej maszynie deweloperskiej (nie na serwerze) było to około 50 rekordów na sekundę.
jrdioko
4
Zawsze unikaj iteracji, jeśli to możliwe, unikaj używania 'all', który ładuje każdy rekord od razu do pamięci RAM, a ponieważ update_attributes już zapisuje automatycznie, dodatkowe wywołanie save! sprawi, że cała operacja potrwa dwa razy dłużej.
ryan0