---
title: 'Model–Görünüm–Denetleyici Çerçevesi'
source: 'https://academia.sh/tr/kurslar/mvc-cercevesi'
language: tr
updated: '2026-10-21T05:03:40+00:00'
license: 'CC BY-SA 4.0'
lessons: 13
outcomes:
    - 'Model, görünüm ve denetleyici katmanlarıyla bir web uygulamasının istek akışını kurar'
    - 'Sözleşmelerin, yönlendirmenin ve filtrelerin bir isteği hangi koda ulaştırdığını açıklar'
    - 'Veri modelini göçler, ilişkiler ve doğrulamalarla yönetir, N+1 sorgu sorununu ölçerek giderir'
    - 'Bir değerin yalnız bu istekte mi, sonraki istekte mi, oturumda mı kaldığını ya da kalıcı mı olduğunu ölçerek ayırt eder'
    - 'Arka plan işleri ve önbellekle isteği hafifletir, kimlik doğrulama ve güvenlik varsayılanlarını uygular'
---

# Model–Görünüm–Denetleyici Çerçevesi

Bir web çerçevesinde her istek sıfırdan başlar, ama bazı değerler isteğin sonunda kaybolmaz: bir bildirim mesajı yalnız bir sonraki sayfada görünür, giriş yapan kullanıcının kimliği o tarayıcının bütün isteklerinde vardır, kaydedilen ürünü başka bir tarayıcı da okur. Kurs her düzeneği tek bir soruyla ölçer — bu değer hangi isteğe kadar kalıyor? Sözleşmeler, yönlendirme, denetleyiciler, görünümler, nesne–tablo bağlaması, göçler, doğrulamalar, sorgular, kimlik doğrulama, arka plan işleri, önbellekleme, test ve güvenlik varsayılanları aynı çerçeveden geçirilir.

## Çerçeve Yapısı

Şablon ve dosya adlarını yapılandırma yerine sözleşmeden türeten çerçeve, yol kalıplarını denetleyici ve eyleme çeviren yönlendirme, eylemden önce koşan filtreler ve bir sonraki isteğe taşınan bildirimler, bir kez derlenip her çizimde kaçışlanan görünümler.

### Yapılandırma Yerine Sözleşme

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/yapilandirma-yerine-sozlesme

Kursun ölçü ekseni bir değerin hangi isteğe kadar kaldığıdır. Ders çekirdeği koşturur, on üç gözlemi dört kalışa ayırır ve şablon adının yazılmadığını, adlardan türetildiğini ve kalışının bu istek olduğunu gösterir.

Önceki kurs, bildirimsel arayüzlerle kapandı: `alan :fiyat, 0` bir çağrıydı ve sınıfa yöntem ekliyordu. Bir web çerçevesi bu tür satırların üstüne kurulur, ama yeni bir şey sorar. Web'de her istek sıfırdan başlar: denetleyici yeni kurulur, parametreler boştur, isteğe özgü depo boştur. Buna rağmen bazı değerler isteğin bitiminde kaybolmaz. Bir bildirim mesajı (flash) sonraki sayfada görünür, giriş yapmış kullanıcının kimliği her sayfada vardır, kaydedilen ürünü başka bir tarayıcı da okur.

Kurs her düzeneği tek bir soruyla ölçer: **bu değer hangi isteğe kadar kalıyor?** Cevabın kategori adı **kalış**tır ve dört değeri vardır. **Bu istek**: değer yalnız yazıldığı istekte okunur. **Sonraki istek**: aynı istemcinin yalnız bir sonraki isteğinde okunur. **Oturum**: aynı istemcinin bütün sonraki isteklerinde okunur, başka istemcide okunmaz. **Kalıcı**: başka bir istemcinin isteğinde de okunur. Çıktıda bu dört ad ASCII yazılır: `bu_istek`, `sonraki`, `oturum`, `kalici`.

#### Model, Görünüm, Denetleyici

**Model–görünüm–denetleyici (model–view–controller, MVC)**, bir uygulamayı üç sorumluluğa böler. **Model** veriyi ve veriyle ilgili kuralları taşır. **Görünüm (view)** veriyi kullanıcının göreceği çıktıya çevirir. **Denetleyici (controller)** isteği alır, modele sorar, sonucu görünüme verir. Dördüncü bir parça üçünün önünde durur: **yönlendirme (routing)**, gelen yolu hangi denetleyicinin hangi **eylemi**ne (action) gideceğine çevirir.

Bir isteğin yolu bu sırayı izler: yönlendirme, denetleyici, model, görünüm. Kursun çekirdeğinde dört parçanın her biri küçük bir karşılıkla bulunur: `yonlendir`, `UrunlerDenetleyicisi`, `Urun` ve `sablon_derle`. Bu çekirdek bir çerçeve değildir, bir çerçevenin modelidir. Ölçülen her blok saf Ruby'dir; hiçbir hazır çerçeve kullanılmaz.

#### Çekirdek

Çekirdek iki dosyadır. İlki `kalis.rb`, küçük bir istek döngüsü ve kalışı sınıflandıran bir yardımcıdır. Bu blok kaynaktır; çıktısı yoktur, diske yazılır ve öteki dosyalar onu `require_relative` ile çeker.

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

Dört sınıf birlikte çalışır. `Baglam` her istekte yenidir ve `params`, `flash` ile `istek_deposu`'nu boş alır. `Uygulama` bütün isteklerde ortak olan `depo`'yu ve istemciye göre seçilen oturumu tutar. `Flash`, yazılan değeri isteğin sonunda oturuma bırakır; sonraki istek onu alır ve siler. `kalis` kuralı koşturur: istek 1'de istemci A değeri yazar ve okur, istek 2 ve 3'te A, istek 4'te istemci B yalnız okur. B görüyorsa değer kalıcıdır, A'nın üçüncü isteği görüyorsa oturum, yalnız ikinci istek görüyorsa sonraki istek, hiçbiri görmüyorsa bu istektir. **ÇY1.** Kalıcı depo burada bellektedir ve süreç bitince gider. Gerçek bir uygulamada bu yer veritabanı, önbellek ya da kuyruktur; kural yalnız tek süreç içinde anlamlıdır.

#### On Üç Gözlem

İkinci dosya `degerler.rb`, çerçevenin küçük modellerini ve on üç gözlemi taşır. Her gözlem bir `yaz` ve bir `oku` lambda'sından oluşur. Dosya sonunda hepsini `kalis`'e verir ve tabloyu basar.

```ruby
# degerler.rb — on uc gozlem: bu deger hangi istege kadar kaliyor
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end

# Sozlesme: sablon adi yazilmaz, denetleyici ve eylem adindan turetilir.
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"

# Nesne-iliski esleyici: bir satir bir kayda, tablo bir modele karsilik gelir.
module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

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

# Denetleyici: her istekte yeni bir ornek; filtre eylemden once calisir.
class UrunlerDenetleyicisi
  def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
  def self.filtreler = @filtreler || []

  once :urunu_bul

  def initialize(baglam)
    @baglam = baglam
  end

  def calistir(eylem)
    self.class.filtreler.each { |filtre| send(filtre) }
    public_send(eylem)
  end

  def goster = @urun

  private

  def urunu_bul
    @urun = Urun.bul(@baglam, @baglam.params[:id])
  end
end

def denetleyici(b) = b.istek_deposu[:denetleyici] ||= UrunlerDenetleyicisi.new(b)

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

def kuyruga_al(b, is_adi, *argumanlar) = b.uygulama.depo[:kuyruk] << [is_adi, argumanlar]

def onbellekle(b, anahtar) = b.uygulama.depo[:onbellek][anahtar] ||= yield

def csrf_belirteci(b) = b.oturum[:csrf] ||= "belirtec-#{b.uygulama.depo[:sayac] += 1}"

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
gozlemler = {
  "sablon adi"     => [->(b) { b.params.merge!(yonlendir("/urunler/7")) },
                       ->(b) { b.params[:eylem] && sablon_adi(b.params) }],
  "yol parametresi" => [->(b) { b.params.merge!(yonlendir("/urunler/7")) },
                        ->(b) { b.params[:id] }],
  "flash"          => [->(b) { b.flash[:bildirim] = "urun kaydedildi" },
                       ->(b) { b.flash[:bildirim] }],
  "derlenen sablon" => [->(b) { sablon_derle(b, "urunler/goster", "<h1>{{ad}}</h1>") },
                        ->(b) { b.uygulama.depo[:sablonlar]["urunler/goster"]&.call(ad: "<b>kalem</b>") }],
  "kayit"          => [kalem, ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }],
  "sema sutunu"    => [->(b) { goc(b, :urunler, :stok) },
                       ->(b) { b.uygulama.depo[:sema][:urunler].find { |s| s == :stok } }],
  "dogrulama hatasi" => [->(b) { b.istek_deposu[:kayit] = Urun.olustur(b, ad: "", fiyat: 5) },
                         ->(b) { b.istek_deposu.dig(:kayit, :hatalar, 0) }],
  "sorgu onbellegi" => [->(b) { kalem.call(b); Urun.bul(b, 1) },
                        ->(b) { b.istek_deposu.dig(:sorgular, [:urunler, 1], :ad) }],
  "kullanici kimligi" => [->(b) { b.oturum[:kullanici_id] = 42 },
                          ->(b) { b.oturum[:kullanici_id] }],
  "kuyruktaki is"  => [->(b) { kuyruga_al(b, "fatura_gonder", 1) },
                       ->(b) { b.uygulama.depo[:kuyruk].first }],
  "parca onbellegi" => [->(b) { onbellekle(b, "urun/1") { "<li>kalem</li>" } },
                        ->(b) { b.uygulama.depo[:onbellek]["urun/1"] }],
  "denetleyici degiskeni" => [->(b) { kalem.call(b); b.params[:id] = 1; denetleyici(b).calistir(:goster) },
                              ->(b) { denetleyici(b).instance_variable_get(:@urun)&.fetch(:ad) }],
  "csrf belirteci" => [->(b) { csrf_belirteci(b) }, ->(b) { b.oturum[:csrf] }],
}

adet = { "bu_istek" => 0, "sonraki" => 0, "oturum" => 0, "kalici" => 0 }

printf("%-22s%-14s%-13s%s\n", "gozlem", "kalis", "kanit", "deger")
gozlemler.each do |ad, (yaz, oku)|
  tur, kanit, deger = kalis(yaz, oku)
  adet[tur] += 1
  printf("%-22s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end

puts
adet.each { |tur, n| printf("%-14s%d\n", tur, n) }
puts "toplam gozlem: #{adet.values.sum}"
```
```
gozlem                kalis         kanit        deger
sablon adi            bu_istek      A1 -- -- --  "urunler/goster"
yol parametresi       bu_istek      A1 -- -- --  7
flash                 sonraki       A1 A2 -- --  "urun kaydedildi"
derlenen sablon       kalici        A1 A2 A3 B4  "<h1>&lt;b&gt;kalem&lt;/b&gt;</h1>"
kayit                 kalici        A1 A2 A3 B4  "kalem"
sema sutunu           kalici        A1 A2 A3 B4  :stok
dogrulama hatasi      bu_istek      A1 -- -- --  "ad bos olamaz"
sorgu onbellegi       bu_istek      A1 -- -- --  "kalem"
kullanici kimligi     oturum        A1 A2 A3 --  42
kuyruktaki is         kalici        A1 A2 A3 B4  ["fatura_gonder", [1]]
parca onbellegi       kalici        A1 A2 A3 B4  "<li>kalem</li>"
denetleyici degiskeni bu_istek      A1 -- -- --  "kalem"
csrf belirteci        oturum        A1 A2 A3 --  "belirtec-1"

bu_istek      5
sonraki       1
oturum        2
kalici        5
toplam gozlem: 13
```

On üç gözlemin 5'i bu istekte, 1'i sonraki istekte, 2'si oturumda kaldı; 5'i kalıcıdır. Kanıt sütunu dört isteğin dökümüdür: değeri gören istek adıyla, görmeyen `--` ile yazılır. Bu istekte kalanların hepsi isteğin bağlamında doğar: şablon adı, yol parametresi, doğrulama hatası, sorgu önbelleği ve denetleyicinin örnek değişkeni. Bağlam her istekte yenilendiği için hiçbiri A2'ye ulaşmaz. Kalıcı olanlar `Uygulama#depo`'dadır: kayıt, şema sütunu, derlenmiş şablon, kuyruktaki iş ve parça önbelleği. Bunlar gerçekte beş ayrı yerdir, ama kural onları ayırmaz, çünkü davranışları aynıdır: başka bir istemci de okur. **ÇY2.** Kural değerin davranışını okur, saklandığı yeri okumaz. Aynı sınıfa düşmeleri onların aynı şey olduğunu söylemez; ilerleyen derslerde bu cümlenin iki yüzü de ölçülür.

Bu derste tablonun ilk satırı incelenir.

#### Şablon Adı Yazılmaz

`sablon adi` satırı bu istek sınıfına düştü ve değeri `"urunler/goster"` oldu. Bu ad hiçbir yerde yazılmadı. `yonlendir("/urunler/7")` denetleyici ve eylem adını verdi, `sablon_adi` da ikisini bir yola çevirdi. Bu bir **sözleşmedir (convention)**: şablon, denetleyici adıyla eylem adının birleşimi olarak beklenir ve bunu söyleyen bir yapılandırma satırı yoktur. Yapılandırma yerine sözleşme (convention over configuration) ilkesi budur: yaygın durumun cevabı varsayılandır, yalnız sapma yazılır.

Aynı yardımcıyı farklı adlarla çağırmak türetmenin işlediğini gösterir. `degerler.rb` koşarken tabloyu basar, bu yüzden bu ölçüm ayrı bir dosyada kendi tanımıyla koşar; `sablon_adi` satırı `degerler.rb`'dekinin birebir kopyasıdır.

```ruby
# sozlesme.rb — sablon adinin turetilmesi
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"

p sablon_adi(denetleyici: "urunler", eylem: "goster")
p sablon_adi(denetleyici: "urunler", eylem: "duzenle")
p sablon_adi(denetleyici: "siparisler", eylem: "goster")
```
```
"urunler/goster"
"urunler/duzenle"
"siparisler/goster"
```

Eylem adı değişince şablon adı onunla değişti. Üçüncü satır sözleşmenin sınırını gösterir: `siparisler` adında bir denetleyici çekirdekte yok, ama `sablon_adi` bir ad üretti. Sözleşme adı üretir; böyle bir şablonun var olup olmadığına bakmaz. **ÇY3.** Bu yalnız çekirdeğin tek satırlık `sablon_adi`'si için ölçüldü. Gerçek bir çerçevenin dosya aramasının, eksik şablon karşısında ne yaptığının hiçbiri bu derste sınanmadı.

Bu soru, M08/K06'nın sorusundan ayrıdır. Orada bir davranışın kaynaktaki sebebi ölçüldü: çağrı mı, açıklama mı, varsayılan mı. Şablon adı oradaki dille bir varsayılandır. Burada sebep sorulmaz, kalış sorulur: ad varsayılandan türese de değeri yalnız bu istekte okunur.

#### Sözleşme Eksik Girdiyle Ne Üretir

Sözleşme bir kuraldır; kural girdiyi doğrulamaz. Adı üreten tek satırın eksik parametreyle ne verdiğine bakmak, sözleşmenin nerede bittiğini gösterir.

```ruby
# eksik.rb — eksik parametreyle uretilen ad
require_relative "kalis"

# Sozlesme: sablon adi yazilmaz, denetleyici ve eylem adindan turetilir.
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"

p sablon_adi(denetleyici: "urunler", eylem: "goster")
p sablon_adi(denetleyici: "urunler")
p sablon_adi({})
p sablon_adi(denetleyici: "urunler", eylem: "")
```
```
"urunler/goster"
"urunler/"
"/"
"urunler/"
```

Eylem eksik olduğunda hata çıkmadı; ad `urunler/` oldu. Hiç parametre verilmeyince ad yalnızca `/` oldu. Boş dizgi ile eksik anahtar aynı sonucu verdi, çünkü dizge içine `nil` de boş dizgi olarak girer. Sözleşme adı kurar, adın anlamlı olup olmadığına bakmaz. Bu yüzden eksik bir ad, hata olarak değil, derlenmiş şablon deposunda bulunamayan bir anahtar olarak ortaya çıkar: arama sessizce `nil` döner. Hatayı yakalamak isteyen kod, adı üretmeden önce denetleyici ve eylemin dolu olduğunu kendisi sınamalıdır; sözleşme bu sınamayı üstlenmez.

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

Sözleşme yapılandırmayı kaldırır, değeri saklamaz. Bunu iki biçimde ölçmek mümkündür: aynı ad bir sonraki istekte yok mudur, ve yönlendirme hiç çalışmayan bir istekte ne olur.

```ruby
# sinir.rb — turetilen adin kalisi
require_relative "kalis"

def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"

yaz = ->(b) { b.params.merge!(denetleyici: "urunler", eylem: "goster") }
oku = ->(b) { b.params[:eylem] && sablon_adi(b.params) }
p kalis(yaz, oku)

uygulama = Uygulama.new
p uygulama.istek("A") { |b| oku.call(b) }
p uygulama.istek("A") { |b| b.params.merge!(denetleyici: "urunler", eylem: "goster"); oku.call(b) }
p uygulama.depo[:sablonlar]
```
```
["bu_istek", "A1 -- -- --", "urunler/goster"]
nil
"urunler/goster"
{}
```

İlk satır tablodaki sonucun aynısıdır: yazan istek adı görür, sonraki üç istek görmez. İkinci satır yönlendirmenin çalışmadığı isteği gösterir. `params[:eylem]` boş olduğu için `oku` `nil` verdi; yani ad hesaplanmadı, hesaplanabilecek bir kaynak da yoktu. Üçüncü satırda yönlendirme aynı isteğe yeniden yazıldı ve ad aynı çıktı. Son satır adın hiçbir yere bırakılmadığını söyler: uygulamanın şablon deposu hâlâ boş. Ad her istekte, parametrelerden yeniden hesaplanır. **ÇY4.** Bu ölçüm tek bir parametre çiftiyle yapıldı; ad üretmenin kalışı farklı bir sözleşme için ayrıca ölçülmelidir.

#### Özet

- Bir değerin **kalış**ı, hangi isteğe kadar okunabildiğidir: bu istek, sonraki istek, oturum ya da kalıcı.
- Çekirdekte on üç gözlemin 5'i bu istekte, 1'i sonraki istekte, 2'si oturumda kaldı; 5'i kalıcıdır.
- Kalış kuralı değerin davranışını okur; değerin saklandığı yeri okumaz.
- Şablon adı yazılmaz, denetleyici ve eylem adından türer; sözleşme adı üretir ama şablonun varlığını denetlemez.
- Bu dersin kalışı **bu istek**tir: türetilen ad her istekte yeniden hesaplanır ve depoya bırakılmaz.

#### Sonraki Adım

Şablon adı, yönlendirmenin verdiği denetleyici ve eylem adından türedi. Peki yönlendirme o adları nereden aldı? Bir sonraki ders bir yolu parçalarına ayıran kalıbı ölçer ve yoldaki `7` sayısının bir sonraki isteğe geçip geçmediğine bakar.

### Yönlendirme

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/yonlendirme

Yönlendirme bir yolu denetleyici, eylem ve kimliğe çevirir. Ders yol parametresinin bir tam sayıya dönüştüğünü, kalıba uymayan yolun reddedildiğini, kalıp sırasının sonucu değiştirdiğini ve değerin yalnız bu istekte kaldığını ölçer.

Önceki ders, şablon adının denetleyici ve eylem adından türediğini gösterdi, ama o adların kendisini bir yönlendirme vermişti. **Yönlendirme (routing)**, gelen yolu hangi denetleyicinin hangi eylemine gideceğine çevirir. Bu dersin sorusu daha dar: yol `/urunler/7` olduğunda `7` sayısı nereye gider ve o istek bittikten sonra nerede durur?

Kalış ekseni önceki dersten sürer: **bu değer hangi isteğe kadar kalıyor?** Bu dersin gözlemi `yol parametresi` satırıdır. Çekirdeğin `kalis.rb` dosyası aşağıda yeniden verilir; blok kaynaktır, çıktısı yoktur 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
```

#### Kalıp, Eylem ve Kimlik

Çekirdeğin yönlendirmesi tek bir kalıp taşır. Kalıp bir **kaynak temelli rota**dır: `/urunler/` ile başlar, ardından bir kimlik gelir ve hedef `urunler#goster` olarak yazılmıştır. Hedef iki parçaya ayrılır: `#` öncesi denetleyici, sonrası eylem. Kimlik, kalıpta adlandırılmış bir parçadır (`id`) ve eşleşmeden okunur.

Aşağıdaki dosya `degerler.rb`'den yalnız yönlendirme tanımlarını birebir alır ve `kalis`'i kendi iki lambda'sıyla çağırır.

```ruby
# yonlendirme.rb — yol parametresinin kalisi
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end

yaz = ->(b) { b.params.merge!(yonlendir("/urunler/7")) }
oku = ->(b) { b.params[:id] }
p kalis(yaz, oku)
p yonlendir("/urunler/7")
p yonlendir("/urunler/7")[:id].class
```
```
["bu_istek", "A1 -- -- --", 7]
{denetleyici: "urunler", eylem: "goster", id: 7}
Integer
```

İlk satır tablodaki sonucun aynısıdır: yol parametresi yalnız `A1`'de okundu, `A2`'de okunmadı. İkinci satır yönlendirmenin ürettiği karma yapıyı gösterir: denetleyici, eylem ve kimlik. Üçüncü satır kimliğin `Integer` olduğunu söyler. Yoldaki `7` bir dizgidir; `yonlendir` onu `to_i` ile çevirmiştir. Böylece denetleyici kimliği sayı olarak alır ve karşılaştırmayı dizgi üzerinden yapmaz. **ÇY5.** Çevirme `\d+` kalıbının eşleştirdiği rakamlarla sınırlıdır; sayı biçiminin başka yazılışları bu ölçüme girmedi.

#### Uymayan Yol

Yönlendirmenin ikinci işi yolu reddetmektir. Çekirdekteki kalıp `\d+` ister ve `\z` ile biter. İki yol bu iki kuralı sınar.

```ruby
# reddet.rb — kaliba uymayan yollar
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end

p yonlendir("/urunler/abc")
p yonlendir("/urunler/7/")
p yonlendir("/urunler/7?sira=2")
p yonlendir("/urunler/7")
```
```
nil
nil
nil
{denetleyici: "urunler", eylem: "goster", id: 7}
```

`/urunler/abc` rakam olmadığı için, `/urunler/7/` sonunda fazladan bir eğik çizgi olduğu için, `/urunler/7?sira=2` ise sonunda bir **sorgu dizgisi** (query string) taşıdığı için eşleşmedi. Üçüncüsü çekirdeğin bir sınırıdır: yol ile sorgu dizgisinin ayrılması yönlendirmeden önce yapılmalıdır, çünkü kalıp yolun tamamını ister. Üçünde de `yonlendir` `nil` verdi, yani çekirdek reddi bir istisna olarak değil, bir yanıt yokluğu olarak bildirir. `\z` çapası kalıbı yolun sonuna bağlar; o olmasaydı `7/` de eşleşirdi. Kimin `nil`'i nasıl yorumlayacağı yönlendirmenin değil, onu çağıranın işidir. **ÇY6.** Reddin biçimi bu çekirdeğe özgüdür; başka bir yönlendirme aynı durumda farklı bir yanıt verebilir ve bu ders onu ölçmedi.

#### Kalıp Sırası

Tek kalıp varken sıra sorusu çıkmaz. Tablo büyüdüğünde ilk eşleşen kalıp kazanır; çekirdeğin `yonlendir`'i de tabloyu baştan sona gezer ve ilk eşleşmede döner. Bu davranışı iki kalıpla ölçmek için kendi küçük tablomuzu kurarız. Burada `ilk_eslesen` adlı yeni bir yardımcı vardır; çekirdekte karşılığı yoktur.

```ruby
# sira.rb — iki kalipta sira
def ilk_eslesen(tablo, yol)
  tablo.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    return [hedef, eslesme[:id]]
  end
  nil
end

genis = { %r{\A/urunler/(?<id>[^/]+)\z} => "urunler#goster_ad",
          %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }
dar = genis.to_a.reverse.to_h

p ilk_eslesen(genis, "/urunler/7")
p ilk_eslesen(dar, "/urunler/7")
p ilk_eslesen(dar, "/urunler/abc")
```
```
["urunler#goster_ad", "7"]
["urunler#goster", "7"]
["urunler#goster_ad", "abc"]
```

İki tablo aynı iki kalıbı taşır, yalnız sırası farklıdır. Geniş kalıp önde olduğunda `/urunler/7` sayı yoluna değil, ad yoluna gitti. Dar kalıp önde olduğunda aynı yol sayı yoluna gitti, ve sayı olmayan `/urunler/abc` geniş kalıba düştü. **ÇY7.** Bu ölçüm yalnız iki kalıp ve üç yol içindir; daha çok kalıpta çakışma düzeni ayrıca sınanmalıdır. Kalıp tablosunda sıra bir yapılandırmadır, ama sözleşme gibi sessiz değildir: yer değişince sonuç değişir.

#### İç İçe Yol

Bir kaynağın altında başka bir kaynak olabilir: bir ürünün yorumları gibi. Yol iki kimlik taşır ve kalıpta iki adlandırılmış parça gerekir. Aşağıdaki kalıp bu dersin kendi tanımıdır ve çekirdek tablosuna eklenmez; çekirdeğin `yonlendir`'i bu yolu tanır mı, önce o sorulur.

```ruby
# ic_ice.rb — iki kimlikli yol
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end
IC_ICE = %r{\A/urunler/(?<id>\d+)/yorumlar/(?<yorum_id>\d+)\z}

yol = "/urunler/7/yorumlar/3"
p yonlendir(yol)
eslesme = IC_ICE.match(yol)
p eslesme.names
p eslesme.named_captures.transform_values(&:to_i)
```
```
nil
["id", "yorum_id"]
{"id" => 7, "yorum_id" => 3}
```

Çekirdeğin tek kalıbı yolu reddetti, çünkü `\z` kimlikten sonra bitişi ister. İç içe kalıp iki parçayı adıyla verdi. Adlandırılmış parçalar sırayla değil adla okunur; bu yüzden `yorum_id`'nin `id`'den sonra ya da önce gelmesi sonucu değiştirmez. Yoldaki iki sayı da dizgidir ve `to_i` ile tam sayıya çevrildi. **ÇY8.** Bu kalıp yalnız bu dersin dosyasında durur; çekirdeğin `YOLLAR` tablosuna eklenseydi sıra konusu yeniden doğardı.

