---
title: Göçler
source: 'https://academia.sh/tr/kurslar/mvc-cercevesi/gocler'
course: 'Model–Görünüm–Denetleyici Çerçevesi'
language: tr
updated: '2026-10-21T05:03:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Şemaya sütun ekleyen ileri göçü ve onu geri alan göçü saf Ruby ile kurar'
    - 'Eklenen sütunun kalışını dört istekte ölçer ve kalıcı olduğunu gösterir'
    - 'Aynı göçün iki kez koşmasının şemada iz bıraktığını ve geri almanın satırlara dokunmadığını ölçer'
    - 'Göçten önce yazılmış satırlarda yeni sütunun bulunmadığını gösterir'
---

# Göçler

Tablo şemasını sürüm sürüm değiştiren göçlerin ölçümü; eklenen sütunun kalıcı olduğunu, eski satırlara ulaşmadığını ve göçlerin neden numaralandığını gösterir.

Önceki ders, kaydedilen bir ürünün başka bir istemcide de okunduğunu gösterdi. O ölçümde ürünün iki niteliği vardı: `ad` ve `fiyat`. Bir ürüne stok sayısı da eklenmek istendiğinde ne olur? Tablonun biçimi, yani **şema** (schema), elle düzeltilen bir dosya olarak değil, kayıtlı adımlarla değişir. Bu adımlara **göç** (migration) denir: şemayı bir durumdan ötekine taşıyan, adlandırılmış ve sıralanmış bir adımdır. Şemaya eklenen sütun eski satırlarda var mı?

Eksen değişmedi: **bu değer hangi isteğe kadar kalıyor?** Bir sütun şemada durur; şema, kayıtlar gibi uygulamanın deposundadır. Bu yüzden beklenen kalış **kalıcı**dır. Ders bunu ölçer, sonra göçün iki sınırını gösterir: aynı göç iki kez koşarsa ne olur ve göçten önce yazılmış satırlar yeni sütunu görür mü? Değişikliğin nereye ulaştığı M12/K02'de ayrıca ölçülmüştü; orada soru "değişiklik hangi hedefe ulaşıyor" idi, burada soru ulaşan değişikliğin ne kadar kaldığıdır.

## Ölçüm Çekirdeği

Birinci dosya, kursun her dersinde aynı olan istek döngüsü ve kalış sınıflandırıcıdır. Bu blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir.

```ruby
# kalis.rb — kucuk bir istek dongusu ve bir degerin hangi istege kadar kaldigini siniflandiran yardimci

# Flash: bu istekte yazilan deger yalniz bir sonraki istege tasinir.
class Flash
  def initialize(gelen)
    @gelen = gelen
    @yeni = {}
  end

  def [](anahtar) = @yeni.fetch(anahtar) { @gelen[anahtar] }

  def []=(anahtar, deger)
    @yeni[anahtar] = deger
  end

  def birakilan = @yeni
end

# Her istek yeni bir baglam alir: params, flash ve istek deposu her istekte bostur.
class Baglam
  attr_reader :uygulama, :params, :oturum, :flash, :istek_deposu

  def initialize(uygulama, oturum)
    @uygulama = uygulama
    @params = {}
    @oturum = oturum
    @flash = Flash.new(oturum.delete(:flash) || {})
    @istek_deposu = {}
  end
end

# Uygulamanin deposu butun isteklerde ve butun istemcilerde aynidir;
# oturum istemciye gore secilir.
class Uygulama
  attr_reader :depo

  def initialize
    @oturumlar = Hash.new { |h, istemci| h[istemci] = {} }
    @depo = { urunler: {}, sema: { urunler: %i[ad fiyat] }, sablonlar: {},
              kuyruk: [], onbellek: {}, sayac: 0 }
  end

  def istek(istemci)
    baglam = Baglam.new(self, @oturumlar[istemci])
    sonuc = yield baglam
    baglam.oturum[:flash] = baglam.flash.birakilan unless baglam.flash.birakilan.empty?
    sonuc
  end
end

# Kural: istek 1'de (istemci A) deger yazilir; istek 2 ve 3 (A) ile istek 4 (B) okur.
# B goruyorsa kalici; A'nin ucuncu istegi goruyorsa oturum; yalniz ikinci istek
# goruyorsa sonraki; hicbiri gormuyorsa bu_istek.
def kalis(yaz, oku)
  uygulama = Uygulama.new
  ilk = uygulama.istek("A") { |b| yaz.call(b); oku.call(b) }
  raise ArgumentError, "deger yazildigi istekte okunamadi" if ilk.nil?

  gorulen = [["A", 2], ["A", 3], ["B", 4]].map do |istemci, sira|
    uygulama.istek(istemci) { |b| oku.call(b) }.nil? ? "--" : "#{istemci}#{sira}"
  end
  tur = if gorulen[2] != "--" then "kalici"
        elsif gorulen[1] != "--" then "oturum"
        elsif gorulen[0] != "--" then "sonraki"
        else "bu_istek"
        end
  [tur, ["A1", *gorulen].join(" "), ilk]
end
```

