https://ujk-ujk.blogspot.com/2025/03/ruby-temelleri-8.html
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 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