---
title: 'Denetleyiciler ve Filtreler'
source: 'https://academia.sh/tr/kurslar/mvc-cercevesi/denetleyiciler-ve-filtreler'
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:
    - 'Filtrenin eylemden önce koştuğunu ve filtre listesinin sınıfa bağlı olduğunu ölçer'
    - 'Denetleyici örneğinin her istekte yeniden kurulduğunu ve örnek değişkeninin istekte kaldığını gösterir'
    - 'Kaydet, yönlendir, bildirimi göster akışında bildirim mesajının (flash) kalışını ölçer'
    - 'Aynı düzeneğin kullanılışa göre başka bir kalışa düşebildiğini gösterir'
---

# 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.