Bu dersin kesiti tek bir yöntemdir. Bir göç, en yalın biçimiyle, şemadaki bir tabloya bir sütun adı ekler. Şema `depo[:sema]` karma yapısındadır: anahtar tablo adı, değer sütun adlarının listesidir.

```ruby
# goc.rb — sema degisikligi: bir tabloya bir sutun eklenir
require_relative "kalis"

def goc(b, tablo, sutun) = b.uygulama.depo[:sema][tablo] << sutun
```

Bu blok da kaynaktır. Çekirdek bir çerçeve değil, bir çerçevenin modelidir; bu yöntem tek bir ileri göçü temsil eder. Çoğu göç düzeneği her göçü bir sınıf ya da dosya olarak yazar ve ileri yönün yanında geri yönü de tanımlayabilir. Aşağıda bu dersin kendi yardımcıları aynı ayrımı küçük ölçekte kurar.

## Sütunun Kalışı

Yazan lambda `urunler` tablosuna `stok` sütununu ekler; okuyan lambda şemada bu sütunu arar.

```ruby
# olcum.rb — sema sutunu gozleminin kalisi
require_relative "goc"

yaz = ->(b) { goc(b, :urunler, :stok) }
oku = ->(b) { b.uygulama.depo[:sema][:urunler].find { |s| s == :stok } }
p kalis(yaz, oku)
```
```
["kalici", "A1 A2 A3 B4", :stok]
```

Kalış **kalıcı**: B'nin dördüncü isteği de `:stok` sütununu şemada buldu. Kanıt satırında dört istek de var ve hiçbirinde `--` yok. Şema uygulamanın deposundadır ve uygulama bütün istemcilere hizmet eder; bir istemcinin yürüttüğü göç ötekilerin de gördüğü şemayı değiştirir. **VK6.** Bu ölçüm tek süreçtedir: göçün süreç yeniden başladıktan sonra da kaldığı, çekirdeğin bellekteki deposunda ölçülemez.

Göçü kimin koşturduğu kalışı değiştirmez. Aynı sütun B istemcisinin isteğinde eklenirse A'nın sonraki isteği onu görür.

```ruby
# baska_istemci.rb — goc bir istemcide kosar, ote istemci okur
require_relative "goc"

uygulama = Uygulama.new
uygulama.istek("B") { |b| goc(b, :urunler, :stok) }
uygulama.istek("A") { |b| p b.uygulama.depo[:sema][:urunler] }
```
```
[:ad, :fiyat, :stok]
```

Şema istemciye değil uygulamaya aittir: bir istemcinin isteğinde değişen şema, sonraki her istekte, hangi istemciden gelirse gelsin, değişmiş hâliyle görünür. Bu, önceki dersteki kayıtların kalıcılığıyla aynı nedene dayanır; ikisi de `depo`da durur. **VK9.** Burada göç isteğin içinde koşturuldu çünkü çekirdekte başka bir yer yoktur; göçün gerçek bir uygulamada nerede koşturulduğu bu ölçümün konusu değildir.

## İleri ve Geri

Göçler iki yöndedir. İleri göç şemayı değiştirir, **geri alma** değişikliği eski hâline getirir. Çekirdekte yalnız ileri yön vardır; geri alma bu dersin yardımcısıdır. Önce şemanın göçten önceki ve sonraki durumuna, sonra geri almadan sonraki durumuna bakılır.

```ruby
# ileri_geri.rb — ileri ve geri goc
require_relative "goc"

def goc_geri(b, tablo, sutun) = b.uygulama.depo[:sema][tablo].delete(sutun)

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  sema = b.uygulama.depo[:sema]
  p sema[:urunler]
  goc(b, :urunler, :stok)
  p sema[:urunler]
  goc_geri(b, :urunler, :stok)
  p sema[:urunler]
end
```
```
[:ad, :fiyat]
[:ad, :fiyat, :stok]
[:ad, :fiyat]
```