#### Yolu Geri Üretmek

Yönlendirme yoldan hedefe gider. Uygulamanın başka bir yeri hedeften yola gitmek ister: bir bağlantı yazarken `/urunler/7`'yi elle yazmak yerine hedefi ve kimliği vermek. Bu yön de yönlendirmenin tanımına bağlıdır. İki yön ayrı yazılırsa ayrışabilir.

```ruby
# geri.rb — hedeften yol uretmek
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end
def yol_uret(hedef, id) = "/#{hedef.split('#').first}/#{id}"

yol = yol_uret("urunler#goster", 7)
p yol
p yonlendir(yol)
p yonlendir(yol_uret("urunler#goster", "abc"))
```
```
"/urunler/7"
{denetleyici: "urunler", eylem: "goster", id: 7}
nil
```

Üretilen yol çekirdeğin kalıbıyla geri çözüldü ve aynı kimliği verdi: gidiş dönüş tutarlı. Ama `yol_uret` kimliğin ne olduğuna bakmadı. `"abc"` verilince kalıbın reddedeceği bir yol üretti ve yönlendirme onu `nil` yaptı. Üretim kalıbı bilmiyor, kalıp üretimi bilmiyor; ikisini aynı tanıma bağlamak yönlendirme katmanının işidir. **ÇY9.** Bu ölçüm tek bir kalıp ve tek bir hedef içindir; çok kalıplı bir tabloda geri üretimin hangi kalıbı seçeceği ayrıca tanımlanmalıdır.

#### Kimliğin Sınırları

Kalıp yalnız rakam ister, ama kaç rakam ve hangi değer olduğu konusunda bir şey söylemez. Uç değerleri yoldan geçirip kimliğin ne olduğuna bakmak, `Integer` çevirmesinin neyi garanti ettiğini gösterir.

```ruby
# kimlik.rb — kimlik olarak gelen uc degerler
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end

["/urunler/007", "/urunler/0", "/urunler/99999999999999999999"].each do |yol|
  sonuc = yonlendir(yol)
  p [yol, sonuc && sonuc[:id], sonuc && sonuc[:id].class]
end
```
```
["/urunler/007", 7, Integer]
["/urunler/0", 0, Integer]
["/urunler/99999999999999999999", 99999999999999999999, Integer]
```

Baştaki sıfırlar atıldı: `007` yolu `7` kimliğini verdi, yani iki farklı yol aynı kaynağa gider. `0` geçerli bir kimlik sayıldı; kalıp sıfırı reddetmez, yalnız bir kaydın o kimlikle var olup olmadığı sonradan anlaşılır. Çok uzun rakam dizisi de reddedilmedi ve yine bir `Integer` oldu, çünkü Ruby'de tam sayının sabit bir üst sınırı yoktur. **ÇY10.** Yönlendirme kimliğin biçimini doğrular, varlığını değil; aynı kaynağa giden birden çok yol adresi, önbellekleme ve bağlantı karşılaştırması açısından ayrıca düşünülmesi gereken bir sonuçtur. Bu yüzden bir bağlantının kimliğini karşılaştıran kod, yolu değil yönlendirmenin verdiği tam sayıyı karşılaştırmalıdır; iki yol metni farklı olsa da çözülen kimlik aynıdır.

#### Kaynaktan Rota Üretmek

Kaynak temelli rotanın adı, bir kaynak adından bir rota kümesinin türemesinden gelir. Çekirdek yalnız `goster` kalıbını taşır, ama aynı fikir bir adı alıp birkaç kalıp üretebilir. Aşağıdaki iki yardımcı bu dersin kendi saf Ruby kodudur ve çekirdekte karşılığı yoktur. Kalıp, **HTTP yöntemi** (GET, POST, DELETE) ile birlikte aranır; aynı yol farklı yöntemle farklı eyleme gidebilir. Yöntem burada Ruby yöntemi değil, isteğin türüdür; adı `istek_yontemi` konur.

```ruby
# kaynak.rb — bir kaynak adindan rota uretmek
def kaynak_rotalari(ad)
  {
    ["GET", %r{\A/#{ad}\z}] => "#{ad}#liste",
    ["GET", %r{\A/#{ad}/(?<id>\d+)\z}] => "#{ad}#goster",
    ["POST", %r{\A/#{ad}\z}] => "#{ad}#olustur",
    ["DELETE", %r{\A/#{ad}/(?<id>\d+)\z}] => "#{ad}#sil",
  }
end

def rota_bul(tablo, istek_yontemi, yol)
  tablo.each do |(fiil, kalip), hedef|
    next unless fiil == istek_yontemi
    eslesme = kalip.match(yol) or next
    return [hedef, eslesme.names.empty? ? nil : eslesme[:id].to_i]
  end
  nil
end

tablo = kaynak_rotalari("urunler").merge(kaynak_rotalari("siparisler"))
p tablo.size
p rota_bul(tablo, "GET", "/urunler")
p rota_bul(tablo, "GET", "/siparisler/3")
p rota_bul(tablo, "POST", "/urunler")
p rota_bul(tablo, "DELETE", "/urunler/7")
p rota_bul(tablo, "POST", "/urunler/7")
```
```
8
["urunler#liste", nil]
["siparisler#goster", 3]
["urunler#olustur", nil]
["urunler#sil", 7]
nil
```

İki kaynak adı sekiz rota verdi: her ad dört kalıp üretti. `/urunler` GET ile `liste`ye, POST ile `olustur`a gitti; aynı yol, farklı eylem. `/siparisler/3` ikinci kaynağın `goster`ine gitti ve kimlik `3` oldu. Son satırda `/urunler/7` yolu var, ama POST ile tanımlı bir kalıp yok; sonuç `nil`. Yol tanınır, yöntem tanınmazsa da reddedilir. Bu kümeyi elle yazmak yerine adından üretmek yönlendirmedeki sözleşmedir: kaynak adı verilir, kalıplar türer. **ÇY11.** Bu dört kalıp bir tercihtir; bir kaynağın kaç eylemi olacağı bu ölçümden çıkmaz.

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

Yol parametresi URL'de taşınır, ama değeri sonraki isteğe geçmez. Çekirdeğin sonucu zaten bunu söyledi: `A2` kanıt sütununda `--`. Buradaki ölçüm aynı değerin iki ayrı isteğe nasıl ulaştığını ayırır.

```ruby
# sinir.rb — parametre URL'den gelir, istekten istege gecmez
require_relative "kalis"

# Yonlendirme: kaynak temelli bir yol kalibi denetleyici, eylem ve kimlige cevrilir.
YOLLAR = { %r{\A/urunler/(?<id>\d+)\z} => "urunler#goster" }

def yonlendir(yol)
  YOLLAR.each do |kalip, hedef|
    eslesme = kalip.match(yol) or next
    denetleyici, eylem = hedef.split("#")
    return { denetleyici:, eylem:, id: eslesme[:id].to_i }
  end
  nil
end

uygulama = Uygulama.new
p uygulama.istek("A") { |b| b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }
p uygulama.istek("A") { |b| b.params[:id] }
p uygulama.istek("A") { |b| b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }

ayni_yol = ->(b) { b.params.merge!(yonlendir("/urunler/7")); b.params[:id] }
p kalis(ayni_yol, ayni_yol)
```
```
7
nil
7
["kalici", "A1 A2 A3 B4", 7]
```

İlk istek `7` okudu. İkinci istek yolu yeniden istemedi ve `params[:id]` boş geldi: `nil`. Üçüncü istek yolu yeniden verdi ve `7`'yi yeniden aldı. Son satır bunun bir tuzağını gösterir. `oku` lambda'sı yönlendirmeyi kendisi yeniden koşturunca, her istek ve her istemci değeri gördü; kural bunu `kalici` sayar. Değer saklanmadı, her seferinde yoldan yeniden türedi. Kural davranışı okur, bu yüzden aynı düzenek kullanılışa göre başka bir kalışa düşer. Değeri bir sonraki isteğe taşımak yönlendirmenin değil, istemcinin işidir: bir bağlantı kimliği yeniden yola yazar; bir form ise onu sorgu dizgisiyle ya da istek gövdesiyle taşır. **ÇY12.** Bu ölçüm bellekteki çekirdekle yapıldı; gerçek bir istemcinin kimliği başka yoldan (başlık, çerez) taşıyıp taşımadığı sınanmadı.

#### Özet

- Yönlendirme bir yolu denetleyici, eylem ve kimliğe çevirir; kalıpta adlandırılmış parça kimliği verir ve kimlik bir `Integer` olur.
- Kalıba uymayan yol reddedilir: `/urunler/abc` ve sonunda eğik çizgi taşıyan `/urunler/7/` çekirdekte `nil` verir.
- Kalıp tablosunda ilk eşleşen kazanır; iki kalıbın sırası değişince aynı yol başka eyleme gider.
- Bu dersin kalışı **bu istek**tir: yol parametresi yalnız yolu taşıyan istekte okunur.
- Değeri sonraki isteğe taşımak istemcinin işidir; yolu her seferinde yeniden vermek değeri kalıcı gösterebilir ama saklamaz.

#### Sonraki Adım

Yönlendirme eylemin adını verdi, ama eylemi çalıştıran sınıf henüz sahnede değil. Bir sonraki ders denetleyiciyi ve eylemden önce koşan filtreyi ölçer, ve kaydedip başka sayfaya yönlendirdikten sonra bildirim mesajının neden bir kez göründüğüne bakar.

### Denetleyiciler ve Filtreler

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/denetleyiciler-ve-filtreler

Denetleyici her istekte yeniden kurulur ve filtre eylemden önce koşar. Ders filtre listesinin sınıfa bağlı olduğunu, denetleyici örneğinin isteğe bağlı olduğunu ve bildirim mesajının (flash) yalnız sonraki isteğe taşındığını ölçer.

Önceki ders, yönlendirmenin `urunler#goster` hedefini verdiğini ve yoldaki `7`'nin yalnız o istekte okunduğunu gösterdi. Hedefi karşılayan sınıf **denetleyici (controller)**dir. Bu dersin sorusu iki parçalı: denetleyici nesnesi bir istekten ötekine yaşar mı, ve bir eylemin başında koşan **filtre (filter)** neyi hatırlar? Üçüncü bir soru ikisine bağlanır: ürünü kaydedip başka bir sayfaya yönlendirdikten sonra gösterilen bildirim mesajı (flash) neden bir kez görünür?

M10/K02'de bir değerin ömrünü dilin hangi kapsamda bitirdiği soruldu; o derleyicinin kararıydı. Burada ömrü dil değil istek döngüsü belirler: `@urun` Ruby'de nesne yaşadıkça yaşar, ama denetleyici her istekte yeniden kurulduğu için değer bu istekte kalır.

Çekirdeğin `kalis.rb` dosyası bir kez daha aşağıda verilir. Blok kaynaktır, çıktısı yoktur 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
```

#### Filtre ve Sınıfa Bağlı Liste

Çekirdeğin denetleyicisi filtreyi `once :urunu_bul` satırıyla bildirir. Bu bir çağrıdır ve sınıfın bir listesine ad ekler; `calistir` eylemi çağırmadan önce o listedeki her adı sırayla çalıştırır. Aşağıdaki blok önce listenin kendisine bakar, sonra aynı sınıftan türeyen boş bir sınıfa, sonra da ürün yokken ve ürün varken eylemi koşturur.

```ruby
# filtre.rb — filtre listesi ve filtrenin bulduğu urun
require_relative "kalis"

# Nesne-iliski esleyici: bir satir bir kayda, tablo bir modele karsilik gelir.
module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

# Denetleyici: her istekte yeni bir ornek; filtre eylemden once calisir.
class UrunlerDenetleyicisi
  def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
  def self.filtreler = @filtreler || []

  once :urunu_bul

  def initialize(baglam)
    @baglam = baglam
  end

  def calistir(eylem)
    self.class.filtreler.each { |filtre| send(filtre) }
    public_send(eylem)
  end

  def goster = @urun

  private

  def urunu_bul
    @urun = Urun.bul(@baglam, @baglam.params[:id])
  end
end

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }

p UrunlerDenetleyicisi.filtreler

class Alt < UrunlerDenetleyicisi; end
p Alt.filtreler

uygulama = Uygulama.new
p uygulama.istek("A") { |b| b.params[:id] = 99; UrunlerDenetleyicisi.new(b).calistir(:goster) }
p uygulama.istek("A") { |b| kalem.call(b); b.params[:id] = 1; UrunlerDenetleyicisi.new(b).calistir(:goster) }
```
```
[:urunu_bul]
[]
nil
{ad: "kalem", fiyat: 12, id: 1}
```

İlk satır liste, `urunu_bul` adını taşıyor. İkinci satır şaşırtıcıdır: `Alt` aynı sınıftan türediği hâlde listesi boş. Filtre listesi sınıfın kendi örnek değişkeninde durur ve alt sınıfa geçmez; alt sınıf bu filtreleri kendisi bildirmelidir. **ÇY13.** Bu ölçüm tek bir bildirim biçimi içindir; filtre listesini sınıf düzeyinde bir sabitte tutan bir çerçeve alt sınıfa farklı davranabilir. Üçüncü satır, kimliği `99` olan ürün olmadığında eylemin `nil` verdiğini gösterir: filtre ürünü bulamadı, eylem boş bir örnek değişkeni döndürdü ve hiçbir hata çıkmadı. Dördüncü satırda ürün önce oluşturuldu ve filtre onu buldu. Eylem kimliğe hiç dokunmadı; ürünü hazırlayan filtredir. Bu işbölümü filtrenin asıl nedenidir: ürünü bulma satırı her eylemde yinelenmez, tek yerde durur ve eylem yalnız hazırlanmış değeri kullanır. Karşılığı bir bağımlılıktır; eylemi filtre koşmadan çağıran kod, `goster`'in `nil` verdiğini ancak sonuca bakınca fark eder. Önceki dersin yönlendirmesi kimliği `params`'a yazdığı için filtre ona güvenebilir, ama yönlendirme koşmamışsa filtre `nil` kimlikle arar ve ürün bulamaz.

Bu dersteki çekirdeğin modeli bir kolaylık yapar ve bir kez söylenmelidir: `Urun.bul` depoda saklanan nesnenin kendisini verir. Birçok nesne–tablo eşleyicisi (object-relational mapper, ORM) her okumada tablodan yeni bir nesne kurar. Bu fark ölçülen kalışı değiştirmez, çünkü kalış değerin hangi isteğe kadar okunabildiğine bakar, nesnenin aynı olup olmadığına değil.

#### Denetleyici Her İstekte Yeni

Önceki kurslarda bir nesnenin yaşadığı sürece değerlerini tuttuğunu gördük: Ruby'de nesne, ona ulaşan kod kaldığı sürece yaşar. Web'de buna bir ek gerekir. Denetleyici nesnesi yaşayabilir, ama çerçeve onu her istekte yeniden kurduğu için örnek değişkeni o istekte kalır. Aşağıdaki blok iki ardışık istekte denetleyiciyi kurar ve ikincisinde birincinin örneğiyle aynı olup olmadığına bakar.

```ruby
# yeni_ornek.rb — denetleyici ornegi istekten istege tasinmaz
require_relative "kalis"

# Nesne-iliski esleyici: bir satir bir kayda, tablo bir modele karsilik gelir.
module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

# Denetleyici: her istekte yeni bir ornek; filtre eylemden once calisir.
class UrunlerDenetleyicisi
  def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
  def self.filtreler = @filtreler || []

  once :urunu_bul

  def initialize(baglam)
    @baglam = baglam
  end

  def calistir(eylem)
    self.class.filtreler.each { |filtre| send(filtre) }
    public_send(eylem)
  end

  def goster = @urun

  private

  def urunu_bul
    @urun = Urun.bul(@baglam, @baglam.params[:id])
  end
end

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }

uygulama = Uygulama.new
uygulama.istek("A") { |b| kalem.call(b) }

birinci = nil
sonuc = uygulama.istek("A") do |b|
  b.params[:id] = 1
  birinci = UrunlerDenetleyicisi.new(b)
  birinci.calistir(:goster)
end
p sonuc
sonuc = uygulama.istek("A") do |b|
  ikinci = UrunlerDenetleyicisi.new(b)
  [ikinci.equal?(birinci), ikinci.goster]
end
p sonuc
```
```
{ad: "kalem", fiyat: 12, id: 1}
[false, nil]
```

İlk istekte filtre ürünü bulup `@urun`'a yerleştirdi ve eylem onu verdi. İkinci istek yeni bir örnek kurdu: `equal?` yanıtı `false`, ve `goster` `nil` verdi, çünkü filtre bu örnekte koşmadı ve `@urun` hiç atanmadı. Birinci istekteki örnek hâlâ bellekteydi, ama ikinci istek ona ulaşmadı. Denetleyicinin örnek değişkenleri bu yüzden **bu istek** kalışındadır. Bir değeri sonraki isteğe taşımak gerekiyorsa denetleyicinin dışında bir yere yazılmalıdır.

**ÇY14.** Ölçüm, çekirdeğin her istekte yeni bir `Baglam` ve yeni bir örnek kurduğu varsayımına dayanır. Örneği yeniden kullanan bir sunucu, aynı sınıfı farklı bir kalışla çalıştırabilir.

#### Filtre Sırası ve Yarıda Kalan Zincir

Birden çok filtre bildirildiğinde sıra bildirim sırasıdır. Bir filtre hata verirse ne olacağı da çekirdeğin `calistir`'ının yapısından çıkar. Aşağıdaki sınıf bu dersin kendi örneğidir ve çekirdeğin `UrunlerDenetleyicisi`'ne eklenmez; aynı iki satırlık düzeneği, olay kaydı tutan bir sınıfta yeniden kurar.

```ruby
# siralama.rb — filtre sirasi ve hata veren filtre
class Deneme
  def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
  def self.filtreler = @filtreler || []

  once :birinci, :ikinci

  attr_reader :kayit

  def initialize = @kayit = []

  def calistir(eylem)
    self.class.filtreler.each { |filtre| send(filtre) }
    public_send(eylem)
  end

  def eylem = @kayit << :eylem

  private

  def birinci = @kayit << :birinci
  def ikinci = raise(ArgumentError, "ikinci filtre durdu")
end

d = Deneme.new
begin
  d.calistir(:eylem)
rescue ArgumentError => e
  p e.class
end
p d.kayit
```
```
ArgumentError
[:birinci]
```

Birinci filtre koştu, ikinci hata verdi ve eylem hiç çağrılmadı: kayıtta `:eylem` yok. Zincir, hatanın çıktığı noktada kesilir. Bu çekirdekte bir tasarım seçimidir, çünkü `each` hata yükselince durur ve `public_send` satırına hiç gelinmez. Gerçek bir çerçevede bir filtrenin yanıtı kendisinin vermesi ve zinciri durdurması da mümkündür; çekirdek bunu modellemez. **ÇY15.** Ölçüm, çekirdeğin `each` ile sıralı çalıştırma yaptığı ve hatayı yakalamadığı varsayımına dayanır.

#### Kaydet, Yönlendir, Bildirimi Göster

Bu dersin örneğinde bir formun gönderilmesi üç istekle izlenir. İlkinde ürün kaydedilir ve bildirim mesajı (flash) yazılır. Eylem sonra başka bir sayfaya yönlendirir. İkinci istekte yeni sayfa bildirimi okur ve gösterir. Üçüncü istekte sayfa yenilenir ve bildirim yoktur.

```ruby
# bildirim.rb — kaydet, yonlendir, mesaji goster
require_relative "kalis"

# Nesne-iliski esleyici: bir satir bir kayda, tablo bir modele karsilik gelir.
module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

uygulama = Uygulama.new
p uygulama.istek("A") { |b| s = Urun.olustur(b, ad: "kalem", fiyat: 12); b.flash[:bildirim] = "urun kaydedildi"; s }
p uygulama.istek("A") { |b| b.flash[:bildirim] }
p uygulama.istek("A") { |b| b.flash[:bildirim] }
```
```
{id: 1, hatalar: []}
"urun kaydedildi"
nil
```

İlk istek kaydı oluşturdu ve kimliği `1` verdi; `hatalar` boş. İkinci istek bildirimi okudu: `"urun kaydedildi"`. Üçüncü istek aynı anahtarı okudu ve `nil` aldı. Bildirim yazıldığı istekten bir sonrakine taşındı ve orada tüketildi. Çekirdeğin `Baglam` sınıfı her istekte oturumdaki `:flash` anahtarını silerek okur; bu yüzden bir bildirim bir kez okunabilir. Kaydın kendisi ise üç istekte de depoda durdu, çünkü ürün uygulama deposuna yazılmıştı.

#### Okunmayan ve Üzerine Yazılan Bildirim

İki durum daha sorulabilir: bildirim sonraki istekte okunmazsa üçüncü istekte hâlâ durur mu, ve aynı anahtar aynı istekte iki kez yazılırsa hangi değer taşınır?

```ruby
# okunmayan.rb — okunmayan ve uzerine yazilan bildirim
require_relative "kalis"

uygulama = Uygulama.new
p uygulama.istek("A") { |b| b.flash[:bildirim] = "ilk"; b.flash[:bildirim] = "son"; b.flash[:bildirim] }
p uygulama.istek("A") { |b| b.params[:sayfa] = 1 }
p uygulama.istek("A") { |b| b.flash[:bildirim] }
```
```
"son"
1
nil
```

İlk istekte iki yazış oldu ve okuma son değeri verdi. İkinci istek bildirimi hiç okumadı, yalnız başka bir iş yaptı. Üçüncü istek `nil` aldı. Bildirimin yaşaması, okunmasına değil, bir isteğin başlamasına bağlıdır: her yeni `Baglam` oturumdaki bildirimi alıp siler. Okunmayan bildirim, okunsaydı gideceği yerden aynı anda gider. **ÇY16.** Bu ölçüm üç istek ve tek bir anahtar içindir.

Aynı düzeneği çekirdeğin sınıflandırıcısıyla ölçmek, bildirimin kalışını bir sözcükle adlandırır.

```ruby
# denetleyiciler.rb — bildirimin kalisi
require_relative "kalis"

yaz = ->(b) { b.flash[:bildirim] = "urun kaydedildi" }
oku = ->(b) { b.flash[:bildirim] }
p kalis(yaz, oku)
```
```
["sonraki", "A1 A2 -- --", "urun kaydedildi"]
```

`A1 A2 -- --` dizisi bildirimin yazıldığı istekte ve bir sonrakinde görüldüğünü, üçüncü istekte ve başka istemcide görülmediğini söyler. Kalış **sonraki istek**tir: aynı istemcinin yalnız bir sonraki isteğinde okunur. Bu, çekirdeğin on üç gözleminden yalnız birinin sonucudur; öteki gözlemlerin hiçbiri bu kalışa düşmedi.

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

Bildirimin sonraki isteğe taşınması düzeneğin değil, kullanılışın sonucudur. Okuyan kod değeri her okuduğunda yeniden yazarsa, aynı düzenek değeri oturum boyunca taşır.

```ruby
# sinir.rb — ayni duzenek baska bir kalisa dusebilir
require_relative "kalis"

yaz = ->(b) { b.flash[:sayac] = 1 }
oku = ->(b) { v = b.flash[:sayac]; b.flash[:sayac] = v if v; v }
p kalis(yaz, oku)
```
```
["oturum", "A1 A2 A3 --", 1]
```

Bildirim bu kez `["oturum", "A1 A2 A3 --", 1]` olarak sınıflandı: aynı istemcinin üçüncü isteğinde de okundu, başka istemcide okunmadı. Değiştirilen şey düzenek değil, okuyan kodun davranışıdır. Okurken yeniden yazan her istek değeri bir sonrakine geçirir; zincir ancak bir istek yazmadan okuduğunda kopar. **ÇY17.** Bu ölçüm yalnız bir anahtar ve üç istek içindir; zincirin başka bir sunucuda ne kadar sürdüğü ölçülmedi.

#### Özet

- Filtre eylemden önce koşar ve eylemin kullanacağı değeri hazırlar; filtre listesi sınıfa bağlıdır ve alt sınıfa geçmez.
- Denetleyici her istekte yeni bir örnek olarak kurulur; örnek değişkeni yalnız o istekte kalır.
- Kaydet, yönlendir, göster akışında bildirim mesajı (flash) yazıldığı istekten bir sonrakine taşınır ve orada tüketilir.
- Bu dersin kalışı **sonraki istek**tir: bildirim yalnız bir sonraki istekte okunur.
- Aynı düzenek kullanılışa göre başka kalışa düşer: okurken yeniden yazan kod bildirimi oturuma çevirir.

#### Sonraki Adım

Denetleyici ürünü hazırladı ve bildirimi taşıdı, ama çıktıyı üretmedi. Bir sonraki ders görünümü ve şablonu ölçer: bir şablon kaç kez derlenir, yerel değerler nasıl kaçışlanır ve derlenmiş şablon hangi kalışta durur. Bildirim mesajı (flash) bu derste sonraki isteğe taşındı; şablonun derlenmiş hâli ise isteğe değil, uygulamaya bağlı bir yerde duracak ve başka bir kalışa düşecek.

### Görünümler ve Şablonlar

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/gorunumler-ve-sablonlar

Şablon bir kez derlenir ve yerel değerler her çizimde kaçışlanır. Ders kaçışlamanın ne yaptığını, eksik yerel değerin hata verdiğini, kısmi şablonda çift kaçışlamanın doğduğunu ve derlenmiş şablonun kalıcı olduğunu ölçer.

Önceki ders, denetleyicinin ürünü hazırladığını ve bildirim mesajını (flash) sonraki isteğe taşıdığını gösterdi. Hazırlanan değer sonunda bir **görünüm (view)**e girer ve görünüm onu kullanıcının göreceği metne çevirir. Görünümün kaynağı bir **şablon (template)**tir: sabit metin ile `{{ad}}` gibi doldurulacak yerlerden oluşan bir dizgi. Bu dersin iki sorusu var. Değer şablona girerken ne olur: yerel değer olarak verilen `<b>kalem</b>` çıktıda etiket mi olur, metin mi? Ve şablonun kendisi, çizildikten sonra nerede durur ve hangi isteğe kadar kalır?

Çekirdeğin `kalis.rb` dosyası bir kez daha aşağıda verilir. Blok kaynaktır, çıktısı yoktur 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
```

