---
title: 'İlişkiler ve Doğrulamalar'
source: 'https://academia.sh/tr/kurslar/mvc-cercevesi/iliskiler-ve-dogrulamalar'
course: 'Model–Görünüm–Denetleyici Çerçevesi'
language: tr
updated: '2026-10-21T05:03:41+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Doğrulamanın ilk hatada durmadığını ve başarısız kaydın tabloya yazılmadığını ölçer'
    - "Bir-çok ilişkisini saf Ruby'de kurar ve doğrulamanın silinen ürünün siparişlerini yetim bırakmasını ölçer"
    - 'Doğrulama hatasının kalışının bu istek olduğunu dört istekte gösterir'
    - 'Hatanın bu istekte kalması ile bildirim mesajının sonraki isteğe taşınması arasındaki farkı ölçer'
---

# İlişkiler ve Doğrulamalar

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.