Üç çıktı satırı üç durumdur: göçten önce, ileri göçten sonra, geri almadan sonra. Geri alma şemayı birinci duruma döndürdü. Bir göçün iki yönü olması, şemanın bir sürüm dizisi olduğunu söyler: her sürüm bir öncekinin üstüne kurulur ve bir yönde ilerleyen, öteki yönde geri dönebilen bir yol oluşturur. Bu ders yalnız şema tarafını ölçer; satırların içindeki değerler aşağıdaki sınırlayıcı ölçümde ayrıca sorulur.

Geri almanın etkisi hangi isteğe kadar kalıyor? Aşağıdaki ölçüm sütunu ekler, bir satıra değer yazar, sütunu geri alır ve şemayı ve satırı ayrı ayrı okur. Sonra aynı geri almayı kalış kuralından geçirir.

```ruby
# geri_kalis.rb — geri alinan sutun ve satirdaki degeri
require_relative "goc"

def goc_geri(b, tablo, sutun) = b.uygulama.depo[:sema][tablo].delete(sutun)

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  goc(b, :urunler, :stok)
  b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1, stok: 5 }
  goc_geri(b, :urunler, :stok)
  p b.uygulama.depo[:sema][:urunler].include?(:stok)
  p b.uygulama.depo[:urunler][1].key?(:stok)
end

yaz = lambda do |b|
  goc(b, :urunler, :stok)
  goc_geri(b, :urunler, :stok)
end
oku = ->(b) { b.uygulama.depo[:sema][:urunler].include?(:stok) }
p kalis(yaz, oku)
```
```
false
true
["kalici", "A1 A2 A3 B4", false]
```

Şema artık `:stok` sütununu içermiyor, ama satır anahtarını ve `5` değerini taşımaya devam ediyor. Bu modelde geri alma yalnız şemaya dokunur; satırlardaki veriyi silmez. Kalış satırı, geri alınmış şemanın da kalıcı olduğunu gösterir: B'nin dördüncü isteği bile `false` okur, yani sütunun yokluğu A'nın isteğiyle sınırlı kalmadı. Gerçek bir göç düzeneğinde geri almanın satırlardaki değeri de silip silmediği düzeneğe bağlıdır ve burada ölçülmemiştir. Bu modelde şemayı eski hâline getiren adım veriyi eski hâline getirmedi.

## Yeni Tablo ve Eksik Tablo

`goc` yalnız var olan bir tabloya sütun ekler. Şemada olmayan bir tabloya aynı çağrı yapılırsa ne olur? Ölçüm, yakalanan istisnanın yalnız sınıfını yazdırır.

```ruby
# eksik_tablo.rb — var olmayan tabloya sutun eklemek
require_relative "goc"

def tablo_olustur(b, tablo, *sutunlar)
  b.uygulama.depo[:sema][tablo] = sutunlar
end

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  begin
    goc(b, :siparisler, :adet)
  rescue => e
    p e.class
  end
  tablo_olustur(b, :siparisler, :urun_id, :adet)
  p b.uygulama.depo[:sema].keys
  p b.uygulama.depo[:sema][:siparisler]
end
```
```
NoMethodError
[:urunler, :siparisler]
[:urun_id, :adet]
```

Var olmayan tabloya sütun eklemek `NoMethodError` verdi: şemada o ad yoktu ve `nil` üzerinde ekleme yöntemi çağrıldı. Tablo oluşturmak ayrı bir göç türüdür. `tablo_olustur` tabloyu sütun listesiyle şemaya yazdı; şema artık iki tablo içeriyor. Sonraki derste `Siparis` kayıtları bu tabloyu kullanacak. **VK7.** Burada `siparisler` yalnız şemada tanımlanır; tabloya satır yazılmaz.

## Göçler Neden Numaralanır

Aynı ileri göç iki kez koşarsa şema ne olur? `goc` bir liste sonuna ekler ve eklediğini hatırlamaz.

```ruby
# iki_kez.rb — ayni goc iki kez
require_relative "goc"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  goc(b, :urunler, :stok)
  goc(b, :urunler, :stok)
  p b.uygulama.depo[:sema][:urunler]
end
```
```
[:ad, :fiyat, :stok, :stok]
```