#### Kaçışlama

Çekirdeğin görünüm katmanı iki yardımcıdan oluşur. `kacisla` metindeki dört karakteri karşılığıyla değiştirir. `sablon_derle` bir şablon kaynağını bir kez bir lambda'ya çevirir; o lambda çağrıldıkça yerel değerleri `kacisla`'dan geçirerek yerlerine koyar. **Kaçışlama (escaping)**, kullanıcıdan gelen metnin sayfada işaretleme olarak değil metin olarak görünmesini sağlar.

```ruby
# kacisla.rb — dort karakterin kacislanmasi ve iki kez kacislama
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

p kacisla('<script>alert("x")</script>')
p kacisla("Tom & Jerry")
p kacisla(kacisla("<b>"))
p kacisla(nil)
p kacisla(12)
```
```
"&lt;script&gt;alert(&quot;x&quot;)&lt;/script&gt;"
"Tom &amp; Jerry"
"&amp;lt;b&amp;gt;"
""
"12"
```

İlk satır dört karakterin hepsini gösterir: `<` ve `>` etiket sınırlarını, `"` öznitelik sınırını, `&` ise kaçışlamanın kendi başlangıcını korur. Etiket olarak yorumlanabilecek bir metin artık yalnızca metindir. İkinci satırda `&` önce kaçışlandı ve `&amp;` oldu. Üçüncü satır çift kaçışlamanın sonucudur: `<b>` ilk geçişte `&lt;b&gt;` oldu, ikinci geçişte `&` karakterleri yeniden değişti ve metin `&amp;lt;b&amp;gt;` biçimine geldi. Sayfada bu, kullanıcıya `&lt;b&gt;` yazısını gösterir. Son iki satır `kacisla`'nın her girdiyi önce dizgiye çevirdiğini gösterir: `nil` boş dizgi, `12` ise `"12"` oldu.

Kaçışlamanın şablon motorunda olması bir tercihtir. Denetleyici ya da model değeri kaçışlayıp saklasaydı, aynı veri bir konsolda, bir dışa aktarmada ya da başka bir görünümde `&lt;` yazısıyla görünürdü. Kaçışlama çıktının biçimine aittir, bu yüzden çizim anında ve yalnız çizilen yerde yapılır. Değerin kendisi olduğu gibi saklanır ve her görünüm kendi biçimine göre kaçışlar; veriyi iki kez dönüştürmek, çift kaçışlamanın ilk nedenidir.

**ÇY18.** Bu ölçüm yalnız dört karakter içindir. Tek tırnak çekirdekte kaçışlanmaz; tek tırnakla sınırlanan bir öznitelik değerine kullanıcı metni koymak bu kaçışlamayla güvenli değildir.

#### Eksik Yerel Değer

Şablondaki her `{{ad}}` yerine bir yerel değer gerekir. Çekirdeğin lambda'sı yerel değeri `fetch` ile okur. Bu, eksik bir anahtarın sessizce boş dizgi vermesini değil, hata vermesini sağlar.

```ruby
# eksik.rb — yerel deger verilmeyen yer
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

uygulama = Uygulama.new
sablon = sablon_derle(Baglam.new(uygulama, {}), "urunler/goster", "<h1>{{ad}}</h1>")

p sablon.call(ad: "kalem")
begin
  sablon.call({})
rescue KeyError => e
  p e.class
end
p sablon.call(ad: "kalem", fiyat: 12)
```
```
"<h1>kalem</h1>"
KeyError
"<h1>kalem</h1>"
```

Doğru yerel değerle şablon `<h1>kalem</h1>` verdi. Yerel değer verilmeyince `KeyError` çıktı; hata iletisi basılmadı çünkü iletinin biçimi sürüme bağlıdır. Üçüncü çağrı, şablonda karşılığı olmayan fazladan bir yerel değerle çalıştı ve hata vermedi: şablon yalnız kendi yerlerini arar, fazlalığı yok sayar. Eksik değerin hata olması, fazla değerin olmaması bir tasarım dengesidir. Bir şablonu yazan, yerel değerlerin eksiksiz geldiğini ister; ama bir denetleyici şablona gereğinden çok değer geçirebilir, bu hata sayılmaz.

**ÇY19.** Ölçülen davranış `fetch` kullanan çekirdeğe aittir. Eksik değeri boş dizgi sayan bir şablon motoru, aynı şablonda hata vermeden boş bir başlık üretir.

#### Kısmi Şablon ve Çift Kaçışlama

Büyük bir sayfa küçük şablonlara bölünür. Bir şablonun başka bir şablonun içinde çizilen parçası **kısmi şablon (partial)** adını alır. Çekirdekte kısmi şablon için ayrı bir yardımcı yoktur; derlenmiş bir şablon başka bir şablonun yerel değerine verilebilir. Aşağıdaki yardımcı bu dersin kendi saf Ruby yardımcısıdır ve çekirdeğe eklenmez.

```ruby
# kismi.rb — kismi sablonun ciktisini dis sablona vermek
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

def kismi(b, ad, yerel) = b.uygulama.depo[:sablonlar].fetch(ad).call(yerel)

uygulama = Uygulama.new
b = Baglam.new(uygulama, {})
sablon_derle(b, "urunler/satir", "<li>{{ad}}</li>")
sablon_derle(b, "urunler/liste", "<ul>{{govde}}</ul>")

satir = kismi(b, "urunler/satir", ad: "kalem")
p satir
p kismi(b, "urunler/liste", govde: satir)
p kismi(b, "urunler/liste", govde: kacisla("a&b"))
```
```
"<li>kalem</li>"
"<ul>&lt;li&gt;kalem&lt;/li&gt;</ul>"
"<ul>a&amp;amp;b</ul>"
```

Kısmi şablon `<li>kalem</li>` verdi. Bu çıktı dış şablona yerel değer olarak verilince dış şablon onu da kaçışladı ve liste `<ul>&lt;li&gt;kalem&lt;/li&gt;</ul>` oldu: sayfada kullanıcı etiketi değil, etiketin yazısını görür. Çekirdeğin modelinde bir dizginin zaten çizilmiş ve güvenli olduğunu işaretlemenin yolu yoktur; her yerel değer, kaynağı ne olursa olsun, kaçışlanır. Üçüncü satır aynı sorunun öteki yüzünü gösterir: değer çağıran tarafta önceden elle kaçışlanırsa dış şablon onu bir kez daha kaçışlar ve `&` karakteri yeniden değişir. Çift kaçışlamadan kurtulmanın yolu, değeri hiç kaçışlamadan vermek değildir; o durumda çekirdek yine kaçışlar. Kısmi şablonun çıktısı bu yüzden dış şablona yerel değer olarak taşınamaz; çıktıyı bu modelde dış şablonun kaynağına gömmek gerekir.

**ÇY20.** Gerçek şablon motorları çizilmiş çıktıyı güvenli diye işaretlemenin bir yolunu sunar ve çift kaçışlama sorununu o yolla çözer. Çekirdek bunu modellemez; bu yüzden ölçülen davranış, işaret taşıyan bir motorda görülmez.

#### Yerel Değerin Türü

Şablon yerel değer olarak dizgi bekler, ama `kacisla` her girdiyi önce `to_s` ile dizgiye çevirir. Dizgi olmayan değerlerin sayfaya nasıl girdiğine bakmak, denetleyicinin şablona ne verebileceğini gösterir.

```ruby
# tur.rb — dizgi olmayan yerel degerler
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

p kacisla(:ad)
p kacisla(1.5)
p kacisla([1, "<"])
p kacisla({ a: 1 })
```
```
"ad"
"1.5"
"[1, &quot;&lt;&quot;]"
"{a: 1}"
```

`:ad` değeri ile ondalık sayının çıktısı beklenen biçimdedir. Dizi ve karma yapı ise Ruby'nin kendi gösterimleriyle dizgiye çevrildi ve ardından kaçışlandı: dizideki `"` karakterleri `&quot;`, `<` karakteri `&lt;` oldu. Sayfa bu durumda kullanıcıya Ruby'nin iç gösterimini verir, ki bu çoğunlukla istenen çıktı değildir. Denetleyici şablona gösterilecek metni hazır vermelidir; şablon yalnız dizgiyi kaçışlar, biçimlendirme yapmaz.

#### Bir Şablon, Çok Çizim

Derlenmiş şablon uygulama deposunda tek bir kayıt olarak durur. Aynı şablonun farklı yerel değerlerle çağrılması depoyu büyütmez; çizilen çıktı da saklanmaz.

```ruby
# cizim.rb — bir sablon, uc cizim
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

uygulama = Uygulama.new
b = Baglam.new(uygulama, {})
sablon_derle(b, "urunler/goster", "<h1>{{ad}}</h1>")
depo = uygulama.depo[:sablonlar]

p depo["urunler/goster"].call(ad: "kalem")
p depo["urunler/goster"].call(ad: "defter")
p depo["urunler/goster"].call(ad: "silgi")
p depo.size
p depo.keys
```
```
"<h1>kalem</h1>"
"<h1>defter</h1>"
"<h1>silgi</h1>"
1
["urunler/goster"]
```

Üç çağrı üç ayrı çıktı verdi ve hiçbiri ötekini etkilemedi: yerel değerler her çağrıda lambda'ya girdi olarak geçer, lambda'nın içinde durmaz. Depoda yine tek kayıt vardı ve anahtarı şablonun adıydı. Çizim bu yüzden **bu istek** kalışındadır: çıktı yalnız çağrıyı yapan koda döner, hiçbir yere bırakılmaz. Kalıcı olan yalnızca şablonun derlenmiş hâlidir.

#### Şablon Adı ve Arama

İlk derste şablon adının denetleyici ve eylem adından türediğini gördük. Bu adın bir anlamı yok değildir: depodaki derlenmiş şablonlar bu adla aranır. Adı üreten sözleşme ile adı arayan depo arasında bir bağ vardır, ama bu bağ yalnız adın eşleşmesiyle kurulur.

```ruby
# arama.rb — turetilen adla sablon aramak
require_relative "kalis"

# Sozlesme: sablon adi yazilmaz, denetleyici ve eylem adindan turetilir.
def sablon_adi(params) = "#{params[:denetleyici]}/#{params[:eylem]}"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

uygulama = Uygulama.new
b = Baglam.new(uygulama, {})
depo = uygulama.depo[:sablonlar]
ad = sablon_adi(denetleyici: "urunler", eylem: "goster")

p depo[ad]
begin
  depo.fetch(ad)
rescue KeyError => e
  p e.class
end

sablon_derle(b, ad, "<h1>{{ad}}</h1>")
p depo.fetch(ad).call(ad: "kalem")
```
```
nil
KeyError
"<h1>kalem</h1>"
```

Şablon derlenmeden önce `depo[ad]` `nil` verdi; `fetch` ise `KeyError` fırlattı. Hangisinin seçileceği, eksik şablonun sessizce mi yoksa bir hatayla mı belirtileceğini seçer. Derlemeden sonra aynı ad şablonu buldu ve çizim yapıldı. Sözleşme adı üretir, depo adı arar; birinin adı üretmesi ötekinin o adla bir şablon derlediği anlamına gelmez. **ÇY21.** Ölçüm tek bir ad içindir ve adın üretildiği ve derlendiği anda tutarlı olduğunu varsayar.

#### Derlenmiş Şablonun Kalışı

Şablon bir kez derlenir ve sonuç uygulama deposuna yazılır. Derlenmiş şablonun hangi isteğe kadar okunduğunu çekirdeğin sınıflandırıcısına sorabiliriz.

```ruby
# gorunumler.rb — derlenmis sablonun kalisi
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

yaz = ->(b) { sablon_derle(b, "urunler/goster", "<h1>{{ad}}</h1>") }
oku = ->(b) { b.uygulama.depo[:sablonlar]["urunler/goster"]&.call(ad: "<b>kalem</b>") }
p kalis(yaz, oku)
```
```
["kalici", "A1 A2 A3 B4", "<h1>&lt;b&gt;kalem&lt;/b&gt;</h1>"]
```

Sonuç `["kalici", "A1 A2 A3 B4", "<h1>&lt;b&gt;kalem&lt;/b&gt;</h1>"]` oldu. Şablon dört okumanın dördünde de vardı, ve B istemcisi de onu okudu. Kalış **kalıcı**dır, çünkü şablon istekte değil uygulama deposunda durur. Üçüncü sütun aynı zamanda kaçışlamanın etkisini gösterir: yerel değer olarak verilen `<b>kalem</b>` çıktıda etiket değil metin oldu.

Burada ölçülen şey çizilmiş çıktı değildir; çizim her çağrıda yeniden yapılır. Kalıcı olan, şablonun derlenmiş hâlidir. Bu fark önemlidir: derleme pahalı bir iştir ve bir kez yapılır, çizim ise ucuzdur ve her istekte tekrarlanır.

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

Derlemenin bir kez yapılması, şablon kaynağı değiştiğinde yeni kaynağın görülmeyeceği anlamına da gelir. Çekirdek aynı ad için ikinci bir derlemede mevcut şablonu korur.

```ruby
# sinir.rb — ayni adla ikinci derleme eski sablonu korur
require_relative "kalis"

# Gorunum: sablon bir kez derlenir; yerel degerler her cizimde kacislanir.
def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def sablon_derle(b, ad, kaynak)
  b.uygulama.depo[:sablonlar][ad] ||= lambda do |yerel|
    kaynak.gsub(/\{\{(\w+)\}\}/) { kacisla(yerel.fetch(Regexp.last_match(1).to_sym)) }
  end
end

uygulama = Uygulama.new
b = Baglam.new(uygulama, {})
depo = uygulama.depo[:sablonlar]

sablon_derle(b, "urunler/goster", "<h1>{{ad}}</h1>")
p depo["urunler/goster"].call(ad: "a")

sablon_derle(b, "urunler/goster", "<h2>{{ad}}</h2>")
p depo["urunler/goster"].call(ad: "a")

depo.delete("urunler/goster")
sablon_derle(b, "urunler/goster", "<h2>{{ad}}</h2>")
p depo["urunler/goster"].call(ad: "a")
```
```
"<h1>a</h1>"
"<h1>a</h1>"
"<h2>a</h2>"
```

İkinci derleme yeni kaynağı görmedi: şablon hâlâ `<h1>a</h1>` verdi. Çekirdek, adı daha önce derlenmiş bir şablonu yeniden derlemez; yeni kaynak sessizce yok sayılır. Şablon depodan silindikten sonra aynı çağrı `<h2>a</h2>` verdi. Yani derlenmiş şablonu yenilemenin tek yolu, onu depodan çıkarmaktır. **ÇY22.** Bu ölçüm bellekte tutulan bir depo içindir; kaynak dosyadan okunan bir çerçevede değişikliği algılama ayrıca tasarlanır.

#### Özet

- Şablon motoru her yerel değeri kaçışlar; kaçışlama dört karakteri değiştirir ve iki kez uygulanırsa `&amp;lt;` gibi bir çıktı verir.
- Eksik bir yerel değer `KeyError` verir; fazladan bir yerel değer yok sayılır.
- Kısmi şablonun çıktısı dış şablona yerel değer verilirse yeniden kaçışlanır; çekirdekte çizilmiş çıktıyı güvenli diye işaretlemenin yolu yoktur.
- Bu dersin kalışı **kalıcı**dır: derlenmiş şablon uygulama deposunda durur ve bütün isteklerde, bütün istemcilerde okunur.
- Aynı adla ikinci derleme eski şablonu korur; yenilemek için şablonun depodan silinmesi gerekir.

#### Sonraki Adım

Denetleyici bildirimi taşıdı ve görünüm şablonu uygulama deposunda tuttu; kaydedilen ürün de aynı depoda duruyor. Bir sonraki konu bu depoyu açar: kaydedilen bir ürünün başka bir tarayıcıda da görünmesi için verinin nerede durması gerektiğini ve model katmanının bu kalışı nasıl kurduğunu ölçer.

## Veri Katmanı

Tablo satırlarını nesnelere bağlayan model katmanı, şemayı sürüm sürüm değiştiren göçler, kayıtlar arasındaki ilişkiler ve kaydı yazılmadan önce durduran doğrulamalar, zincirlenen sorgular, istek içi sorgu önbelleği ve N+1 sorgu sorunu.

### Nesne–Tablo Bağlaması

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/nesne-tablo-baglamasi

Bir tablo satırını nesneye, bir tabloyu modele bağlayan katmanın ölçümü; kaydedilen değerin başka bir istemcide de okunduğunu ve modelin bu çekirdekteki sınırını gösterir.

Önceki konunun son dersi, derlenmiş bir şablonun herkes için ortak olduğunu ve bir kez derlendikten sonra bayatlayabildiğini ölçtü: şablon bir istemcide derlendi, başka bir istemci aynı derlenmiş şablonu çalıştırdı. Şablon çizilirken bir ürünü gösterir. O ürünün nereden geldiği ise henüz sorulmadı. Bu konu ona ayrılmıştır: bir kullanıcı formdan ürünü kaydetti; ürünü başka bir tarayıcıdaki başka bir kullanıcı okuyor mu?

Kursun ekseni aynıdır: **bu değer hangi isteğe kadar kalıyor?** Dört kalış vardır. Değer yalnız yazıldığı istekte duruyorsa kalışı **bu istek**, aynı istemcinin yalnız bir sonraki isteğinde duruyorsa **sonraki istek**, aynı istemcinin bütün sonraki isteklerinde ama başka istemcide yoksa **oturum**, başka bir istemcide de okunuyorsa **kalıcı**dır. Bu dersin gözlemi bir ürün kaydıdır ve beklenen kalış kalıcıdır; ama kalışı bir etiket söylemez, ölçüm söyler.

Dersin kavramı **nesne–tablo bağlaması** (object-relational mapping) ve onu kuran yazılım parçası olan **eşleyici**dir. Veritabanında veri tablolarda, tablolar satırlarda durur; programda veri nesnelerde durur. Eşleyici iki dünyayı birbirine bağlar: bir tablo bir **modele**, tablonun bir satırı o modelin bir **kaydına** karşılık gelir. Aynı kavram M12/K04'te PHP ile kurulmuştu. Orada ölçülen şey "bu parça bu istekte koştu mu" sorusuydu; burada kavram aynı, ölçü başkadır: kaydın kendisi değil, kaydın hangi isteğe kadar kaldığı sorulur.

#### Ölçüm Çekirdeği

Çekirdek bir çerçeve değil, bir çerçevenin modelidir; sandbox'ta çerçeve kütüphanesi yoktur ve ağ kapalıdır. İlk dosya, kursun her dersinde birebir aynı olan küçük istek döngüsü ve kalışı sınıflandıran yardımcı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
```

`Uygulama` bütün isteklerde ve bütün istemcilerde aynı `depo`yu taşır; oturum ise istemciye göre seçilir. Her istek yeni bir `Baglam` alır ve `istek_deposu` her istekte boştur. `kalis` yazan ve okuyan iki lambda'yı alır: birinci istekte A istemcisi ikisini de çalıştırır, sonra A'nın iki isteği ve B'nin bir isteği yalnız okur. Değeri B görüyorsa kalış kalıcıdır; B görmüyor ama A'nın üçüncü isteği görüyorsa oturum; yalnız ikinci istek görüyorsa sonraki; hiçbiri görmüyorsa bu istektir. Kural değerin davranışını okur, saklandığı yeri okumaz.

Bu dersin dosyası modeli verir. Kesit, çekirdekten dersin ihtiyaç duyduğu kadarıdır: `Urun` modeli.

```ruby
# baglama.rb — urun modeli: tablo bir modele, satir bir kayda karsilik gelir
require_relative "kalis"

module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end
```

Bu blok da kaynaktır. `olustur` bir kayıt yazar: tablo `depo[:urunler]` karma yapısıdır, anahtarı kimlik, değeri satırdır. Kimlik tablonun boyundan türer — ilk kayıt `1`, ikincisi `2` — ve satır, verilen niteliklere kimliğin eklenmesiyle oluşur. Doğrulama satırları şimdilik yalnız hata listesini doldurur; üçüncü ders onlara döner. `bul` bir kimlikle satırı okur; sorgu önbelleği dördüncü dersin konusudur, burada yalnız `depo[:urunler][id]` okuması önemlidir.

#### Kaydın Kalışı

Kaydedilen ürünü başka bir tarayıcı okuyor mu? Yazan lambda bir `kalem` kaydeder, okuyan lambda tablodan birinci satırın adını alır.

```ruby
# olcum.rb — kayit gozleminin kalisi
require_relative "baglama"

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
oku = ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }
p kalis(kalem, oku)
```
```
["kalici", "A1 A2 A3 B4", "kalem"]
```

Üç alan okunur. Kalış **kalıcı**: kural B'nin dördüncü isteğinde de değeri gördü. Kanıt satırı `A1 A2 A3 B4` dört isteğin hepsinin `kalem`i okuduğunu söyler; hiçbirinde `--` yok. Son alan, birinci istekte okunan değerdir. Önceki konunun sonunda sorulan sorunun cevabı budur: kaydedilen ürünü başka tarayıcı okuyor. **VK1.** Bu ölçüm tek süreç içindir. Çekirdeğin deposu bellektedir; ölçüm, süreç kapanıp yeniden açıldığında verinin kalıp kalmadığını kapsamaz.

Kalıcılığın nedeni `depo`nun nerede durduğudur. Flash ve istek deposu `Baglam` ile birlikte her istekte yeniden doğar; oturum istemciye bağlıdır; `depo` ise `Uygulama` nesnesindedir ve uygulama bütün istekleri karşılar. Model, değeri en uzun yaşayan yere yazdığı için kalış kalıcı çıktı.

Kalıcılığın bir sonucu daha vardır: tablo istemciler arasında ortaktır, bu yüzden iki istemcinin kayıtları aynı kimlik dizisine girer. A bir ürün oluşturur, B başka bir ürün oluşturur, A tabloyu okur.

```ruby
# iki_istemci.rb — iki istemci, tek tablo
require_relative "baglama"

uygulama = Uygulama.new
uygulama.istek("A") { |b| Urun.olustur(b, ad: "kalem", fiyat: 12) }
uygulama.istek("B") { |b| Urun.olustur(b, ad: "defter", fiyat: 30) }
uygulama.istek("A") do |b|
  tablo = b.uygulama.depo[:urunler]
  p tablo.keys
  p tablo.values.map { |satir| satir[:ad] }
end
```
```
[1, 2]
["kalem", "defter"]
```

A'nın üçüncü isteği iki kaydı da gördü; B'nin kaydı `2` kimliğini aldı, çünkü kimlik tablonun boyundan türer ve tablo A'nın kaydıyla birlikte tek parçadır. Kalıcı bir değer yalnız yazan istemciye ait olmaz: bir istemcinin yazdığı, bir başkasının kimliğini belirler. **VK5.** İki istek burada art arda koşar; aynı anda iki istek geldiğinde kimliklerin çakışıp çakışmayacağı bu çekirdekte ölçülmemiştir ve bu ders bunu iddia etmez.

#### Modelin Üstlendiği İş

Eşleyici olmasaydı her denetleyici tabloya kendi yolunu bulmak zorunda kalırdı: satırı hangi anahtarla yazacağını, kimliği nasıl üreteceğini, okuduğu satırın hangi alanlara sahip olduğunu bilirdi. Model bu bilgiyi tek yerde toplar. `olustur` kimliği üretir ve satırı yazar; `bul` kimlikle satırı okur; çağıran, tablonun karma yapısının nasıl kurulduğunu bilmeden iki yöntemle çalışır. Bu, MVC'nin (Model–Görünüm–Denetleyici, Model–View–Controller) model katmanıdır: denetleyici isteği yorumlar, görünüm sayfayı çizer, model ise verinin nerede ve hangi biçimde durduğunu bilen tek katmandır.

Bu ayrımın kazancı, değişikliğin tek yerde yapılmasıdır. Satırın biçimi değişirse yalnız `olustur` ve `bul` güncellenir; onları çağıran kod aynı kalır. Bu derste ölçülen kalıcılık da modelin bir sonucudur: kaydı hangi yöntemin yazdığı bilindiği için kaydın nereye yazıldığı da bilinir.

#### Tablo, Satır ve Kimlik

Aynı modelin içine bir istek boyunca bakılır: iki kayıt oluşturulur, tablonun anahtarları ve ikinci satır yazdırılır, olmayan bir kimlik sorulur.

```ruby
# satir.rb — tablo, satir ve kimlik
require_relative "baglama"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  ilk = Urun.olustur(b, ad: "kalem", fiyat: 12)
  ikinci = Urun.olustur(b, ad: "defter", fiyat: 30)
  p [ilk[:id], ikinci[:id]]
  p b.uygulama.depo[:urunler].keys
  p b.uygulama.depo[:urunler][2]
  p Urun.bul(b, 99)
end
```
```
[1, 2]
[1, 2]
{ad: "defter", fiyat: 30, id: 2}
nil
```

İki `olustur` çağrısı `[1, 2]` kimliklerini verdi ve tablonun anahtarları aynı listedir: kimlik, satırın tablodaki adresidir. İkinci satır, verilen iki nitelik ile kimliğin birleşimidir. Olmayan kimlik hata fırlatmadı, `nil` verdi; modelin "bulunamadı" cevabı bir istisna değil, bir değerdir. Bu yüzden `bul`u çağıran kod `nil` ihtimalini kendisi ele alır: modelin dilinde "yok", bir istisna ile değil, `nil` ile söylenir. **VK2.** Kimlikler tablonun boyundan türediği için bu ölçüm hiç silme yapılmadığı varsayımıyla geçerlidir; silme bu çekirdekte yoktur ve ölçülmemiştir.

#### Girdi ile Satır

`olustur` satırı, çağıranın verdiği karma yapıdan değil, `merge` ile kurulan yeni bir karma yapıdan oluşturur. Bu yüzden çağıranın elindeki nitelikler ile tablodaki satır aynı kalışı paylaşmaz. Ölçüm nitelikleri bir yerel değişkene koyar, kaydı oluşturur, sonra değişkeni değiştirir.

```ruby
# girdi.rb — cagiranin karmasi ile tablodaki satir
require_relative "baglama"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  nitelikler = { ad: "kalem", fiyat: 12 }
  Urun.olustur(b, nitelikler)
  nitelikler[:ad] = "degisti"
  p nitelikler.key?(:id)
  p b.uygulama.depo[:urunler][1][:ad]
  p nitelikler.equal?(b.uygulama.depo[:urunler][1])
