Rails – Callbacks

Kullanıcılardan herhangi biri hesabını iptal ettiği zaman adminlere bilgilendirme maili gitmesi, bir işlem tetiklendiğinde – başka bir işlemin de gerçekleşmesi veya bir model nesnesi oluşturduğunuzda, başka bir model nesnesinin de onla ilişkili olarak oluşması gibi durumları tanımlarken ihtiyaç duyduğumuz metodların en sık kullanılanları Rails’te 6 tanedir. Bunlar:

  • before_create
  • after_create
  • before_save
  • after_save
  • before_destroy
  • after_destroy

Zaten bu ifadelerin ne iş yaptığı isimlerinden de anlaşıldığı için tekrarlamayacağım.

before_” ile başlayan herhangi bir callback false döndürdüğü taktirde, uygulamanızın çalışma sürecini durdurabileceği için dikkatli kullanılmalıdır.

İlerde uygulamanız dallanıp budaklandığında modeliniz kayıt işlemi yapmıyorsa öncelikli olarak “before_*” ifadelerini kontrok faydalı olabilir.

Örneğin kullanıcılardan biri blog yazınıza yorum yazdığı zaman, yazının yazarına bilgilendirme emaili gitmesini istiyorsunuz varsayalım. Comment modeli içerisinde böyle bir durumu aşağıdaki gibi kurgularız:

create” işleminden sonra yapılmasını istediğiniz işleri doğrudan “after_create” metodu içerisine yazabilirsiniz. Ancak bu yöntem oldukça pratik olmasına rağmen, farklı işlemleri yapmak için çalışan kodları ard arda yazdığınızda kodunuzun okunabilirliği azalacaktır. Bu yüzden aşağıdaki gibi bir yol izleyerek her bir eylemi ayrı metod olarak tanımlamak ve daha sonra bunları after_create‘ten sonra virgülle ayırarak çağırarak daha mantıklı olur:

User Modelinin Güncellenmesi

User modelimizin altında bulunan “password” alanı, şifreleri plain-text olarak muhafaza ettiği için güvenlik zaaflarına sebebiyet verebilir. Bu tür hassas dataları her zaman encrypt etmek gereklidir. Bu yüzden öncelikle veritabanında ki “password” alanını “hashed_password” olarak tekrar adlandıralım:

Şimdi migration dosyamızı hazırlayalım:

Migration’ı çalıştıralım:

Şimdi ise User modelimizi bu değişikliğe uygun şekilde hazırlayalım:

Ruby’nin built-in kütüphanelerinden biri olan Digest ile şifreleri hash’leyebiliriz ancak bu örnekte kullanılan SHA1 şifreleme algoritmasının production ortamı için pekte kullanışlı olduğu söylenemez. Production için BCrypt kullanımı düşünülebilir:

https://github.com/codahale/bcrypt-ruby

Şimdi hazırladığımız User modelini satır satır inceleyelim:

  • require ‘digest’ => Şifreleri encrypt edebilmek için Ruby’nin built-in kütüphanelerinden biri olan Digest’i çağırdık.
  • attr_accessor :password => Burada Ruby’ye “password” için reader ve writer metodları oluşturmasını söyledik çünkü veritabanımızda artık “password” diye bir alan bulunmuyor ve bu yüzden de “password” isminde bir metod Active Record tarafından otomatik olarak oluşturulmuyor. Sonuç olarak “password“ü hala bir şekilde encrypt edilmeden önce set etmeye ihtiyacımız olduğu için kendi “niteleyicimizi (attribute)” oluşturduk. Bu niteleyici herhangi bir model niteleyicisi gibi çalışmasına rağmen, model kaydedildiğinde veritabanına kayıt edilmez.
  • before_save :encrypt_new_password => Burada ki “before_save” callback’i Active Record’a, kayıt yapmadan önce “encrypt_new_password” metodunu çalıştırmasını söylüyor. Burada kayıt yapmaktan kasıt hem create hemde update işlemi.
  • encrypt_new_password => Bu metod sayesinde yalnızca “password” alanı dolu ise şifrenin hashlenmesi sağlanır. Aksi halde mevcut olan şifre korunur. Böylece, kullanıcı şifresini güncellemek istemediğinde şifresini encrypt etmemiş oluruz. Eğer password alanı boş ise, “return if password.blank?” ile metoddan işlem yapmadan dönebiliriz. Ancak password alanı dolu ise metodumuz “self.hashed_password = encrypt(password)” ile girilen password’ü şifreyecektir.
  • encrypt => Bu metod Digest kütüphanesini kullanarak, ona gönderilen veriyi SHA1 ile şifreler. Ayrıca şifreleme sonucunu yani hash‘i döndürür.
  • password_required? => Kullandığımız validasyonları pratikleştirmek için hazırladığımız bir metodtur. Bu metod sayesinde “hashed_password” niteleyicisinin boş olup olmadığını – veya “password” erişicisinin (accessor) yeni bir şifrenin oluşturulmasında kullanılıp kullanılmadığını kontrol ettirebiliriz.
  • self.authenticate => Metodun ismine bakarak bunun bir class metodu olduğunu yani instance yerine doğrudan bir class üzeride çalıştığını söyleyebiliriz. Yani bu metoda bir instance üzerinden değil, doğrudan class üzerinden erişiriz. Örneklemek gerekirse; “@user = User.new & @user.authenticate” şeklinde erişmek yerine doğrudan “self.authenticate” şeklinde erişim sağlarız. Authenticate metodumuz biri e-mail adresi diğeri ise plain-text şifre olmak üzere iki tane argüman alıyor ve “find_by_email” metodu sayesinde ilgili e-mail adresi ile eşleşen kullanıcının bulunabilmesini sağlıyor. Eğer metod sayesinde kullanıcı bulunduysa zaten “user” değişkenine atanıyor, bulunamadıysa bu değişken “nil” olarak kalıyor. Metod içerisinde belirtilen “return user if user && user.authenticated?(password)” ifadesi sebebiyle metodumuz ancak user bulunabildiğinde ve bu user authenticated olduğunda true döndürecektir.
  • authenticated? => Bu metod basitçe; girilen password’ü önce hash‘ler, daha sonra ise saklanmış olan mevcut hash ile karşılaştırır. Eğer bunlar eşleşiyorsa true döndürür, eşleşmiyor ise false döndürür.

Hazırladığımız uygulamayı test edelim:

Sürecin Özeti

  • Authenticate metoduna email adresi ve plain-text şifre olmak üzere iki parametre gönderiyoruz.
  • Gönderdiğimiz şifre metod tarafından hashlenir ve veritabanında hash’li olarak tutulan şifreyle karşılaştırır.
  • Eğer hash’ler eşleşirse authentication başarılı olur, eşleşmez ise başarısız olarak “nil” döndürür.

Son olarak, eklediğimiz bu yeni özellikler için db/seeds.rb dosyamızı düzenleyelim:

Leave a Reply