9 Mart 2025 Pazar

Ruby Temelleri 8

https://ujk-ujk.blogspot.com/2025/03/ruby-temelleri-8.html
İçindekiler +

     Selam Ruby'nin temel tekniklerine kaldığımız yerden devam ediyoruz.



    Enumerator Sınıfı

    Bir Enımerator elemanlarına iterasyon uygulanabilen bir nesnedir. Bir koşul karşılanana kadar döngüye girmek yerine, nesne gerektiği gibi eleman değerlerini sıralar (enumerates). 


    -- Kendi Enumeratörümüz

    Fibonachi sayıları için bir enumeratör yapalım.

    fibonacci = Enumerator.new do |yielder|
      a = b = 1
      loop do
        yielder << a
        a, b = b, a + b
      end
    end

     Şimdi fibonacci nesnesinde her Enumerable metodunu çalıştırabiliriz. 

    fibonacci.take 10
    #=> [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

    Sınırsız bir döngü oluşturduk, umarım yanlışlıkla tüm elemanları istemeye kalkmayız. 

    Daha basit bir örnek verelim.

    class Foo
      include Enumerable
      def each
        yield 1
        yield 1, 2
        yield
      end
    end
    Foo.new.each_entry{ |eleman| p eleman }
    # 1
    # [1, 2]
    # nil
    Foo.new.each{ |eleman| p eleman }
    # 1
    # 1
    # nil




    -- Mevcut metodlar

    Örneğin each gibi bir iterasyon metodu blok verilmeden çağrıldığında bir Enumerator nesnesi dönmelidir. Bunu enum_for metodu ile yapabiliriz.

    def each
      return enum_for :each unless block_given?

      yield :x
      yield :y
      yield :z
    end

    Bu sayede programlayıcı enumerable metodlarını zincirleme kullanabilir.

    p ("a".."d").each #=> #<Enumerator: "a".."d":each>
    p ("a".."d").each.drop(2) #=> ["c", "d"]
    p ("a".."d").each.drop(2).map(&:upcase) #=> ["C", "D"]
    p ("a".."d").each.drop(2).map(&:upcase).first #=> "C"



    -- Rewind işlemi

    Enumerator'u tekrar başlatmak için rewind metodu kullanılır.

    = Enumerator.new do |yielder|
      x = 0
      loop do
        yielder << x
        x += 1
      end
    end

    p .next  #=> 0

    p .next  #=> 1

    p .next  #=> 2

    .rewind

    p .next  #=> 0





    C Genişlemeleri

    C genişlemeleri iki ana parçadan oluşur.

    • C kodunun kendisi
    • Genişleme konfigürasyon dosyası

    İlk genişlememizi yazmak için aşağıdaki kodu extconf.rb adında bir dosya içine yazalım.

    extconf.rb

    require 'mkmf'

    create_makefile('hello_c')

    Dikkat edilmesi gereken noktalar var.

    İlki hello_c ismi derlenmiş genişlememizin alacağı isim. require ile birlikte kullandığımız şey olacaktır.

    İkincisi extconf.rb dosyası ismi herhangi bir şey olabilir, ama native gem tasarlarken tercih edilen isimdir. 

    Şimdi C kodumuzu hello.c isimli dosyanın içine yazalım. Sonrada ruby extconf.rb && make komutunu terminalde çalıştıralım.

    hello.c

    #include <stdio.h>
    #include "ruby.h"

    VALUE world(VALUE self) {
      printf("Hello World!\n");
      return Qnil;
    }

    // Bu modül için başlangıç metodu
    void Init_hello_c() {
      VALUE HelloC = rb_define_module("HelloC");
      rb_define_singleton_method(HelloC, "world", world, 0);
    }

    Kod hakkında çaıklamalar:

    Init_hello_c başlangıç metodu adındaki hello_c kelimesi extconf.rb içinde create_makefile metoduna verdiğimiz argüman değerle aynı olmalıdır. Aksi takdirde Ruby genişleme kodunuzu bulamayacaktır. 

    rb_define_module metodu çağrısı HelloC adında C fonksiyonlarımızı altında toplayacağımız bir Ruby modülü üretir. 

    Son olarak rb_define_singleton_method metodu modül seviyesinde world adlı bir metod üretiyor. Direk HelloC modülüne bağlı, bu sayede HelloC.world komutu ile metodu çağırabiliyoruz. 

    Derlenmiş genişlemeyi make ile elde ettikten sonra C genişlememiz içinde tanımladığımız metodu kullanabiliriz.

    $ ruby extconf.rb && make
    creating Makefile
    compiling hello.c
    linking shared-object hello_c.so

    Şimdi bir irb oturumu açalım.

    >> require './hello_c'
    => true
    >> HelloC.world
    Hello World!
    => nil

    Yaşasın artık basit basit kodları C genişlemesi olarak hazırlayıp, sadece .so derlenmiş dosyasını klasörümüze ekleyip, kodlarımızı gizli yapabiliriz, aman kimse çalmasın kodlarımızı..





    Ruby Struct Yapılar

    Struct yapılar karmaşık verileri saklamak için kendi veri tipimizi tanımlamakta çok kullanılır. Deyim yapısı.

    Structure = Struct.new :özellik_adı



    -- Veriler için yeni yapılar tanımlamak

    Struct sınıfı verilen bağıl özellikler ve erişim metodlarına sahip yeni sınıflar tanımlar. 

    Kişi = Struct.new :adı, :soyadı

    Daha sonra üretilen sınıftan yeni nesneler oluşturabiliriz.

    Kişi = Struct.new :adı, :soyadı

    kişi = Kişi.new 'John', 'Doe'
    # => #<struct Kişi adı="John", soyadı="Doe">

    kişi.adı
    # => "John"

    kişi.soyadı
    # => "Doe"




    -- Bir struct sınıfına ilave yapmak


    Kişi = Struct.new :adı do
      def selamla(birisi)
        "Merhaba #{birisi}! Ben #{adı}!"
      end
    end

    Kişi.new('Ümit').selamla 'Hasan'
    # => "Merhaba Hasan! Ben Ümit!"




    -- Özelliklere erişmek


    Kişi = Struct.new :adı
    alice = Kişi.new 'Alice'

    alice['adı']  # => "Alice"
    alice[:adı]   # => "Alice"
    alice[0]      # => "Alice"


    İstersek iç içe yapılar oluşturarak daha karmaşık verileri paketleyebiliriz.

    Adres = Struct.new :cadde, :sokak, :numara
    Kişi = Struct.new :adı, :soyadı, :adres

    kişi = Kişi.new "Ümit", "Kayacık", Adres.new("Anadolu cd.", "Elmas sk.", "25/A")
    kişi.adres.sokak
    #=> "Elmas sk."





    MetaProgramlama

    Metaprogramlama iki şekilde açıklanabilir.

    Başka programları (veya kendisini) veri olarak kullanan bilgisayar programları. Yada çalışma zamanında yapılacak şeyleri derleme zamanında yapan programlar.

    Başka bir deyişle : Metaprogramlama, çalışma zamanında hayatınızı kolaylaştırmak için kod yazan kodlar yazmaktır.




    -- Oluşum değerlendirmesi kullanarak 'with' uyarlaması

    Birçok programlama dilleri programcıların metod uygulanan nesneyi göstermemesini sağlayan with deyimine sahiptir. 

    with deyimi Ruby'de oluşum değerlendirmesi ( instance_eval ) ile kolayca uyarlanır. 

    def with(nesne, &blok)
      nesne.instance_eval &blok
    end

    with metodu nesneler üzerindeki metodları sorunsuz bir şekilde çalıştırmak için kullanılabilir.

    with hash do
      store :key, :value
      p has_key? :key       # => true
      p values              # => [:value]
    end




    -- Bir nesnenin metodlarını görmek


    -- -- Bir nesneyi incelemek

    Bir nesnenin cevap verdiği public metodlarının listesine methods veya public_methods metodları ile erişebiliriz. 

    class Foo
      def bar; 42; end
    end
    f = Foo.new
    def f.yay; 17; end
    p f.methods.sort

    #=> [:!, :!=, :!~, :<=>, :==, :===, :__id__, :__send__, :bar,
    #   :class, :clone, :define_singleton_method, :display, :dup,
    #   :enum_for, :eql?, :equal?, :extend, :freeze, :frozen?, :hash,
    #   :inspect, :instance_eval, :instance_exec, :instance_of?,
    #   :instance_variable_defined?, :instance_variable_get,
    #   :instance_variable_set, :instance_variables, :is_a?, :itself,
    #   :kind_of?, :method, :methods, :nil?, :object_id, :pretty_inspect,
    #   :pretty_print, :pretty_print_cycle, :pretty_print_inspect,
    #   :pretty_print_instance_variables, :private_methods, :protected_methods,
    #   :public_method, :public_methods, :public_send, :remove_instance_variable,
    #   :respond_to?, :send, :singleton_class, :singleton_method, :singleton_methods,
    #   :tap, :then, :to_enum, :to_s, :yay, :yield_self]

    Daha da filtrelenmiş bir sonuç için tüm nesnelerde ortak olan metodları çıkartabiliriz.

    p (f.methods - Object.methods).sort
    #=> [:bar, :yay]

    Alternatif olarak methods ya da public_methods'a false argümanı verebiliriz.

    p f.methods(false) # public ve protected singleton metodlar
    #=> [:yay]

    p f.public_methods(false) # public metodlar
    #=> [:yay, :bar]


    Bir nesnenin private ve protected metodlarına private_methods ve protected_methods metodları ile erişiriz.

    p f.private_methods.sort
    #  [:Array, :Complex, :Float, :Hash, :Integer, :Rational, :String,
    #  :__callee__, :__dir__, :__method__, :`, :abort, :at_exit,
    #  :autoload, :autoload?, :binding, :block_given?, :caller,
    #  :caller_locations, :catch, :eval, :exec, :exit, :exit!, :fail,
    #  :fork, :format, :gem, :gem_original_require, :gets, :global_variables,
    #  :initialize, :initialize_clone, :initialize_copy, :initialize_dup,
    #  :iterator?, :lambda, :load, :local_variables, :loop, :method_missing,
    #  :open, :p, :pp, :print, :printf, :proc, :putc, :puts, :raise, :rand,
    #  :readline, :readlines, :require, :require_relative, :respond_to_missing?,
    #  :select, :set_trace_func, :singleton_method_added, :singleton_method_removed,
    #  :singleton_method_undefined, :sleep, :spawn, :sprintf, :srand, :syscall,
    #  :system, :test, :throw, :trace_var, :trap, :untrace_var, :warn]

    p f.protected_methods.sort
    # []


    Aynı methods ya da public_methods gibi bu metodlara da false argümanı vererek , kalıtımla gelen metodları liste dışı bırakabiliriz.


    -- -- Bir sınıf veya modülü incelemek

    methods, public_methods, protected_methods, ve private_methods, metodlarına ilave olarak sınıflar ve modüller  instance_methods, public_instance_methods, protected_instance_methods, ve private_instance_methods metodları ile o sınıf yada modülden oluşturulan nesnelerin metodlarını listeler. Bunlara da yukarıda anlatıldığı gibi false argümanı vererek kalıtımla gelen metodları dışarıda bırakabiliriz.

    p Foo.instance_methods.sort

    p Foo.instance_methods(false)

    Son olarak eğer "bu metodların isimleri neydi" diye hatırlamaya çalışırsak methods metodunu bir filtreleme ile kullanıp hatırlayabiliriz.

    p f.methods.grep(/methods/)
    #  [:methods, :singleton_methods, :protected_methods,
    #  :private_methods, :public_methods]

    p Foo.methods.grep(/methods/)
    #  [:instance_methods, :protected_instance_methods,
    #  :public_instance_methods, :undefined_instance_methods,
    #  :private_instance_methods, :methods, :singleton_methods,
    #  :protected_methods, :private_methods, :public_methods]

    Tabi, sınıfın da kendi metodları var çeşit çeşit.



    -- Metod gönderme

    Ruby'de bir nesnede metod çağırmak yerine "nesneye mesaj göndermek" tabiri çok kullanılır. Metoda tepki vermek , mesaja cevap vermek gibi de kullanılır. Bir nesneye mesaj göndermek için send metodu kullanılır, yani send metodu ile o nesnede bir metod çağrısı yapılabilir. send metodu temel sınıf olan Object sınıfının bir oluşum metodudur. Verilen ilk argüman , nesneye gönderilecek olan mesajdır - yani çağrılacak olan metod bilgisi, bu bir string veya bir symbol değer olabilir ama sembol tercih edilir. Sonraki argümanlar çağırmaya çalıştığımız metoda geçirilecek olan argümanlardır. 

    class Merhaba
      def merhaba(*args)
        puts 'Merhaba ' + args.join(' ')
      end
    end
    m = Merhaba.new
    m.send :merhaba, 'sevgili', 'okuyucular'  
    #=> Merhaba sevgili okuyucular
    #=> Burada :merhaba method ve gerisi o metoda verilecek argümanlar


    Daha açıklayıcı bir örnek

    class Hesap
      attr_accessor :isim, :email, :notlar, :adres

      def assign_values(values)
        values.each_key do |k, v|
          # send metodu
          # 'self.isim = value[k]' satırını nasıl ifade eder
          self.send("#{k}=", values[k])
        end
      end
    end

    user_info = {
      isim: 'Ümit',
      email: 'test@example.com',
      adres: '132 rastgele cd.',
      notlar: "sıkıcı müşteri"
    }

    hesap = Hesap.new
    #Eğer özellik sayısı artarsa kodumuz karışmaya başlar
    #--------- Kötü yöntem --------------
    hesap.isim = user_info[:isim]
    hesap.adres = user_info[:adres]
    hesap.email = user_info[:email]
    hesap.notlar = user_info[:notlar]

    # --------- Meta Programlama yöntemi --------------
    hesap.assign_values(user_info) # tek satırla birçok özellik değeri

    puts hesap.inspect


    Not: send artık pek tavsiye edilmiyor private metodları da çağıran __send__ (ki aslında bu send metodunun orjinalidir, üzerine yazılsa dahi bu şekil çağrılırsa orjinali gelir) metodu ya da (tavsiye edilen) public_send metodu kullanılıyor.



    -- Metodları dinamik tanımlamak

    Ruby'de programımızın yapısını çalışma zamanında da değiştirebiliriz. Bunu yapmanın yöntemlerinden biri Ruby'nin bir metodu bulamayınca otomatik çağırdığı method_missing metodunu kullanmak. Rails'çiler bunu çok yapar. 

    Diyelim bir sayının başka bir sayıdan büyük olmasını 777.büyük_mü_123_ten? şeklinde bir metodla test etmek istiyoruz. 

    # Numeric sınıfını aç
    class Numeric
      # `method_missing` üzerine yaz
      def method_missing(method_name,*args)
        # method_name istediğimiz deyime benziyor mu?
        if method_name.to_s.match /^büyük_mü_(\d+)ten\?$/
          # metod ismindeki sayıyı çek
          diğer_sayı = $1.to_i # eşleşen ilk grup
          # diğer sayı ile karşılaştırma sonucunu dön
          self > diğer_sayı
        else
          # eğer metod adı istediğimizle uyuşmuyorsa
          # `method_missing`in önceki tanımının işlemesini sağla
          super
        end
      end
    end


    method_missing işlerken dikkat edilmesi gereken bir başka konu respond_to? metodunu da elden geçirmemiz gerekir. 

      def respond_to?(method_name, include_all = false)
        method_name.to_s.match(/^büyük_mü_(\d+)ten\?$/) || super
      end


    Bunu unutursak 777.büyük_mü_123_ten? çalışır ama 777.respond_to?(:büyük_mü_123_ten?) false değer döner.

    Bir de ten yerine den ya da dan da olabilir sayıya göre. Programın son hali.

    class Numeric
      # `method_missing` üzerine yaz
      def method_missing(method_name,*args)
        # method_name istediğimiz deyime benziyor mu?
        if method_name.to_s.match /^büyük_mü_(\d+)_(ten|den|dan)\?$/
          # metod ismindeki sayıyı çek
          diğer_sayı = $1.to_i # eşleşen ilk grup
          # diğer sayı ile karşılaştırma sonucunu dön
          self > diğer_sayı
        else
          # eğer metod adı istediğimizle uyuşmuyorsa
          # `method_missing`in önceki tanımının işlemesini sağla
          super
        end
      end

      def respond_to?(method_name, include_all = false)
        method_name.to_s.match(/^büyük_mü_(\d+)_(ten|den|dan)\?$/) || super
      end
    end

    p 777.büyük_mü_123_ten?   #true
    p 6.büyük_mü_7_den?       #false
    p 77.büyük_mü_66_dan?     #true
    p 600.respond_to?(:büyük_mü_1000_den?)
    #<MatchData "büyük_mü_1000_den?" 1:"1000" 2:"den">



    -- Bir nesnenin oluşum değişkenlerini görmek

    Bir nesnenin oluşum değişkenlerini instance_variables, instance_variable_defined?, ve instance_variable_get metodları ile sorgulamak mümkündür. Oluşum değişkenlerini değiştirmek için de instance_variable_set ve remove_instance_variable metodları kullanılır.

    class Foo
      attr_reader :bar
      def initialize
        @bar = 42
      end
    end
    f = Foo.new
    p f.instance_variables                #=> [:@bar]
    p f.instance_variable_defined?(:@baz) #=> false
    p f.instance_variable_defined?(:@bar) #=> true
    p f.instance_variable_get(:@bar)      #=> 42
    f.instance_variable_set(:@bar, 17)    #=> 17
    p f.bar                               #=> 17
    f.remove_instance_variable(:@bar)     #=> 17
    p f.bar                               #=> nil
    p f.instance_variables                #=> []


    Oluşum değişken isimleri @ karakteri ile birlikte yazılmalıdır, unutursanız hata verir.

    f.instance_variable_defined?(:jim)
    #=> NameError: `jim' is not allowed as an instance variable name




    -- Oluşum nesnesinde kod çalıştırmak

    Bir nesnenin instance_eval metodu , verilen kod bloğunu o nesne kapsamında çalıştırır. 

    nesne = Object.new

    nesne.instance_eval do
      @değişken = :değer
    end

    p nesne.instance_variable_get :@değişken  # => :değer


    instance_eval metodu kod bloğunu çalıştırırken self değerini nesne değerine eşitler.

    p nesne.instance_eval { self == nesne }  # => true


    Metodun alıcı nesnesi bloğa yegane argüman olarak gönderilir. 

    p nesne.instance_eval { |argument| argument == nesne }  # => true


    Benzer metod olan instance_exec bu noktada farklı davranır. O, bloğa kendi argümanlarını gönderir.

    nesne.instance_exec :@değişken do |isim|
      p instance_variable_get isim  # => :değer
    end




    -- Global ve yerel değişkenleri okumak

    Kernel modülü global ve yerel değişkenleri için global_variables ve local_variables metodları içerir.

    kediler  = 42
    $demo = "çalışıyor"
    p global_variables.sort
    #=> [:$!, :$", :$$, :$&, :$', :$*, :$+, :$,, :$-0, :$-F, :$-I, :$-W, :$-a, :$-d,
    #=> :$-i, :$-l, :$-p, :$-v, :$-w, :$., :$/, :$0, :$:, :$;, :$<, :$=, :$>, :$?,
    #=> :$@, :$DEBUG, :$FILENAME, :$LOADED_FEATURES, :$LOAD_PATH, :$PROGRAM_NAME,
    #=> :$VERBOSE, :$\, :$_, :$`, :$demo, :$stderr, :$stdin, :$stdout, :$~]

    p local_variables
    #=> [:kediler]


    Oluşum değişkenlerinden farklı olarak yerel ve global değişkenleri okumaki değiştirmek veya yok etmek için metodlar yoktur. Direk kendilerini kullanırsınız. Bununla beraber eğer bir değişkene ismiyle ulaşıp bir şeyler yapmak için eval metodundan yararlanılabilir.

    var = "$demo"
    p eval(var)           #=> "çalışıyor"
    eval("#{var} = 17")
    p $demo             #=> 17


    eval metoduna verilen string içindeki kod çalıştırılır. 

    >> eval "2+5"
    => 7
    >> a = 3
    >> eval "a * 2"
    => 6


    Default olarak eval metodu değişkenlere bulunulan kapsamda erişir. Başka bir kapsamdaki değişkene erişmek için o kapsamın binding değeri ikinci argümanda kullanılabilir.

    def local_variable_get(isim, kapsam=nil)
      foo = :içerde
      eval(isim, kapsam)
    end

    def test_1
      foo = :dışarda1
      p local_variable_get("foo")
      # kapsam verilmeden çağrılmış
    end

    def test_2
      foo = :dışarda2
      p local_variable_get("foo", binding)
      # bulunulan kapsam ile çağrılmış
    end
     
    test_1 #=> :içerde
    test_2 #=> :dışarda2


    Ruby dökümanındaki örnek de ilginç.

    def get_binding(str)
      return binding
    end
    str = "hello"
    eval "str + ' Fred'"                      #=> "hello Fred"
    eval "str + ' Fred'", get_binding("bye")  #=> "bye Fred"


    Anlamak için get_binding metod tanımında parametre adını değiştirirsek verilen hatadan görebiliriz. İkinci eval satırı "bye" string'i kapsamında str değişkeni arıyor ve metoda gönderilen "bye" değerini kullanıyor.




    -- Sınıf değişkenlerini görmek

    Sınıfların ve modüllerin de nesneler gibi değişkenlerine erişme metodları vardır.

    p Module.methods.grep(/class_variable/)
    #=> [:class_variables, :class_variable_get, :remove_class_variable,
    #=>  :class_variable_defined?, :class_variable_set]

    class Foo
      @@instances = 0
      def initialize
        @@instances += 1
      end
    end

    class Bar < Foo; end

    5.times{ Foo.new }
    3.times{ Bar.new }
    p Foo.class_variables                   #=> [:@@instances]
    p Bar.class_variables                   #=> [:@@instances]
    p Foo.class_variable_get(:@@instances)  #=> 8
    p Bar.class_variable_get(:@@instances)  #=> 8


    Oluşum değişkenlerinde olduğu gibi sınıf değişkenlerinde de isimi @@ olamadan yazarsak hata alırız.

    p Bar.class_variable_defined?( :instances )
    #=> NameError: `instances' is not allowed as a class variable name




    -- Metodları string'den dinamik üretmek

    Ruby define_method metodunu modüller ve sınıflarda metod tanımlamak için private olarak bulundurur. Ancak metodun gövdesi ya bir Proc nesnesi ya da başka bir metod olmalıdır. Bir string'den metod üretmenin yöntemi eval kullanarak string'den bir Proc üretmek olabilir. 

    xml = <<ENDXML
    <metodlar>
      <metod ismi="go">puts "Gidiyorum!"</metod>
      <metod ismi="stop">7*6</metod>
    </metodlar>
    ENDXML

    class Foo
      def self.add_method(isim, kod)
        gövde = eval( "Proc.new{ #{kod} }" )
        define_method(isim, gövde)
      end
    end

    require 'nokogiri' # gem install nokogiri
    doc = Nokogiri.XML(xml)
    doc.xpath('//metod').each do |meth|
      Foo.add_method( meth['ismi'], meth.text )
    end
    f = Foo.new
    p Foo.instance_methods(false)  #=> [:go, :stop]
    p f.public_methods(false)      #=> [:go, :stop]
    f.go                           #=> "Gidiyorum!"
    p f.stop                       #=> 42


    İşte böyle, meta programlamada biraz yapılabileceklerin sınırları zorlanıyor. 



    -- Oluşum nesnelerinde metod tanımlamak

    Daha önce de gördük Ruby'de bir sınıfın oluşum nesnesine sadece ona ait olan bir metod tanımlayabiliyoruz. Böylece sınıftan üretilen diğer nesneleri etkilemeden sadece bir nesnenin davranışını değiştirebiliriz. 

    class Örnek
      def metod1(foo)
        puts foo
      end
    end

    # exp nesnesinde metod2'yi tanımlar
    exp = Örnek.new
    exp.define_singleton_method(:metod2) {puts "Metod2"}

    # parametresi olan metod tanımlama
    exp.define_singleton_method(:metod3) {|isim| puts isim}

    exp.metod1 "metod1"   #=> metod1
    exp.metod2            #=> Metod2
    exp.metod3 "Ümit"     #=> Ümit

    exp2 = Örnek.new
    exp2.metod3 "Hasan" # (NoMethodError)

    Bir diğer yöntem direk def bloğunda nesne adını kullanarak.

    def exp.metod3(isim)
      puts isim
    end





    Dinamik Kod Çalıştırma

    Dinamik kod çalıştırma için kullanılan deyimler.

    • eval “kod”
    • eval "kod", kapsam
    • eval "kod", proc
    • kapsam.eval “kod” # eval "kod", kapsam ile aynı

    Parametre       Anlamı
    --------------------------------------
    "kod"           Herhangi Ruby kodu
    kapsam          Binding sınıfı oluşumu
    proc            Proc sınıfı oluşumu



    -- Bir string'i çalıştırmak

    Çalışma zamanında herhangi bir string değer kod olarak çalıştırılabilir.

    class Örnek
      def self.foo
        p :foo
      end
    end

    eval "Örnek.foo" #=> :foo



    -- Bir kapsam içinde çalıştırma

    Ruby yerel değişkenler ve self değerini binding nesnesi kapsamında değerlendirir. Bulunulan kapsamı Kernel#binding metodu ile öğreniriz, ve string içindeki kodu bu kapsamda Binding#eval metodu ile çalıştırırız.

    b = proc do
      yerel_değişken = :local
      binding
    end.call

    b.eval "yerel_değişken" #=> :local


    gibi.

    def fake_sınıf_eval sınıf, kod = nil, &blok
      sınıf_binding = sınıf.send :eval, "binding"
     
      if blok
        sınıf.class_eval &blok
      else
        sınıf_binding.eval kod
      end
    end

    class Örnek end
       
    fake_sınıf_eval Örnek, "def self.foo; p :foo; end"
       
    fake_sınıf_eval(Örnek) do
      def bar
        p :bar
      end
    end
       
    Örnek.foo #=> :foo
    Örnek.new.bar #=> :bar




    -- Oluşumda kod çalıştırmak

    Tüm nesnelerde instance_eval metodu vardır. Verilen kodu hedef nesne kapsamında çalıştırır. 

    nesne = Object.new

    nesne.instance_eval do
      @değişken = :değer
    end

    p nesne.instance_variable_get :@değişken  # => :değer


    instance_eval  metodu kod bloğu boyunca self değerine nesneyi koyar. 

    p nesne.instance_eval { self == nesne }  # => true


    Metodun alıcı nesnesi ayrıca bloğa yegane argüman olarak gönderilir. 

    p nesne.instance_eval { |argüman| argüman == nesne }  # => true


    Bundan başka instance_exec metodu sadece bu noktada farklılık gösterir. Bu metod kendi argümanını bloğa geçirir. 

    nesne.instance_exec :@değişken do |isim|
      p instance_variable_get isim  # => :değer
    end





    Mesaj Gönderme Tabiri

    Nesne yönelimli programlamada nesneler mesajlar alır ve on cevap verirler. Ruby'de metodlar nesnelerin işlevleridir. Bir nesnede metod çağırmak , o nesneye bir mesaj göndermek, metodun bir değer dönmesi de mesajın cevabı olarak bilinir. 

    Ruby'de mesaj gönderme işlemi dinamiktir. Bir nesneye nasıl cevap vereceğini bilmediği bir mesaj yollandığında, Ruby bu durumu işlemek için ön tanımlı bazı kurallara sahiptir. Bu kuralları anlaşılmayan mesajlara kendi kurallarımızla cevap vermek için kullanabiliriz. 

    Ne zaman bir nesne mesaj alsa Ruby şu kontrolleri yapar:

    • Eğer bu nesnenin bir tekil sınıfı (singleton) varsa , mesaja cevap verebiliyor mu?
    • Nesnenin sınıfında veya sınıfının atalarında mesaja cevap var mı?
    • Sınıftan başlanır, ebeveynine, dedesine vs sırayla atalarına bekılır.




    -- Kalıtım zincirinde mesajın ilerleyişi

    class Örnek
      def örnek_metod
        :örnek
      end

      def altörnek_metod
        :örnek
      end

      def kaybolmamış_metod
        :örnek
      end

      def method_missing isim
        return :örnek if isim == :kayıp_örnek_metod
        return :örnek if isim == :kayıp_altörnek_metod
        return :altörnek if isim == :kaybolmamış_metod
        super
      end
    end

    class AltÖrnek < Örnek
      def altörnek_metod
        :altörnek
      end

      def method_missing isim
        return :altörnek if isim == :kayıp_altörnek_metod
        return :altörnek if name == :kaybolmamış_metod
        super
      end
    end

    s = AltÖrnek.new


    AltÖrnek#altörnek_metod metodu çağrılınca Ruby ilk önce AltÖrnek sınıfı kalıtım zincirine bakar. 

    p AltÖrnek.ancestors
    # => [AltÖrnek, Örnek, Object, Kernel, BasicObject]


    İlk önce AltÖrnek sınıfında bu metoda bakar , varsa Örnek sınıfına bile bakmaz onu kullanır.

    p s.altörnek_metod # => :altörnek


    AltÖrnek'te cevap bulamazsa Örnek sınıfına geçer. Eğer örnek_metod mesajı gönderirsek , Ruby AltÖrnek sınıfında cevap bulamayınca Örnek sınıfına geçer. 

    p s.örnek_metod # => :örnek


    Ruby tüm sınıflardaki metodları kontrol ettikten sonra cevap bulamazsa metod_missing metoduna cevap verecek mi diye bakar. Eğer kayıp_altörnek_metod çağrısı yaparsak Ruby metodu AltÖrnek sınıfında bulamayınca yukarı Örnek sınıfına geçer, orada da bulamayınca daha yukarı geçer. Hiç bir sınıfta bulamazsa tekrar başa döner AltÖrnek sınıfı method_missing metodu mesaja cevap veriyor mu diye baştan başlar. Ve orada kayıp_altörnek_metod  mesajına cevap bulur ve onu cevap olarak geri döner.

    p s.kayıp_altörnek_metod # => :altörnek


    Bununla beraber kaybolmamış_metod çağrısına AltÖrnek sınıfında cevap yoktur, ama hemen method_missing'e gidilmez. Önce zincirde üst sınıflarda var mı diye bakar. 

    p s.kaybolmamış_metod # => :örnek




    -- Modüllerde mesaj ilerlemesi

    Ruby nesnenin ataları zincirinde ilerler , bu zincirde sınıflar olduğu gibi modüller de olabilir. Sınıflarda üste doğru ilerleme modüllerde de aynı kurallar ile davranır.

    class Örnek
    end

    module Öncü
      def initialize *args
        return super :default if args.empty?
        super
      end
    end

    module İlkInclude
      def foo
        :ilk
      end
    end

    module İkinciInclude
      def foo
        :ikinci
      end
    end

    class AltÖrnek < Örnek
      prepend Öncü
      include İlkInclude
      include İkinciInclude

      def initialize data = :altörnek
        puts data
      end
    end

    p AltÖrnek.ancestors
    # => [Öncü, AltÖrnek, İkinciInclude, İlkInclude,
    #      Örnek, Object, Kernel, BasicObject]

    s = AltÖrnek.new # => :default
    p s.foo # => :ikinci


    Zincirde ilk başta AltÖrnek sınıfından da önce prepend ile sınıfa eklediğimiz Öncü modülü geliyor. Sonra sınıfın kendisi, sonra en son eklenen modülden devam ediyor. 



    -- Mesajları durdurmak

    Mesajı durdurmanın iki yolu var,

    • Tanımlı olmayan mesajları durdurmak için method_missing kullanarak
    • Mesajı durdurmak için zincirin içinde bir yerde bir metod tanımlamak.


    Mesajı durdurduktan sonra yapabileceklerimiz,

    • Mesajı cevaplamak
    • Mesajı başka bir yere göndermek
    • Mesajı ya da cevabını değiştirmek


    method_missing ile mesajı durdurup cevap vermek.

    class Örnek
      def foo
        @foo
      end

      def method_missing isim, veri
        return super unless isim.to_s =~ /=$/
        isim = isim.to_s.sub(/=$/, "")
        instance_variable_set "@#{isim}", veri
      end
    end

    e = Örnek.new

    p e.foo # => nil
    e.foo = :foo
    p e.foo # => :foo


    Örnek sınıfında foo= diye bir metod tanımı yok. method_missing cevap veriyor ve foo= metod adından @foo değerine atama yapacağını anlayıp mesaja cevap veriyor. 


    Mesajı yakalamak ve değiştirmek.

    class Örnek
      def initialize başlık, gövde
      end
    end

    class AltÖrnek < Örnek
    end


    Şimdi diyelim verimiz "başlık:gövde" şeklinde ve Örnek sınıfına bu mesajı göndermeden ayırıp göndermek istiyoruz. AltÖrnek sınıfında initialize metodu tanımında bunu yapabiliriz. 

    class Örnek
      def initialize başlık, gövde
      end
    end

    class AltÖrnek < Örnek
      def initialize ham_veri
        işlenmiş_veri = ham_veri.split ":"

        super işlenmiş_veri[0], işlenmiş_veri[1]
      end
    end


    Mesajı yakalayıp başka nesneye göndermek.

    class ObscureLogicProcessor
      def process data
        :ok
      end
    end

    class NormalLogicProcessor
      def process data
        :not_ok
      end
    end

    class WrapperProcessor < NormalLogicProcessor
      def process data
        return ObscureLogicProcessor.new.process data if data.obscure?

        super
      end
    end


    Gelen verinin türüne göre iki sınıftan birine mesaj gönderiliyor. 





    Keyword Argümanlar

    Keyword argümanlar Ruby 2.0'la beraber kullanıma başladı. Basit bir kullanımını görelim.

    def söyle(mesaj: "Merhaba Dünya")
      puts mesaj
    end

    söyle
    # => "Merhaba Dünya"

    söyle mesaj: "Bugün Pazartesi"
    # => "Bugün Pazartesi"


    Hatırlatma yapalım , aynı metod key argüman olmadan da tanımlanabilir.

    def söyle(mesaj = "Merhaba Dünya")
      puts mesaj
    end

    söyle
    # => "Merhaba Dünya"

    söyle "Bugün Pazartesi"
    # => "Bugün Pazartesi"


    Daha küçük Ruby versiyonlarında key argüman yerine hash parametre kullanabilirsiniz. Bu halihazırda Ruby 2.0 öncesi ile uyumlu kalmaya çalışan kütüphanelerde yaygın kullanılır.

    def söyle(seçenekler = {})
      mesaj = seçenekler.fetch(:mesaj, "Merhaba Dünya")
      puts mesaj
    end

    söyle
    # => "Merhaba Dünya"

    söyle mesaj: "Bugün Pazartesi"
    # => "Bugün Pazartesi"




    -- Yıldız operatörü ile sıralanmamış key argüman kullanımı

    Keyfi sayıda key argüman kullanan bir metodu ** operatörü yardımıyla tanımlarız. 

    def söyle(**args)
      puts args
    end

    söyle foo: "1", bar: "2"
    # {:foo=>"1", :bar=>"2"}


    Argümanlar bir hash içine toplanır. Bu hash değeri istediğimiz elemanları sağlamak için manipüle edebiliriz. 

    def söyle(**args)
      puts args[:mesaj] || "Mesaj bulunamadı"
    end

    söyle foo: "1", bar: "2", mesaj: "Merhaba Dünya"
    # Merhaba Dünya

    söyle foo: "1", bar: "2"
    # Mesaj bulunamadı


    Yıldız operatörünü key argüman için kullanmak, argüman doğrulamasını engeller. Metod bilinmeyen bir key için ArgumentError hatası vermez. 

    Ancak bu sorunu key argümanları bir hash içinde metoda ** ile vererek vererek aşabiliriz.

    def söyle(mesaj: nil, önce: "<p>", sonra: "</p>")
      puts "#{önce}#{mesaj}#{sonra}"
    end

    args = { mesaj: "Merhaba Dünya", sonra: "</p><hr>" }
    söyle(**args)
    # <p>Merhaba Dünya</p><hr>

    args = { mesaj: "Merhaba Dünya", foo: "1" }
    söyle(**args)
    # => ArgumentError: unknown keyword: foo


    Bu yöntem genellikle gelen argümanları işleyip başka bir metoda gönderirken de kullanılır.

    def içteki(foo:, bar:)
      puts foo, bar
    end

    def dıştaki(birşey, foo: nil, bar: nil, baz: nil)
      puts birşey
      params = {}
      params[:foo] = foo || "Default foo"
      params[:bar] = bar || "Default bar"
      içteki(**params)
    end

    dıştaki "Merhaba:", foo: "Custom foo"
    # Merhaba:
    # Custom foo
    # Default bar




    -- Key argüman kullanımı

    Bir metod tanımında key parametreyi adını kullanarak belirtiriz.

    def söyle(mesaj: "Merhaba Dünya")
      puts mesaj
    end

    söyle
    # => "Merhaba Dünya"

    söyle mesaj: "Bugün Pazartesi"
    # => "Bugün Pazartesi"


    Birçok key parametre kullanırken tanımlamada sırası önemli değildir. 

    def söyle(mesaj: "Merhaba Dünya", önce: "<p>", sonra: "</p>")
      puts "#{önce}#{mesaj}#{sonra}"
    end

    söyle
    # => "<p>Merhaba Dünya</p>"

    söyle mesaj: "Bugün Pazartesi"
    # => "<p>Bugün Pazartesi</p>"

    söyle sonra: "</p><hr>", mesaj: "Bugün Pazartesi"
    # => "<p>Bugün Pazartesi</p><hr>"


    Key parametreler ve yeri sabit parametreler karışık kullanılabilir.

    def söyle(mesaj, önce: "<p>", sonra: "</p>")
      puts "#{önce}#{mesaj}#{sonra}"
    end

    söyle "Merhaba Dünya", önce: "<span>", sonra: "</span>"
    # => "<span>Merhaba Dünya</span>"


    Tanımlanmayan bir key argüman vermek hataya sebep olur.

    def söyle(mesaj: "Merhaba Dünya")
      puts mesaj
    end

    söyle foo: "Merhaba"
    # => ArgumentError: unknown keyword: foo




    -- Zorunlu key argüman

    Zorunlu key argüman Ruby 2.1 ile kullanıma başladı ve key parametre kullanımını geliştirdi. 

    Zorunlu key argüman için metod parametre tanımında default değer vermeden sadece key parametre adı girilir.

    def söyle(mesaj:)
      puts mesaj
    end

    söyle mesaj: "Merhaba Dünya"
    # => "Merhaba Dünya"

    söyle
    # => ArgumentError: missing keyword: mesaj


    Ayrıca zorunlu ya da zorunlu olmayan key parametreleri karıştırabiliriz.

    def söyle(önce: "<p>", mesaj:, sonra: "</p>")
      puts "#{önce}#{mesaj}#{sonra}"
    end

    söyle mesaj: "Merhaba Dünya"
    # => "<p>Merhaba Dünya</p>"

    söyle mesaj: "Merhaba Dünya", önce: "<span>", sonra: "</span>"
    # => "<span>Merhaba Dünya</span>"

    söyle
    # => ArgumentError: missing keyword: mesaj





    DateTime değerler

    DateTime değerler tarih ve saat ifade etmek için kullanılır. Genel deyim yapısı.

    DateTime.new(yıl, ay, gün, saat, dakika, saniye)


    DateTime sınıfını kullanmadan önce date kütüphanesini programa katmalıyız. 

    require "date"

    DateTime.new(yıl, ay, gün, saat, dakika, saniye)




    -- String'den DateTime değer oluşturmak

    DateTime.parse sınıf metodu bir string'den tarih zaman değerini formatını tahmin ederek yapar. 

    >> DateTime.parse('Jun, 8 2016')
    => #<DateTime: 2016-06-08T00:00:00+00:00 ((2457548j,0s,0n),+0s,2299161j)>
    >> DateTime.parse('201603082330')
    => #<DateTime: 2016-03-08T23:30:00+00:00 ((2457456j,84600s,0n),+0s,2299161j)>
    >> DateTime.parse('04-11-2016 03:50')
    => #<DateTime: 2016-11-04T03:50:00+00:00 ((2457697j,13800s,0n),+0s,2299161j)>
    >> DateTime.parse('24-11-2016 03:50 -0300')
    => #<DateTime: 2016-11-24T03:50:00-03:00 ((2457717j,24600s,0n),-10800s,2299161j)>




    -- DateTime.new metodu


    DateTime.new(2014,10,14)
    # => #<DateTime: 2014-10-14T00:00:00+00:00 ((2456945j,0s,0n),+0s,2299161j)>


    Şu anda zaman.

    >> DateTime.now
    => #<DateTime: 2025-03-14T13:35:59+03:00 >



    -- DataTime değere gün ekleyip çıkarmak

    DateTime + Fixnum (gün sayısı)

    >> DateTime.new(2015,12,30,23,0) + 1
    => #<DateTime: 2015-12-31T23:00:00+00:00>


    DateTime + Float (gün sayısı)

    >> DateTime.new(2015,12,30,23,0) + 2.5
    => #<DateTime: 2016-01-02T11:00:00+00:00>


    DateTime + Rational (gün sayısı)

    >> DateTime.new(2015,12,30,23,0) + Rational(1,2)
    => #<DateTime: 2015-12-31T11:00:00+00:00)>





    Doğruluk

    Kural olarak kod içinde çift negatiflemeden kaçınmak gerektiği söylenir. Çift negatifleme yerine daha okunabilir bir kod kullanılabilir.

    Örneğin şunun yerine

    def user_exists?
      !!user
    end


    bu kullanılabilir.

    def user_exists?
      !user.nil?
    end




    -- Tüm nesneler boolean değere dönüştürülebilir

    Bir değerin doğruluğunu çift negatifleme ile ölçebiliriz. Tipi ne olursa olsun tüm değerler boolean bir karşılığa sahiptir.

    >> !!1234
    => true
    >> !!"Hello, world!"
    => true
    >> !!true
    => true
    >> !!{a:'b'}
    => true


    nil ve false dışında tüm geçerli değerler doğru kabul edilir.

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




    -- if .. else yapısında bir nesnenin doğruluğu kullanılabilir


    if 'hello'
      puts 'hey!'
    else
      puts 'bye!'
    end


    Bu kod konsola "hey!" yazar.





    Ruby'de JSON

    JSON (JavaScript Object Notation) kullanışlı bir veri dönüşüm biçimi. Birçok web uygulaması veriyi göndermek ve almak için bu veri yapısını kullanır. 

    Ruby'de de JSON veri ile çalışabilirsiniz. 

    Öncelikle öncelikle json kütüphanesini programımıza dahil ederek JSON.parse sınıf metodunu kullanabiliriz.

    >> require 'json'
    >> j = '{"a": 1, "b": 2}'
    >> puts JSON.parse(j)
    {"a"=>1, "b"=>2}


    JSON veri sanki bir hash değeri string içine koymuşuz gibi duruyor. Zaten JSON.parse sınıf metodu da bize bir hash değer dönüyor. 

    İşlemin tersini yaparken de Hash#to_json oluşum metodunu kullanırız. 

    >> h = { 'a' => 1, 'b' => 2 }
    >> json = h.to_json
    >> puts json
    => {"a":1,"b":2}



    -- JSON ifadede sembol kullanmak

    JSON değerleri Ruby semboller ile kullanmak için JSON.parser metodunda symbolize_names opsiyonunu kullanırız.

    >> json = '{ "a": 1, "b": 2 }'
    >> puts JSON.parse(json, symbolize_names: true)
    => {:a=>1, :b=>2}



    Evet, bu bölüm de çok uzadı, burada keselim. Sonraki, bölüme örtülü alıcılar ve self değeri ile başlayacağız inşallah. Şimdilik kalın sağlıcakla..






    Hiç yorum yok:

    Yorum Gönder