end

yaz = lambda do |b|
  nitelikler = { ad: "kalem", fiyat: 12 }
  Urun.olustur(b, nitelikler)
  nitelikler[:ad] = "degisti"
end
oku = ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }
p kalis(yaz, oku)
```
```
false
"kalem"
false
["kalici", "A1 A2 A3 B4", "kalem"]
```

Çağıranın karma yapısı `id` anahtarını hiç almadı, tablodaki satır ise `"kalem"` olarak kaldı ve iki nesne aynı nesne değil. Değiştirilen yerel değişken isteğin içindedir ve istekle birlikte gider; tablodaki satır kalıcıdır. Kalış kuralı da bunu doğrular: yazan lambda kayıttan sonra yerel karma yapıyı değiştirse bile B'nin dördüncü isteği `"kalem"` okur. Çekirdek modelde kopya yazarken alınır, okurken alınmaz: sonraki bölümde `bul` tablodaki nesnenin kendisini verir. Bu asimetri modelin bir özelliğidir; gerçek bir eşleyicinin iki yönde ne yaptığı burada ölçülmemiştir.

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

Model gerçek bir eşleyicinin tamamı değildir ve sınırı bir yerde görünür. `bul`, tablodaki satırı mı, onun bir kopyasını mı döndürüyor? İki nesnenin aynı nesne olup olmadığı `equal?` ile sorulur. Sonra okunan kayıt, kaydetmeden değiştirilir.

```ruby
# sinir.rb — model saklanan nesnenin kendisini dondurur
require_relative "baglama"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  okunan = Urun.bul(b, 1)
  p okunan.equal?(b.uygulama.depo[:urunler][1])
  okunan[:ad] = "degisti"
  p b.uygulama.depo[:urunler][1][:ad]
end
```
```
true
"degisti"
```

`bul` tablodaki nesnenin kendisini döndürdü: `equal?` `true` verdi. Okunan kayıt değiştirilince tablo da değişti; ortada bir kaydetme çağrısı yoktur. Birçok eşleyicide durum böyle değildir: her okuma, satırdan yeni bir nesne kurar ve nesne üzerindeki değişiklik ancak açık bir kaydetmeyle satıra yazılır. Bu bir iddia olarak değil, çekirdek modelin sınırı olarak okunmalıdır. Aradaki farkı küçük bir yardımcı görünür kılar; yardımcı bu dersin kendi kodudur ve böyle bir eşleyicinin okuma biçimini taklit eder.

```ruby
# kopya.rb — gercek esleyicinin okuma bicimini taklit eden yardimci
require_relative "baglama"

def taze_oku(b, id) = b.uygulama.depo[:urunler][id]&.dup

def kaydet(b, kayit)
  b.uygulama.depo[:urunler][kayit[:id]] = kayit.dup
end

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  kopya = taze_oku(b, 1)
  kopya[:ad] = "degisti"
  p b.uygulama.depo[:urunler][1][:ad]
  kaydet(b, kopya)
  p b.uygulama.depo[:urunler][1][:ad]
  p kopya.equal?(b.uygulama.depo[:urunler][1])
end
```
```
"kalem"
"degisti"
false
```

Kopya değiştirildiğinde tablo `"kalem"` olarak kaldı; değişiklik ancak `kaydet` çağrısıyla tabloya geçti. Son satır, tablodaki nesnenin okunan kopyayla aynı nesne olmadığını doğrular: `false`. Kalıcılık bu yardımcıda değişikliğin kaydedilmesine bağlıdır. **VK3.** Bu yardımcı bir kopya alıp kaydı geri yazar; gerçek bir eşleyicinin ne yaptığını bütünüyle temsil etmez, yalnız kaydetmeden önce ve sonra tablonun ne gördüğünü ayırır.

Son olarak aynı iki davranış kalış kuralının önüne konur: kaydı oluşturan ama ardından yalnız bellekteki nesneyi değiştiren yazan lambda.

```ruby
# ayni_nesne.rb — kaydetmeden degistirilen kayit
require_relative "baglama"

yaz = lambda do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Urun.bul(b, 1)[:ad] = "degisti"
end
oku = ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }
p kalis(yaz, oku)
```
```
["kalici", "A1 A2 A3 B4", "degisti"]
```

Kalış yine kalıcı ve değer `"degisti"`. Çekirdek modelde okunan nesne ile tablodaki nesne aynı olduğu için kaydetme adımı yoktur: kayda yapılan her dokunuş, doğrudan başka istemcinin de okuduğu yere yazılır. Bu yüzden çekirdekte kalışı kalıcı olan şey kaydın oluşturulması kadar, kayıt nesnesine yapılan her değişikliktir. Bu, modelin bir sadeleştirmesidir ve gerçek eşleyicinin davranışı olarak okunmamalıdır.

#### Kaydı Tutmak ile Kaydetmek

Karşıt ölçüm, aynı bilginin tabloya yazılmadan yalnız istekteki bir değişkende tutulmasıdır. Yazan lambda ürünün adını `istek_deposu`na koyar; tabloya hiçbir şey yazmaz.

```ruby
# tutulan.rb — tabloya yazilmayan ayni bilgi
require_relative "baglama"

yaz = ->(b) { b.istek_deposu[:urun] = { ad: "kalem", fiyat: 12 } }
oku = ->(b) { b.istek_deposu[:urun]&.fetch(:ad) }
p kalis(yaz, oku)
```
```
["bu_istek", "A1 -- -- --", "kalem"]
```

Değer aynı, `"kalem"`; kalış değişti. Kanıt satırı `A1 -- -- --` yalnız birinci istekte okunduğunu gösterir ve kural **bu istek** der. İki ölçümdeki fark verinin içeriğinde değil, nereye yazıldığındadır: `olustur` değeri tabloya yazdı, bu lambda bir istek değişkenine. Nesne–tablo bağlaması tam bu farkı kurar. Modelin yazdığı şey, istek bittiğinde silinmeyen yerdir. Bir denetleyicinin yerel değişkenindeki ürün adı ise istekle birlikte kaybolur. **VK4.** Bu karşıt ölçüm değişkeni `istek_deposu`nda tutar; ölçülen, bu çekirdekte tabloya yazılmayan bir değerin hiçbir isteğin ötesine geçmediğidir, başka bir saklama yerinin davranışı değildir.

#### Özet

- Nesne–tablo bağlaması, bir tabloyu bir modele, tablonun bir satırını o modelin bir kaydına bağlar; bu bağı kuran parçaya eşleyici denir.
- Bu dersin gözlemi olan kayıt, kalışı kalıcı olan değerdir: ilk istekte kaydedilen ürün A'nın sonraki iki isteğinde ve B'nin dördüncü isteğinde okundu.
- Kalıcılığın nedeni `depo`nun uygulamada durmasıdır; istek deposu, flash ve oturum bu kalışı vermez.
- Kimlikler tablodan türer ve olmayan kimlik `nil` verir; modelin cevabı bir istisna değildir.
- Çekirdek model saklanan nesnenin kendisini döndürür; birçok eşleyici her okumada yeni nesne kurar ve değişiklik ancak kaydetmeyle tabloya geçer.
- `olustur`, çağıranın karmasından kopya kurar; sonradan değiştirilen karma satırı etkilemez.

#### Sonraki Adım

Kayıt kalıcıdır, ama kayıtların biçimi bir tablo şemasına bağlıdır. Şemaya sonradan bir sütun eklendiğinde bu eski satırlarda var mı? Bir sonraki ders, şemayı değiştiren göçleri aynı ölçüyle okur.

### Göçler

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/gocler

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.

### İlişkiler ve Doğrulamalar

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/iliskiler-ve-dogrulamalar

Kaydı yazılmadan önce durduran doğrulamaların ve kayıtları birbirine bağlayan bir-çok ilişkisinin ölçümü; doğrulama hatasının yalnız bu istekte kaldığını ve bildirim mesajından ayrıldığını gösterir.

Önceki ders, şemaya eklenen bir sütunun kalıcı olduğunu ve göçten önce yazılmış satırlara ulaşmadığını gösterdi. Şema ve satırlar tanımlıydı; ama bir kayıt tabloya girerken her değeri kabul etmek zorunda mıdır? Fiyatı negatif bir ürün ya da adı boş bir ürün tabloya yazılırsa, o satırı okuyan her istemci bozuk veriyi okur. Kayıt yazılmadan önce kontrol eden kurallara **doğrulama** (validation) denir. Reddedilen bir kaydın hatası nerede duruyor?

Bu soru ders boyunca aynı ölçüyle sorulur: **bu değer hangi isteğe kadar kalıyor?** Doğrulama hatası, ürünü kaydetmeye çalışan kullanıcıya gösterilmek için üretilir; kullanıcı formu düzeltip yeniden gönderecektir. Bu bilginin bir sonraki istekte hâlâ elde olması gerekir mi? Ders cevabı ölçer. Dersin ikinci kavramı **ilişki**dir: bir kaydın başka bir kaydı kimlik alanıyla göstermesi. Ders ilişkiyi de aynı ölçüyle okur.

#### Ö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
```

Çekirdek bir çerçeve değil, bir çerçevenin modelidir. Dersin ikinci dosyası, çekirdekten yalnız doğrulamayı taşıyan `olustur` yöntemini alır. Doğrulamalar kayıt yazılmadan önce çalışır ve her kuralın başarısızlığı hata listesine bir ileti ekler.

```ruby
# iliskiler.rb — urun modeli: dogrulamalar kayit yazilmadan once calisir
require_relative "kalis"

module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end
end
```

Bu blok da kaynaktır. `olustur` bir karma yapı döndürür: doğrulama geçerse `id` ve boş bir `hatalar` listesi, geçmezse yalnız `hatalar` listesi. Kayıt yazma satırlarına yalnız hata listesi boşsa ulaşılır; `return` onları atlar.

#### Doğrulamanın Hata Listesi

Doğrulama ilk hatada durur mu, yoksa bütün kuralları mı çalıştırır? Aşağıdaki ölçüm iki geçerli kayıt oluşturur, sonra ad boş ve fiyat negatif bir kayıt dener.

```ruby
# hatalar.rb — dogrulama hatalari ve tablonun boyu
require_relative "iliskiler"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  p Urun.olustur(b, ad: "kalem", fiyat: 12)
  p Urun.olustur(b, ad: "defter", fiyat: 30)
  sonuc = Urun.olustur(b, ad: "", fiyat: -1)
  p sonuc[:hatalar]
  p sonuc.key?(:id)
  p b.uygulama.depo[:urunler].size
end
```
```
{id: 1, hatalar: []}
{id: 2, hatalar: []}
["ad bos olamaz", "fiyat negatif olamaz"]
false
2
```

Geçerli kayıtlar kimlik ve boş hata listesi döndürdü. Başarısız kayıt iki hata birden verdi: doğrulama ilk hatada durmadı, her kuralı denedi. Bu, formu düzelten kullanıcıya bütün sorunları tek seferde gösterebilmeyi sağlar. Sonuçta `id` anahtarı yok ve tablonun boyu `2` kaldı: başarısız kayıt tabloya hiç girmedi. Reddedilen kayıt var olmadığı için hatayı tabloda aramak anlamsızdır; hata yalnız `olustur`un döndürdüğü değerde vardır.

Doğrulamanın neyi reddettiği, yazılan kuralların tam karşılığıdır. Sınır değerleri bunu gösterir: sıfır fiyat, yalnız boşluktan oluşan ad ve hiç verilmeyen fiyat.

```ruby
# sinirlar.rb — kuralin tam karsiligi
require_relative "iliskiler"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  p Urun.olustur(b, ad: "kalem", fiyat: 0)[:hatalar]
  p Urun.olustur(b, ad: " ", fiyat: 5)[:hatalar]
  p Urun.olustur(b, ad: "defter", fiyat: nil)[:hatalar]
  p b.uygulama.depo[:urunler].size
end
```
```
[]
[]
[]
3
```

Üç kayıt da kabul edildi ve tablonun boyu `3`. Fiyat kuralı yalnız negatif sayıyı reddeder; sıfır ve `nil` geçer, çünkü `nil.to_i` sıfırdır. Ad kuralı yalnız boş metni reddeder; bir boşluk karakteri boş sayılmaz. Doğrulama niyeti değil yazılan kuralı uygular: boşluklu adın ya da fiyatsız ürünün geçmesini istemeyen biri bunu ayrı bir kural olarak yazmak zorundadır.

#### Hatanın Kalışı

Dersin gözlemi, adı boş bir ürünün hatasıdır. Yazan lambda sonucu `istek_deposu`na koyar; okuyan lambda ilk hata iletisini alır.

```ruby
# olcum.rb — dogrulama hatasi gozleminin kalisi
require_relative "iliskiler"

yaz = ->(b) { b.istek_deposu[:kayit] = Urun.olustur(b, ad: "", fiyat: 5) }
oku = ->(b) { b.istek_deposu.dig(:kayit, :hatalar, 0) }
p kalis(yaz, oku)
```
```
["bu_istek", "A1 -- -- --", "ad bos olamaz"]
```

Kalış **bu istek**. Kanıt satırı `A1 -- -- --`: hata yalnız yazıldığı istekte okundu; A'nın ikinci ve üçüncü isteğinde ve B'nin dördüncü isteğinde yok. Neden böyle olduğu `istek_deposu`nun doğasındadır: her istek yeni bir `Baglam` alır ve istek deposu boş başlar. Hata, kaydedilmediği için tabloda yoktur; istek deposunda olduğu için istek bitince gider. **VK10.** Bu ölçümde hata `istek_deposu`na kaydedilmiştir. Hatayı başka bir yerde tutan bir uygulama başka bir kalış verebilir; bu dersin iddiası yalnız hata istek deposundayken kalışın bu istek olduğudur.

#### Bir-Çok İlişkisi

Bir ürünün birden çok siparişi olabilir. Bu **bir-çok ilişkisi**dir (one-to-many); aynı ilişkiye siparişten ürüne bakıldığında ise **ait olma ilişkisi** (belongs to) denir. Çekirdekte ilişki için hiçbir düzenek yoktur; kayıtlar yalnız karma yapılardır. Bu yüzden ilişki, dersin kendi saf Ruby yardımcısıyla kurulur: sipariş kaydı ürünün kimliğini bir alanda taşır ve bir ürünün siparişleri bu alana göre süzülerek bulunur.

```ruby
# siparis.rb — bir-cok iliskisi: siparis urunun kimligini tasir
require_relative "iliskiler"

module Siparis
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "urun bulunamadi" unless b.uygulama.depo[:urunler].key?(nitelikler[:urun_id])
    hatalar << "adet pozitif olmali" unless nitelikler[:adet].to_i.positive?
    return { hatalar: } unless hatalar.empty?

    tablo = (b.uygulama.depo[:siparisler] ||= {})
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  def self.urunun_siparisleri(b, urun_id)
    (b.uygulama.depo[:siparisler] || {}).values.select { |s| s[:urun_id] == urun_id }
  end
end
```

Bu blok da kaynaktır. `Siparis.olustur` da bir doğrulama taşır: sipariş verilen ürün tabloda yoksa kayıt reddedilir. İlişkiyi bir doğrulama korur; bağlanacak ürün olmadan sipariş yazılmaz. `urunun_siparisleri` ilişkinin öteki yönüdür: ürünün kimliğini alır, sipariş tablosunu bu kimliğe göre süzer.

```ruby
# iliski.rb — iki urun, uc siparis
require_relative "siparis"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Urun.olustur(b, ad: "defter", fiyat: 30)
  Siparis.olustur(b, urun_id: 1, adet: 3)
  Siparis.olustur(b, urun_id: 1, adet: 1)
  Siparis.olustur(b, urun_id: 2, adet: 5)
  p Siparis.urunun_siparisleri(b, 1).map { |s| s[:adet] }
  p Siparis.urunun_siparisleri(b, 2).map { |s| s[:adet] }
  p Siparis.urunun_siparisleri(b, 99)
  p Siparis.olustur(b, urun_id: 99, adet: 1)
  p b.uygulama.depo[:siparisler].size
end
```
```
[3, 1]
[5]
[]
{hatalar: ["urun bulunamadi"]}
3
```

Birinci ürünün siparişleri `[3, 1]`, ikincisinin `[5]`. Olmayan ürünün siparişi yoktur: boş liste, bir hata değil. Olmayan ürüne verilen sipariş reddedildi ve hata iletisi döndü; sipariş tablosunun boyu `3` kaldı. İlişkinin kendisi ayrı bir veri değildir: sipariş kaydındaki `urun_id` alanıdır. Bu yüzden ilişkinin kalışı, ilişkiyi taşıyan kaydın kalışıdır. Aşağıdaki ölçüm bunu kural önünde sınar.

```ruby
# iliski_kalis.rb — iliskinin kalisi
require_relative "siparis"

yaz = lambda do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Siparis.olustur(b, urun_id: 1, adet: 3)
  Siparis.olustur(b, urun_id: 1, adet: 1)
end
oku = ->(b) { Siparis.urunun_siparisleri(b, 1).size }
p kalis(yaz, oku)
```
```
["kalici", "A1 A2 A3 B4", 2]
```

İlişki kalıcı: dört istekte de ürünün iki siparişi okundu. Sipariş kayıtları tablodadır ve tablo uygulamada durur; ilişki bir ek veri değil, kayıttaki bir kimlik olduğu için kayıtla birlikte kalır. **VK11.** Süzme tablonun tamamını dolaşır; sipariş sayısı büyüdüğünde bu yaklaşımın maliyeti ölçülmemiştir, dersin iddiası yalnız kalışla ilgilidir.

#### Ait Olma Yönü ve Yetim Kayıt

İlişkinin öbür yönü siparişten ürüne gider. Sipariş ürünün kendisini değil kimliğini taşıdığı için, ürüne ulaşmak tabloda o kimliği aramak demektir. Bu, ürün değiştiğinde siparişin de değişmiş görüneceği anlamına gelir. Ölçüm sipariş verildikten sonra ürünün adını değiştirir ve adı siparişten okur.

```ruby
# ait_olma.rb — siparisten urune okuma
require_relative "siparis"

okuma = ->(b, id) { b.uygulama.depo[:urunler][b.uygulama.depo[:siparisler][id][:urun_id]] }

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Siparis.olustur(b, urun_id: 1, adet: 3)
  p okuma.call(b, 1)[:ad]
  b.uygulama.depo[:urunler][1][:ad] = "kursun kalem"
  p okuma.call(b, 1)[:ad]
  p b.uygulama.depo[:siparisler][1].key?(:ad)
end

yaz = lambda do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Siparis.olustur(b, urun_id: 1, adet: 3)
  b.uygulama.depo[:urunler][1][:ad] = "kursun kalem"
end
oku = ->(b) { okuma.call(b, 1)[:ad] }
p kalis(yaz, oku)
```
```
"kalem"
"kursun kalem"
false
["kalici", "A1 A2 A3 B4", "kursun kalem"]
```

Sipariş ürünün adını kendi içinde taşımıyor: `key?(:ad)` `false` verdi. Ad değişince sipariş üzerinden okunan ad da değişti ve değişiklik kalıcı: B'nin dördüncü isteği de yeni adı okur. Bu modelde ilişki bir kimlik olduğu için ilişkili kaydın güncel hâli görünür; gerçek eşleyicilerde yüklenmiş nesnenin bayatlayıp bayatlamadığı burada ölçülmemiştir. Geçmişteki fiyatı ya da adı siparişte korumak istenirse değer siparişe ayrıca kopyalanmalıdır; bu dersin modeli bunu yapmaz.

Ürün silinirse ne olur? Sipariş kaydı ürünün kimliğini taşımaya devam eder. Doğrulama yalnız sipariş yazılırken çalıştığı için var olan siparişleri korumaz.

```ruby
# yetim.rb — urunu silinen siparis
require_relative "siparis"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  Siparis.olustur(b, urun_id: 1, adet: 3)
  b.uygulama.depo[:urunler].delete(1)
  p Siparis.urunun_siparisleri(b, 1).size
  p b.uygulama.depo[:urunler][1]
  p Siparis.olustur(b, urun_id: 1, adet: 1)
end
```
```
1
nil
{hatalar: ["urun bulunamadi"]}
```

Ürünün silinmesine rağmen kimliği `1` olan ürünün hâlâ `1` siparişi var ve ürün tablosunda o kimlik `nil`. Yeni bir sipariş ise `urun bulunamadi` hatasıyla reddedildi. Doğrulama ilişkiyi yeni kayıtlar için korur, var olan kayıtlar için korumaz; ürünü olmayan sipariş, yani yetim kayıt, silme anında ilişkili kayıtlar da ele alınarak (silmeyi reddederek, ilişkili kayıtları silerek ya da boşaltarak) önlenir; bu modelde böyle bir adım yoktur. Gerçek eşleyicilerin silmede ilişkili kayıtlara ne yaptığı eşleyiciye ve yapılandırmaya bağlıdır ve burada ölçülmemiştir.

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

Doğrulama hatası kayıtla saklanmaz: geçerli kayıt kalıcıdır, hata ise bu istekte kalır. Peki hatayı bir sonraki sayfaya taşımak gerekirse? Bu, bildirim mesajının (flash) işidir ve kalışı **sonraki istek**tir. Üç düzenek aynı ölçüde yan yana konur: hatayı istek deposunda tutmak, hatayı bildirim mesajına koymak ve geçerli kaydı kaydetmek.

```ruby
# karsit.rb — hata, bildirim mesaji ve gecerli kayit
require_relative "iliskiler"

hata = ->(b) { Urun.olustur(b, ad: "", fiyat: 5) }

istek_deposunda = ->(b) { b.istek_deposu[:kayit] = hata.call(b) }
istek_okuma = ->(b) { b.istek_deposu.dig(:kayit, :hatalar, 0) }
p kalis(istek_deposunda, istek_okuma)

bildirimde = ->(b) { b.flash[:hata] = hata.call(b)[:hatalar].first }
bildirim_okuma = ->(b) { b.flash[:hata] }
p kalis(bildirimde, bildirim_okuma)

kayitta = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
kayit_okuma = ->(b) { b.uygulama.depo[:urunler][1]&.fetch(:ad) }
p kalis(kayitta, kayit_okuma)
```
```
["bu_istek", "A1 -- -- --", "ad bos olamaz"]
["sonraki", "A1 A2 -- --", "ad bos olamaz"]
["kalici", "A1 A2 A3 B4", "kalem"]
```

Aynı hata iletisi iki kalış verdi. İstek deposunda yalnız bu istekte okundu; bildirim mesajına konunca A'nın ikinci isteğinde de okundu ve üçüncüde kayboldu. Geçerli kayıt ise kalıcıdır. Doğrulama hatasının kendisi hep aynı değerdir; kalışı onu nereye koyduğunuzla belirlenir. Kalış kuralı hatanın içeriğine değil, davranışına bakar.

Bu, formun yeniden gösterilmesi için bir sonuç taşır. Ürün formunu gönderen istek reddedilirse, kullanıcıya hatalı form aynı istekte çizilmelidir: hata o istekte vardır, bir sonraki istekte olmayabilir. Sonraki bir sayfaya yönlendirilecekse hata bildirim mesajına konmalıdır. İki yolu aşağıdaki ölçüm gösterir.

```ruby
# form.rb — hata ayni istekte cizilir, sonraki istekte yok
require_relative "iliskiler"

uygulama = Uygulama.new
cizim = uygulama.istek("A") do |b|
  sonuc = Urun.olustur(b, ad: "", fiyat: -1)
  b.istek_deposu[:kayit] = sonuc
  sonuc[:hatalar].empty? ? "kaydedildi" : "form: #{sonuc[:hatalar].join(', ')}"
end
p cizim
p uygulama.istek("A") { |b| b.istek_deposu[:kayit] }
```
```
"form: ad bos olamaz, fiyat negatif olamaz"
nil
```

Birinci istek hatalı formu iki hatayla birlikte çizdi. Aynı istemcinin hemen sonraki isteği `istek_deposu[:kayit]` için `nil` okudu: hata orada artık yok. **VK12.** Bu çizim yalnız metin üretir; gerçek bir görünüm şablonunu çalıştırmaz. Ölçülen, hatanın hangi istekte okunabildiğidir, formun görüntüsü değildir.

#### Özet

- Doğrulama kayıt yazılmadan önce çalışır, bütün kuralları dener ve hata listesi döndürür; başarısız kayıt tabloya yazılmaz.
- Bir-çok ilişkisi bir kayıttaki kimlik alanıdır; ilişkinin kalışı onu taşıyan kaydın kalışı olduğu için kalıcıdır.
- Bu dersin gözlemi olan doğrulama hatası, kalışı bu istek olan değerdir: ad boş ürünün hatası yalnız yazıldığı istekte okundu.
- Hata kayıtla saklanmaz: geçerli kayıt kalıcıdır, hata yalnız bu istekte durur.
- Ait olma yönünde sipariş ürünün adını değil kimliğini taşır; ürün adı değişince sipariş üzerinden yeni ad okunur. Doğrulama yalnız yazarken çalışır; ürün silinirse sipariş yetim kalır, doğrulama var olan kayıtları korumaz.
- Hatayı bir sonraki sayfaya taşımak bildirim mesajının (flash) işidir ve kalışı sonraki istektir; formu yeniden göstermek için hata aynı istekte çizilmelidir.

#### Sonraki Adım

Kayıtlar, ilişkiler ve doğrulamalar yazılabilir hâle geldi. Bir istek bir ürünün siparişlerini okurken kaç kez veritabanına gidiyor? Bir sonraki ders, aynı sorgunun bir istekte kaç kez veritabanına gittiğini ölçer.

### Sorgu Arayüzü ve N+1 Sorunu

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/sorgu-arayuzu-ve-n-arti-bir

Veritabanına giden sorguların sayısını ölçer: sorgu önbelleğinin bu istekte kaldığını, N+1 sorununu ve istekli yüklemeyle çözümünü, önbelleğin boş sonucu da sakladığını gösterir.

Önceki ders, doğrulama hatasının yalnız yazıldığı istekte okunduğunu ve ilişkinin sipariş kaydındaki bir kimlik alanından ibaret olduğunu gösterdi. Bir ürünün siparişlerini okumak için sipariş tablosu süzülüyordu. Peki bir istek, bir sayfayı doldurmak için veritabanına kaç kez gidiyor? Tek bir ürünü okumak bir sorgudur; yüz siparişin ürününü ayrı ayrı okumak yüz sorgudur. Sorgu sayısı, bir sayfanın ne kadar sürede çizileceğini belirleyen sayılardan biridir.

