24 Mart 2025 Pazartesi

Rails 7 Denemeler 3

https://ujk-ujk.blogspot.com/2025/03/rails-7-denemeler-3.html
İçindekiler +

    Selam Rails 7 denemelerine Ruby programlama temellerini göreceğimiz bir bölümle devam ediyoruz. Önceki bölümde yazdığımız "yeni_app" uygulamamız üzerinden devam edeceğiz. 




    Rails Aromalı Ruby 

    Önceki bölümde Rails açısından önemli olan bazı Ruby programlama teknikleri kullandık. Ruby çok büyük bir programlama dili, neyse ki Rails uygulaması geliştirmek için bilmemiz gerekenler çok da fazla değil. Bu bölümde Rails'de kullanılan Ruby tekniklerine bir göz atacağız. İleride uygulamalar geliştirdikçe Ruby dili hakkında daha geniş bilgileri tekrarlarla öğrenmeye devam edeceğiz. 


    Motivasyon

    Son bölümde gördüğümüz üzere, bir Rails uygulaması geliştirmek için, altında yatan Ruby dilini bilmeye pek fazla gerek olmadı. Uygulama geliştirmeyi ve hatta testleri yazmayı becerebildik. Testler yazıp sonra bu testlerin hata vermesi sonucu düzenlemelere gittik. Bu ileride de hep böyle olacak , sürekli gerçekleşen problemleri çözerek ilerleyeceğiz. Bu esnada Ruby bilgimizin bizi kısıtlamaması gerekir. 


    Mevcut yardımcı kodlar

    Son uygulamamızda statik sayfa görsellerindeki tekrarlanan kodlardan kurtulmak için bu görsel kodlarını app/views/layouts/application.html.erb dosyasında olduğunu görmüştük. 

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= yield(:title) %> | Yeni App</title>
        <meta name="viewport" content="width=device-width,initial-scale=1">
        <meta name="apple-mobile-web-app-capable" content="yes">
        <%= csrf_meta_tags %>
        <%= csp_meta_tag %>

        <%= yield :head %>

        <link rel="manifest" href="/manifest.json">
        <link rel="icon" href="/icon.png" type="image/png">
        <link rel="icon" href="/icon.svg" type="image/svg+xml">
        <link rel="apple-touch-icon" href="/icon.png">
        <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
      </head>

      <body>
        <%= yield %>
      </body>
    </html>


    Burada bir satıra odaklanalım

        <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

    Burada Rails'de mevcut olan stylesheet_link_tag yardımcı metodu tüm görsellerde application.css stil dosyası içerilmesi için kullanılmış (daha fazla bilgi için Rails API bakınız). Bu dosya app/assets/stylesheets/ dizininde yer alıyor ve şimdilik için boş. Bu uzmanlaşmış bir Rails programcısı için basit görünebilir, ancak en azından 4 adet karmaşık gelebilecek Ruby tekniği var: mevcut Rails metodu, parantezler olmadan metod çağırma, semboller ve hash değerler. Bu bölümde bunları göreceğiz.



    Kendi yardımcı kodlarımız

    Rails'de bulunan birçok yardımcı metod yanında , bir Rails uıygulaması geliştirirken kendi yardımcı dosyalarımızı da tanımlama imkanımız vardır. application.html.erb ana yerleşim dosyası içinde sayfa etiketini belirlemek için <title> tag içine yazdığımız koda bakalım.

        <title><%= yield(:title) %> | Yeni App</title>


    Bu kod her sayfa görseli başında provide metodu ile verilen :title değerini sayfa etiketine koyuyor.

    <% provide(:title, "Ana Sayfa") %>

    <h1>Yeni Uygulama</h1>
    <p>Bu uygulama ile Rails <b>Statik Sayfaları</b> öğreniyoruz</p>


    Fakat eğer provide ile bir :title vermezsek? Eğer görsel dosyamızda provide satırını silersek sayfa etiketi  | Yeni App olacaktır. Yani genel olarak kullanılabilecek bir Yeni App title değerimiz var ama başına | karakteri de geliyor. Bu sıkıntıyı çözmek için kendimize bir yardımcı kod yazalım. Yardımcı kodumuzda full_title adında bir yardımcı metodumuz olsun. Metodumuz eğer provide ile bir :title değeri verilmediyse etiketi sadece Yeni App olacak şekilde ayarlasın. 

    Yardımcı kodlarımız için app/helpers/application_helper.rb dosyasını kullanabiliriz, bu yardımcı kodlar dosyası tüm uygulamada geçerlidir. Eğer sadece bir kontrolöre ait yardımcı kodlar yazacaksak o kontrolöre özel mesela static_pages_helper.rb dosyasını kullanabiliriz. Biz tüm uygulamada geçerli olsun diye application_helper.rb dosyasını kullanalım. Şimdilik dosya içinde bir yardımcı metod tanımı yok. İçine ilave yapalım. 

    module ApplicationHelper
      # Her sayfa için bütün etiket yazısını döner
      def full_title(page_title = '')
        base_title = "Yeni App"
        if page_title.empty?
          base_title
        else
          page_title + " | " + base_title
        end
      end
    end


    Şimdi yerleşim dosyamız olan application.html.erb dosyasındaki 

        <title><%= yield(:title) %> | Yeni App</title>

    satırını

        <title><%= full_title yield(:title) %></title>

    olarak değiştirelim. Ana Sayfa sayfamızda etiketin genel etiket şeklinde olmasını istiyoruz , öyleyse görsel kodundan provide satırını çıkaralım. 

    home.html.erb

    <h1>Yeni Uygulama</h1>
    <p>Bu uygulama ile Rails <b>Statik Sayfaları</b> öğreniyoruz</p>

    Etiket değiştiği için test yaparsak olumsuz sonuç alırız, aslında test kodunu da değiştirmemiz gerekiyor çünkü Ana Sayfa'da etikette sadece Yeni App görmek istiyoruz. 

    test/controllers/static_pages_controller_test.rb

    require "test_helper"

    class StaticPagesControllerTest < ActionDispatch::IntegrationTest
      test "should get home" do
        get static_pages_home_url
        assert_response :success
        assert_select "title", "Yeni App"
      end
    .....


    Yardımcı kodlara ufak bir giriş yaptık. Benzer şeyler Rails uygulaması geliştirirken bize çok lazım olacak. Bu yüzden Ruby programlama dili hakkında birkaç şey öğrenmek iyi olacak, mesela modüller, metod tanımlamak, opsiyonel metod argümanları, yorumlar, yerel değişken atamaları, boolean değerler, akış kontrolü, string birleştirmeleri ve dönen değerler. Bu bölümde bize gerekebilecek Ruby programlama esasları üzerinde duracağız. 

    Not : Ruby programlama dili hakkında daha geniş bilgilendirmeyi Ruby Temelleri yazı dizimde bulabilirsiniz.



    String'ler ve metodları

    Ruby öğrenirken temel aracımız Rails konsol olacak. Rails konsolu interaktif Ruby uygulaması irb temelli olduğu için Ruby dilinin tüm özelliklerini deneyebileceğimiz bir ortamdır. Öncelikle Rails konsolun istediğimiz yapılandırma ile açılması için root klasörümüze .irbrc adında bir dosya varsa içeriğini değiştirelim yoksa ekleyelim. Bu amaçla Linux nano editörünü kullanabiliriz.

    $ nano ~/.irbrc

    Dosya içeriğine şunları yazalım.

    IRB.conf[:PROMPT_MODE] = :SIMPLE
    IRB.conf[:AUTO_INDENT_MODE] = false

    Ctrl+x tuşlayınca , çıkmadan önce "dosyayı kaydetmek ister misiniz" diye sorar, y tuşuna basınca dosya ismini yazar ve enter basınca kaydederek çıkar. Dosya yoksa da eklenmiş olur. 

    Şimdi Rails konsol çalıştırırsak basit prompt ile açılacaktır (tabi eğer normal irb de kullanıyorsanız o da artık basit prompt ile açılacak). 

    $ rails console
    Loading development environment (Rails 7.2.2)
    >> 


    Default olarak konsol development ortamında çalışır, diğerleri test ve production ortamlarıdır. Bu bölümde deneyeceklerimiz için bulunulan ortamın önemi yok, ama ileride olabilir. Konsolda rahatça her şeyi deneyebilirsiniz , bir şeyleri bozma ihtimaliniz oldukça düşük. Bir şekilde döngüde falan kilitlenirse Ctrl+C tuşlayarak çıkabilir ya da Ctrl+D tuşlayarak komple konsoldan çıkabilirsiniz. Ama normal çıkış yolu prompt'ta exit yazmaktır. Bir deneme yapalım.

    >> 8 + 4 #tamsayı toplama
    => 12


    # işareti ile başlayan yazılar Ruby yorumlayıcı için bir kod değil bir yorumdur, ve # işaretinden satır sonuna kadar yazılanları dikkate almaz. Burada sadece size göstermek için yorumlara bir şeyler yazıyorum, deneme yaparken yorumları yazmak zorunda değilsiniz.  Burada benim yazdığım kodlar standart irb renklerinde değil, çünkü standart renklendirmesi çok kötü ve ben kullanmıyorum, kodları burada daha anlaşılır görünsün diye VSCode editöründe renklendiriyorum. Rails konsolda renklendirme olmasın isterseniz ~/.irbrc dosyasına 

    IRB.conf[:USE_COLORIZE] = false

    satırını da eklemelisiniz. 



    String'ler

    String'ler bir web uygulamasının muhtemelen en önemli parçası. Çünkü server'lar tarayıcılara cevap dönerken her zaman string değerlerden oluşan bir doküman dönerler. Konsolda string denemeleri yapalım.

    >> ""     # Boş bir string
    => ""
    >> "foo"  # boş olmayan bir string
    => "foo"

    Bunlar string değerler (string literals), ve çift tırnak işareti ( " ) içinde tanımlanmış. Konsol her satırda yazdığımız kodu işletir ve sonucunu yazar. Sadece string bir değer girdiğimiz için aynı girdiğimiz değeri bize döner. 

    String değerleri + işareti ile birleştirebiliriz. 

    >> "foo" + "bar"  # string concatenation
    => "foobar"

    Buna birleştirme (concatenation) denir. Bu bir toplama işlemi değildir, sayılar toplanır ve kuralları matematik kuralları olarak bellidir. String nesneler için Ruby'de tanımlı + metodu ise solundaki ve sağındaki string'leri birleştirip yeni bir string nesnesi oluşturur. Bu arada bu foobar anlamı üzerine ilginç bir yazı var. 

    Diğer bir string birleştirme yöntemi olarak enterpolasyon denen bir yöntem daha vardır. 

    >> adı = "Ümit"     # değer ataması
    => "Ümit"
    >> "#{adı} Kayacık" # enterpolasyon
    => "Ümit Kayacık"

    Burada adı değişkenine "Ümit" string değerini sakladık. Sonra enterpolasyon ile "#{adı} Kayacık" string değeri içine enjekte ettik. Çift tırnakla ifade edilmiş bir string içinde #{...} şeklinde bir ifade varsa Ruby yorumlayıcı bunu bir Ruby kodu olarak görür ve süslü parantez içinde verilen kodu çalıştırıp, kod sonucunda oluşan değeri oraya yazar. 

    >> "2 ile 3'ü toplarsak : #{2+3} yapar"
    => "2 ile 3'ü toplarsak : 5 yapar"

    İstediğimiz birleştirme tekniğini kullanabiliriz, genelde kod okunabilirliğine göre yazılımcı karar verir.

    >> adı = "Ümit"
    => "Ümit"
    >> soyadı = "Kayacık"
    => "Kayacık"
    >> adı + " " + soyadı # boşluk ile birleştirme
    => "Ümit Kayacık"
    >> "#{adı} #{soyadı}" # enterpolasyon eşdeğeri
    => "Ümit Kayacık"

    Birçok yazılımcı enterpolasyonu tercih eder, çünkü özellikle birleştirilecek çok değer varsa sonuçta oluşacak string'i hayal etmek enterpolsayonda daha kolaydır. + ile birleştirmek biraz sakil kalıyor. 



    Yazdırmak

    Bir string değeri ekrana yazdırmak için en çok kullanılan Ruby metodu puts ( put es - put string kısaltması).  

    >> puts "foo"  # put string
    foo
    => nil

    puts metodu yan etkili çalışır. Yani şöyle açıklayalım, Ruby metodları  bir işler yapar ve işleri bitince bir değer geri dönerler. puts metodunun yaptığı iş ekrana argümanda verilen değeri yazdırmak, dönen değeri ise olmayan şey, yani nil değeri (diğer programlama dillerinde de olmayan değerlere null veya none gibi isimler verilir). Burada yan etki diye bahsettiğimiz ekrana değeri yazması. Yukarıdaki örneğe bakarsak metoddan dönen değer => prompt'u sonrasına yazılır, arada kalan foo çıktısı ise metod çalışırken onun yaptığı yan etkidir. 

    nil değerine gelelim, nil olmayan şeydir, sıfır değildir - sıfır bir sayıdır ve vardır (Ruby için bir Integer nesnesidir), nil ,  boş string ("") değildir - boş string bile bir string nesnedir, nil , false değer de değildir - false boolean bir değerdir ve Ruby için o da bir nesnedir. Aslında nil de Ruby için bir nesne, çünkü Ruby'de her şey bir nesne.

    >> nil.class
    => NilClass
    >> nil == 0
    => false
    >> nil == false
    => false
    >> nil == ""
    => false

    Gördüğümüz gibi nil değeri NilClass sınıfının bir nesnesi, Ruby kullandıkça anlamı kafamızda daha da oturacak.

    puts metodu denememizde bir ayrıntı var, puts metodu işini bitirince kursörü bir alt satıra taşır. Yani yeni satıra geçer. Ama aynı şekilde ekrana bir değer yazdırmak için kullanılan print metodu alt satıra geçmez. 

    >> print "foo"
    foo=> nil

    Alt satıra geçmeyince ortalık nasıl da karıştı, irb tarafından metod çıktısı hemen foo yazısı arkasına yazıldı, çünkü print metodu kursörü hemen işinin bittiği yerde bıraktı. 

    print metodu genellikle aynı satırda başka başka değerleri yan yana yazmak amacı ile kullanılır. Ancak print metoduna kursörü yeni satıra geçirmesi için bir yönlendirme de yapabiliriz.

    >> print "foo\n"
    foo
    => nil

    String değerin sonuna \n ekleyince aynı puts metodu gibi çalıştı. Ters slash n ( \n ) eğer çift tırnakla yazılmış bir string değer içinde kullanılırsa, string değer ekrana yazdırılırken tam o noktada alt satıra (new line) geçilir. Tek tırnak içinde verilmiş string değerlerde bir şey ifade etmez.

    >> print "f\noo\n"
    f
    oo
    => nil
    >> print 'foo\n'
    foo\n=> nil



    Tek tırnaklı stringler

    Hep çift tırnakları gördük, ama az evvel tek tırnaklı ifadenin başka davranış gösterdiğinden bahsettik. String değerler tek tırnak karakterleri içinde de verilebilir.

    >> 'foo'
    => "foo"
    >> 'foo' + 'bar'
    => "foobar"

    Sanki Ruby bize "sen bunu tek tırnak içinde verdin, ama ben bunu çift tırnak içine çevirdim" demek istiyor. Tek tırnaklı string değerin en büyük farkı Ruby tek tırnaklı ifade ile verilen string değer içinde enterpolasyon yapmaz. 

    >> '#{foo} bar'
    => "\#{foo} bar"

    Burada çok çok çok önemli ayrıntıyı gördünüz mü? Ruby bize cevap verirken çift tırnak içinde ifade edecek ya , orada da enterpolasyon olmasın diye # karakteri önüne ters slash koyarak escape ediyor , yani o #{ karakterlerinin eylem yapmasını engelliyor. 

    Eğer çift tırnaklarda her şeyler yapılıyor ama tek tırnaklarda yapılamıyorsa tek tırnaklar niye var? Çünkü bazen yazdıklarımızın eylem yapmasını istemeyiz, sadece yazdığımız gibi görünsün isteriz. 

    >> '\n'   # sadece ekrana \n yazdırmak istiyorum
    => "\\n"
    >> puts '\n'
    \n
    => nil
    >> puts "\\n"
    \n
    => nil

    Ters slash karakterini çift tırnaklı string içinde ifade edebilmek için hep önüne bir tane daha ters slash eklemek zorundayız. 

    >> dosya_konumu = 'C:\users\ujk'
    => "C:\\users\\ujk"

    Gördüğümüz gibi C:\users\ujk değerini çift tırnakla ifade edebilmek için  "C:\\users\\ujk" şeklinde yazmak gerekiyor, bu bazen kafa karıştırıcı olabilir. Bunu belirtmişken bir noktaya daha dikkatinizi çekmek isterim C:\users\ujk\ değerini tek tırnakla yazarken 'C:\users\ujk\\' şeklinde yazmak gerekir. Sebebi tek tırnak ifadelerdeki nadir escape karakter uygulamalarından biri olan \' notasyonu tek tırnak içinde tek tırnak karakteri ifade etmek için kullanılır, biz o noktaya ters slash yazmak istediğimiz için \\ yazmak zorundayız. 

    >> puts 'C:\users\ujk\\'
    C:\users\ujk\
    => nil
    >> puts 'C:\users\ujk\''
    C:\users\ujk'
    => nil

    Bunlar tek tırnak içinde kullanılan 2 adet escape karakter uygulama örnekleri.

    Sonuçta tek tırnak ya da çift tırnak şeklinde ifade etmek , eğer kurallarına uyarsak her yerde kullanabileceğimiz ifadeler. Tercih bize kalmış.



    Nesneler ve onlara mesaj göndermek

    Ruby'de her şey bir nesnedir, string'ler de nesnedir, nil değeri de nesnedir. Bunun anlamını Ruby dilini kullanmaya devam ettiğimiz sürece daha da pekiştireceğiz. 

    Basit bir kavramla yola çıkalım, "nesneler mesajlara cevap verir".  Mesela string bir değer ifade eden bir nesne length mesajına cevap verir ve sahip olduğu karakter sayısını cevap olarak döner.

    >> "foobar".length
    => 6
    >> "ığüşöç".length
    => 6 

    İyi, bari Türkçe karakterlere de doğru cevap veriyor. Bir nesneye mesaj göndermek için nesne arkasına nokta ve arkasından mesajımız yazılır. Bu mesajlar o nesnenin metodlarıdır, yani length metodu Ruby String sınıfının bir yerlerinde tanımlanmıştır ki string değer bu mesaja cevap veriyor. Mesela bir string değer empty? mesajına da cevap veriri.

    >> "foobar".empty?
    => false
    >> "".empty?
    => true

    Bu mesaj gönderme ve cevap alma jargonunu Ruby send metodunu kullanarak şöyle de gösterebiliriz.

    >> "foobar".send "length"
    => 6
    >> "foobar".send :length
    => 6

    Yani nesneye metod ismini gönderiyoruz ve o da bize cevap veriyor. 

    empty? metoduna geri dönelim Ruby'de metod ismi ? ile bitiyorsa teamül olarak o metod boolean bir değer ( true/false ) dönüyor demektir. Ruby'cilerin amacı cümle kurar gibi program yazmak olduğu için bu eğilim vardır. Bir örnek verelim.

    >> s = "foobar"
    => "foobar"
    ?> if s.empty?
    ?>   "string değer içi boş"
    ?> else
    ?>   "string değer boş değil"
    >> end
    => "string değer boş değil"

    Burada 

    if s.empty?
      "string değer içi boş"
    else
      "string değer boş değil"
    end

    Bloğu tam bir konuşma gibi görünüyor. Ruby dilinin geliştiricileri böyle notasyonları sever. 

    Bir'den fazla karşılaştırma yapmak için elsif (else + if) deyimi kullanırız. 

    ?> if s.nil?
    ?>   "değişken değeri nil"
    ?> elsif s.empty?
    ?>   "değer boş string"
    ?> elsif s.include?("foo")
    ?>   "değer içinde foo var"
    >> end
    => "değer içinde foo var"

    Boolean değerler && ("and"), || ("or") ve ! ("not") operatörleri yardımıyla birleştirilerek kullanılabilir. 

    >> x = "foo"
    => "foo"
    >> y = ""
    => ""
    >> puts "iki string de boş" if x.empty? && y.empty?
    => nil
    >> puts "iki string de boş" if x.empty? and y.empty?
    => nil
    >> puts "string'lerden biri boş" if x.empty? || y.empty?
    string'lerden biri boş
    => nil
    >> puts "string'lerden biri boş" if x.empty? or y.empty?
    string'lerden biri boş
    => nil
    >> puts "x boş değil" if !x.empty?
    x boş değil
    => nil
    >> puts "x boş değil" if not x.empty?
    x boş değil
    => nil

    x bize karşı boş değilmiş , bunu da öğrendik. 

    Ruby'de her şey bir nesnedir (bu lafı daha çok duyacaksınız), nil değeri bile bir nesnedir ve örneğin to_s metoduna (mesajına) cevap verir.

    >> nil.to_s
    => ""

    String'e dönüştürülmüş değeri bir boş string ama kendisi bir string nesnesi değil ve örneğin empty? metoduna cevap vermez. 

    >> nil.empty?
    'undefined method `empty?' for nil:NilClass (NoMethodError)

    Gelen hata mesajının bir kısmını kalabalık etmesin diye sildim. Ancak nil değerinin string'e dönüştürülmüş halinin boş string olup olmadığını sorgulayabiliriz. 

    >> nil.to_s.empty?
    => true

    Bu şekilde metodları arka arkaya sıralama işine zincirleme metod çağrısı adı verilir. Örneğimize bakarsak, soldan sağa doğru gidilir. Önce nil nesnesinde to_s metodu çağrılır ve çağrı sonucu bize bir string değer nesnesi döner. Sonra bu string nesnede empty? metodu çağrılır ve boş bir string olduğu için true değer döner. Çok uzamadığı müddetçe zincirleme metod çağrıları yapmayı severim ama JavaScript'çilerin yaptığı gibi de satırlar boyunca uzaması pek mantıklı gelmiyor.

    Bir nesnenin nil olup olamadığını anlamak için nil? metodunu o nesnede çağırırız. 

    >> "foo".nil?
    => false
    >> "".nil?
    => false
    >> nil.nil?
    => true

    nil? metodu nesnelerin en tepedeki atası Object sınıfında tanımlıdır ve her nesneye uygulanabilir. Sadece nil nesnesi bu metoda true cevap verir. Ee ne işe yarayacak? demeyin, bir değişkenin nil değerde olup olmadığını bilmek çok kullanılır.

    ?> def bar(v=nil)
    ?>   puts "argüman nil değerde" if v.nil?
    ?>   puts "argüman #{v} değerinde" unless v.nil?
    >> end
    => :bar

    >> bar
    argüman nil değerde
    => nil

    >> bar 2
    argüman 2 değerinde
    => nil

    Örnekte bar metoduna default değeri nil olan v parametresi tanımlanmış. Metod içinde v.nil? sorgusu yaparak metod çağrılırken argümanda bir değer verilmiş mi test edebiliriz.

    Birkaç örnektir puts "x boş değil" if not x.empty? gibi if yapısının değişik bir kullanımını görüyoruz. Satır sonunda bir if ya da unless ifadesi varsa o satırdaki kod sadece koşul olumlu olursa çalışır. Yani x boş string değilse ekrana "x boş değil" yazar. Alternaif olarak puts "x boş değil" unless x.empty? kullanılabilir. Bu unless ifadesi diğer birçok programlama dilinde kullanılmaz , yerine if koşulu değillenerek kullanılır, bazen güzel görünüyor , bazen de kafa karıştırıcı olabiliyor. 

    Bir değerin var olduğunu sorgulamak için Ruby de genelde !! (bang bang) operatörü kullanılır. 

    >> !!nil
    => false
    >> !!false
    => false
    >> !!12
    => true
    >> !!"foo"
    => true
    >> !!12.5
    => true

    >> a = nil
    >> !!a
    => false

    >> b = "bar"
    ?> if !!b
    ?>   "b değeri var"
    >> end
    => "b değeri var"




    Metod tanımlamaları

    Konsolda metod tanımlamaları da yapabiliriz. Son Rails uygulamamızda full_title adında bir yardımcı metod tanımlamıştık hatırlarsanız. Konsolda metod tanımlamak biraz yorucu ama burada amacımız öğrenmek , yoksa metodları Ruby kod dosyaları içinde tanımlayıp denemek daha kolay olacaktır. 

    Örnek olarak parametre olarak bir string değer alan ve bunun boş olup olmamasına göre mesaj yazan bir string_mesaj metodu tanımlayalım. 

    ?> def string_mesaj(str = "")
    ?>   if str.empty?
    ?>     "Bu bir boş string"
    ?>   else
    ?>     "String boş değil"
    ?>   end
    >> end
    => :string_mesaj

    >> puts string_mesaj("foobar")
    String boş değil
    => nil
    >> puts string_mesaj("")
    Bu bir boş string
    => nil
    >> puts string_mesaj
    Bu bir boş string
    => nil


    Son denemede görüldüğü gibi metodu hiç argüman vermeden de çağırabiliyoruz, çünkü metod tanımında parametre tanımlaması yaparken parantez içinde str = "" ifadesi kullandık, bu Ruby'ye "eğer argümanda bir değer verilmezse str parametresini boş string olarak kullan" demek oluyor. 

    Bu arada parametre ve argüman kavramlarını ben çok karıştırıyorum (diğer birçokları gibi), ama yine de size anlatmaya çalışayım. Metod tanımında görülen str değişkeni o metodun parametresidir, argüman ise metod çağrısı yapılırken bu parametre için çağrıda verilen değerdir. Yani def string_mesaj(str = "") deki  str parametredir ve puts string_mesaj("foobar") daki "foobar" argümandır. Örnek olarak "metod argümanı yanlış verilmiş" ya da "metod parametre değeri yanlış verilmiş" gibi.  

    Ruby örtülü metod dönüşleri kullanabilir. Eğer return kelimesi kullanarak metoddan dönecek değer üstüne basa basa belirtilmemişse kod çalışırken en son oluşan değer metoddan dönen değer olur. return kelimesi kullanarak metoddan dönmeyi tercih edersek metod tanımlaması şöyle yapılır.

    ?> def string_mesaj(str = "")
    ?>   return "Bu bir boş string" if str.empty?
    ?>   return "String boş değil"
    >> end



    Etiket yardımcı metoduna dönelim

    Daha önce pek de anlatmadan uyguladığımız full_title yardımcı metoduna bir daha dönelim.

    application_helper.rb

    ....
      def full_title(page_title = '')
      # opsiyonel argümanla metod tanımlaması
        base_title = "Yeni App"
        # değer ataması
        if page_title.empty?
        # boolean test  
          base_title
          # örtülü dönen değer
        else
          page_title + " | " + base_title
          # string birleştirme
        end
      end
    ....

    Öğrendiklerimiz kodu daha iyi anlamamıza yardımcı oluyor. 

    Bu metod tanımlamasını ApplicationHelper modülü içinde yaptık. Bu modülü include deyimi kullanarak diğer Ruby sınıflarında kullanabiliriz. Ruby kodlar yazarken modülleri kullanacağımızı ayrıca belirtmemiz gerekir, ancak Rails uygulamalarında yardımcı metod modülleri ilgili kısımda otomatikman kullanılabilir haldedir. Yani ApplicationHelper modülü içinde tanımlanan metodlar tüm uygulamada kullanılabilir, StaticPagesHelper modülü içinde tanımlanan metodlar StaticPages kontrolörü ve görsellerinde kullanılabilir, include ile ayrıca belirtmemize gerek yok. 



    Diğer veri tipleri

    Her ne kadar web uygulamaları yoğun olarak string değerler kullansa da , bazı durumlarda bu string'leri oluşturmak için başka veri tiplerinde değerlere de ihtiyaç duyulabilir. Bu kısımda Rails uygulamalarında gerekebilecek veri tiplerini öğreneceğiz. 



    Array ve Range

    Array bir değerler listesidir. Array tipini bilmek Rails'de çok kullanılan hash veri tiplerini anlamak ve Rails model ilişkilerini (örneğin has_many gibi) anlamak için faydalı olacaktır. Şimdiye kadar string değerleri anlamaya çalıştık. String değerlerden array değer elde etmek için split metodu kullanılır. 

    >> "foo bar    baz".split # kelimelerine ayır
    => ["foo", "bar", "baz"]

    Bu işlemin sonucu oluşan ["foo", "bar", "baz"] değeri bir array değerdir, virgüllerle birbirinden ayrılan değerler dizisi köşeli parantez içinde gösteriliyor. Default olarak split metodu string değeri boşlukları kullanarak parçalar ama istediğimiz ayırıcıyı metod argümanında verebiliriz.

    >> "fooxbarxbaz".split("x")
    => ["foo", "bar", "baz"]
    >> "foo x bar x baz".split(" x ")
    => ["foo", "bar", "baz"]

    Aşağı yukarı tüm programlama dillerinde olduğu gibi Ruby array değerlerinde de eleman index'leri (sıra numaraları) sıfırdan başlar. 

    >> a = [42, 8, 17]
    => [42, 8, 17]
    >> a[0]
    => 42  # array elemanlarına ulaşmak için
    # köşeli parantez içinde index girilir
    >> a[1]
    => 8
    >> a[2]
    => 17
    >> a[-1]  # index negatif olabilir
    => 17


    Köşeli parantez içinde index değeri girmek yanında Ruby array elemanlarının bazılarına ulaşmak için metodlara da sahiptir. 

    >> a.first
    => 42
    >> a.second
    => 8
    >> a.last
    => 17
    >> a.last == a[-1]
    => true

    Diğer dillerde array'in son elemanına ulaşmak için kodlar yazmak gerekebilirken Ruby'de sonuncu, sondan 3. gibi elemanlara kolayca ulaşılabilmesi çok üstün bir özellik.

    Son denemedeki == operatörü diğer birçok dilde olduğu gibi değer eşitlik karşılaştırma operatörü , iki tarafındaki değer eşitse true döner. Eşit olmama durumunu karşılaştırmak için de != operatörü kullanılır.

    >> x = a.length # array eleman sayısı
    => 3
    >> x == 3
    => true
    >> x == 1
    => false
    >> x != 1
    => true
    >> x >= 1
    => true
    >> x < 1
    => false

    length haricinde başka bazı metodları da array üzerinde kullanabiliriz.

    >> a.empty?       # array boş mu?
    => false
    >> a.include?(42) # içinde 42 değeri var mı?
    => true
    >> a.sort         # değerleri sırala
    => [8, 17, 42]
    >> a.reverse      # ters çevir
    => [17, 8, 42]
    >> a.shuffle      # değerleri karıştır
    => [8, 42, 17]
    >> a
    => [42, 8, 17]

    Dikkat ettiyseniz bu metodların hiç biri array içeriğini değiştirmedi , yapılan işlemlerin sonucu yeni array nesneleri üretildi ve a değişkeni değeri aynı kaldı. Array içeriğinin değişmesi için metodların sonunda ! işareti ile yazılan (bang method) versiyonları da vardır.

    >> a
    => [42, 8, 17]
    >> a.sort!
    => [8, 17, 42]
    >> a
    => [8, 17, 42]

    Bir array'e yeni eleman eklemek için push metodunu ya da eşdeğeri olan << operatörünü kullanabiliriz (shovel operator - kürek operatör).

    >> a = [42, 8, 17]  # orjinal değerlere dönelim
    => [42, 8, 17]
    >> a.push(6)        # array'e 6 değeri ekle
    => [42, 8, 17, 6]
    >> a << 7           # array'e 7 değeri ekle
    => [42, 8, 17, 6, 7]
    >> a << "foo" << "bar"  # zincirleme ekleme
    => [42, 8, 17, 6, 7, "foo", "bar"]

    Son deneme çok şey anlatıyor. Array'e yeni elemanları zincirleme arka arkaya ekleyebiliriz. Ayrıca diğer birçok dilden çok farklı bir özellik olarak Ruby array'leri değişik veri tiplerinde değerleri içerebilirler. Bu yüzden önce Ruby öğrenip sonradan başka programlama dilleri ile kod yazanlar en çok bu array özelliğini ararlar.

    String'leri split ile array'e dönüştürdüğümüz gibi aarray'leri de join metodu ile birleştirebiliriz. 

    >> a
    => [42, 8, 17, 6, 7, "foo", "bar"]
    >> a.join # hiç bir şeysiz birleştirme
    => "4281767foobar"
    >> a.join(", ")  # birşeyler koyarak birleştirme
    => "42, 8, 17, 6, 7, foo, bar"


    Array'lerin yakın akrabası Range'ler, kendilerini anlamak için to_a metodu yardımıyla array'e dönüştürmek çok yardımcı olur. 

    >> 0..9
    => 0..9
    >> 0..9.to_a  # pardon to_a metodunu 9 için çağırdık
    => undefined method `to_a' for 9:Integer (NoMethodError)`
    >> (0..9).to_a
    => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

    3. satırdaki o hata işlem önceliklerinden kaynaklanıyor, 0..9 range değerinin to_a metodunu çağıracağına 9 değerinin to_a metodunu çağırıyor. Bu yüzden range değeri parantez içine almak zorunda kaldık.

    Range değerler bir aralık belirler 0..9 sıfır ve dokuz dahil tüm sayılar demek, "a".."d" a ve d dahil tüm harfler demek.

    >> ("a".."d").to_a
    => ["a", "b", "c", "d"]

    Bunun Türkçe harfler olan karşılığı için biraz uğraşmak gerekiyor. Bir örnek yazmışlığım var. 

    Range değerler array içinden bir kısım elemanı ayıklamak için kullanılabilir. 

    >> a = %w[foo bar baz quux] # %w notasyonu ile string array üretmek
    => ["foo", "bar", "baz", "quux"]
    >> a[0..2] # 0, 1 ve 2 indexli elemanları getir
    => ["foo", "bar", "baz"]

    -1 index değerini range üst sınırında kullanmakla array uzunluğundan bağımsız bir noktadan sonraki tüm elemanları getirebiliriz. 

    >> a = (0..9).to_a
    => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    >> a[2..(a.length-1)]
    => [2, 3, 4, 5, 6, 7, 8, 9]
    >> a[2..-1]
    => [2, 3, 4, 5, 6, 7, 8, 9]

    Bir küçük ayrıntı daha range afade ederken 3 nokta kullanılırsa üst sınır değeri dışarıda bırakılır.

    >> (0...9).to_a
    => [0, 1, 2, 3, 4, 5, 6, 7, 8]




    Bloklar

    Ruby'de bloklar deyince akla başka bir kod bloğu kullanım tekniği gelir. Ruby metodlarından bazıları argüman olarak kod bloğu alabilirler. Bu özellik Ruby'nin en kuvvetli ve en karmaşık özelliklerinden biridir. 

    >> (1..5).each { |x| puts x * 2 }
    2
    4
    6
    8
    10
    => 1..5

    Bu kod 1..5 aralığında (range değer) each metodunu çağırır ve metoda { |x| puts x * 2 } kod bloğunu argümanda gönderir. Aslında ben argümanda gönderir diyorum da tam öyle değil, böyle bir kod bloğu geleceği metod tanımlamasında metod parametrelerinde yoktur, bu yüzden "argüman olarak kod bloğu göndermek" yerine "metoda kod bloğu göndermek" desek daha doğru olacak. 

    each metoduna bir kod bloğu verilirse gruptaki her eleman için süslü parantez içinde verilen kod bloğu çalıştırılır. |x| demek o turdaki eleman değeri x değişkeninde olacak demektir. Yani ilk önce x=1 için kod çalışır ve ekrana 2 yazar , sonra x=2 için kod çalışır ve ekrana 4 yazar vs. 1, 2, 3, 4, 5 sayıları için ekrana 2, 4, 6, 8, 10 yazılır.

    Daha uzun kod blokları olacaksa süslü parantez yerine do....end bloğu olarak da verilebilir. 

    ?> (1..5).each do |sayı|
      ?>   puts 2 * sayı
      ?>   puts "--"
      >> end
      2
      --
      4
      --
      6
      --
      8
      --
      10
      --

    gibi. Aynı süslü parantez gibi her turdaki değer |  | arasındaki değişkene verilir. Programcılık geçmişiniz fazla değilse kod bloklarını anlamanın kestirme bir yolu yok, kullanımlarını gördükçe daha iyi kavramaya başlarsınız. 

    Aşağıda birkaç örnek kullanım daha var.

    >> 3.times { puts "HEY" } # 3 kere tekrarlar
    HEY
    HEY
    HEY
    => 3
    >> 3.times { |x| puts x } # değişken kullanarak
    0
    1
    2
    => 3
    >> (1..5).map { |i| i**2 } # üssü alınıyor (**)
    => [1, 4, 9, 16, 25]
    >> %w[a b c].map { |char| char.upcase }
    => ["A", "B", "C"]
    >> %w[A B C].map { |char| char.downcase }
    => ["a", "b", "c"]

    Burada gördüğümüz map metodu array veya range elemanlarının her biri için bloğu işletip sonucu yine bir array olarak döner. Değerleri dönüştürmekte ve filtrelemekte çok kullanılır, ileride örneklerini görmeye devam edeceğiz, yaygın kullanılan bir metoddur. 

    Son iki örnekte eleman değerlerine sadece kendilerine ait bir tane metod uygulanıyor, bunun bir kısaltılmış yazım tekniği var. 

    >> %w[a b c].map(&:upcase)
    => ["A", "B", "C"]
    >> %w[A B C].map(&:downcase)
    => ["a", "b", "c"] 

    Burada map metoduna &:upcase değeri parantez içinde veriliyor ama metodun bir parametresine falan gitmiyor, başında & karakteri olması sayesinde metod bunu bir Proc nesnesi (bir çeşit kod bloğu) olarak algılıyor ve ismi sembol olarak verilen metodu her eleman için uyguluyor. Bu tekniği Rails geliştiricileri kullanmaya başladı ve çok işe yaradığı görülünce şimdi Ruby core kütüphanesinde de mevcut hale getirildi. 

    Blok kullanımını testleri incelerken görmüştük aslında.

      test "should get help" do
        get static_pages_help_url
        assert_response :success
        assert_select "title", "Yardım | Yeni App"
      end

    Buradaki do..end bloğunun artık test metoduna gönderilen bir blok olduğunu çok daha iyi anlayabiliyoruz. 

    Range meselesini kapatmadan önce bir zincirleme metod örneği verelim.

    >> (1..36).to_a.shuffle[0..5]
    => [14, 10, 29, 20, 35, 12]
    >> (1..36).to_a.shuffle[0..5]
    => [15, 26, 21, 22, 10, 30]
    >> (1..36).to_a.shuffle[0..5]
    => [30, 36, 31, 22, 23, 15]

    Alın size loto oynayın, ama onun da çıkma olasılığını da hesaplayıp boşuna uğraşmayın. 

    >> (1..36).to_a.combination(6).to_a.length
    => 1947792




    Hash ve semboller

    Hash'ler tamsayı index zorunluluğu olmayan array'ler olarak düşünülebilir. Hash index'leri (ya da hash için kullanılan tabir olan key'leri) tamsayı yada başka herhangi bir nesne olabilir. Örneğin string değerleri key olarak kullanabiliriz. 

    >> kullanıcı = {}  # {} boş bir hash değer
    => {}
    >> kullanıcı["adı"] = "Ümit" # key = "adı" , value = "Ümit"
    => "Ümit"
    >> kullanıcı["soyadı"] = "Kayacık" # key = "soyadı" , value = "Kayacık"
    => "Kayacık"
    >> kullanıcı["adı"]   # array gibi indis köşeli parantez içinde
    => "Ümit"
    >> kullanıcı          # bir hash değerin ifade ediliş şekli
    => {"adı"=>"Ümit", "soyadı"=>"Kayacık"}

    Hash değerler süslü parantez içinde virgülle ayrılmış key-value (indis-değer) çiftleri şeklinde ifade edilir. İçinde key-value çifti olmayan {} şeklinde boş bir hash değer ifade eder. Burada süslü parantez kullanımının az önce gördüğümüz bloklarla bir alakası yoktur. Her ne kadar array'e benzer gibi görünse de hash değerlerin en büyük farkı key-value çiftleri değil hash içinde elemanların sırasının sabit kalma garantisinin olmamasıdır. Eğer elemanların sırası önemliyse array kullanmalıyız. 

    Bir hash değeri adım adım köşeli parantezli ifadelerle oluşturmak yerine en son örnekteki gösterim şekliyle hash değer olarak bir kerede ifade edebiliriz. 

    >> kullanıcı = { "adı" => "Ümit", "soyadı" => "Kayacık" }
    => {"adı"=>"Ümit", "soyadı"=>"Kayacık"}

    Burada biz ifade ederken değerler, operatörler vs aralarında boşluk kullandık, ama Ruby gösterimde boşluk kullanmıyor, bu tamamen görsel bir tercih a=5 de yazabiliriz a = 5 de, (2+3)*8 de yazabiliriz (2 + 3) * 8 de. 

    Şimdiye kadar hash ifadesinde string key değeri kullandık, ama Rails^de key değeri olarak sembol çok kullanılır. Semboller string'e benzer yazı ifadeleridir ama tırnak içine konmak yerine başına iki nokta üst üste konarak ifade edilir. Örneğin :adı bir sembol değerdir. Sembolleri fazla kabiliyetleri olmayan string'ler olarak düşünebiliriz. 

    >> "adı".split("")
    => ["a", "d", "ı"]
    >> :adı.split("")
    => undefined method `split' for :adı:Symbol (NoMethodError)`
    >> "foobar".reverse
    => "raboof"
    >> :foobar.reverse
    => undefined method `reverse' for :foobar:Symbol (NoMethodError)`

    Aralarındaki fark aynı sınıftan olmamaları

    >> "adı".class
    => String
    >> :adı.class
    => Symbol

    String yerine sembol kullanmanın amacı hafızada daha az yer kaplamasıdır. Örneğin her ürettiğiniz string , aynı değer sahip olsa bile farklı nesne olarak üretilir.

    >> "adı".object_id
    => 80540
    >> "adı".object_id
    => 81240

    Aynı string değeri yazdığımız halde Ruby ikisine ayrı nesneler üretiyor. Aynı şeyi sembol ile denersek.

    >> :adı.object_id
    => 9272988
    >> :adı.object_id
    => 9272988

    Gördüğümüz gibi aynı değerdeki sembol nesneleri Ruby için aynı nesnedir, yani hafızada tek bir yer işgal eder. Bu sebeple Ruby programcıları sembolleri kullanmayı tercih ederler. 

    Semboller başka programlama dillerinin çok azında bulunur ve bu yüzden başlangıçta sıkıcı gibi gelebilir. Rails, sembolleri çok yoğun kullanır. Bu yüzden kısa zamanda uyum sağlayacaksınız. String'lerden farklı olarak sembol değer içinde her karakter kullanılamaz

    >> :foo-bar
    => undefined local variable or method `bar' (NameError)`

    >> :2foo
    => unexpected integer literal,(SyntaxError)

    Bir harf ile başladığı ve normal kelime karakterleri kullanıldığı sürece sıkıntı yok. 

    Yukarıdaki örnekte kullandığımız kullanıcı hash ifadesinin sembol key değerleri ile yazılmış hali.

    >> kullanıcı = { :adı => "Ümit", :soyadı => "Kayacık" }
    => {:adı=>"Ümit", :soyadı=>"Kayacık"}
    >> kullanıcı[:adı]   # key = :adı elemanına erişim
    => "Ümit"
    >> kullanıcı[:şifre] # olamayan key değere erişim
    => nil


    Hash değer tanımlamalarında sembol key kullanımı yaygın olduğu için, Ruby daha rahat ve kolay anlaşılabilir bir ifade yapısı destekler.

    >> h1 = { :adı => "Ümit", :soyadı => "Kayacık" }
    => {:adı=>"Ümit", :soyadı=>"Kayacık"}
    >> h2 = { adı: "Ümit", soyadı: "Kayacık" }
    => {:adı=>"Ümit", :soyadı=>"Kayacık"}
    >> h1 == h2
    => true

    Buradaki { adı: "Ümit", soyadı: "Kayacık" } yapısını Ruby programcıları çok sever, bu yüzden bu şekilde hash ifadeleri ile daha defalarca karşılaşacağız. Bu yapı aslında örneğin JavaScript gibi dillerdeki hash ifadelere de benzer. Ancak bu şekilde sembol kullanımı sadece hash key'ler için geçerlidir, hash dışında bir anlam ifade etmez.

    >> a = :adı
    => :adı
    ?> b = adı:  # ?

    Hash değerleri (values) hemen her şey olabilir hatta başka hash değer de olabilir (iç içe hash). 

    >> params = {}
    => {}
    >> params[:user] = { name: "Ümit Kayacık", email: "ujk@example.com" }
    => {:name=>"Ümit Kayacık", :email=>"ujk@example.com"}
    >> params
    => {:user=>{:name=>"Ümit Kayacık", :email=>"ujk@example.com"}}
    >> params[:user][:name]
    => "Ümit Kayacık"


    Bu params değişken adına alışın, çünkü Rails öğrenirken çok karşılaşacağız, Rails'çilerin tercih ettiği bir değişken adı. Bu şekilde "iç içe hash" ya da "hash'in hash'i" yapılar Rails içinde çok kullanılır. 

    Array ve Range gibi Hash de each metoduna cevap verir. Bir örnek verelim.

    >> mesaj = { success: "Çalıştı!", danger: "Çakıldı" }
    => {:success=>"Çalıştı!", :danger=>"Çakıldı"}
    ?> mesaj.each do |key, value|
    ?>   puts "#{key.inspect} key'i değeri #{value.inspect}"
    >> end

    :success key'i değeri "Çalıştı!"
    :danger key'i değeri "Çakıldı"

    Dikkat edersek bu sefer key, value şeklinde eleman değeri çift olarak bloğa geliyor. Burada bir ayrıntı daha var değerleri yazdırırken key ve value değişkenlerinin inspect metodlarını kullanıyoruz. Böylece değerlerin Ruby programlama dilinde kullanılan değer ifadesi şeklinde yazılması sağlanıyor. inspect kullanmasak çıktımız

    ?> mesaj.each do |key, value|
    ?>   puts "#{key} key'i değeri #{value}"
    >> end
    success key'i değeri Çalıştı!
    danger key'i değeri Çakıldı

    olurdu ki sanırım yukarıdaki gösterim daha anlamlı. Normal şartlarda puts metodu bir değeri yazdırırken o değerin to_s metodunu çağırır ve dönen cevabı yazar, bu da kısa bilgi olsun. 

    inspect kullanımı için birkaç örnek daha yapalım.

    >> puts (1..5).to_a
    1
    2
    3
    4
    5
    >> puts (1..5).to_a.inspect
    [1, 2, 3, 4, 5]
    >> puts :name, :name.inspect
    name
    :name
    >> puts "Çalıştı", "Çalıştı".inspect
    Çalıştı
    "Çalıştı"

    Aslında Ruby'nin değerleri yazarken değerin inspect metoduna cevabını yazan hazır bir metodu da var, sadece p harfi. 

    >> p (1..5).to_a
    [1, 2, 3, 4, 5]
    => [1, 2, 3, 4, 5]
    >> p :name
    :name
    => :name





    CSS olayına geri dönelim

    Örnek uygulamamızda application.html.erb uygulama yerleşim şablonu dosyamızda uygulama geneli kullanılacak CSS stillerini belirleyen bir satır vardı. 

        <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

    Şimdi biraz daha anlıyor gibiyiz. Rails yardımcı metodu stylesheet_link_tag çağrılıyor. Fakat hala gizemli yerler var. İlk soru, parantez neden kullanılmıyor? Cevap, Ruby metod çağrılarında parantez kullanmak zorunlu değildir.

        <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
        <%= stylesheet_link_tag("application", "data-turbo-track": "reload") %>

    Bu iki satır aynı işi yapar. 

    İkinci soru, metoda verilen ikinci argüman hash değer ama süslü parantezler nerede? Cevap, bir metod çağrısında hash değer son argümansa süslü paranteze gerek yoktur. 

        <%= stylesheet_link_tag("application", "data-turbo-track": "reload") %>
        <%= stylesheet_link_tag("application", { "data-turbo-track": "reload" } ) %>

    Bu iki satır da aynı şeyi ifade ediyor.

    Neyse zamanla alışırız, Railsçi olmak istiyorsak geliştiricilerinin düşünce yapısını benimsememiz gerekir. Rails metodları buna benzer birçok key-value çifti argüman alabilir. Örneğin önceki Rails versiyonlarında bu satır.

        <%= stylesheet_link_tag "application", media: "all",
            "data-turbolinks-track": "reload" %>

    şeklindeymiş. Böyle çok argümanlı metod çağrıları yaparken virgülden sonra alt satıra geçersek Ruby satırın hala devam etmekte olduğunu anlayacaktır. Şimdi aslımıza dönelim

    <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>

    Bu kod satırı stylesheet_link_tag metodunu iki argümanla çağırıyor. Stil dosyasının path değerini belirten bir string değer (application.css dosyasını işaret ediyor) ve ikinci verilen key-value çifti data-turbo-track özniteliği değerini belirliyor. Server'ı çalıştırıp tarayıcıda açtığımız her hangi bir sayfanı kaynağına bakarsak 

    <link rel="stylesheet" href="/assets/application-.....css" data-turbo-track="reload" />

    data-turbo-track="reload" öznitelik değeri sayfada elemanlardan biri değişince otomatik olarak sayfayı tekrar yükleyerek stillerin değişime uygun kalmasını sağlıyormuş.

    Son olarak Ruby kodumuz <%= ve %> arasında olduğu için, çalıştırılıp dönen değeri HTML kodunda oraya yazılıyor. Demek ki metoddan, az önce sayfa kaynak kodunda gördüğümüz satır dönmüş. 




    Ruby sınıf yapıları

    Ruby'de her şey bir nesnedir demiştik. Az sonra kendi nesnelerimizi tanımlayacağız. Birçok nesne yönelimli programlama dili gibi Ruby de nesneleri ve metodlarını tanımlamak için sınıf yapısını kullanır. Bu sınıfların oluşumları olarak nesneler üretilir. Eğer nesne yönelimli programlama olayına yabancıysanız bu biraz karmaşık gelebilir, örneklerle daha iyi anlarız. 


    Üretici metodlar 

    Şimdiye kadar birçok Ruby nesnesi oluşturduk aslında ama bunu açıkça yapmadık. Örneğin çift tırnaklar içine yazı yazarak bir string nesnesi oluşturduk, buna değer ifadesi ile nesne üretmek deniyor. 

    >> s = "foobar"
    => "foobar"
    >> s.class
    => String

    Gördüğümüz kodda string nesnesi olan s nesnesi class metoduna cevap veriyor ve üretildiği sınıfın adını dönüyor. 

    Değer ifadesi ile üretmek yerine bir String nesnesini standart üretici metodunu kullanarak da üretebiliriz. Bunu yapmak için String sınıfının new metodunu kullanırız. 

    >> s = String.new 'foobar'
    => "foobar"
    >> s.class
    => String
    >> s == "foobar"
    => true

    Metoda argüman verirken parantez içine koymadım , nasılsa Ruby'de parantez kullanmak zorunlu değil. Burada String nesneyi üretici metodunu kullanarak üretiyoruz, ve bu değer ifadesi ile nesne üretmekle aynı işi yapıyor. Ancak bu şekilde ne yaptığımızı çok daha açıkça görüyoruz. 

    Array sınıfı oluşum nesnelerini de benzer teknikle üretebiliriz. 

    >> a = [1, 2, 3]
    => [1, 2, 3]
    >> b = Array.new [1, 2, 3]
    => [1, 2, 3]
    >> a == b
    => true

    Hash sınıfı oluşum nesnesi üretmek için üretici metod kullanma şekli farklıdır. String ya da Array sınıfında new metodu nesne içindeki değeri argüman olarak alırken, Hash sınıfı new metodu argümanda bir hash değer almaz, elemanlar için default değeri alır. 

    >> h = Hash.new # default değer verilmemiş
    => {}
    >> h[:foo]
    => nil      # olmayan key nil değer döner
    >> h = Hash.new 0 # default değer sıfır
    => {}
    >> h[:foo]
    => 0        # olmayan key sıfır döner

    Eğer bir metod direk sınıfta çağrılıyorsa (String.new gibi) ona sınıf metodu (class method) denir. Bir sınıfın new metodu çağrılınca o sınıftan üretilmiş bir nesne döner, buna sınıfın oluşumu (instance of class) denir. Bir oluşum nesnesinde çağrılan metod, örneğin 

    >> s.length
    => 6

    Burada length metodu s nesnesinde çağrılıyor , bu şekilde nesnede çağrılan metoda oluşum metodu (instance method) denir. Sınıf metodu , sınıf oluşumu, oluşum metodu kavramları çok defa karşımıza çıkacak. 



    Sınıf kalıtımı

    Sınıfları öğrenirken superclass metodu ile sınıf hiyerarşisini görmek faydalı olacaktır. 

    >> s = "foobar"
    => "foobar"
    >> s.class
    => String
    >> s.class.superclass
    => Object
    >> s.class.superclass.superclass
    => BasicObject
    >> s.class.superclass.superclass.superclass
    => nil

    Hiyerarşiyi görsel ifade etmek gerekirse.


    Ruby'de hemen her sınıfın en büyük atası BasicObject sınıfıdır. Sınıf olayını daha iyi anlamak için kendi sınıfımızı oluşturalım bakalım.

    ?> class Word
    ?>   def palindrome?(string)
    ?>     string == string.reverse
    ?>   end
    >> end
    => :palindrome?

    Şimdi Word sınıfımızın bir oluşum nesnesini üretip onun palindrome? metodunu deneyelim.

    >> w = Word.new
    => #<Word:0x00007f3510c7ec68>
    >> w.palindrome? "foobar"
    => false
    >> w.palindrome? "level"
    => true

    Sadece bir metod tanımlamak için bir sınıf tanımlamak saçma kalabilir ama amacımız yapıyı öğrenmek. Bir şeyler ilgimizi çekmiş olmalı, biz öyle bir şey tanımlamadığımız halde sınıfımız new metoduna sahip. Ruby bir class tanımlaması yapılınca otomatik olarak onun new metdounu tanır, ha biz kendi üretim metodumuzu yazacaksak o başka, onu da göreceğiz. 

    Bir sınıf tanımı class SınıfAdı .... end bloğu şeklinde yazılıyor. Sınıf adları Ruby için sabit değerdir ve Ruby'de sabitler büyük harfle başlamalıdır. Bir'den fazla kelimeden oluşan sınıf isimlerinde her kelime ilk harfi büyük olacak şekilde bitişik yazılması ise Ruby tavsiyesidir. 

    Sınıf tanımı içinde direk def metod_adı .... end bloğu olarak tanımlanan metodlar ise o sınıfın oluşum nesnesi metodlarıdır, sınıf metodları farklı tanımlanır. Bu yüzden palindrome? metodunu kullanmamız için Word sınıfından w oluşum nesnesini ürettik ve w nesnesinde palindrome? metodunu çağırdık. 

    Şimdi olayı biraz geliştirelim Word sınıfımız string değer işlemek amaçlı üretildi, o halde String sınıfı özelliklerine sahip olsa daha iyi değil mi? İşte bunu sağlamak için Word sınıfını String sınıfından kalıtımla üretiriz ki String sınıfı özelliklerine sahip olsun. Eski Word sınıfı tanımlamasından kurtulmak için Rails konsolundan çıkıp tekrar başlatmak iyi olacaktır. 

    ?> class Word < String  # String sınıfından kalıtımla üretme
    ?>   def palindrome?
    ?>     self == self.reverse # self string'in kendisi oluyor
    ?>   end
    >> end
    => :palindrome?

    Ufak ama önemli değişiklikler var. class Word < String ile sınıfımızın String sınıfından kalıtım yoluyla üretileceğini yani onun tüm özelliklerine sahip olacağını belirtiyoruz (üretici metodu dahil). Artık elimizdeki nesnemiz de zaten String nesnesi gibi bir değere sahip olduğuna göre bu değeri direk kullanabiliriz. Bu yüzden palindrome? metodumuzu nesnemizin değerini kullanacak şekilde değiştirdik. Burada self kelimesi dikkat edilmesi gereken bir ifade. self kelimesi Ruby dilinde bulunulan ortamı ifade eder , burada oluşum metodu içinde kullanıldığı için self değeri oluşum nesnesini ifade eder, ama mesela metod dışında ama class içinde olsaydı o zaman sınıfı ifade edecekti. 

    Deneme yapalım.

    >> s = Word.new "level"
    => "level"
    >> s.palindrome?
    => true
    >> s.length
    => 5

    Word sınıfı String sınıfından üretildiği için String nesneleri için geçerli olan metodlar Word sınıfı nesnelerinde de geçerlidir. Sınıfımızın hiyerarşisine de bir bakalım.

    >> s.class
    => Word
    >> s.class.superclass
    => String
    >> s.class.superclass.superclass
    => Object
    >> s.class.superclass.superclass.superclass
    => BasicObject 

    Yine gittik Adem babamıza dayandık. 

    Bir küçük ayrıntıyı atlamayalım. Oluşum metodu içinde nesnenin başka bir oluşum metodunu çağırırken self kullanmamıza gerek yok aslında, Ruby nesnesi belirtilmemiş metodu direk içinde bulunulan nesneye uygular.

    self == self.reverse
    # yerine
    self == reverse
    # yazmak yeterlidir

    Konsolda da bir deneme yapalım

    >> self
    => main
    >> abbzzrt
    => undefined local variable or method
       `abbzzrt' for main:Object (NameError)

    Konsolda kodlarımızı koşturduğumuz yer de aslında main adında bir nesneymiş ve bir metod çağırınca main nesnesinde o metod tanımlı mı? diye bakıyor. 



    Mevcut sınıfın davranışına müdahale

    Kalıtımın çok kuvvetli bir teknik olmasının yanında şu meşhur palindrome? metodumuz tüm String nesneler için geçerli olsa daha güzel olmaz mı? 

    >> "level".palindrome?
    => undefined method `palindrome?'
       for "level":String (NoMethodError)

    Müthiş bir kabiliyet olarak Ruby biz ölümlülerin String sınıfını tekrar açıp içine müdahale etmemize imkan tanıyor. 

    ?> class String
    ?>   # değerin tersi de aynı mı sorgusu
    ?>   def palindrome?
    ?>     self == reverse
    ?>   end
    >> end
    => :palindrome?
    >> "deified".palindrome?
    => true

    Gördük ki,  deified (tanrılaştırılmış) kelimesinin tersi de aynı. 

    Sınıflara müdahale edebilmek çok güzel bir teknik ama bundan önemlisi bunun sorumluluğunu almak. Gerçekten iyi bir sebebiniz yoksa mevcut sınıfların davranışlarına müdahale etmekten kaçınınız. Rails kendince çok haklı sebeplerle bazı davranışlara ilaveler yapmış Örneğin String sınıfına blank? adında bir ilave yaparak bir web uygulamasında kullanıcıların boş string haricinde sadece boşluklardan oluşan bir değer girmesini de engellemeyi amaçlamış. Rails konsolunda olduğumuza göre bu metod da konsola yüklenmiş olmalıdır. Deneyelim

    >> "".blank?
    => true
    >> "     ".empty?
    => false
    >> "     ".blank?
    => true
    >> nil.blank?
    => true 

    Son satıra bakılırsa NilClass sınıfına da blank? metodu tanımlı. Rails aslında bunu String sınıfının atası olan Object sınıfına metodu ekleyerek yapmış. İleride Rails'in Ruby sınıflarına yaptığı ilavelerle karşılaşmaya devam edeceğiz. 




    Bir kontrolör sınıfı

    Sınıf yapısını son Rails uygulaması örneğimizde StaticPages kontrolöründe görmüştük.

    app/controllers/static_pages_controller.rb 

    class StaticPagesController < ApplicationController
      def home
      end

      def help
      end

      def about
      end
    end

    Artık bu kodun ne anlama geldiği hakkında biraz fikrimiz oluştu. StaticPagesController sınıfı ApplicationController sınıfından kalıtımla üretilmiş, yani ona ait bir sürü özelliğe sahip. Rails konsolumuz bulunduğumuz uygulama konseptinde açıldığına göre bu kontrolör sınıfını konsolda kullanabiliriz.

    >> controller = StaticPagesController.new
    => #<StaticPagesController:0x0000000006cf70>
    >> controller.class
    => StaticPagesController
    >> controller.class.superclass
    => ApplicationController
    >> controller.class.superclass.superclass
    => ActionController::Base
    >> controller.class.superclass.superclass.superclass
    => ActionController::Metal
    >> controller.class.superclass.superclass.superclass.superclass
    => AbstractController::Base
    >> controller.class.superclass.superclass.superclass.superclass.superclass
    => Object

    Kontrolör eylemlerini de konsolda çağırabiliriz. 

    >> controller.home
    => nil

    Hata vermedi ama bizim tarayıcı sorunca bir HTML sayfa dönüyordu. Rails nesneleri Ruby nesneleri değil , o yüzden Ruby konsoldaki davranışları ve bir server üzerinden tarayıcıların isteklerine karşı davranışları arasında bir fark var. Kırk fırın ekmek yersek belki nasıl olduğunu anlarız, şimdilik geçelim.



    User adında bir sınıf

    Bir tane User sınıfı yapalım. Hatrılarsanız bir uygulamamızda User adında bir modelimiz vardı. Ona benzer bir şey. 

    Şimdiye kadar hep konsolda işimizi yaptık ama bu sefer bir Ruby kod dosyası içinde sınıfımızı tanımlayalım ve sonra da konsolda kullanalım. Uygulama klasöründe olduğumuza göre , oraya example_user.rb adında bir dosya ekleyelim. İçinde şu olsun

    example_user.rb

    class User
      attr_accessor :name, :email
      def initialize(attributes = {})
        @name = attributes[:name]
        @email = attributes[:email]
      end
      def formatted_email
        "#{@name} <#{@email}>"
      end
    end

    Biraz fazla şey var burada baştan başlayalım

      attr_accessor :name, :email

    Sınıfımızdan oluşturulacak nesnelere iki öznitelik kazandırıyor , name ve email öznitelikleri. Bunun uzun yolunu anlatmak gerekirse bu öznitelikleri oluşum nesnesine ekleyebilmek için bunların okuma ve yazma metodlarını tanımlamak gerekiyordu , yani bu satır olmasa

      def name
        @name
      end
      def name=(val)
        @name = val
      end
      def email
        @email
      end
      def email=(val)
        @email = val
      end

    bu satırları eklemek zorundaydık. Bkz bu sayfa

    İlk metodumuzun adı initialize , bu metod adı Ruby sınıfları için özeldir ve sınıfın new metodu çağrılınca Ruby eğer varsa bu metodu çağırarak oluşum nesnesini üretir. 

      def initialize(attributes = {})
        @name = attributes[:name]
        @email = attributes[:email]
      end

    Nesnemiz oluşturulurken hash değer olarak verilen argüman içindeki :name ve :email key'lerine karşı verilmiş değerlerle oluşum değişkenleri @name ve @email başlatılıyor. attributes parametresinin default değeri boş bir hash olarak verildiğine göre yeni bir User nesnesini :name ve :email değerleri vermeden boş olarak oluşturabiliriz. 

    İkinci olarak sınıfımızda formatted_email metodumuz bize isim ve email değerlerinden oluşturulmuş bir bir yazı derliyor. 

      def formatted_email
        "#{@name} <#{@email}>"
      end

    @name ve @email oluşum değişkenleri (@ karakteri ile başlayan değişkenler oluşum değişkenidir) ve bu yüzden nesnenin tüm metodlarında kendilerine erişilebiliyor. 

    Konsolu açalım ve bu sınıfımızı kullanalım.

    >> require "./example_user"
    => true     # dosya yükleme başarılı
    >> example = User.new
    => #<User:0x00007f8bbf8d9558 @email=nil, @name=nil>
    >> example.name
    => nil
    >> example.name = "Example User"
    => "Example User"
    >> example.email = "user@example.com"
    => "user@example.com"
    >> example.formatted_email
    => "Example User <user@example.com>"

    Burada require satırında .rb uzantıyı vermeye gerek yok Ruby ilk önce .rb uzantılı dosyayı arar ve bulursa ekler. Ancak tam erişim için "./example_user" şeklinde aynı klasörde olduğunu belirtmeliyiz , yoksa Ruby gider dosyayı kendine tanımlı kütüphanelerin klasörlerinde arar. Sonrasındaki kod boş bir User nesnesi üretir. Daha sonra example nesnesinin name ve email özelliklerine değerler veriyoruz (bu işi attr_accessor satırı sayesinde yapabiliyoruz). 

    >> example.name = "Example User"

    Şimdi değerleri girerek nesne üretelim.

    >> user = User.new name: "Ümit Kayacık", email: "ujk@example.com"
    => #<User:0x00007fdef667d018 @email="ujk@example.com", @name="Ümit Kayacık">
    >> user.formatted_email
    => "Ümit Kayacık <ujk@example.com>"

    Hash değeri verirken süslü parantez kullanmamıza gerek yok. 



    Bu bölümde neler öğrendik?

    • Ruby string değerleri işlemek için birçok metoda sahip.
    • Ruby'de her şey bir nesnedir.
    • Metod tanımlaması def kelimesiyle yapılır. 
    • Sınıf tanımlaması class kelimesiyle yapılır.
    • Rails görselleri statik HTML veya ERB (gömülü Ruby) kodlar olabilir.
    • Ruby'de mevcut veri yapıları array, range ve hash
    • Enumerable verilerde iterasyon yapmak için blok kodlar kullanılır.
    • Semboller de string'ler gibi yazı ifade eder ama kabiliyetleri azdır
    • Ruby nesnelerde kalıtımı destekler.
    • Mevcut Ruby sınıfları açıp düzenleyebiliriz.
    • “deified” kelimesi bir palindrome


    Yeni bir bölüme geçeceğiz, bu yüzden sayfayı bitirelim. Sonraki bölümde görsellerimizi stilllerle güzelleştireceğiz. Şimdilik kalın sağlıcakla..








    Hiç yorum yok:

    Yorum Gönder