Şemada `:stok` iki kez var. Modelin kendisinde sürüm izi yoktur: hangi göçün koştuğunu hatırlayan bir kayıt yoktur ve ikinci çağrı birincinin tekrarı mı, yeni bir göç mü, anlaşılmaz. Gerçek bir göç düzeneği tam bu sebeple her göçe bir numara verir ve hangi numaraların uygulandığını bir sürüm tablosunda tutar. Bu dersin yardımcısı aynı fikri küçük ölçekte kurar: numarası uygulanmış bir göç ikinci kez koşmaz.

```ruby
# numarali.rb — uygulanan gocleri hatirlayan yardimci
require_relative "goc"

def numarali_goc(b, no, tablo, sutun)
  uygulananlar = (b.uygulama.depo[:uygulananlar] ||= [])
  return false if uygulananlar.include?(no)

  goc(b, tablo, sutun)
  uygulananlar << no
  true
end

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  p numarali_goc(b, 1, :urunler, :stok)
  p numarali_goc(b, 1, :urunler, :stok)
  p b.uygulama.depo[:sema][:urunler]
  p b.uygulama.depo[:uygulananlar]
end

yaz = ->(b) { numarali_goc(b, 1, :urunler, :stok) }
oku = ->(b) { b.uygulama.depo[:uygulananlar]&.first }
p kalis(yaz, oku)
```
```
true
false
[:ad, :fiyat, :stok]
[1]
["kalici", "A1 A2 A3 B4", 1]
```

İlk çağrı `true` verdi ve göçü koşturdu; aynı numarayla ikinci çağrı `false` verdi ve şemaya dokunmadı. Şemada `:stok` bir kez duruyor. Uygulananlar listesi `[1]`: göç numarası da şema gibi uygulamanın deposunda durur ve kalışı kalıcıdır, çünkü B'nin dördüncü isteği bile listenin ilk öğesini görür. Sürüm kaydı, göçün sonucunu değil, göçün koştuğunu hatırlar. **VK8.** Burada numara bir tam sayıdır ve göçler tek tek, sırayla çağrılır; sıra bozulursa ne olacağı ölçülmemiştir.

Kaydın neyi doğruladığı ayrıca sorulabilir. Aynı sütunu iki farklı numarayla eklemek, kaydın numarayı mı yoksa göçün içeriğini mi izlediğini gösterir.

```ruby
# iki_numara.rb — ayni sutun, iki numara
require_relative "goc"

def numarali_goc(b, no, tablo, sutun)
  uygulananlar = (b.uygulama.depo[:uygulananlar] ||= [])
  return false if uygulananlar.include?(no)

  goc(b, tablo, sutun)
  uygulananlar << no
  true
end

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  p numarali_goc(b, 1, :urunler, :stok)
  p numarali_goc(b, 2, :urunler, :stok)
  p b.uygulama.depo[:sema][:urunler]
  p b.uygulama.depo[:uygulananlar]
end
```
```
true
true
[:ad, :fiyat, :stok, :stok]
[1, 2]
```

İkinci çağrı `true` verdi ve `:stok` şemaya ikinci kez girdi. Kayıt numarayı izler, göçün ne yaptığını izlemez: aynı değişikliği başka bir numarayla getiren bir göç engellenmez. Numaralamanın verdiği koruma, numaraların doğru verilmesine dayanır. Gerçek düzeneklerin göç dosyasının içeriğinden bir sağlama değeri tutup tutmadığı burada ölçülmemiştir.

## Sınırlayıcı Ölçüm

Şemaya eklenen sütun kalıcıdır. Peki göçten önce yazılmış bir satır bu sütuna sahip mi? Şema ile verinin kalışı ayrı sorulardır. Aşağıdaki ölçüm bir satırı doğrudan tabloya yazar, göçü koşturur ve hem şemaya hem satıra bakar.

```ruby
# sinir.rb — gocten once yazilmis satir
require_relative "goc"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
  goc(b, :urunler, :stok)
  p b.uygulama.depo[:sema][:urunler].include?(:stok)
  p b.uygulama.depo[:urunler][1].key?(:stok)
  b.uygulama.depo[:urunler][2] = { ad: "defter", fiyat: 30, id: 2, stok: 5 }
  p b.uygulama.depo[:urunler].values.map { |satir| satir.key?(:stok) }
end
```
```
true
false
[false, true]
```