**Sorgu arayüzü** (query interface), kayıtları koşulla, kimlikle ya da ilişkiyle okuyan yöntemlerin toplamıdır. Bu ders arayüzün kendisini değil, arayüzün arkasında kaç sorgunun koştuğunu ölçer. Ölçü kursun hep aynı sorusudur: **bu değer hangi isteğe kadar kalıyor?** Burada ölçülen değer, aynı sorgunun ikinci kez sorulmasını önleyen **sorgu önbelleği**dir (query cache). Adı önbellek olduğu için kalıcı sanılabilir. Ders bunu ölçer.

#### Ö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
```

Çekirdek bir çerçeve değil, bir çerçevenin modelidir. İkinci dosya, ürün modelini ve kimlikle okuyan `bul` yöntemini taşır. `bul`, önce istek deposundaki sorgu önbelleğine bakar; kimlik orada yoksa sayacı bir artırır, tabloyu okur ve sonucu önbelleğe yazar.

```ruby
# sorgu.rb — urun modeli: bul ayni istekte ayni sorguyu bir kez calistirir
require_relative "kalis"

module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    # Ayni istekte ayni sorgu ikinci kez veritabanina gitmez.
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end
```

Bu blok da kaynaktır. Önbellek anahtarı `[:urunler, id]` çiftidir: hangi tablo ve hangi kimlik. Önbellek ve sayaç `istek_deposu`nda durur; yani her istek için yeniden başlar.

#### Önbelleğin Kalışı

Dersin gözlemi sorgu önbelleğidir. Yazan lambda bir ürün oluşturup kimliğiyle okur; okuyan lambda önbellekteki kaydın adına bakar.

```ruby
# olcum.rb — sorgu onbellegi gozleminin kalisi
require_relative "sorgu"

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
yaz = ->(b) { kalem.call(b); Urun.bul(b, 1) }
oku = ->(b) { b.istek_deposu.dig(:sorgular, [:urunler, 1], :ad) }
p kalis(yaz, oku)
```
```
["bu_istek", "A1 -- -- --", "kalem"]
```

Kalış **bu istek**. Kanıt satırı `A1 -- -- --`: önbellekteki kayıt yalnız yazıldığı istekte okundu; A'nın sonraki isteklerinde ve B'nin isteğinde yok. Önbelleğin adı değerin uzun yaşadığı izlenimini verir, ama ölçülen değer istek deposundadır ve istek bitince gider. Kalıcı olan, ürünün kendisidir; önbellekteki kopya değil. **VK13.** Bu ölçümde önbellek `istek_deposu`ndadır. Önbelleği uygulama deposunda tutan bir düzenek başka bir kalış verirdi; bu dersin iddiası yalnız istek deposundaki önbelleğin kalışıyla ilgilidir.

#### Sorgu Sayısı

Önbelleğin işe yaradığını görmek için sayaca bakılır. Üç ürün bir istekte oluşturulur; sonraki istekte üç farklı kimlikle `bul` çağrılır, sonra aynı üç kimlikle yeniden.

```ruby
# sayac.rb — ayni kimlik ikinci kez sorgulanmaz
require_relative "sorgu"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  %w[kalem defter silgi].each_with_index { |ad, i| Urun.olustur(b, ad:, fiyat: 10 + i) }
end

sayilar = uygulama.istek("A") do |b|
  [1, 2, 3].each { |id| Urun.bul(b, id) }
  ilk = b.istek_deposu[:sorgu_sayisi]
  [1, 2, 3].each { |id| Urun.bul(b, id) }
  [ilk, b.istek_deposu[:sorgu_sayisi]]
end
p sayilar
p uygulama.istek("A") { |b| b.istek_deposu[:sorgu_sayisi] }
```
```
[3, 3]
nil
```

Üç farklı kimlik üç sorgu saydı. Aynı kimliklerle ikinci tur sayacı oynatmadı: `3` kaldı. Sonraki istekte sayaç `nil`: istek deposu yeni istekle birlikte boş başladığı için sayaç da, önbellek de sıfırdan başlar. Aynı sorgu iki ayrı istekte iki kez çalışır.

Kursun önceki bir dersinde "kaç kez koştu" sorusu ölçü olmuştu; bu derste de bir sayı basılıyor. Fark şudur: burada sayı kendi başına ölçülen şey değil, önbelleğin işe yaradığının kanıtıdır. Dersin sınıflandırdığı değer yine kalıştır: sorgu önbelleği bu istekte kalır.

Önbelleğin aynı sorguyu tanıması anahtara bağlıdır. Anahtar `[:urunler, id]` çiftidir ve çift, kimliğin türüyle birlikte karşılaştırılır. Formdan ya da adres satırından gelen bir kimlik çoğu zaman metin olarak gelir; aşağıdaki ölçüm aynı kimliği tam sayı ve metin olarak sorar.

```ruby
# anahtar.rb — ayni kimlik, iki tur
require_relative "sorgu"

uygulama = Uygulama.new
uygulama.istek("A") { |b| Urun.olustur(b, ad: "kalem", fiyat: 12) }

uygulama.istek("A") do |b|
  p Urun.bul(b, 1)[:ad]
  p Urun.bul(b, "1")
  p Urun.bul(b, "1".to_i)[:ad]
  p b.istek_deposu[:sorgu_sayisi]
  p b.istek_deposu[:sorgular].keys
end
```
```
"kalem"
nil
"kalem"
2
[[:urunler, 1], [:urunler, "1"]]
```

`1` ve `"1"` iki ayrı anahtar oldu: sayaç `2` gösterdi, yani önbellek ikinci çağrıyı aynı sorgu saymadı. Metin kimlikle yapılan sorgu ayrıca `nil` döndürdü, çünkü tablonun anahtarları tam sayıdır. `"1".to_i` ise ilk anahtarla örtüştü: üçüncü çağrı önbellekten döndü ve sayaç artmadı. Bu çekirdek modelin bir özelliğidir: kimliği tam sayıya çevirmek çağıranın işidir. Gerçek eşleyicilerin metin kimliği kendiliğinden çevirip çevirmediği burada ölçülmemiştir; ölçülen, önbelleğin anahtar eşitliğine bağlı olduğudur ve eşitlik türü de kapsar. Sorgu sayısını düşük tutmak isteyen kod, kimliği önbelleğe girmeden önce tek bir türe çevirmelidir.

#### N+1 Sorunu

Bir sayfa, siparişleri ve her siparişin ürününü göstersin. Önce siparişler okunur: bir sorgu. Sonra her sipariş için ürünü ayrı bir sorguyla okumak, sipariş sayısı kadar sorgu daha demektir. Sipariş sayısı N ise toplam N+1 sorgudur. Buna **N+1 sorunu** (N+1 problem), ilişkili kaydı ancak erişildiğinde çeken yaklaşıma da **tembel yükleme** (lazy loading) denir; kayıt başına bir kez erişildiğinde bu, her kayıt için ayrı bir sorgu demektir. Çözümü, ilişkili kayıtların tek sorguyla topluca çekildiği **istekli yükleme**dir (eager loading).

Bu derste önbellekten bağımsız, yalnız sayaçla ölçülen küçük bir sorgu yardımcısı kurulur. Her yöntem, bir sorgu çalıştırdığında sayacı artırır.

```ruby
# siparis_sorgu.rb — sorgu sayan yardimcilar
require_relative "sorgu"

module SiparisSorgu
  def self.say(b)
    b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
  end

  def self.siparisler(b)
    say(b)
    b.uygulama.depo[:siparisler].values
  end

  def self.urun(b, id)
    say(b)
    b.uygulama.depo[:urunler][id]
  end

  def self.urunler(b, idler)
    say(b)
    b.uygulama.depo[:urunler].values_at(*idler)
  end
end
```

Bu blok da kaynaktır. `siparisler` tüm siparişleri tek sorguyla, `urun` bir ürünü tek sorguyla, `urunler` birden çok ürünü yine tek sorguyla okur. Aşağıdaki ölçüm beş siparişi üç yolla okur: tembel, istekli ve `Urun.bul` önbelleğiyle.

```ruby
# n_arti_bir.rb — ayni sayfa, uc yol, uc sorgu sayisi
require_relative "siparis_sorgu"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  %w[kalem defter silgi].each_with_index { |ad, i| Urun.olustur(b, ad:, fiyat: 10 + i) }
  kayitlar = [1, 1, 2, 3, 2].each_with_index.to_h do |urun_id, i|
    [i + 1, { id: i + 1, urun_id:, adet: i + 1 }]
  end
  b.uygulama.depo[:siparisler] = kayitlar
end

tembel = uygulama.istek("A") do |b|
  adlar = SiparisSorgu.siparisler(b).map { |s| SiparisSorgu.urun(b, s[:urun_id])[:ad] }
  [adlar, b.istek_deposu[:sorgu_sayisi]]
end

istekli = uygulama.istek("A") do |b|
  kayitlar = SiparisSorgu.siparisler(b)
  urunler = SiparisSorgu.urunler(b, kayitlar.map { |s| s[:urun_id] }.uniq).to_h { |u| [u[:id], u] }
  adlar = kayitlar.map { |s| urunler[s[:urun_id]][:ad] }
  [adlar, b.istek_deposu[:sorgu_sayisi]]
end

onbellekli = uygulama.istek("A") do |b|
  adlar = SiparisSorgu.siparisler(b).map { |s| Urun.bul(b, s[:urun_id])[:ad] }
  [adlar, b.istek_deposu[:sorgu_sayisi]]
end

p tembel
p istekli
p onbellekli
p tembel[0] == istekli[0] && istekli[0] == onbellekli[0]
```
```
[["kalem", "kalem", "defter", "silgi", "defter"], 6]
[["kalem", "kalem", "defter", "silgi", "defter"], 2]
[["kalem", "kalem", "defter", "silgi", "defter"], 4]
true
```

Üç yol aynı beş adı üretti, ama sorgu sayıları farklı. Tembel yol `6` sorgu çalıştırdı: siparişler için bir, her sipariş için bir daha, yani 1+5. İstekli yol `2` sorgu çalıştırdı: siparişler için bir, tekil ürünler için bir. Sipariş sayısı büyüse de istekli yolun sayısı iki kalır; tembel yolun sayısı siparişle birlikte büyür. Üçüncü yol ikisinin arasındadır: önbellek tekrar eden kimlikleri bir kez sorgular, bu yüzden `4` sorgu çalıştı, yani 1 artı üç farklı ürün. Önbellek N+1'i kaldırmaz, farklı kimlik sayısına indirir; farklı kimlik de sipariş kadar çoksa sayı yine büyür.

**VK14.** Bu ölçüm sorgu sayısını sayar, süreyi ölçmez. Sorgu başına maliyet bu bellek içi modelde sıfıra yakındır; gerçek bir veritabanında her sorgunun bir gidiş geliş maliyeti olduğu bu derste ölçülmemiştir, dersin iddiası yalnız sayıların farkıdır.

#### İstekli Yüklemede Olmayan Kimlik

İstekli yükleme ilişkili kayıtlar için sorguyu bire indirir, ama sonucun biçimini de değiştirir. Bu modelde topluca okunan kimliklerden biri tabloda yoksa sonuç listesinde o yer `nil` kalır; gerçek eşleyicilerin eksik kayıtla ne yaptığı burada ölçülmemiştir. Ölçüm, bir siparişin ürünü silinmiş olduğunda istekli yolun ne yaptığına bakar.

```ruby
# eksik_urun.rb — istekli yuklemede olmayan kimlik
require_relative "siparis_sorgu"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  b.uygulama.depo[:siparisler] = {
    1 => { id: 1, urun_id: 1, adet: 2 },
    2 => { id: 2, urun_id: 99, adet: 1 }
  }
end

uygulama.istek("A") do |b|
  kayitlar = SiparisSorgu.siparisler(b)
  urunler = SiparisSorgu.urunler(b, kayitlar.map { |s| s[:urun_id] }.uniq)
  p urunler.map { |u| u&.fetch(:ad) }
  p b.istek_deposu[:sorgu_sayisi]
  begin
    urunler.to_h { |u| [u[:id], u] }
  rescue => e
    p e.class
  end
end
```
```
["kalem", nil]
2
NoMethodError
```

Siparişler için bir, ürünler için bir sorgu çalıştı ve sayaç `2` gösterdi; ama sonuç listesinde olmayan kimliğin yeri `nil`. Bu `nil` üzerinde `u[:id]` çağırmak `NoMethodError` verdi. Topluca kurulan karma yapı, tek bir eksik kayıt yüzünden hiç kurulamadı. İstekli yükleme sorgu sayısını sabit tutar, ama eksik ilişkili kaydı işleme sorumluluğunu çağıran koda bırakır. Bir önceki dersteki yetim sipariş burada `NoMethodError` olarak görünür.

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

Önbellek yalnız bulunan kayıtları mı saklar? Olmayan bir kimlikle ilk sorgu `nil` döner. Sorgu önbelleği bu `nil`i de saklıyorsa, olmayan kimliğin ikinci sorgusu veritabanına gitmez. Bunun bir yan etkisi de olur: aynı istekte kayıt sonradan eklenirse, önbellek hâlâ `nil` döndürür.

```ruby
# sinir.rb — onbellek nil'i de saklar
require_relative "sorgu"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  %w[kalem defter silgi].each_with_index { |ad, i| Urun.olustur(b, ad:, fiyat: 10 + i) }
end

sonuc = uygulama.istek("A") do |b|
  [1, 2, 3].each { |id| Urun.bul(b, id) }
  Urun.bul(b, 99)
  Urun.bul(b, 99)
  sayac = b.istek_deposu[:sorgu_sayisi]
  onceki = Urun.bul(b, 4)
  Urun.olustur(b, ad: "cetvel", fiyat: 8)
  sonraki = Urun.bul(b, 4)
  [sayac, onceki, sonraki, b.uygulama.depo[:urunler][4][:ad]]
end
p sonuc
p uygulama.istek("A") { |b| Urun.bul(b, 4)[:ad] }
p uygulama.istek("A") { |b| b.istek_deposu[:sorgu_sayisi] }
```
```
[4, nil, nil, "cetvel"]
"cetvel"
nil
```

Üç farklı kimlik ve olmayan `99` kimliği dört sorgu saydı; `99`un ikinci çağrısı sayacı oynatmadı. Önbellek `nil` sonucu da sakladığı için bunu boş bir kayıt olarak hatırladı. Dördüncü kimlikle aynı şey oldu: ürün henüz yokken `nil` önbelleğe girdi; ürün oluşturulduktan sonra aynı istekte `bul(4)` yine `nil` döndü, oysa tabloda ürün `"cetvel"` olarak vardı. Önbellek bu istekte bayat kaldı. Bir sonraki istek önbelleği sıfırdan başlattığı için aynı ürünü doğru okudu, ve sayaç yeni istekte yine `nil` oldu.

Bu, önbelleğin kalışının ters yüzüdür. Önbellek yalnız bu istekte durduğu için bayatlığı da yalnız bu istekle sınırlıdır; bir sonraki istek temiz başlar. Bir istek içinde okuyup sonra yazan kod, önbelleğin yazmadan önceki hâlini okuyabilir. **VK15.** Burada önbellek yazmada geçersiz kılınmıyor. Yazmada önbelleği boşaltan bir düzenek bu bayatlığı önlerdi; böyle bir düzenek bu derste kurulmadı ve ölçülmedi.

#### Özet

- Sorgu arayüzü kayıtları kimlikle, koşulla ya da ilişkiyle okur; bir istekte kaç sorgunun koştuğu sayaçla ölçülür.
- Bu dersin gözlemi olan sorgu önbelleği, kalışı bu istek olan değerdir: önbellekteki kayıt yalnız yazıldığı istekte okundu.
- Aynı istekte aynı kimlik ikinci kez sorgulanmaz; yeni istekte sayaç ve önbellek sıfırdan başlar.
- N+1 sorunu, ilişkili kaydı her kayıt için ayrı sorguyla çekmektir; istekli yükleme sorguları ikiye indirir, önbellek yalnız tekrar eden kimlikleri eler.
- Önbellek anahtarı kimliğin türünü de içerir; kimliği türe çevirmeden gelen `"1"` ayrı sorgu sayılır. İstekli yüklemede tabloda olmayan kimlik, bu modelde listede `nil` bırakır ve sonucu kuran kod bunu ayrıca ele almalıdır.
- Önbellek `nil` sonucu da saklar; aynı istekte sonradan eklenen kayıt görünmez, ama bu bayatlık yalnız o istekle sınırlıdır.

#### Sonraki Adım

Veri katmanı tamam: kayıt, şema, ilişki, doğrulama ve sorgu sayısı ölçüldü. Giriş yapan bir kullanıcı sonraki sayfada nasıl tanınıyor? Bir sonraki konu, kimlik doğrulamada bu sorunun cevabını ölçer.

## Uygulama Servisleri

Oturumda taşınan kimlik ve eylem başına yetki denetimi, isteğin dışına alınan arka plan işleri, parça önbelleği ve bayat içerik, yalıtılmış uygulamalarla test ve çerçevenin varsayılan olarak açtığı güvenlik korumaları.

### Kimlik Doğrulama ve Yetkilendirme

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/kimlik-dogrulama-ve-yetkilendirme

Giriş yapan kullanıcının kimliğinin hangi isteğe kadar kaldığının, yetki kararının ise neden her istekte yeniden çıktığının ölçümü.

Önceki derslerde bir değerin bir isteğin içinde doğup o istekle birlikte gittiğini gördük: yol parametresi, doğrulama hatası, sorgu önbelleği ve denetleyicinin örnek değişkeni. Sorgu önbelleği adına rağmen bu istek kalışındaydı; ikinci istek sayacı sıfırdan başlattı. Bildirim mesajı (flash) yalnız bir sonraki isteğe geçmişti. Bu derste ilk kez bir değerin oturumda, aynı istemcinin bütün sonraki isteklerinde kaldığını ölçüyoruz. Soru şu: giriş yapan kullanıcı, sonraki her sayfada kim olduğunu nereden biliyor?

**Kimlik doğrulama (authentication)**, isteği gönderenin kim olduğunu belirleme işidir; sonucu bir kimliktir. **Yetkilendirme (authorization)**, o kimliğin şu eylemi yapmaya izni olup olmadığını belirleme işidir; sonucu bir evet ya da hayırdır. İkisi sık birlikte anılır ama zaman bakımından farklı davranırlar ve bu ders o farkı ölçer. Ölçü ekseni kursun ekseni: **bu değer hangi isteğe kadar kalıyor?** Dört kalış vardır: **bu istek**, **sonraki istek**, **oturum** ve **kalıcı**. M11/K03'te de kalıcı ile oturum karşı karşıya gelmişti, ama orada soru bir değeri kimin görebildiğiydi. Burada kimsenin kimi gördüğü sorulmuyor; soru zamandır. Aynı değeri yazan istekten sonra kaç istek daha görüyor? M12/K03'te ise bir sonucu kimin değiştirebildiği ölçülmüştü; burada değiştirenle ilgilenmiyoruz, kalışı ölçüyoruz.

#### Çekirdek

Dersin ölçümleri küçük bir istek döngüsüne dayanır. Aşağıdaki dosya kursun çekirdeğidir ve olduğu gibi verilir. `Uygulama` bütün isteklerin ve istemcilerin ortak deposunu tutar; her istek yeni bir `Baglam` alır; `oturum` ise istemciye göre seçilen bir karma yapıdır. `kalis(yaz, oku)` bir değeri birinci istekte yazar, sonra aynı istemcinin iki isteğinde ve başka bir istemcinin bir isteğinde okur, değerin nerede görüldüğüne bakıp kalışı sınıflandırır.

```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 blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir. Çekirdek bir çerçeve değil, bir çerçevenin modelidir: `oturum` bir karma yapıdır, kalıcı depo ise süreç belleğindedir. Gerçek bir uygulamada oturum bir çerez ve bir depo üzerinden, kalıcı depo ise bir veritabanı ya da önbellek deposu üzerinden çalışır; kural değerin davranışını okur, saklandığı yeri okumaz. Ölçümler tek bir uygulama süreci içinde anlamlıdır.

#### Kimliğin Kalışı

Giriş yapmak, oturuma bir kullanıcı kimliği yazmaktır. Çıkış yapmak onu silmektir. Bir kimliği her istekte sormak yerine bir kez doğrulayıp oturumda tutmanın nedeni de budur: sonraki istekler parolayı yeniden almaz, oturumdaki kimliğe bakar. Aşağıdaki kesit giriş, çıkış ve kimlik okuma yardımcılarını, ayrıca bir yetki tablosunu tanımlar.

```ruby
# kimlik.rb — giris, cikis, kimlik okuma ve kimlikten turetilen yetki
require_relative "kalis"

YETKILER = { 42 => %i[goster duzenle], 7 => %i[goster] }

def giris_yap(b, kullanici_id)
  b.oturum[:kullanici_id] = kullanici_id
end

def cikis_yap(b) = b.oturum.delete(:kullanici_id)

def kimlik(b) = b.oturum[:kullanici_id]

def izinli_mi?(b, eylem) = (YETKILER[kimlik(b)] || []).include?(eylem)

def goster(ad, tur, kanit, deger)
  printf("%-14s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
```

Bu blok da kaynaktır. `YETKILER` kimlikten izin listesine giden bir tablodur; `izinli_mi?` kimliği oturumdan okur, tabloda arar ve bir mantıksal değer döndürür. Kimliği hiç bilinmeyen biri için `YETKILER[nil]` `nil` döner, `|| []` onu boş listeye çevirir ve sonuç `false` olur. Şimdi birinci gözlem: oturuma yazılan kullanıcı kimliği.

```ruby
# kullanici.rb — kullanici kimligi hangi istege kadar kaliyor
require_relative "kimlik"

yaz = ->(b) { b.oturum[:kullanici_id] = 42 }
oku = ->(b) { b.oturum[:kullanici_id] }
goster("kimlik", *kalis(yaz, oku))
```
```
kimlik        oturum        A1 A2 A3 --  42
```

Kimlik A'nın üç isteğinde de göründü, B'nin isteğinde görünmedi. Bu **oturum** kalışıdır: değer yazan istemcinin bütün isteklerine kalır, başka istemcilere geçmez. Kalıcı olmaması bilerek seçilmiş bir sonuçtur. Kimlik uygulamanın ortak deposuna yazılsaydı B de A'nın kimliğiyle görünürdü. **UY1.** Oturumun A'nın isteklerinde kaldığı yalnız üç istek boyunca ölçüldü; oturumun ne zaman sona ereceği, yani zaman aşımı ya da çerezin silinmesi bu çekirdekte modellenmedi.

#### Her İstemcinin Kendi Kimliği Vardır

Oturum istemciye göre seçildiği için iki istemci aynı uygulamada farklı kimliklerle çalışabilir ve birbirinin kimliğini görmez. Aşağıdaki ölçümde A kullanıcı 42 olarak, B kullanıcı 7 olarak girer; sonra her istemci kimliğini okur. Son adımda B çıkış yapar ve A'nın kimliğine yeniden bakılır.

```ruby
# istemciler.rb — iki istemci, iki kimlik
require_relative "kimlik"

uygulama = Uygulama.new
uygulama.istek("A") { |b| giris_yap(b, 42) }
uygulama.istek("B") { |b| giris_yap(b, 7) }
["A", "B", "A"].each do |istemci|
  puts "#{istemci} #{uygulama.istek(istemci) { |b| kimlik(b) }.inspect}"
end
uygulama.istek("B") { |b| cikis_yap(b) }
puts "cikis sonrasi A #{uygulama.istek("A") { |b| kimlik(b) }.inspect}"
puts "cikis sonrasi B #{uygulama.istek("B") { |b| kimlik(b) }.inspect}"
```
```
A 42
B 7
A 42
cikis sonrasi A 42
cikis sonrasi B nil
```

Her istemci yalnız kendi kimliğini okudu ve B'nin çıkışı A'nın oturumuna dokunmadı. Kimliğin oturum kalışında olmasının pratik anlamı budur: değer istemciye bağlıdır, uygulamanın ortak deposuna değil. Bu ayrım, aynı sunucu sürecine bağlanan çok sayıda kullanıcının birbirine karışmamasının dayanaklarından biridir; ölçüm iki istemciyle yapıldı.

#### Yetki Kararı Her İstekte Yeniden Çıkar

Kimlik oturumda kaldığına göre yetki kararı da kalıyor mu? Karar kimlikten türetilir ve türetme ucuzdur; ders onu oturuma yazıp yazmamayı bir seçim olarak ele alır. Önce türetmenin kendisine bakalım: iki kullanıcı ve giriş yapmamış bir istemci, aynı iki eylem için sorulur.

```ruby
# yetki.rb — ayni kimlik, iki eylem, uc istemci
require_relative "kimlik"

uygulama = Uygulama.new
uygulama.istek("A") { |b| giris_yap(b, 42) }
uygulama.istek("B") { |b| giris_yap(b, 7) }

["A", "B", "C"].each do |istemci|
  sonuc = uygulama.istek(istemci) { |b| [izinli_mi?(b, :goster), izinli_mi?(b, :duzenle)] }
  puts "#{istemci} #{sonuc.inspect}"
end
```
```
A [true, true]
B [true, false]
C [false, false]
```

Aynı iki soru, üç farklı cevap verdi ve fark yalnız oturumdaki kimlikten geldi. C hiç giriş yapmadığı için iki eylemde de reddedildi. Yetkilendirme, kimlik doğrulamanın ürettiği değeri okuyan ayrı bir adımdır: kimlik olmadan karar çıkmaz, ama karar da kimliğin içinde saklı değildir.

Şimdi kararın kalışını ölçelim. Karar giriş yapılan istekte hesaplanıp yalnız o isteğin istek deposuna konuyor.

```ruby
# karar.rb — yetki karari hangi istege kadar kaliyor
require_relative "kimlik"

yaz = lambda do |b|
  giris_yap(b, 42)
  b.istek_deposu[:karar] = izinli_mi?(b, :duzenle)
end
oku = ->(b) { b.istek_deposu[:karar] }
goster("yetki karari", *kalis(yaz, oku))
```
```
yetki karari  bu_istek      A1 -- -- --  true
```

