Ders 07 / 13
İ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.
Yayın Güncelleme
Bu derste
- 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
İçindekiler
Ö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şkidir: 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.
# 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.
# 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.
# 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 olusturun 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.
# 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_deposuna koyar; okuyan lambda ilk hata iletisini alır.
# 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_deposunun 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_deposuna 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şkisidir (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.
# 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.
# 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.
# 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.
# 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.
# 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 istektir. Üç düzenek aynı ölçüde yan yana konur: hatayı istek deposunda tutmak, hatayı bildirim mesajına koymak ve geçerli kaydı kaydetmek.
# 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.
# 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.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.