Şema `:stok` sütununu içeriyor, ama göçten önce yazılan satır `:stok` anahtarını taşımıyor: `key?` `false` verdi. Göçten sonra yazılan satırda anahtar var. Şema ile satır tek bir şey değildir: şema, satırların nasıl olması gerektiğini söyler, mevcut satırları değiştirmez. Bu modelde eski satırlar yeni anahtarı hiç taşımaz; ilişkisel bir veritabanında ise yeni sütun eski satırlarda boş (NULL) ya da varsayılan değerle görünür. İki durumda da anlamlı bir değer için eski satırlar ayrıca doldurulmalıdır. Bu yüzden iki iş ayrılır: sütunu ekleyen şema göçü ve eski satırlara bir değer yazan veri göçü. Aşağıdaki yardımcı, eksik anahtarı olan her satıra bir varsayılan yazar.

```ruby
# doldur.rb — eski satirlara yeni sutunu yazan veri goc
require_relative "goc"

def sutunu_doldur(b, tablo, sutun, deger)
  b.uygulama.depo[tablo].each_value { |satir| satir[sutun] = deger unless satir.key?(sutun) }
end

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
  goc(b, :urunler, :stok)
  p b.uygulama.depo[:urunler].values.map { |satir| satir.key?(:stok) }
  sutunu_doldur(b, :urunler, :stok, 0)
  p b.uygulama.depo[:urunler][1]
end

yaz = lambda do |b|
  b.uygulama.depo[:urunler][1] = { ad: "kalem", fiyat: 12, id: 1 }
  goc(b, :urunler, :stok)
end
oku = ->(b) { b.uygulama.depo[:urunler][1]&.key?(:stok) }
p kalis(yaz, oku)
```
```
[false]
{ad: "kalem", fiyat: 12, id: 1, stok: 0}
["kalici", "A1 A2 A3 B4", false]
```

Önce eski satır `false` verdi; doldurmadan sonra satırda `stok: 0` var. Son satır iki kalışı yan yana koyar: bu oku lambda'sı satırda sütun anahtarını arar ve `false` bulur, ama `false` bir değer olduğu için kalış kuralı onu görülmüş sayar ve her istekte aynı değeri okur. Satıra göçten sonra anahtar eklenmediği sürece B'nin dördüncü isteği de `false` okur. Aynı göç, şemada kalıcı bir sütun, satırda kalıcı bir yokluk bıraktı. Bu iki kalıcılığı birbirinden ayırmak gerekir: okuyucu şemayı soruyorsa `:stok` vardır; satıra soruyorsa yoktur. Kalış kuralı bu farkı göstermez, çünkü kural bir değerin davranışını okur; yokluğun bir hata mı, bir varsayılan mı olduğunu satırı okuyan kod belirler.

## Özet

- Göç, şemayı bir durumdan ötekine taşıyan adlandırılmış bir adımdır; ileri yönü sütun ekler, geri alma yönü onu siler.
- Bu dersin gözlemi olan şema sütunu, kalışı kalıcı olan değerdir: `:stok` sütunu A'nın sonraki iki isteğinde ve B'nin dördüncü isteğinde şemada okundu.
- Çekirdek modelde sürüm izi yoktur; aynı göç iki kez koşunca `:stok` iki kez yazıldı, bu yüzden göçler numaralanır ve uygulananların listesi tutulur.
- Göç şemayı değiştirir, mevcut satırları değiştirmez: göçten önce yazılan satırda yeni anahtar yoktur, doldurma ayrı bir adımdır.
- Bu modelde geri alma şemayı eski hâline getirir, satırlardaki veriye dokunmaz. Kayıt numarayı izler, göçün içeriğini izlemez; aynı numaralı farklı içerik tekrar uygulanmaz.
- Sütunun kalışı ile verinin kalışı ayrı şeylerdir; ikisi de kalıcıdır, ama biri sütunun varlığını, öteki satırdaki değerin yokluğunu korur.

## Sonraki Adım

Şema ve satırlar artık tanımlı. Ama bir kayıt tabloya girmeden önce kurallara uymalıdır, ve bir kayıt başka bir kayda bağlanabilir. Reddedilen bir kaydın hatası nerede duruyor? Bir sonraki ders, ilişkileri ve doğrulamaları aynı ölçüyle okur.