Karar **bu istek** kalışında. Sonraki istekte istek deposu boş başlar ve karar yeniden hesaplanır. Bunun pratik sonucu şudur: bir kullanıcının izni değişirse, bir sonraki istek yeni izinle çalışır; eski kararı taşıyan bir kopya yoktur. Karar oturuma yazılsaydı, izin tablosunun değişmesi o oturum bitene kadar görünmezdi. **UY2.** Burada karar ucuz bir tablo aramasıdır. Hesabı pahalı bir kararı saklamak ayrı bir tasarım sorusudur ve bir sonraki iki derste, işleri ve önbelleği anlatırken kalış açısından ele alınır.

#### Kararı Oturuma Yazarsak

Yetki kararının neden her istekte yeniden hesaplandığını yukarıda bir seçim olarak ele aldık. Seçimin sonucunu ölçmek için öteki yolu deneyelim: karar giriş yapılan istekte hesaplanıp kimlikle birlikte oturuma yazılıyor.

```ruby
# oturumda_karar.rb — oturuma yazilan yetki karari hangi istege kadar kaliyor
require_relative "kimlik"

yaz = lambda do |b|
  giris_yap(b, 42)
  b.oturum[:karar] = izinli_mi?(b, :duzenle)
end
oku = ->(b) { b.oturum[:karar] }
goster("oturumda karar", *kalis(yaz, oku))
```
```
oturumda kararoturum        A1 A2 A3 --  true
```

Karar kimlik gibi **oturum** kalışına geçti: aynı istemcinin bütün sonraki isteklerinde görünüyor. Kalışın uzaması kararı doğru yapmaz. Bunu görmek için izin tablosunu karar yazıldıktan sonra değiştirelim ve iki yolu yan yana koyalım: oturumda saklanan karar ile her istekte yeniden hesaplanan karar.

```ruby
# bayat_karar.rb — izin tablosu degisince saklanan karar ile yeniden hesaplanan karar
require_relative "kimlik"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  giris_yap(b, 42)
  b.oturum[:karar] = izinli_mi?(b, :duzenle)
end
YETKILER[42] = %i[goster]
saklanan = uygulama.istek("A") { |b| b.oturum[:karar] }
yeniden = uygulama.istek("A") { |b| izinli_mi?(b, :duzenle) }
puts "saklanan #{saklanan}, yeniden hesaplanan #{yeniden}"
```
```
saklanan true, yeniden hesaplanan false
```

İzin geri alındığı hâlde oturumdaki karar eski değeri söylemeye devam etti; yeniden hesaplanan karar ise yeni durumu gösterdi. Bayatlama, kalışı uzatılmış bir değerin kaynağıyla bağının kopmasıdır. Aynı sorunla Önbellekleme dersinde bir parça için karşılaşacağız. Kimliği oturumda tutmanın ve kararı tutmamanın nedeni de buradadır: kimlik, çıkış yapılana kadar (ya da oturum sona erene kadar) değişmeyen bir olgudur; karar ise kimlikten başka bir şeye, izin tablosuna da bağlıdır.

#### Giriş Yapmamış ve Tanınmayan Kimlik

Yetki kararı kimlikten türediği için kimliğin bulunmadığı iki durum vardır: istemci hiç giriş yapmamıştır ya da oturumdaki kimlik izin tablosunda yer almaz. İki durumda da `izinli_mi?` bir hata fırlatmak yerine karar vermek zorundadır. Aşağıdaki ölçüm üç istemciyi yan yana koyar: girişsiz bir istemci, tabloda olmayan 99 numaralı kullanıcı ve tabloda olan 42 numaralı kullanıcı.

```ruby
# taninmayan.rb — kimligi olmayan ve tanimayan istemcilerin yetki karari
require_relative "kimlik"

uygulama = Uygulama.new
uygulama.istek("B") { |b| giris_yap(b, 99) }
uygulama.istek("C") { |b| giris_yap(b, 42) }
%w[A B C].each do |istemci|
  kimlik, karar = uygulama.istek(istemci) { |b| [kimlik(b), izinli_mi?(b, :goster)] }
  puts "#{istemci} #{kimlik.inspect} #{karar}"
end
```
```
A nil false
B 99 false
C 42 true
```

Girişsiz istemcinin kimliği boştur ve karar olumsuzdur; 99 numaralı kullanıcının kimliği vardır, ama izin tablosunda karşılığı olmadığı için karar yine olumsuzdur. Kimlik ile yetki ayrı iki sorudur: birincisi "bu istemci kim?", ikincisi "bu kişi bunu yapabilir mi?". Kimliğin oturumda bulunması, yetkinin bulunduğu anlamına gelmez. Karar tablodan okunduğu için, tabloda olmayan her kimlik varsayılan olarak yetkisiz kalır.

#### Çıkış Yapmak

Çıkış, oturumdan kimliği silmektir. Kuralımız yazıyı yalnız birinci istekte yapar; çıkışı ölçmek için `oku` lambda'sı kendi çağrı sayısını tutar ve ikinci istekte kimliği okuduktan sonra siler. Birinci ve ikinci istek kimliği görür, üçüncü ve dördüncü görmez.

```ruby
# cikis.rb — ikinci istekte cikis yapilirsa kalis ne olur
require_relative "kimlik"

yaz = ->(b) { giris_yap(b, 42) }
cagri = 0
oku = lambda do |b|
  cagri += 1
  deger = kimlik(b)
  cikis_yap(b) if cagri == 2
  deger
end
goster("cikis", *kalis(yaz, oku))
```
```
cikis         sonraki       A1 A2 -- --  42
```

Kural bu değeri **sonraki istek** olarak sınıflandırdı, çünkü yalnız ikinci istek onu gördü. Aynı kimlik, çıkış olmadan **oturum** kalışındaydı. Bu bir çelişki değil, kuralın davranışı okuduğunun kanıtıdır: değer oturumda saklanıyor, ama çıkış onu erken siliyor ve kalış kısalıyor. Çıkışın amacı da budur. **UY3.** Çıkışı ikinci istekte yaptırmak ölçümün düzeneğidir; gerçek bir uygulamada çıkışın hangi istekte geleceğini kimse önceden bilmez.

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

Bu dersin kuralı bir sınırı da taşır. `kalis`, değeri yazıldığı istekte bile okuyamıyorsa bir sınıflandırma yapmaz, istisna fırlatır. Bunu yazan tarafın hiçbir şey yazmadığı bir düzenekle ölçelim.

```ruby
# sinir.rb — hic deger yazilmazsa kural ne yapiyor
require_relative "kimlik"

begin
  kalis(->(b) { }, ->(b) { kimlik(b) })
rescue => e
  puts e.class
end
```
```
ArgumentError
```

Kural `ArgumentError` fırlattı. Yani **yazıldığı istekte okunamayan bir değer hiçbir kalışa sınıflandırılamaz**; kalış, değerin en az bir kez görünmesini ön koşul sayar. Giriş yapılmamış bir istemcinin kimliği bu yüzden bir kalış taşımaz: yoktur. Dersin dört ölçümü de bu sınırın içinde kaldı. Kimlik oturumda, karar bu istekte, oturuma yazılan karar oturumda, çıkışta kimlik sonraki istekte kaldı. Sınır ayrıca bir uyarıdır: bir değerin kalışını ölçmeden önce, değerin ilk istekte gerçekten var olduğundan emin olmak gerekir. Kimliği okuyan bir `oku` lambda'sı yanlış anahtara bakarsa, kuralın cevabı kalış değil bir hata olur.

#### Özet

- Kimlik doğrulama bir kimlik üretir, yetkilendirme o kimlikten bir evet ya da hayır türetir; ikisi ayrı adımlardır.
- Oturuma yazılan kullanıcı kimliği, yazan istemcinin bütün isteklerinde görünür ve başka istemcide görünmez; iki istemci aynı uygulamada farklı kimliklerle çalışır. Bu dersin kalışı **oturum**dur.
- Yetki kararı kimlikten her istekte yeniden hesaplanırsa **bu istek** kalışındadır ve izin değişikliği bir sonraki istekte görünür; oturuma yazılırsa kalışı oturuma uzar ve izin değişince bayatlar.
- Girişsiz ya da tabloda olmayan kimlik varsayılan olarak yetkisiz kalır; kimliğin bulunması yetkinin bulunduğunu göstermez.
- Çıkış yapılan istek kimliği oturumdan siler; silen istekten sonra kimlik kaybolur ve kural onu **sonraki istek** olarak görür.
- `kalis`, yazıldığı istekte okunamayan değeri sınıflandırmaz ve `ArgumentError` fırlatır.

#### Sonraki Adım

Kimlik oturumda kalıyor, karar her istekte yeniden çıkıyor; peki istek bittikten sonra yapılması gereken bir iş nerede bekler? Bir sonraki ders arka plan işlerini ölçer: kuyruğa konan bir işin hangi isteğe kadar kaldığını ve bu kalışın oturumdan neden farklı olduğunu.

### Arka Plan İşleri

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/arka-plan-isleri

Kuyruğa konan bir işin kaydının hangi isteğe kadar kaldığının, işçi onu boşaltınca ne olduğunun ve işin sonucunun neden aynı kalışta olmadığının ölçümü.

Önceki derste kimliğin oturumda kaldığını, yetki kararının ise her istekte yeniden çıktığını ölçtük. Her iki değer de bir isteğin içinde, o isteğin yanıtını beklerken hesaplanıyordu. Fakat bir uygulamanın yapacağı her iş yanıtı beklemek zorunda değildir. Bir fatura göndermek, bir görseli küçültmek ya da bir raporu hazırlamak, kullanıcı yanıtı aldıktan sonra da yapılabilir. Bu derste soru şu: istek bittikten sonra koşacak bir iş, o ana kadar nerede bekler ve bu bekleyiş kaç istek sürer?

**Arka plan işi (background job)**, isteğin yanıtını geciktirmemek için sonraya bırakılan işe verilen addır. İşi sonraya bırakmak, onu yapılacak bir kayda çevirmektir: iş adı ve argümanlardan oluşan bir kayıt, bir **kuyruğa (queue)** konur. Kuyruğu daha sonra bir **işçi (worker)** boşaltır ve kayıttaki işi koşturur. M12/K04'te de bir parçanın bu istekte koşmayıp ertelendiği görülmüştü; orada ölçülen şey kodun koşup koşmadığıydı. Burada kodu bırakıp değeri ölçüyoruz: bu istekte koşmasa bile kuyruktaki kayıt hangi isteğe kadar yaşıyor? Ölçü ekseni kursun ekseni: **bu değer hangi isteğe kadar kalıyor?** Dört kalışı hatırlayalım: **bu istek**, **sonraki istek**, **oturum** ve **kalıcı**.

#### Çekirdek

Ölçümler yine aynı küçük istek döngüsüne dayanır. Dosya kursun çekirdeğidir ve olduğu gibi verilir. Kuyruk bu çekirdekte `Uygulama#depo` içindeki bir dizidir; gerçek bir uygulamada kuyruk bir dış depoda durur ve çekirdek onu modellemez, yalnız davranışını taşır.

```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 blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir. Çekirdek bir çerçeve değil, bir çerçevenin modelidir. Kural değerin davranışını okur, saklandığı yeri okumaz; ölçümler tek bir uygulama süreci içinde anlamlıdır.

#### Kuyruk, İş ve İşçi

Bir iş, bir ad ve bir argüman listesidir. Kuyruğa alma, bu ikiliyi kuyruk dizisinin sonuna eklemektir; işçi ise baştan okur, işin adından koşturulacak kodu bulur ve argümanlarla çağırır. Aşağıdaki kesit bu üç parçayı tanımlar. İş tablosu `ISLER`, adı koda çeviren bir karma yapıdır.

```ruby
# isler.rb — kuyruga alma, is tablosu ve isci
require_relative "kalis"

def kuyruga_al(b, is_adi, *argumanlar) = b.uygulama.depo[:kuyruk] << [is_adi, argumanlar]

ISLER = { "fatura_gonder" => ->(fatura_id) { "fatura #{fatura_id} gonderildi" } }

def isci_calistir(b)
  kuyruk = b.uygulama.depo[:kuyruk]
  sonuclar = []
  until kuyruk.empty?
    is_adi, argumanlar = kuyruk.shift
    sonuclar << ISLER.fetch(is_adi).call(*argumanlar)
  end
  sonuclar
end

def goster(ad, tur, kanit, deger)
  printf("%-14s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
```

Bu blok da kaynaktır. `isci_calistir` kuyruk boşalana kadar baştaki kaydı çıkarır, `ISLER` tablosunda arar ve sonuçları bir dizide toplar. Önce işi isteğin içinde değil, isteğin dışında koşturduğumuzu, yani yanıtın işin bitmesini beklemediğini görelim. Bir istek yalnız kaydı bırakıp yanıt veriyor; kayıt, ayrı bir çağrıda çalışan işçiye kadar kuyrukta bekliyor.

```ruby
# erteleme.rb — istek yalniz kaydi birakir, isi isci kosturur
require_relative "isler"

uygulama = Uygulama.new
yanit = uygulama.istek("A") { |b| kuyruga_al(b, "fatura_gonder", 1); "yanit hazir" }
p yanit
p uygulama.depo[:kuyruk].size
p uygulama.istek("isci") { |b| isci_calistir(b) }
p uygulama.depo[:kuyruk].size
```
```
"yanit hazir"
1
["fatura 1 gonderildi"]
0
```

İstek yanıtını verdi ve kuyrukta bir kayıt kaldı. İşçi ayrı bir çağrıda kaydı çıkarıp işi koşturdu, kuyruk yeniden boşaldı. Yanıt ile işin arasındaki bu aralık, arka plan işinin temel gerekçesidir; kullanıcı yanıtı, işin süresini beklemeden alır. **UY4.** Çekirdekte işçi, istekten sonra elle çağrılan bir yöntemdir. Gerçek bir işçi çoğu zaman ayrı bir süreçtir ve ne zaman koşacağını ölçüm belirlemez; burada bu zamanlama modellenmedi.

#### Kuyruktaki Kaydın Kalışı

Birinci gözlem, kuyruğa alınan işin kaydıdır. Yaz lambda'sı kaydı bırakır, oku lambda'sı kuyruktaki ilk kaydı döndürür.

```ruby
# kuyruk.rb — kuyruktaki is hangi istege kadar kaliyor
require_relative "isler"

yaz = ->(b) { kuyruga_al(b, "fatura_gonder", 1) }
oku = ->(b) { b.uygulama.depo[:kuyruk].first }
goster("kuyruktaki is", *kalis(yaz, oku))
```
```
kuyruktaki is kalici        A1 A2 A3 B4  ["fatura_gonder", [1]]
```

Kayıt dört isteğin dördünde de göründü; B'nin isteği de dahil. Bu **kalıcı** kalıştır. Oturumdan farkı açıktır: kayıt bir istemciye bağlı değildir, uygulamanın ortak deposundadır. Bir istemcinin isteğiyle kuyruğa konan fatura işini, başka bir istemcinin isteğini işleyen süreç de görür; zaten işçinin işi budur. M12/K04'te kayıt "ertelendi" sınıfındaydı, yani bu istekte koşmadı. Burada o erteleme, kaydın isteğin ötesine geçmesiyle aynı şeydir: koşmayan kayıt, işçi gelene kadar uygulamanın ortak deposunda bekler.

Kuyruğun sırası da bu kalışın parçasıdır. İki işi sırayla alıp kuyruğa bakalım.

```ruby
# sira.rb — iki is kuyruga girdigi sirayla durur
require_relative "isler"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  kuyruga_al(b, "x")
  kuyruga_al(b, "y", 2)
end
p uygulama.depo[:kuyruk]
```
```
[["x", []], ["y", [2]]]
```

Argümansız bir iş boş bir liste taşır, `y` işi ise `2` değerini. Sıra korundu: işçi önce `x`'i, sonra `y`'yi alır. **UY5.** Yalnız iki işle ölçüldü; çok sayıda işte sırayı bozabilecek bir şey, örneğin birden çok işçinin aynı anda kuyruğu okuması, bu çekirdekte yoktur.

#### Kayıt Bir Kez Koşar

Kuyruk bir kaydı tutar; işçi onu çıkarır. Çıkarma, kaydı kuyruktan siler. Bunun bir sonucu vardır: aynı işçiyi ikinci kez çağırmak aynı işi yeniden koşturmaz, çünkü ortada koşturulacak kayıt kalmamıştır. Ölçmek için bir iş kuyruğa alınır ve işçi art arda iki istekte çağrılır.

```ruby
# tekrar.rb — isci iki kez cagrilirsa is kac kez kosar
require_relative "isler"

uygulama = Uygulama.new
uygulama.istek("A") { |b| kuyruga_al(b, "fatura_gonder", 1) }
p uygulama.istek("isci1") { |b| isci_calistir(b) }
p uygulama.istek("isci2") { |b| isci_calistir(b) }
p uygulama.depo[:kuyruk].size
```
```
["fatura 1 gonderildi"]
[]
0
```

İlk çağrı işi koşturdu, ikinci çağrı boş bir liste döndürdü. İşin bir kez koşması, kaydın kuyruktan çıkmasının doğrudan sonucudur; bunun için ayrıca bir "bu iş yapıldı" işareti gerekmedi. Bu da işin kalışını kuyrukta bulunmasına bağlar: kayıt kuyruktan çıktığı anda iş, bu çekirdek için artık yoktur. Bir işin koştuğunu sonradan öğrenmek isteyen kod, kuyrukta bu bilgiyi bulamaz. Bu, tek işçili bu çekirdeğin sonucudur; gerçek kuyruk sistemlerinin teslim garantisi ayrıca incelenir.

#### Kuyruğa Konan Argümanın Kalışı

Kuyruğa alınan bir işin argümanı, kayıtla birlikte kalıcıdır. Fakat kayıt argümanın bir kopyasını mı, yoksa kendisini mi tutuyor? Bu soru önemlidir, çünkü isteği yapan kod argümanı kuyruğa koyduktan sonra değiştirebilir. Ölçüm şöyle yapılır: bir liste kuyruğa argüman olarak verilir, sonra aynı liste değiştirilir ve kuyruktaki kayda bakılır.

```ruby
# arguman.rb — kuyruga konan arguman, sonradan degisirse kayit ne gorur
require_relative "isler"

uygulama = Uygulama.new
liste = [1]
uygulama.istek("A") { |b| kuyruga_al(b, "fatura_gonder", liste) }
liste << 2
p uygulama.depo[:kuyruk]
p uygulama.depo[:kuyruk].first[1].first.equal?(liste)
```
```
[["fatura_gonder", [[1, 2]]]]
true
```

Kayıt, listenin kendisini tuttu; kopyasını değil. İstek bittikten sonra yapılan değişiklik kuyruktaki işe de ulaştı. Dolayısıyla iş koşarken gördüğü argüman, kuyruğa alındığı andaki değer olmayabilir. Kalış açısından bunun anlamı şudur: kayıt kalıcıdır, ama kaydın içindeki nesne kalıcı bir kopya değil, paylaşılan bir nesnedir ve onu tutan her yer onun değişimini görür. Bu çekirdekte bir işin argümanını sabitlemek isteyen kod, kuyruğa almadan önce değeri kopyalar.

#### Bilinmeyen İş Adı

İşçi, işin adından koşturulacak kodu `ISLER` tablosunda arar. Tabloda olmayan bir ad kuyruğa girebilir; kuyruk adı denetlemez. Hata, işçi o kayda geldiğinde ortaya çıkar. Ölçüm iki kayıt kullanır: ilki tabloda olmayan bir ad, ikincisi geçerli bir iş. İstisnanın yalnız sınıfı basılır.

```ruby
# bilinmeyen.rb — tabloda olmayan is adi isciyi nasil durdurur
require_relative "isler"

uygulama = Uygulama.new
uygulama.istek("A") do |b|
  kuyruga_al(b, "bilinmeyen")
  kuyruga_al(b, "fatura_gonder", 1)
end
begin
  uygulama.istek("isci") { |b| isci_calistir(b) }
rescue => e
  puts e.class
end
p uygulama.depo[:kuyruk]
```
```
KeyError
[["fatura_gonder", [1]]]
```

İşçi ilk kayıtta durdu. Kayıt hata vermeden önce kuyruktan çıkarılmıştı, bu yüzden hatalı kayıt kuyrukta kalmadı; ikinci kayıt ise hiç okunmadığı için yerinde duruyor. Bu çekirdekte hatalı bir iş kendi kaydını kaybeder ve kendisinden sonraki işleri geciktirir. Bu çekirdekte bir yeniden deneme düzeneği yoktur; kaybolan kaydı geri getirecek bir şey yazılmadı. Kalış bakımından bu, kaydın kalıcılığının bir sınırıdır: kayıt kuyruktayken kalıcıdır, ama işçinin eline geçtiği an kuyruktaki varlığı biter. Bir işin kuyruktan çıkması işin bittiği anlamına gelmez; kuyruk yalnız çıkarma anını bilir, işin başarıyla bitip bitmediğini bilmez. Hatayı yeniden denemek isteyen çekirdek, kaydı koşturmadan önce kuyruktan silmek yerine, işin bitişini bekleyen ayrı bir kayıt düzeni kurmak zorundadır; gerçek kuyruk sistemlerinde bu düzenek yapılandırılabilir.

#### İşçi Kuyruğu Boşaltınca

Kalıcı kalış sonsuz değildir. Kaydı boşaltan bir işçi, kalışı kısaltır. Ölçmek için oku lambda'sı çağrı sayısını tutar: ikinci istekte kayda baktıktan sonra işçiyi çalıştırır ve kuyruğu boşaltır.

```ruby
# bosaltma.rb — ikinci istekte isci kuyrugu bosaltirsa kalis ne olur
require_relative "isler"

yaz = ->(b) { kuyruga_al(b, "fatura_gonder", 1) }
cagri = 0
oku = lambda do |b|
  cagri += 1
  kayit = b.uygulama.depo[:kuyruk].first
  isci_calistir(b) if cagri == 2
  kayit
end
goster("bosaltilan", *kalis(yaz, oku))
```
```
bosaltilan    sonraki       A1 A2 -- --  ["fatura_gonder", [1]]
```

Kural bunu **sonraki istek** olarak sınıflandırdı: kayıt birinci ve ikinci istekte göründü, işçi çalıştıktan sonra üçüncü ve dördüncü istekte yok. Aynı kayıt işçisiz **kalıcı** idi. Kalışı belirleyen şey saklandığı yer değil, davranıştır: kuyruğu boşaltan bir süreç varsa kayıt, onu işleyen süreç gelene kadar kalır. **UY6.** Boşaltma ikinci istekte düzenek olarak yaptırıldı. Gerçekte işçinin hangi istekte çalışacağı, kaç isteğin kaydı görebileceğini belirler ve bu sayıyı kuyruğun uzunluğuna ve işçinin hızına bırakmak gerekir.

#### Sonucu İşçinin İsteğinde Saklamak

Aşağıdaki sınırlayıcı ölçümde sonuç istek deposunda kalıyor. Peki sonucu işçinin isteğinin içinde uygulamanın ortak deposuna yazarsak kalış değişir mi? Ölçüm, öncekiyle aynı kuyruğa alma ve işçi çağrısını kullanır; fark yalnız sonucun nereye yazıldığıdır.

```ruby
# sonuc_depo.rb — sonucu ortak depoya yazmak kalisi nasil degistirir
require_relative "isler"

yaz = lambda do |b|
  kuyruga_al(b, "fatura_gonder", 1)
  b.uygulama.depo[:sonuc] = isci_calistir(b).first
end
oku = ->(b) { b.uygulama.depo[:sonuc] }
goster("sonuc depoda", *kalis(yaz, oku))
```
```
sonuc depoda  kalici        A1 A2 A3 B4  "fatura 1 gonderildi"
```

Sonuç bu kez **kalıcı** kalıştadır: dört isteğin dördünde de göründü. Değişen yalnız yazılan yerdir. Sonucun hangi isteğe kadar kalacağını işçi değil, sonucu yazan kod belirler. Bu, bir sonraki bölümdeki sonucun doğrudan karşılığıdır: kuyruk "ne oldu" bilgisini taşımaz, onu taşımak isteyen kod bunu uygulamanın ortak deposuna açıkça yazar.

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

Şimdiye kadar kalıcı olan şey işin **kaydı**, yani adı ve argümanlarıydı. Peki işin **sonucu** nerede? `ISLER` tablosundaki lambda bir dize döndürüyor, ama `isci_calistir` o dizeyi yalnız bir dizide topluyor. Sonucu da aynı kalış kuralıyla ölçelim: iş kuyruğa alınıp aynı istekte işçi tarafından koşturuluyor ve sonuç isteğin istek deposuna konuyor.

```ruby
# sonuc.rb — isin sonucu hangi istege kadar kaliyor
require_relative "isler"

yaz = lambda do |b|
  kuyruga_al(b, "fatura_gonder", 1)
  b.istek_deposu[:sonuc] = isci_calistir(b).first
end
oku = ->(b) { b.istek_deposu[:sonuc] }
goster("is sonucu", *kalis(yaz, oku))
```
```
is sonucu     bu_istek      A1 -- -- --  "fatura 1 gonderildi"
```

Sonuç **bu istek** kalışında. İşin argümanı kalıcıydı, sonucu değil. Bunun nedeni basittir: işçi sonucu döndürür, ama döndürülen değeri bir yere yazmak işin kendi görevidir. Sonucun sonraki isteklerde görünmesi isteniyorsa iş, sonucu kendisi saklamalıdır. Bu çekirdekte kuyruk yalnız "ne yapılacak" bilgisini taşır, "ne oldu" bilgisini taşımaz. Bu ayrım bir varsayım hatasına yol açabilir: kuyruğa bir iş atıp sonucunu daha sonra kuyruktan okumayı beklemek, kuyruğun taşımadığı bir değeri aramaktır.

#### Özet

- Arka plan işi, yanıtı geciktirmemek için sonraya bırakılan işin adı ve argümanlarından oluşan bir kayıttır; kuyruk kaydı tutar, işçi boşaltıp koşturur.
- Kuyruğa alınan işin kaydı, istemciden bağımsız olarak uygulamanın ortak deposundadır; bu dersin kalışı **kalıcı**dır. Kuyruk işleri girdikleri sırayla tutar.
- İşçi çalışınca kayıt kuyruktan çıkar ve bir kez koşar; ikinci işçi çağrısı boş döner. İşçi kaydı ikinci istekte boşaltırsa kural onu **sonraki istek** olarak görür.
- Kuyruk argümanın kopyasını değil kendisini tutar; sonradan yapılan değişiklik işe ulaşır. Bilinmeyen iş adı `KeyError` ile işçiyi durdurur ve hatalı kayıt kuyruktan çıkmış olur.
- İşin argümanı kalıcıdır, sonucu değildir; sonucu saklamak işin kendi işidir.
- Sonuç yazıldığı yere bağlıdır: ortak depoya yazılırsa **kalıcı**, istek deposuna yazılırsa **bu istek** kalır.

#### Sonraki Adım

Kuyruk, bir işi ertelemek için kaydı kalıcı kılıyor. Peki aynı hesabı her istekte yeniden yapmak yerine sonucunu kalıcı kılmak istersek ne olur? Bir sonraki ders önbelleklemeyi ölçer: bir parçanın bir kez üretilip sonraki isteklere sunulması ve bu bayatlamanın nasıl yönetildiği.

### Önbellekleme

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/onbellekleme

Bir parçanın bir kez üretilip sonraki isteklere sunulmasının, sorgu önbelleğinden farkının ve anahtar değişmediğinde içeriğin bayatlamasının ölçümü.

Önceki ders, kuyruğa konan bir işin kaydının kalıcı olduğunu ölçtü; kayıt, işi işleyen işçi gelene kadar bekliyordu. Bu derste kalıcı kılınan şey bir kayıt değil, bir hesabın sonucudur. Bir ürün listesinin bir satırını her istekte yeniden çizmek yerine, bir kez çizip sonucunu saklamak ve sonraki isteklere onu sunmak mümkündür. Soru şu: saklanan sonuç hangi isteğe kadar geçerli, ve ne zaman geçerli olmaktan çıkar?

**Önbellek (cache)**, pahalı bir hesabın sonucunu, aynı hesap yeniden istendiğinde tekrar yapmamak için saklayan depodur. **Parça önbelleği**, bir görünümün bir parçasının, örneğin tek bir liste satırının çizilmiş çıktısını saklar. Hangi sonucun hangi hesaba ait olduğunu **önbellek anahtarı** belirler: aynı anahtarla gelen ikinci istek hesabı yapmaz, saklanan sonucu alır. M11/K03'te önbelleğin ortak bir depo olduğu görülmüştü; burada kimin gördüğünü sormuyoruz, ne kadar kaldığını soruyoruz. Ölçü ekseni kursun ekseni: **bu değer hangi isteğe kadar kalıyor?** Dört kalış: **bu istek**, **sonraki istek**, **oturum**, **kalıcı**.

#### Çekirdek

Ölçümler aynı küçük istek döngüsüne dayanır. Dosya kursun çekirdeğidir ve olduğu gibi verilir. Önbellek bu çekirdekte `Uygulama#depo` içindeki bir karma yapıdır; gerçek bir uygulamada önbellek ayrı bir depodur ve çekirdek onun davranışını taşır, kendisini taşımaz.

```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 blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir. Çekirdek bir çerçeve değil, bir çerçevenin modelidir. Kural değerin davranışını okur, saklandığı yeri okumaz; ölçümler tek bir uygulama süreci içinde anlamlıdır.

#### Parça Önbelleği

Önbellekleme yardımcısı tek satırdır: anahtar önbellekte varsa saklanan değeri döndürür, yoksa bloğu koşturur, sonucu anahtarın altına koyar ve döndürür. Dersin kesiti bunu, ölçümde kullanılacak iki küçük yardımcıyla birlikte verir. `Urun` modülü bir önceki dersin kısa bir kopyasıdır ve sorgu önbelleğiyle karşılaştırma için gerekir.

```ruby
# onbellek.rb — onbellekle yardimcisi, parca ureten yardimci ve karsilastirma icin Urun
require_relative "kalis"

def onbellekle(b, anahtar) = b.uygulama.depo[:onbellek][anahtar] ||= yield

def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")

def parca(b, urun)
  onbellekle(b, "urun/#{urun[:id]}/#{urun[:surum]}") { "<li>#{kacisla(urun[:ad])}</li>" }
end

module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

def goster(ad, tur, kanit, deger)
  printf("%-14s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
```

Bu blok da kaynaktır. `onbellekle` anahtarı `||=` ile yazar: değer `nil` ya da `false` ise yeniden hesaplanır, ama parça çıktıları bir dizge olduğu için bu ölçümlerde sorun çıkarmaz. Birinci gözlem, önbelleğe yazılan parçanın kendisidir.

```ruby
# parca.rb — parca onbellegi hangi istege kadar kaliyor
require_relative "onbellek"

yaz = ->(b) { onbellekle(b, "urun/1") { "<li>kalem</li>" } }
oku = ->(b) { b.uygulama.depo[:onbellek]["urun/1"] }
goster("parca", *kalis(yaz, oku))
```
```
parca         kalici        A1 A2 A3 B4  "<li>kalem</li>"
```

Parça dört isteğin dördünde de göründü; B'nin isteği de dahil. Bu **kalıcı** kalıştır ve önbelleğin amacı da budur: bir istemcinin ürettiği parçayı başka bir istemcinin isteği de kullanır. Çekirdekteki model bunu açıkça gösterir. **UY7.** Çekirdekte parça süreç belleğindedir ve hiçbir yaşam süresi yoktur. Gerçek bir önbellek girdilere bir yaşam süresi tanıyabilir ya da dolunca eskiyi atabilir; bu ölçüm onları modellemez.

#### Hesap Bir Kez Yapılır

Önbelleğin kazancı hesabın yinelenmemesidir. Bunu sayarak görelim: aynı anahtarla üç çağrı bir istekte, bir çağrı da başka bir istemcide yapılır; blok kaç kez koşar?

```ruby
# sayim.rb — ayni anahtarla bloga kac kez girilir
require_relative "onbellek"

kosma = 0
uygulama = Uygulama.new
uygulama.istek("A") do |b|
  3.times { onbellekle(b, "urun/1") { kosma += 1; "<li>kalem</li>" } }
end
uygulama.istek("B") { |b| onbellekle(b, "urun/1") { kosma += 1; "<li>kalem</li>" } }
puts "ayni anahtar: #{kosma}"
uygulama.istek("A") { |b| onbellekle(b, "urun/2") { kosma += 1; "<li>silgi</li>" } }
puts "ikinci anahtar: #{kosma}"
```
```
ayni anahtar: 1
ikinci anahtar: 2
```

Dört çağrıya karşın blok bir kez koştu; farklı bir anahtar bloğu bir kez daha çalıştırdı. Anahtar, hesabın kimliğidir: iki çağrı aynı anahtarı kullanıyorsa onlar için aynı hesap yapılmış sayılır.

#### Sorgu Önbelleği ile Karşılaştırma

Önceki bir derste sorgu önbelleğini ölçmüştük. Aynı istekte aynı sorgu ikinci kez veritabanına gitmiyordu, ama bir sonraki istek sayacı sıfırdan başlatıyordu. Aynı kuralı iki önbelleğe birlikte uygulayalım.

```ruby
# karsilastirma.rb — sorgu onbellegi ile parca onbellegi yan yana
require_relative "onbellek"

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
yaz = ->(b) { kalem.call(b); Urun.bul(b, 1) }
oku = ->(b) { b.istek_deposu.dig(:sorgular, [:urunler, 1], :ad) }
goster("sorgu", *kalis(yaz, oku))
yaz = ->(b) { onbellekle(b, "urun/1") { "<li>kalem</li>" } }
oku = ->(b) { b.uygulama.depo[:onbellek]["urun/1"] }
goster("parca", *kalis(yaz, oku))
```
```
sorgu         bu_istek      A1 -- -- --  "kalem"
parca         kalici        A1 A2 A3 B4  "<li>kalem</li>"
```

İkisi de önbellek adını taşır ama kalışları zıttır. Sorgu önbelleği **bu istek** kalışındadır: istek deposunda yaşar ve isteğin sonunda gider. Parça önbelleği **kalıcı** kalışındadır: uygulamanın deposunda yaşar. Aradaki fark, tekrar eden sorgunun aynı istek içindeki tekrar olması, tekrar eden parçanın ise istekler arasındaki tekrar olmasıdır. Sorgu önbelleğinin sınırı bir istektir; bu yüzden bayatlığı (kaynağın gerisinde kalma durumu) da yalnız o istekle sınırlıdır. Parça önbelleğinin sınırı bir istek değildir; bu yüzden eskime onun asıl sorunudur. Bu soruna sıra geldi. Bu iki önbellek aynı kuralla ölçüldüğü için ikisi de aynı çekirdeğin parçasıdır, ama biri isteği, öteki uygulamayı izler.

#### Geçersiz Kılma

Parça kalıcıysa, ürün değiştiğinde ne olur? Önbellek parçayı kaynağından bağımsız saklar; kaynak değiştiğini bilmez. Bu yüzden önbelleği yönetmek, eskiyen girdiyi yenisiyle değiştirmeyi de içerir. Girdiyi eskimiş saymak ve atmak **geçersiz kılma (invalidation)** adını alır. Bir yolu girdiyi silmektir. Öteki yol anahtarı değiştirmektir: eski girdi geçersiz kılınmaz, ama yeni anahtar ona hiç uğramaz. `parca` yardımcımız ikinci yolu kullanır; anahtara ürünün sürümünü katar. Ürün değişince sürüm artar ve anahtar farklı olur.

```ruby
# gecersiz.rb — surumlu anahtar ile gecersiz kilma
require_relative "onbellek"

uygulama = Uygulama.new
urun = { id: 1, ad: "kalem", surum: 1 }
p uygulama.istek("A") { |b| parca(b, urun) }
urun = urun.merge(ad: "silgi", surum: 2)
p uygulama.istek("A") { |b| parca(b, urun) }
p uygulama.depo[:onbellek].keys
```
```
"<li>kalem</li>"
"<li>silgi</li>"
["urun/1/1", "urun/1/2"]
```

Ürün değişince çıktı da değişti. Ama önbelleğin anahtarlarına bakınca eski girdi hâlâ orada: sürümlü anahtar eski girdiyi geçersiz kılmaz, yalnız ona bir daha uğramaz. Eski girdi, hiç kullanılmadığı hâlde önbelleği doldurur; bu bedeli bir yaşam süresi ya da yer açma ilkesi öder. **UY8.** Burada eski girdinin silinmesi gösterilmedi; silmenin hangi anda ve kim tarafından yapılacağı önbelleğin yapılandırmasına bağlıdır.

#### Girdiyi Silerek Geçersiz Kılmak

Sürümlü anahtarın bıraktığı eski girdi, ilk yolun neden ayrı bir ölçüm istediğini gösterir: girdiyi doğrudan silmek. Ölçümde aynı anahtar iki kez kullanılır; arada girdi önbellekten silinir. Blok her koşuşta bir sayaç artırır.

```ruby
# silme.rb — girdiyi silmek bloga yeniden girdirir mi
require_relative "onbellek"

kosma = 0
uygulama = Uygulama.new
2.times { uygulama.istek("A") { |b| onbellekle(b, "k") { kosma += 1; "eski" } } }
puts "silmeden once: #{kosma}"
uygulama.istek("A") { |b| b.uygulama.depo[:onbellek].delete("k") }
uygulama.istek("A") { |b| onbellekle(b, "k") { kosma += 1; "yeni" } }
puts "sildikten sonra: #{kosma}"
p uygulama.depo[:onbellek]
```
```
silmeden once: 1
sildikten sonra: 2
{"k" => "yeni"}
```

Silmeden önce blok bir kez koştu. Girdi silindikten sonra bir sonraki çağrı bloğu yeniden koşturdu ve önbellekte artık yeni değer duruyor. Silme, sürümlü anahtarın tersini yapar: eski girdi gider, anahtar aynı kalır. Bu yolda önbellek şişmez, ama silme işini yapan bir kodun kaynak değiştiğinde çalışması gerekir; çalışmazsa girdi bayat kalır.

#### `nil` Sonucu `onbellekle` Saklamaz

`onbellekle` yardımcısı değeri `||=` ile yazar. Ruby'de `nil` ve `false` yanlış sayıldığı için bu iki değer önbellekte durmaz. Bir hesabın sonucu "bulunamadı" anlamında `nil` ise, hesap her çağrıda yeniden yapılır. Aynı anahtarla iki çağrı yapıp bloğun kaç kez koştuğuna bakalım.

```ruby
# bos.rb — nil donduren hesap onbellege girer mi
require_relative "onbellek"

kosma = 0
uygulama = Uygulama.new
2.times { uygulama.istek("A") { |b| onbellekle(b, "yok") { kosma += 1; nil } } }
puts "nil: #{kosma}"
2.times { uygulama.istek("A") { |b| onbellekle(b, "var") { kosma += 1; "x" } } }
puts "dolu: #{kosma}"
```
```
nil: 2
dolu: 3
```

`nil` döndüren blok iki çağrının ikisinde de koştu; dolu bir değer döndüren blok ise yalnız bir kez koştu. Yardımcının kazancı yalnız önbelleğe giren değerler için vardır. Pahalı bir hesap "yok" sonucuna da varabilir ve bu sonuç her seferinde yeniden hesaplanır. Bunu önlemek isteyen kod, boş sonucu önbelleğe ayrı bir değerle, örneğin boş bir dizgeyle yazar.

#### Anahtar Kimin Değeri Olduğunu Söylemelidir

Parça önbelleği kalıcıdır ve istemciler arasında paylaşılır. Bu, parça bir istemciye özelse bir sorun doğurur. Anahtarda istemciyi ayıran bir şey yoksa ikinci istemci, birincinin parçasını alır. Ölçümde iki istemci oturumlarına farklı kullanıcı numaraları yazar ve aynı anahtarla bir karşılama parçası ister.

```ruby
# paylasilan.rb — istemciye ozel parca ortak anahtarla saklanirsa
require_relative "onbellek"

uygulama = Uygulama.new
uygulama.istek("A") { |b| b.oturum[:kullanici_id] = 42 }
uygulama.istek("B") { |b| b.oturum[:kullanici_id] = 7 }
%w[A B].each do |istemci|
  parca = uygulama.istek(istemci) do |b|
    onbellekle(b, "karsilama") { "<p>kullanici #{b.oturum[:kullanici_id]}</p>" }
  end
  puts "#{istemci} #{parca}"
end
```
```
A <p>kullanici 42</p>
B <p>kullanici 42</p>
```

B, 7 numaralı kullanıcı olduğu hâlde 42 numaralı kullanıcının parçasını gördü. Parçayı üreten ilk istek A'nındı; B'nin isteği aynı anahtarı bulunca bloğa girmedi. Kalıcı kalışın bedeli budur: önbelleğe yazılan değer, yazan isteğin değil, uygulamanın malıdır. Anahtara kullanıcı numarasını katmak sorunu çözer, ama o zaman parçanın paylaşım kazancı kullanıcı sayısı kadar parçaya bölünür. Hangi parçanın herkese ortak, hangisinin kullanıcıya özel olduğuna karar vermek, anahtarı tasarlamanın asıl işidir. Bu karar verilmeden yazılan bir anahtar, ölçümde görüldüğü gibi, bir kullanıcının verisini başka bir kullanıcının yanıtına taşıyabilir ve hata, hiçbir istisna fırlatmadan, yalnız yanlış bir çıktı olarak ortaya çıkar.

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

Anahtar değişmiyorsa, yani sürüm katılmamışsa, kaynak değiştiğinde ne olur? Aynı anahtarla iki çağrı yapalım; ikincisinde blok farklı bir değer üretir.

```ruby
# bayat.rb — anahtar ayni kalirsa icerik bayatlar
require_relative "onbellek"

uygulama = Uygulama.new
p uygulama.istek("A") { |b| onbellekle(b, "k") { "eski" } }
p uygulama.istek("A") { |b| onbellekle(b, "k") { "yeni" } }
```
```
"eski"
"eski"
```

İkinci çağrının bloğu hiç koşmadı. Önbellek anahtarı gördü, saklanan değeri döndürdü ve kaynağın artık `yeni` olduğundan habersiz kaldı. Buna **bayat içerik** denir. Kural yalnız kalışı ölçer; değerin hâlâ doğru olup olmadığı onun sorusu değildir, ve bu ölçüm iki sorunun neden ayrı olduğunu gösterir. Parça önbelleğinin kalıcı kalışı bir avantajdır: istekler arasında hesap paylaşılır. Aynı kalış, geçersiz kılma yapılmadığında bir bedeldir: bayat içerik bütün istemcilere, herkes için aynı biçimde sunulur. Önbelleğe alınan her parça için iki soru birlikte sorulmalıdır: bu hesap ne kadar pahalı, ve kaynağı değiştiğini önbellek nereden öğrenecek?

#### Özet

- Önbellek, pahalı bir hesabın sonucunu saklar; parça önbelleği bir görünümün tek bir parçasını saklar ve anahtarı hesabın kimliğidir.
- Parça önbelleği uygulamanın ortak deposundadır; bu dersin kalışı **kalıcı**dır ve başka bir istemcinin isteği de aynı parçayı kullanır.
- Aynı anahtarla yapılan dört çağrıda blok bir kez koşar; farklı anahtar bloğu yeniden koşturur.
- Sorgu önbelleği **bu istek**, parça önbelleği **kalıcı** kalışındadır; aynı ad iki ayrı kalışı gizler.
- Anahtar değişmedikçe içerik bayatlar; anahtara sürüm katmak bayat girdiye uğramayı keser ama eski girdiyi bırakır, girdiyi silmek ise önbelleği şişirmez ama silme kodunun çalışmasını gerektirir.
- `onbellekle` `nil` ve `false` sonucunu saklamaz; ortak anahtarla saklanan kullanıcıya özel parça başka kullanıcıya görünür.

#### Sonraki Adım

Kalıcı bir parçayı bayatlamadan kullanmak için önce parçanın doğru çalıştığını bilmek gerekir. Bir sonraki ders testi ölçer: bir denetleyicinin örnek değişkenini dışarıdan okumak ve her testin kendi uygulamasını kurduğu bir yalıtım düzeneği.

### Test

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/test

Bir denetleyicinin örnek değişkeninin dışarıdan okunmasının, birim testi ile özellik testi arasındaki farkın ve her testin kendi uygulamasını kurmasının yalıtım üzerindeki etkisinin ölçümü.

Önceki ders, bir parçanın kalıcı kalışının avantajını ve bayatlama bedelini ölçtü. Kalıcı bir değere güvenmek için o değeri üreten kodun doğru çalıştığından emin olmak gerekir; bunu kontrol etmenin yolu testtir. Bir test de bir değerin kalışına dayanır: testin kurduğu durum, testin sonunda nereye kadar ulaşıyor? Bir testin bıraktığı kayıt bir sonrakinin başlangıç durumuna karışıyorsa testler birbirinden bağımsız değildir ve sonuçları sırasına göre değişir.

Bu derste ölçü ekseni aynı kalır: **bu değer hangi isteğe kadar kalıyor?** Dört kalış: **bu istek**, **sonraki istek**, **oturum**, **kalıcı**. Bir denetleyicinin örnek değişkeni bir isteğe aittir; bir uygulamanın deposu ise bütün isteklere. Testin yalıtımı, bu iki kalışın testler arasında karışıp karışmadığına bağlıdır.

#### Çekirdek

Ölçümler aynı küçük istek döngüsüne dayanır. Dosya kursun çekirdeğidir ve olduğu gibi verilir.

```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 blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir. Çekirdek bir çerçeve değil, bir çerçevenin modelidir. Bir fark taşır: çekirdekteki `Urun.bul`, saklanan nesnenin kendisini döndürür; birçok eşleyicide her okuma, satırdan yeni bir nesne kurar. Bu dersteki ölçümler bu farkın gözlemlenebilir sonuçlarına dayanmaz.

#### Denetleyiciyi Dışarıdan Okumak

Bir denetleyici eylemi çalıştırdığında ne yaptığını iki yerde bırakır: döndürdüğü değerde ve örnek değişkenlerinde. İkincisi görünüme taşınan durumdur ve bir **birim testi (unit test)** onu doğrudan okuyabilir. Birim testi, tek bir parçayı, burada tek bir denetleyiciyi, öteki parçalardan ayırarak sınar. Ruby'de örnek değişkeni dışarıdan `instance_variable_get` ile okunur. Kesit, önceki derslerden tanıdık `Urun` ve `UrunlerDenetleyicisi` ile `denetleyici` yardımcısını tek dosyada toplar.

```ruby
# test.rb — Urun, UrunlerDenetleyicisi ve denetleyici yardimcisi
require_relative "kalis"

module Urun
  def self.olustur(b, nitelikler)
    hatalar = []
    hatalar << "ad bos olamaz" if nitelikler[:ad].to_s.empty?
    hatalar << "fiyat negatif olamaz" if nitelikler[:fiyat].to_i.negative?
    return { hatalar: } unless hatalar.empty?

    tablo = b.uygulama.depo[:urunler]
    id = tablo.size + 1
    tablo[id] = nitelikler.merge(id:)
    { id:, hatalar: }
  end

  def self.bul(b, id)
    sorgular = (b.istek_deposu[:sorgular] ||= {})
    sorgular.fetch([:urunler, id]) do
      b.istek_deposu[:sorgu_sayisi] = b.istek_deposu.fetch(:sorgu_sayisi, 0) + 1
      sorgular[[:urunler, id]] = b.uygulama.depo[:urunler][id]
    end
  end
end

class UrunlerDenetleyicisi
  def self.once(*adlar) = (@filtreler ||= []).concat(adlar)
  def self.filtreler = @filtreler || []

  once :urunu_bul

  def initialize(baglam)
    @baglam = baglam
  end

  def calistir(eylem)
    self.class.filtreler.each { |filtre| send(filtre) }
    public_send(eylem)
  end

  def goster = @urun

  private

  def urunu_bul
    @urun = Urun.bul(@baglam, @baglam.params[:id])
  end
end

def denetleyici(b) = b.istek_deposu[:denetleyici] ||= UrunlerDenetleyicisi.new(b)

def goster(ad, tur, kanit, deger)
  printf("%-14s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
```

Bu blok da kaynaktır. İlk ölçüm, eylemden sonra denetleyicinin örnek değişkenini okur ve kalışını sınıflandırır.

```ruby
# degisken.rb — denetleyici degiskeni hangi istege kadar kaliyor
require_relative "test"

kalem = ->(b) { Urun.olustur(b, ad: "kalem", fiyat: 12) }
yaz = ->(b) { kalem.call(b); b.params[:id] = 1; denetleyici(b).calistir(:goster) }
oku = ->(b) { denetleyici(b).instance_variable_get(:@urun)&.fetch(:ad) }
goster("denetleyici", *kalis(yaz, oku))
```
```
denetleyici   bu_istek      A1 -- -- --  "kalem"
```

Örnek değişken yalnız eylemin çalıştığı istekte görüldü. Sonraki istekte `denetleyici(b)` yeni bir denetleyici kurar ve onun `@urun` değeri boştur. Bu **bu istek** kalışıdır: denetleyici, istek deposundan kurulur ve istekle birlikte gider. Bu aynı zamanda bir test için bir kolaylıktır, çünkü bir testin okuduğu durum önceki testten gelmiş olamaz. Kayıt ise farklıdır: `Urun.olustur` uygulamanın deposuna yazar, o depo isteklerden uzun yaşar. İki kalışı birbirinden ayırmak yalıtımın ilk koşuludur. **UY9.** Burada denetleyiciyi doğrudan kurup `calistir` ile çağırdık; bir yönlendirme ya da bir ara katman devreye girmedi, yani ölçüm bir birim testidir.

#### Küçük Bir Doğrulama Yardımcısı

Bir test aracına ihtiyaç duymadan test yazmak mümkündür: beklenen ve gelen değeri karşılaştırıp uyuşmazlıkta bir istisna fırlatan yeterlidir. Ders bir test aracı tanıtmaz; yalnız bir doğrulamanın en küçük biçimini gösterir ve başarısız olduğunda ne olduğunu ölçer.

```ruby
# dogrula.rb — en kucuk dogrulama yardimcisi
require_relative "test"

class DogrulamaHatasi < StandardError; end

def dogrula(beklenen, gelen)
  raise DogrulamaHatasi, "beklenen #{beklenen.inspect}, gelen #{gelen.inspect}" unless beklenen == gelen

  :gecti
end
```

Bu blok da kaynaktır. Yardımcıyı bir ürün oluşturan istekte iki kez çağıralım; ikincisinde beklenen değer yanlış verilir.

```ruby
# basarisiz.rb — gecen ve basarisiz olan dogrulama
require_relative "dogrula"

uygulama = Uygulama.new
sonuc = uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  b.params[:id] = 1
  denetleyici(b).calistir(:goster)
end
p dogrula("kalem", sonuc[:ad])
begin
  dogrula("silgi", sonuc[:ad])
rescue DogrulamaHatasi => e
  puts "#{e.class}: #{e.message}"
end
```
```
:gecti
DogrulamaHatasi: beklenen "silgi", gelen "kalem"
```

İlk doğrulama geçti. İkincisi, beklenen değer gelen değerden farklı olduğu için, bir istisna fırlattı ve istisnanın iletisi iki değeri de yazdı. Bir test aracının yaptığı temelde budur: farkı bir hata olarak bildirmek ve testin geri kalanını bu hatayla durdurmak. Gerisi, yani testleri bulmak, sırayla çalıştırmak ve sonuçları özetlemek, bu çekirdeğin üzerine eklenir.

#### Birim Testi ve Özellik Testi

Birim testi tek bir parçayı sınar. **Özellik testi (feature test)** ise bir kullanıcının izleyeceği yolu baştan sona sınar: istek gelir, işlenir, yanıt döner. İkisi arasındaki fark kapsamdır, ve kapsam arttıkça bir başarısızlığın nedenini bulmak zorlaşır. Aynı davranışı iki kapsamda ölçelim: ürün oluşturulur ve bulunur. Birimde `Urun` ile denetleyici doğrudan çağrılır. Özellikte, bir isteğin içinden geçen yolu taşıyan tek bir çağrı vardır.

```ruby
# kapsam.rb — birim ve ozellik testi ayni davranisi farkli kapsamda sinar
require_relative "dogrula"

# birim: yalniz Urun
uygulama = Uygulama.new
uygulama.istek("A") do |b|
  Urun.olustur(b, ad: "kalem", fiyat: 12)
  p dogrula("kalem", Urun.bul(b, 1)[:ad])
end

# ozellik: bir istegin icinden gecen yol, denetleyici dahil
uygulama = Uygulama.new
uygulama.istek("A") { |b| Urun.olustur(b, ad: "kalem", fiyat: 12) }
yanit = uygulama.istek("B") do |b|
  b.params[:id] = 1
  denetleyici(b).calistir(:goster)
end
p dogrula("kalem", yanit[:ad])
```
```
:gecti
:gecti
```

İki test de geçti, ama biri `Urun`'u yalnız çağırdı; ötekisi başka bir istemcinin isteğine kadar yolu izledi. Özellik testi, bir parçanın başkasıyla birleşimini sınadığı için daha çok şeyi doğrular ve bozulduğunda bozulan şeyi göstermez. Birim testi daha az şeyi doğrular ve bozulduğunda yeri açıktır. İkisi birbirinin yerini tutmaz.

#### Başarısız Test Yazdığını Geri Almaz

Bir doğrulama başarısız olduğunda istisna testin geri kalanını durdurur. Fakat testin o ana kadar yaptığı yazma durur mu, geri alınır mı? Bunu uygulamayı paylaşan bir testte ölçelim: test önce bir ürün oluşturur, sonra yanlış bir beklentiyle başarısız olur. Ardından uygulamanın deposuna bakılır.

```ruby
# kalinti.rb — basarisiz test yazdigi urunu geri alir mi
require_relative "dogrula"

paylasilan = Uygulama.new
begin
  paylasilan.istek("A") do |b|
    Urun.olustur(b, ad: "kalem", fiyat: 12)
    dogrula("silgi", Urun.bul(b, 1)[:ad])
  end
rescue DogrulamaHatasi => e
  puts e.class
end
p paylasilan.depo[:urunler].size
```
```
DogrulamaHatasi
1
```

Doğrulama istisna fırlattı, ama ürün depoda kaldı. İstisna, testin akışını keser; uygulamanın deposuna yapılmış bir yazmayı silmez. Bu, bir hatanın iki sonucu olduğu anlamına gelir: testin kendisi başarısız olur ve onun bıraktığı kalıntı, deponun kalıcı kalışı nedeniyle sonraki testlerin başlangıç durumuna girer. Başarısız bir testin ardından gelen testlerin de bozulabilmesinin nedenlerinden biri budur. Kalıntıyı temizlemeyi testin kendisine bırakmak çözüm sayılmaz, çünkü temizleme kodu da başarısız testin kestiği akışın arkasında kalabilir; daha güvenilir olan, her teste kendi uygulamasını yeniden kurmaktır. Bu çekirdekte kurmanın maliyeti bir `Uygulama.new` çağrısıdır; maliyet bu kadar düşük olduğunda paylaşmanın kazancı bu çekirdekte yoktur.

#### Sıra Sonucu Değiştirirse

Yalıtımın bozulduğunu aşağıdaki sınırlayıcı ölçüm iki kimlikle gösterecek; burada aynı bozulmayı bir testin geçip geçmediği üzerinden ölçelim. İki test aynı uygulamayı paylaşır: biri ilk ürünün kimliğinin `1` olmasını bekler, öteki yalnız bir ürün oluşturur. Testler iki farklı sırayla koşturulur ve her koşuda yeni bir uygulama kurulur.

```ruby
# sira_testi.rb — ayni iki test, iki sirada, ortak uygulamada
require_relative "dogrula"

bekleyen = ->(b) { dogrula(1, Urun.olustur(b, ad: "kalem", fiyat: 12)[:id]) }
yazan = ->(b) { Urun.olustur(b, ad: "silgi", fiyat: 5) }

{ "bekleyen, yazan" => [bekleyen, yazan], "yazan, bekleyen" => [yazan, bekleyen] }.each do |ad, siralama|
  uygulama = Uygulama.new
  sonuclar = siralama.map do |test|
    uygulama.istek("A") { |b| test.call(b) }
    :gecti
  rescue DogrulamaHatasi
    :basarisiz
  end
  puts "#{ad}: #{sonuclar.inspect}"
end
```
```
bekleyen, yazan: [:gecti, :gecti]
yazan, bekleyen: [:gecti, :basarisiz]
```

Aynı iki test, bir sırada geçti, ötekinde başarısız oldu. Testlerin her biri tek başına doğru görünür; sorun, ikisinin uygulamayı paylaşmasındadır. Sıraya bağlı bir başarısızlık, testin bir davranışı değil, testlerin birbirine bıraktığı durumu sınadığının işaretidir.

#### Önbelleği Paylaşan Testler

Yalıtımın önbellek için de geçerli olduğunu bir ölçümle gösterelim. İki test aynı uygulamayı paylaşır. İlk test uygulamanın önbelleğine bir değer yazar. İkinci test ise önbelleğin boş olduğunu varsayarak aynı anahtarı kendi değeriyle doldurmak ister.

```ruby
# onbellek_sizinti.rb — onbellegi paylasan ikinci test kendi degerini gorur mu
require_relative "dogrula"

paylasilan = Uygulama.new
paylasilan.istek("A") { |b| b.uygulama.depo[:onbellek]["k"] ||= "ilk testin degeri" }
okunan = paylasilan.istek("A") { |b| b.uygulama.depo[:onbellek]["k"] ||= "ikinci testin degeri" }
p okunan
yalitilmis = Uygulama.new.istek("A") { |b| b.uygulama.depo[:onbellek]["k"] ||= "ikinci testin degeri" }
p yalitilmis
```
```
"ilk testin degeri"
"ikinci testin degeri"
```

Ortak uygulamada ikinci test, kendi değerini değil birincinin değerini okudu; yalıtılmış uygulamada ise kendi değerini. Önbellek, kalıcı kalışı nedeniyle testler arasında bir sızıntı kanalıdır ve bu sızıntı testin başarısız olduğunu söylemez: ikinci test geçebilir, ama beklediği şeyi sınamamıştır.

#### Üretim Ortamıyla İlişki

M12/K05'te test ortamının üretime çıkıp çıkmadığı ölçülmüştü: bir ortamın yapılandırması bir sürecin parçasıdır ve bir sürecin dışına çıkmaz. Burada aynı ilke bir adım ötede geçerlidir. Test, kendi uygulamasını kurar; o uygulamanın deposu ve oturumları yalnız o testin sürecinde, o testin kurduğu nesnelerin içinde yaşar. Bu bir test için güvenlik sağlar: bir test, üretim uygulamasının kaydını okumaz ya da değiştirmez.

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

Yalıtımın bozulduğu durum, iki testin aynı uygulamayı paylaştığı durumdur. İlk ölçüm, her testin kendi uygulamasını kurduğu durumdur: iki ayrı `Uygulama.new` içinde ilk ürünün kimliği ne?

```ruby
# yalitim.rb — her test kendi uygulamasini kurarsa
require_relative "test"

ilk = Uygulama.new.istek("A") { |b| Urun.olustur(b, ad: "kalem", fiyat: 12)[:id] }
ikinci = Uygulama.new.istek("A") { |b| Urun.olustur(b, ad: "silgi", fiyat: 5)[:id] }
puts "ayri uygulamalar: #{ilk} ve #{ikinci}"
```
```
ayri uygulamalar: 1 ve 1
```

İki test de ilk kayıt olarak `1` kimliğini aldı: biri öteki olmadan çalışabilir ve sırası sonucu etkilemez. İkinci ölçüm, aynı uygulamayı paylaşan iki testi kurar.

```ruby
# paylasim.rb — iki test ayni uygulamayi paylasirsa
require_relative "test"

paylasilan = Uygulama.new
ilk = paylasilan.istek("A") { |b| Urun.olustur(b, ad: "kalem", fiyat: 12)[:id] }
ikinci = paylasilan.istek("A") { |b| Urun.olustur(b, ad: "silgi", fiyat: 5)[:id] }
puts "ortak uygulama: #{ilk} ve #{ikinci}"
```
```
ortak uygulama: 1 ve 2
```

İkinci test, bağımsız çalıştığında `1` alacakken, ilk testten sonra `2` aldı. Bir test `1` bekliyorsa, ortak uygulamada yalnız ilk sırada çalıştığında geçer; başka bir sırada ya da tek başına çalıştırıldığında sonucu değişir. Buna **testler arası sızıntı** denebilir: uygulamanın deposu kalıcı kalışta olduğu için, testin yazdığı değer sonraki testin okuduğu durumu değiştirir. Çözüm yalıtımdır: her test kendi uygulamasını, o uygulamanın deposunu ve istemcilerini baştan kurar. Bu, önceki dersin parça önbelleği için de geçerlidir. Test bir önbelleği paylaşırsa bir testin yazdığı parça sonraki testin gördüğü parça olur ve bayat içerik ile aynı biçimde testin sonucunu değiştirir.

#### Özet

- Denetleyicinin örnek değişkeni **bu istek** kalışındadır; sonraki istekte yeni bir denetleyici kurulur ve değişken boştur.
- Bir birim testi, örnek değişkeni dışarıdan okuyarak tek bir parçayı sınar; en küçük doğrulama yardımcısı, beklenen ile gelen farklıysa bir istisna fırlatır.
- Özellik testi bir yolu baştan sona sınar; birim testi daha az doğrular ama bozulduğunda yeri gösterir.
- Her testin kendi uygulamasını kurması yalıtımı sağlar: ayrı uygulamalarda ilk kimlik `1` ve `1`, paylaşılan uygulamada `1` ve `2`.
- Uygulamanın deposu kalıcı kalışta olduğu için paylaşılan bir uygulama testler arası sızıntıya yol açar: başarısız test kalıntı bırakır, sonuç sıraya bağlı olur.
- Paylaşılan önbellek de sızıntı kanalıdır; ikinci test geçse bile beklediği şeyi sınamamış olabilir.

#### Sonraki Adım

Bir uygulamada doğrulanması gereken son şey, uygulamanın ortak güvenlik varsayılanlarıdır. Bir sonraki ders sahtecilik belirtecinin neden oturumda tutulduğunu sorar ve bu belirtecin kalışını ölçer.

### Güvenlik Varsayılanları

Source: https://academia.sh/tr/kurslar/mvc-cercevesi/guvenlik-varsayilanlari

Sahtecilik belirtecinin neden oturumda tutulduğunun, şablonda varsayılan kaçışlamanın ve aşırı atamaya karşı izin listesinin ölçümü; on üç dersin kalışlarının kapanış tablosu.

Önceki ders, testlerin birbirinden yalıtılmasını ölçtü. Bu ders kursun son uygulama hizmetini ele alır: bir uygulamanın kullanıcı adına hiçbir şey yapmadan da sunması gereken güvenlik varsayılanlarını. Üç tanesi seçildi: sahtecilik belirteci, şablonda kaçışlama ve aşırı atamaya karşı izin listesi. İlk ders sorusu, sahtecilik belirtecinin neden oturumda tutulduğudur.

Ölçü ekseni kursun başından beri aynıdır: **bu değer hangi isteğe kadar kalıyor?** Dört kalış: **bu istek**, **sonraki istek**, **oturum**, **kalıcı**. M12/K03'te sorulan soru başkaydı: bir sonucu kim değiştirebiliyor, sunucu, ortam, istemci ya da üçüncü taraf. Belirteç istemciye gider, formun içinde döner; ama bu sorunun cevabı belirtecin kalışını belirlemez. Kalışı oturumdur, çünkü yazıldığı yer oturumdur.

#### Çekirdek

Ölçümler aynı küçük istek döngüsüne dayanır. Dosya kursun çekirdeğidir ve olduğu gibi verilir.

```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 blok kaynaktır; dosya diske yazılır ve `require_relative` ile çekilir. Çekirdek bir çerçeve değil, bir çerçevenin modelidir. Bir fark taşır: çekirdekteki `Urun.bul`, saklanan nesnenin kendisini döndürür; birçok eşleyicide her okuma, satırdan yeni bir nesne kurar. Bu dersteki ölçümler bu farka dayanmaz.

#### Sahtecilik Belirteci

**Siteler arası istek sahteciliği (CSRF)**, başka bir sitenin, kullanıcının tarayıcısından kullanıcı adına bir istek göndermesidir. Savunma basittir: forma sunucunun ürettiği bir değer konur ve gönderilen değer sunucunun sakladığı değerle karşılaştırılır. Bu, **sahtecilik belirteci**dir. Bu derste ölçülen desende (senkronizasyon belirteci) karşılaştırmanın çalışması için sunucunun belirteci bir yerde saklaması gerekir; belirteci saklamayan durumsuz desenler de vardır, bu ders onları ölçmez. Nerede? Yazıldığı yer, belirtecin kalışını belirler. Kesit iki yardımcı ve bir sonuç satırı biçimi verir: belirteci üreten `csrf_belirteci`, metni kaçışlayan `kacisla` ve ölçüm satırını basan `goster`.

```ruby
# guvenlik.rb — belirtec, kacislama ve sonuc satiri
require_relative "kalis"

def kacisla(metin) = metin.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub('"', "&quot;")
def csrf_belirteci(b) = b.oturum[:csrf] ||= "belirtec-#{b.uygulama.depo[:sayac] += 1}"

def goster(ad, tur, kanit, deger)
  printf("%-16s%-14s%-13s%s\n", ad, tur, kanit, deger.inspect)
end
```

Bu blok da kaynaktır. `csrf_belirteci` oturumda bir değer varsa onu döndürür, yoksa üretip oturuma yazar. Ölçüm, belirteci ilk istekte üretir ve sonraki istekte oturumdan okur.

```ruby
# belirtec.rb — csrf belirteci hangi istege kadar kaliyor
require_relative "guvenlik"

yaz = ->(b) { csrf_belirteci(b) }
oku = ->(b) { b.oturum[:csrf] }
goster("csrf belirteci", *kalis(yaz, oku))
```
```
csrf belirteci  oturum        A1 A2 A3 --  "belirtec-1"
```

Belirteç A'nın üç isteğinde de göründü, B'nin isteğinde görünmedi. Bu **oturum** kalışıdır. Belirteç bir forma gider, ama sunucuda bir istemcinin oturumunda durur. Uygulamanın ortak deposunda olsaydı, kalıcı olurdu ve her istemci aynı değeri görürdü; o zaman bir istemcinin belirteci herkes için geçerli olurdu ve karşılaştırma bir şey kanıtlamazdı. **UY10.** Çekirdekte belirteç bir sayaçtan türer; gerçek bir belirteç tahmin edilemez olmalıdır. Bu ölçüm yalnız kalışı ölçer, üretimin güvenliğini değil.

Belirteç oturumla birlikte gider. Oturumdaki belirteç silinirse sonraki istek yeni bir belirteç üretir; eskisi artık sunucuda yoktur. Ölçümde A belirteç alır, oturumundaki belirteç silinir ve A yeniden belirteç ister; B de kendi belirtecini ister.

```ruby
# yenileme.rb — oturumdaki belirtec silinince ne olur
require_relative "guvenlik"

uygulama = Uygulama.new
ilk = uygulama.istek("A") { |b| csrf_belirteci(b) }
uygulama.istek("A") { |b| b.oturum.delete(:csrf) }
yeni = uygulama.istek("A") { |b| csrf_belirteci(b) }
baska = uygulama.istek("B") { |b| csrf_belirteci(b) }
p [ilk, yeni, baska]
```
```
["belirtec-1", "belirtec-2", "belirtec-3"]
```

Her yeni belirteç ayrı bir değer aldı ve sayaç istemciler arasında ortak olduğu için B'nin belirteci A'nınkilerden farklı çıktı. Belirteç oturumda durduğu sürece vardır; oturumdan silinen belirteç sunucuda yoktur, bu yüzden eski değerle gelen istek karşılaştırmada eşleşmez (bu ölçümde denenmedi).

#### Bir İstemcinin Belirteci Ötekinde Geçer mi?

Kalışın sonucu, istemcilere göre ayrışmasıdır. Aynı uygulamada A, B ve yeniden A istek yapar; her biri kendi oturumundaki belirteci ister.

```ruby
# istemciler.rb — A, B, A sirasiyla belirtec
require_relative "guvenlik"

uygulama = Uygulama.new
a1 = uygulama.istek("A") { |b| csrf_belirteci(b) }
b1 = uygulama.istek("B") { |b| csrf_belirteci(b) }
a2 = uygulama.istek("A") { |b| csrf_belirteci(b) }
puts [a1, b1, a2].inspect
```
```
["belirtec-1", "belirtec-2", "belirtec-1"]
```

A'nın ikinci isteği ilk belirteci aldı; B ayrı bir tane. Şimdi karşılaştırmayı da ekleyelim: gönderilen değer, oturumdaki değerle eşleşiyorsa istek kabul edilir.

```ruby
# karsilastir.rb — gonderilen belirtec oturumdakiyle eslesiyor mu
require_relative "guvenlik"

def csrf_gecerli?(b, gonderilen) = !gonderilen.nil? && gonderilen == b.oturum[:csrf]

uygulama = Uygulama.new
formdaki = uygulama.istek("A") { |b| csrf_belirteci(b) }
uygulama.istek("B") { |b| csrf_belirteci(b) }
p uygulama.istek("A") { |b| csrf_gecerli?(b, formdaki) }
p uygulama.istek("B") { |b| csrf_gecerli?(b, formdaki) }
p uygulama.istek("A") { |b| csrf_gecerli?(b, nil) }
```
```
true
false
false
```

A'nın kendi belirteci A'nın oturumunda geçerli. Aynı değer B'nin oturumunda geçersiz, ve hiç belirteç göndermeyen istek de geçersiz. Başka bir sitenin forma koyabileceği geçerli bir değer yoktur: belirteç yalnız bu istemcinin oturumuna bağlıdır. **UY11.** Burada karşılaştırma düz eşitliktir; gerçek bir uygulama sabit sürede karşılaştırır ve belirteci her formda yenileyebilir. Bunlar ölçülmedi.

#### Şablonda Varsayılan Kaçışlama

İkinci varsayılan Görünümler dersinde ölçüldü: şablon, yerel değerleri yerleştirirken `kacisla`'dan geçirir. Kullanıcının yazdığı metin sayfada işaretleme olarak değil, metin olarak görünür. Güvenli davranış varsayılandır; kaçışlamayı kapatmak ayrıca istenmelidir. Aynı yardımcıyı zararsız bir biçimlemeyle ölçelim.

```ruby
# kacislama.rb — varsayilan kacislama
require_relative "guvenlik"

p kacisla("Tom & Jerry")
p kacisla("<b>kalem</b>")
```
```
"Tom &amp; Jerry"
"&lt;b&gt;kalem&lt;/b&gt;"
```

Üç karakter karşılıklarına döndü. Bu ölçüm yalnız bu üç karakteri kapsar. Yardımcının tanımı çift tırnağı da çevirir; bu ölçüm onu göstermedi. Tek tırnak tanımda yer almaz. **UY12.** Gerçek bir görünüm katmanı başka bağlamları, örneğin bir bağlantının adresini ya da bir yazı dosyasının içini ayrıca ele alır; çekirdek bunları modellemez.

#### Aşırı Atamaya Karşı İzin Listesi

Üçüncü varsayılan, bir formdan gelen alanların kayda doğrudan yazılmasını engeller. Formda yalnız `ad` ve `fiyat` varsa bile, istek fazladan bir alan taşıyabilir. Kayıt tüm alanları alırsa istemci, formda olmayan bir alanı da yazar. Buna **aşırı atama (mass assignment)** denir. Savunma bir **izin listesi**dir: yalnız adı listede olan alanlar geçer.

```ruby
# izin.rb — izin listesi
require_relative "guvenlik"

def izinli(params, *anahtarlar) = params.slice(*anahtarlar)

gelen = { ad: "kalem", fiyat: 12, yonetici: true }
p izinli(gelen, :ad, :fiyat)
```
```
{ad: "kalem", fiyat: 12}
```

`yonetici` alanı sonuca girmedi. İzin listesi bir yasak listesi değildir: listede olmayan her şey, yarın eklenen bir alan da dahil, otomatik olarak dışarıda kalır. **UY13.** Yardımcı yalnız tek düzeyli bir hash'i süzer; iç içe alanlar için ayrı bir kural gerekir ve bu derste yazılmadı.

Listede adı geçen bir alan istekte yoksa ne olur? Yardımcıyı `fiyat` alanı olmayan bir istekle çağıralım.

```ruby
# eksik.rb — listede olup istekte olmayan alan
require_relative "guvenlik"

def izinli(params, *anahtarlar) = params.slice(*anahtarlar)

p izinli({ ad: "kalem" }, :ad, :fiyat)
p izinli({ ad: "kalem" }, :ad, :fiyat).key?(:fiyat)
```
```
{ad: "kalem"}
false
```

Eksik alan sonuçta `nil` değeriyle değil, hiç bulunmayarak yer aldı. İzin listesi bir alanı zorunlu kılmaz; yalnız geçebilecek alanların sınırını çizer. Bir alanın bulunmasını gerektiren denetim ayrı bir kuraldır ve Veri Katmanı konusunda doğrulama olarak ölçüldü.

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

Güvenli bir varsayılan yalnız bir kez ve doğru katmanda uygulanmalıdır. İki kez uygulanırsa güvenlik artmaz, çıktı bozulur. Aynı metin iki kez kaçışlanırsa ne olur?

```ruby
# cift.rb — iki kez kacislama
require_relative "guvenlik"

p kacisla("<")
p kacisla(kacisla("<"))
```
```
"&lt;"
"&amp;lt;"
```

İkinci kaçışlama ilk kaçışlamanın `&` karakterini de değiştirdi; sayfada `<` yerine `&lt;` görünür. Güvenlik varsayılanı bu yüzden tek katmanda durur: şablon kaçışlar, denetleyici kaçışlamaz, kayıt kaçışlanmış hâliyle saklanmaz. Bir değer hangi katmanda kaçışlanacaksa, o katmandan sonra bir daha dokunulmaz. Kısmi şablonlarda da aynı kural geçerlidir; Görünümler dersinde bu bozulma ölçülmüştü.

#### Özet

- Sahtecilik belirteci istemciye gider ama kalışı **oturum**dur: aynı istemcinin üç isteğinde görünür, başka istemcide görünmez.
- A, B, A sırasıyla istek yapan istemciler `"belirtec-1"`, `"belirtec-2"`, `"belirtec-1"` aldı; bir istemcinin belirteci ötekinin oturumunda geçersizdir.
- Şablonda varsayılan kaçışlama, kullanıcı metnini sayfada metin olarak gösterir.
- İzin listesi, formda olmayan alanı kayda yazmaz.
- Aşırı atama izin listesiyle kapanır: eksik alan `nil` olarak değil, hiç bulunmaz; liste yasak listesi değildir, listede olmayan her alan dışarıda kalır.
- Güvenli bir varsayılan tek katmanda uygulanır: `kacisla(kacisla("<"))` `"&amp;lt;"` verir.

#### Kurs Kapanışı

Kursun her dersi aynı soruyu sordu: bu değer hangi isteğe kadar kalıyor? On üç ölçüm, bu soruyu bir gözlemle yanıtladı. Aşağıdaki tablo hepsini kalışına göre toplar.

| Ders | Ölçülen değer | Kalış | Sınırlayıcı ölçüm |
| --- | --- | --- | --- |
| Yapılandırma Yerine Sözleşme | `sablon adi` | bu istek | ad üretilir, şablonun varlığı denetlenmez |
| Yönlendirme | `yol parametresi` | bu istek | değeri sonraki isteğe istemci taşır |
| Denetleyiciler ve Filtreler | `flash` | sonraki istek | her istekte yeniden yazılan flash oturumda görünür |
| Görünümler ve Şablonlar | `derlenen sablon` | kalıcı | ikinci derleme eskisini korur |
| Nesne–Tablo Bağlaması | `kayit` | kalıcı | model saklanan nesnenin kendisini döndürür |
| Göçler | `sema sutunu` | kalıcı | eski satırda yeni sütun yoktur |
| İlişkiler ve Doğrulamalar | `dogrulama hatasi` | bu istek | hata kayıtla saklanmaz |
| Sorgu Arayüzü ve N+1 Sorunu | `sorgu onbellegi` | bu istek | `nil` sonucu da saklanır |
| Kimlik Doğrulama ve Yetkilendirme | `kullanici kimligi` | oturum | yazıldığı istekte okunamayan değer `ArgumentError` verir |
| Arka Plan İşleri | `kuyruktaki is` | kalıcı | sonuç yazıldığı yere bağlıdır |
| Önbellekleme | `parca onbellegi` | kalıcı | anahtar değişmedikçe bayat kalır |
| Test | `denetleyici degiskeni` | bu istek | paylaşılan uygulama testler arası sızıntı yapar |
| Güvenlik Varsayılanları | `csrf belirteci` | oturum | `kacisla(kacisla("<"))` `"&amp;lt;"` verir |

Kalışların dağılımı: **5 bu istek · 1 sonraki istek · 2 oturum · 5 kalıcı**. Tablo kursun bir bulgusunu taşır: kalış bir değerin türüne değil, yazıldığı yere ve okuyan kodun davranışına bağlıdır. Aynı ad iki kalışı gizleyebilir: sorgu önbelleği bu istek, parça önbelleği kalıcı oldu; aynı flash her istekte yeniden yazılınca oturuma döndü.

İki çıkarım yapılabilir. Birincisi, kalıcı olan beş değerin hepsi uygulamanın ortak deposunda durur ve istemcileri ayırmaz; bu yüzden her biri bir sınır taşıdı. İkincisi, oturum ve sonraki istek kalışları istemciye bağlıdır; bunlar yazıldığı istemcinin dışında görünmez.

**UY14.** Tablo yalnız on üç ölçümün sınıflandırmasıdır; dağılım ölçülen değerlerin seçimini yansıtır, bir uygulamadaki bütün değerlerin dağılımını değil. Kalış hücreleri her dersin Özet bölümündeki ifadeden alındı.

Çekirdek model burada da bir gerçeği taşıdı: `Urun.bul` saklanan nesnenin kendisini döndürür, birçok eşleyicide her okuma, satırdan yeni bir nesne kurar. Tablo bu yüzden bir çerçevenin davranışı değil, çerçevenin modelinin davranışıdır.

Kurs burada kapanır. Sıradaki kurs, bir Ruby projesini oluşturan paketleri, bağımlılık yönetimini ve geliştirme araçlarını ele alır. Orada da aynı kural geçerli kalır: tahmin yürütülmez, ölçülür.

