Selam Rails 7 öğrenmeye devam ediyorum.
Basit Login İşlemi
Kullanıcılar artık sitemize kayıt olabiliyor, sıra geldi kullanıcıların sisteme giriş ve çıkış yapmaları için login ve logout işlemlerini tanımlamaya. Basit ama çalışan bir sistem tasarlayacağız, kullanıcı tarayıcısını kapatana kadar giriş yapmış olarak kalmasını sağlayacağız. Sonuç yetkilendirme sisteminde kullanıcılara yetkilerine göre gerekirse site yapısını bile değiştirme izinleri vereceğiz.
Oturumlar
HTTP durumları olmayan bir protokoldür ve her gelen isteği. bağımsız işler. Bu yüzden daha önce yapılmış olan işlemlerle ilgili bilgi sahibi değildir. İşte bunun anlamı giriş yapan kullanıcı hakkında öyle bir şey yapmalıyız ki yetkili olduğunu bilelim. Kullanıcı girişi yapılan web uygulamaları bu yüzden oturumları (session) kullanır. Bu iki bilgisayar arasında yarı kalıcı bir bağlantıdır (mesela kullanıcının bilgisayarındaki tarayıcı ve server'daki Rails uygulaması arasuında bir bağlantı gibi).
Rails'de en yaygın kullanılan oturum açma biçimi cookie (çerez) kullanmaktır. Bunlar kullanıcının bilgisayarının tarayıcısında saklanan küçük yazılardır ve sayfadan sayfaya geçerken değişmediği için kullanıcının oturumunun açık kalması için gereken id değerleri gibi şeyleri kaydetmek için kullanılabilir. Burada Rails'in session metodunu kullanarak kullanıcı tarayıcısını kapatana kadar açık kalacak oturumlar oluşturacağız. Daha ileride buna benzer ama daha uzun süren oturumlar için cookies metodunu göreceğiz.
Oturum oluşturmak için genel eğilim olarak REST yapısında bir model kullanılır. Login sayfasına gidilirse yeni bir oturum için form gösterilir (new). Girişin gerçekleşmesi durumunda bir oturum nesnesi oluşur (create). Kullanıcı sistemden çıkış yapınca oturum nesnesi sonlandırılır (destroy). Kullanıcılar için oluşturduğumuz User modeli kayıtlarını veri tabanında saklıyordu, ancak oturumlar verileri saklamak için cookie kullanacak. Bu ve sonraki bölümde bir Sessions kontrolörü oluşturacağız, bir login formu ve ilgili kontrolör eylemlerini tanımlayacağız. Daha sonra ilgili oturum işleme kodlarını ekleyerek sisteme giriş yapma olaylarını bitireceğiz.
Sessions kontrolörü
REST yapısına karşı gelen giriş çıkış işlemlerine bakarsak, login işlemi new eylemine GET isteği ile bir form gösterecek, form gönderilince (POST) create eylemi çalışacak ve oturumu başlatacak, sistemden çıkmak için de destroy eylemine bir DELETE isteği göndermek gerekir.
Kontrolörümüzü oluşturarak başlayalım.
Sadece new eylemini koyduk, çünkü burada vereceğimiz eylemlere karşılık görsel dosyaları da otomatik olarak üretilir. Bizim sadece new eylemi için bir form görseline ihtiyacımız var, create ve destroy eylemlerinde görsel olmayacağı için komutta kullanmadık daha sonra elle ekleyeceğiz.
Uygulama sayfalarımızın üzerindeki navigasyon barında bağlantısını yapmadığımız bir Giriş Yap linki vardı, ona tıklanınca bir login formu açmayı planlıyoruz. Formumuzda sadece email ve şifre girmek için kutular ve bir gönderme butonu olacak. Yeni kullanıcılar bu sayfaya gelirse kayıt olmak için de bir link koysak iyi olur.
Users resource tanımlarken tüm REST eylemleri kullanmak için
satırı ile tüm eylemleri otomatik tanımlamıştık, ancak Sessions resource için sadece login adrsine GET ve POST isteklerini ve logout adresine yapılan DELETE isteğini bağlamak için tek tek elle routes.rb dosyasına ekleyeceğiz. Ayrıca Rails generate komutuyla otomatik oluşturulan yönlendirmeyi de sileceğiz.
config/routes.rb
İlk yapmamız gereken bu yönlendirmelere göre kontrolör üretilirken otomatik oluşan test rutinini düzenlemek.
test/controllers/sessions_controller_test.rb
Bu eklediğimiz yönlendirmelerin de bir tablosunu düşünürsek
Şu ana kadar bir sürü yönlendirmeyi uygulamamıza ekledik, neler var diye kontrol etmeye kalksak, terminalde görebiliriz.
Bu çıktıyı daha anlaşılabilir olsun diye biraz düzenledim ve şu anda bizim ilgimizde olmayan yönlendirmeleri listeden çıkardım. Amacım şunu söylemek, rails routes komutunu kullanarak uygulamamızda kullanılan adresleri isimlendirilmiş şekillerini ve hangi kontrolörün hangi eylemine ait olduğunu görebiliriz.
Login formu
İlgili kontrolör ve yönlendirmeleri tanımladıktan sonra sıra geldi login formumuzu tanımlamaya. Login formumuz aşağı yukarı kayıt olma formunun sadece email ve şifre olan şekli gibi.
Daha önce kayıt formunda yaptığımız gibi yanlış veri girilerek sisteme giriş yapmaya kalkılınca bir hata mesajı vererek tekrar forma döneceğiz. Ancak daha önce hata mesajları ActiveRecord tarafından otomatik oluşuyordu ve bizim Session nesnemiz bir ActiveRecord nesnesi değil. Bu yüzden oturumlar için flash mesaj yöntemini tercih edeceğiz.
Önce forma odaklanalım, daha önce kayıt olma form görselinde bir blok yapı kullanmıştık.
Oturum açma formu ile kayıt olma formu arasındaki fark bir Session modelimizin olmaması. ve bu yüzden @user gibi bir @session değerimiz yok. Burada form_with metodunu farklı parametreler ile kullanacağız.
ile formun gönderme eyleminin /users adresine bir POST isteği olarak gerçekleşeceğini bildiriyoruz. Üretilen form elemanına bakarsak
olduğunu görürüz. Konu oturumlara gelince hedef URL ve kapsamı belirterek form oluşturacağız.
Şimdi bu bilgiler doğrultusunda login sayfamızın görselini düzenleyelim.
app/views/sessions/new.html.erb
Giriş yap linkini henüz bağlamadık, ancak sayfayı http://localhost:3000/login adresinde görebiliriz.
Kodumuz tarafından oluşturulan formun HTML'i şuna banzer.
Buradan da görüleceği üzere gönderilen formdaki bilgilere params[:session][:email] ve params[:session][:password] isimleri ile erişebileceğiz.
Kullanıcıyı bulmak ve yetkilendirmek
Kayıt işlemlerinde olduğu gibi login işlemlerinde de ilk adım geçersiz girişleri işlemek. Öncelikle form gönderilince neler olduğunu inceleyip, sonrasında geçersiz girişler için bir mesaj yayınlaması yapacağız. En son da geçerli giriş yapılmasını çalışacağız.
Kontrolörümüzde minimalist bir create eylemi ve boş new ve destroy eylemleri tanımlayarak başlayalım. create eyleminde şimdilik sadece sanki geçersiz giriş yapılmış gibi new eylemine bir yönlendirme yapalım.
app/controllers/sessions_controller.rb
Şimdi bir giriş yapmaya çalışıp formu gönderince debug parametrelerinde gönderdiğimiz bilgileri görebiliriz.
Burayı dikkatli incelersek params değerinde iç içe hash değerler mevcut , bunlardan biri
Yani params[:session] değişkeninde
Hash değerini alırız. Bu durumda params[:session][:email] değişkeninde formda gönderilen email adresini ve params[:session][:password] değişkeninde girilen şifreyi okuruz.
Bir diğer deyişle create eylemi içinde (çağrıldığında - form gönderildiğinde) params değişkeni içinde kullanıcıyı yetkilendirmek için gereken tüm bilgiler var. Şimdi ActiveRecord sınıfının sağladığı User.find_by metodu ve has_secure_password ile otomatik olarak gelen authenticate metodlarından yararlanacağız.
Verilen değerlerle eğer kullanıcı email değeri tablomuzda varsa bir User nesnesi oluşturacağız ve bu nesnenin authenticate metodunu verilen şifre ile çağırırsak ve eğer şifre doğruysa true değer dönecektir. Şimdi create eylemimize bu kontrolü yapmak için bir ilave yapalım.
app/controllers/sessions_controller.rb
İlavelerimizde ilk satırda girilen email adresine sahip kullanıcı kaydını veri tabanından buluyoruz. Email adreslerini küçük harf olarak kaydetmiştik, bu yüzden girilen email adresini de küçük harfe çevirerek tabloda arıyoruz. Sonraki satır biraz kafa karıştırıcı gibi görünüyor ama hem Rails hem de Ruby programlamada çok kullanılan bir tekniktir.
Burada && işleminin (and) true çıktı vermesi için her iki tarafında da verilen işlemlerin sonucu true olmalıdır. Eğer user nesnesi yoksa , yani kayıt bulunamadıysa ilk işlem false değeri döner ve && işlemi sağ tarafa bakmadan olumsuz sonuç verir. Ruby'de karşılaştırma yaparken false ve nil harici tüm değerler true kabul edilir. Verilen email adresine sahip bir kullanıcı tabloda bulunmuyorsa user nesnesi değeri nil olacaktır.
Eğer kullanıcı tabloda bulunmuşsa && işlemi sağ tarafına geçilir, ve o kullanıcı nesnesinde authenticate metodu girilen şifre ile çağrılır. Eğer şifre doğruysa authenticate metodu true değer döner ve yetkilendirme için gerekenleri ekleyeceğimiz bloğa girilir.
Flash mesajı ile yönlendirme
Daha önce User nesnelerini eklerken hata olduğunda ActiveRecord tarafından üretilen otomatik mesajları kullanmıştık. Burada aynısını yapamayacağız , çünkü Session nesnemiz bir ActiveRecord nesnesi değil. Burada hata mesajı göndermek için, daha önce yeni kullanıcı kaydı başarılı olunca hoş geldin mesajı vermek için kullandığımız flash değişkenini kullanacağız.
app/controllers/sessions_controller.rb
Flash mesajının yayınlanmasını yerleşim dosyasında yaptığımız için default yerleşimi kullanan her sayfada olduğu gibi login sayfamızda da flash mesajları görünecektir.
Bu koda göre geçersiz kullanıcı giriş yapmaya kalkınca
Elemanı oluşturulacak ve Bootstrap'ta bu eleman için tanımlı sınıflar ile stili otomatik gelecektir. Şimdi geçersiz bir giriş deneyelim.
Gördüğümüz gibi Bootstrap hazır CSS kurallarını kullanarak kolayca görsel etkiler oluşturabiliyoruz. Flash mesajı kodlarken yanına yorumda tam olmadı yazmıştık, nedeni render ile yapılan yönlendirmenin tarayıcıdan yapılan bir istek olarak değerlendirilmemesidir. Bu durumda flash değişkeni tarayıcıdan başka bir istek yapıldığında hala değerini koruyor olacaktır. Yani örneğin geçersiz bir giriş yapmayı denedik ve tekrar mesaj ile aynı form açıldı, ve kullanıcı burada yukarıdan Ana Sayfa linkine tıklayıp geçiş yaptı, ana sayfada hala mesaj görünür kalacaktır.
Sayfayı yenilersek ikinci defa istek yapılınca flash değişkenindeki değer silinecek ve mesaj yok olacaktır. Şimdi bu hatayı test için bir rutin yazıp sonra da hatayı düzeltmeye çalışalım.
Flash için bir test
Bu yanlış flash davranışı uygulamamızda küçük bir bug. Daha önce öğrendiklerimize binaen bu hatayı test eden bir test rutini yazarak yolumuza devam edebiliriz. Login formunun gönderilmesi için kısa bir entegrasyon testi hazırlayacağız. Bu bize daha ileride yapacağımız benzer entegrasyon testleri için de örnek olacak. İlk önce terminalde entegrasyon testini üreterek başlayalım.
Şimdi neler yapacağımızı kafada bir toplayalım.
- Login sayfasını ziyaret edeceğiz
- Kullanıcı giriş formunun sağlıklı yayınlandığını göreceğiz
- Geçersiz bilgilerle dolu bir bir params değeri ile formun gönderilmesini sağlayacağız (POST)
- Kullanıcı giriş formunun tekrar ve beklenen status koduyla geldiğini göreceğiz
- Flash mesajının da geldiğini göreceğiz
- Başka bir sayfayı ziyaret edeceğiz (örn. Ana Sayfa)
- Flash mesajının artık görünmediğini kontrol edeceğiz
Bu doğrultuda hazırladığımız test dosyası kodu.
test/integration/users_login_test.rb
Bu testi şu anda denersek başarısız olacaktır. Sadece bu testi denemek için.
Burada 12. satırda yani
Beklentisi gerçekleşmedi diyor, yani ana sayfaya geçince hala hata mesajı görünüyor (flash değeri bir şey içeriyor, nil değil).
Burada çözümü flash yerine flash.now nesnesini render öncesi kullanarak yaparız. flash.now nesnesi böyle render işlemleri için tanımlanmıştır, ve sadece bulunulan eylem içinde geçerlidir.
app/controllers/sessions_controller.rb
Şimdi test denersek başarılı olacaktır.
Başarılı giriş işlemi
Başarısız giriş denemelerini işledik, sırada başarılı giriş işleminde yapılacaklar var. Bu bölümde bir oturum cookie'si yardımı ile tarayıcı kapatılana kadar kullanıcıyı giriş yapmış göstermesini öğreneceğiz. İleride nasipse tarayıcı kapatılıp açıldıktan sonra da geçerli kalan giriş işlemini yapacağız.
Oturumları yönetebilmek için kontrolörler ve görseller arasında çalışacak bir çok metodlar tanımlamamız gerekiyor. Daha önce görmüştük Ruby buna benzer metodları bir paket içinde toplamak amacıyla Module bloklarını kullanır. Sessions kontrolörümüz oluşturulurken yardımcı kodlarını yerleştirmemiz için helper dosyaları da otomatik olarak üretiliyor.
app/helpers/sessions_helper.rb
Bu yardımcı dosyanın tüm kontrolörlerde geçerli olabilmesi için ana kontrolörümüz (Application controller) içine dahil edilmesi yeterli olacaktır.
app/controllers/application_controller.rb
log_in metodu
Kullanıcının giriş yaptığını kontrol etmek Rails tarafından sağlanan session metodu ile kolayca yapılır (bu metodun bizim tanımladığımız Sessions kontrolörü ile alakası yoktur, Rails metodudur). session nesnesini bir Hash olarak düşünerek aşağıdaki gibi bir atama yapabiliriz.
Bu atama ile kullanıcının tarayıcısında user.id değerinin şifrelenmiş bir versiyonu cookie olarak saklanır. Bunu kullanarak uygulamamızın sayfalarında session[:user_id] değerini o kullanıcının giriş yapmış olduğunu test için kullanabiliriz. Bir kısa bilgi kalıcı cookie oluştururken de cookies metodu kullanılır, session kullanarak üretilen cookie'ler tarayıcı kapanınca silinecektir.
Kullanıcının giriş yapıp yapmadığını birçok yerde kontrol etmek gerekeceği için ilk önce Sessions yardımcı dosyası içinde kullanıcı giriş yapınca cookie oluşturan bir log_in metodu tanımlayalım.
app/helpers/sessions_helper.rb
Geçici cookie'ler session metodu ile üretilirken şifrelenerek üretileceği için tarayıcı ve server arasındaki trafiği izleyen bir saldırganın bu bilgiden kullanıcı id değerini öğrenmesi imkansızdır. Bizim ekstra bir şey yapmamıza gerek yok. Aslında tam da koruma sağlayamaz , değerin ne olduğunu çözemese de saldırgan tarayıcı ve server arasındaki trafiği kopyalayarak kullanabilir. Bu konuda Rails klavuzlarında bir makale mevcut. Ancak şimdilik oldukça güvenilir bir yöntem olarak bu yöntemle devam edeceğiz daha sonra tekrar bu konulara döneriz nasipse.
Bir de Session Fixation diye bir saldırı metodu var , bunu önlemek için de kısaca her kullanıcı girişi öncesi Rails'in reset_session metodunu kullanmak gerekiyor. Şimdi bu bilgiler ışığında Sessions kontrolörü içindeki create eylemimizde kullanıcının giriş yapmasını sağlayan rutini düzenleyelim.
app/controllers/sessions_controller.rb
Buradaki kısaltılmış yönlendirmeye bakalım
Bunu daha önce de görmüştük aslında bu satırı görünce Rails otomatik olarak
kullanıcı profil sayfasına yönlendirme yapar.
Bu ilavelerimiz ile kullanıcı sisteme giriş yapacaktır, ancak bir kontrol edersek kullanıcının giriş yapmış olduğuna dair herhangi bir bilgi sayfalarda görünmeyecektir.
Şu andaki kullanıcı
Kullanıcının id değerini güvenli bir şekilde oturum cookie'sinde sakladıktan sonra, şimdi diğer sayfalarda da bunu okumayı görelim. Bu amaçla tanımlayacağımız current_user metodunu veri tabanından ilgili kullanıcının bilgilerini okumak için kullanacağız. Bu metodun amacı
veya
kodlarının doğru işlem yapmasını sağlamak.
Kullanıcının bilgilerini veri tabanında bulmak için find metodunu kullanabiliriz.
Ancak find metodu verilen id değerinde bir kayıt bulamayınca (giriş yapmamış kullanıcı) Rails bir hata üretecektir. Bunun yerine aynı kontrolörde email ile kullanıcı bulduğumuz gibi
şeklinde find_by metodunu kullanırız. Bu durumda bir hata mesajı üretilmeyecek ve eğer kullanıcı kaydı bulunamazsa user nesnesine nil değeri gelecektir. Şimdi current_user metodunu şöyle tanımlayabiliriz.
Eğer session cookie olarak bulunamazsa metodumuz beklediğimiz gibi nil değer dönecektir. Fakat sayfada current_user defalarca kullanılırsa her biri için veri tabanına tekrar başvuru yapılacaktır. Bunu iyileştirmek için User.find_by sonucunu bir oluşum değişkenine atmak bir Ruby geleneğidir. Böylece bir kere kullanıcı bilgisi veri tabanından çekildikten sonra bu oluşum değeri kullanılabilir.
Bu kodlama notasyonu Ruby'de çok tanıdık bir notasyon ama daha da kısaltılarak kullanılır.
Biliyoruz ki || (veya) işlemi herhangi bir tarafı true verirse onu döner, bu amaçla ilk önce operatörün sol tarafına bakar ve eğer orası boolean olarak Ruby tarafından false değilse (yani değer nil ya da false değilse) o değeri döner operatörün sağ tarafına bakmaz bile. Eğer sol taraf Ruby için boolean false ise operatörün sağ tarafındaki işlemin sonucunu döner. Ruby dilinde bunun da daha kısaltılmış bir versiyonu var.
Bu karmaşık görünüyor ama Ruby'nin meşhur ||= (veya ataması) operatörünü kullanıyor.
Bu ||= operatörü ne ola ki?
Veya ataması operatörü Ruby'de yaygın kullanılır, ve Ruby kodlayanların bunu bilmesi önemlidir. İlk başta büyülü bir şey gibi görünebilir. Basit bir eşitlik ile başlayalım.
Bu işlem için birçok programlama dilinde bir kısaltma vardır.
Diğer işlemler için de benzer operatörler kullanılabilir.
Her durumda x = x işlem y yerine x işlem= y kullanılır.
Başka bir Ruby şablonu da bir değişkene değeri nil ise bir değer atamak , ama nil değilse değerini ellememek. Bunu veya operatörü || kullanarak yaparız.
Boolean olarak nil değeri Ruby tarafından false kabul edileceği için, ilk atamada nil || "bar" işlemi sonucu "bar" değeri dönecektir. Benzer şekilde ikinci atamadaki @foo || "baz" işlemi "bar" || "baz" olarak değerlendirilir ve "bar" değeri döner. Bunun nedeni bir değer nil ya da false değilse boolean olarak true kabul edilir, ve || işlemi ilk bulduğu true değerde işlemi sonlandıracağı için ilk değeri geri dönecektir.
Burada kritik nokta Ruby operatörün iki tarafına boolean karşılığının true ya da false olmasına göre karar verirken, işlem sonucu olarak ilk true kabul ettiği değeri döner. Kafa karıştıran hep bu true ya da false değil de değerin işlem sonucu olarak dönmesi oluyor.
Yukarıdaki bilgiler sonucunda
yazılabilir. İşte bu
eşitliğinin nasıl olduğunu anlamamızı sağlıyor.
Bu açıklamalar doğrultusunda current_user metodumuzu yardımcı dosyamıza ekleyelim.
app/helpers/sessions_helper.rb
Artık çalışan bir current_user metodumuz olduğuna göre uygulamamızın davranışlarını şu andaki kullanıcıya göre değiştirmeye başlayabiliriz.
Yerleşimdeki linkleri kullanıcıya göre değiştirmek
İlk hedefimiz kullanıcı giriş yapmayı başarınca yerleşimdeki linkleri değiştirmek olacak. Mesela giriş yapılınca çıkış yapmak için link olmalı, kullanıcının profiline gitmesi için link olmalı vs. Örneğim bir Hesabım açılan menüsünde Profilim ve Çıkış yap linkleri olsa iyi olur.
Yerleşimdeki linkleri kullanıcının giriş yapıp yapmadığına göre değiştirmek için bir if bloğu kullanabiliriz.
Burada düşündüğümüz logged_in? metodu boolean değer dönen bir metod olacak. Bir kullanıcı giriş yapmışsa oturum değişkenlerinde varolan kullanıcı var mı? diye bakarız. Yani current_user değeri nil olmaması gerekir. Şimdi yardımcı metodlarımıza bunu da ekleyelim.
app/helpers/sessions_helper.rb
Bu ilaveler ile birlikte linklerimizi kullanıcının giriş yaptığına göre değiştirebiliriz. 4 yeni linkimiz olacak bunlardan ikisini ilerideki bölümlerde işleyeceğiz şimdilik sadece oraya koyacağız.
Çıkış yapma linki de daha önce tanımladığımız logout_path adresine gönderecek.
Dikkat ettiyseniz çıkış yapma linkimiz argümanında bir HTTP DELETE isteği yapılacağını belirtiyor, yani session nesnesi silinecek (Tarayıcılar DELETE istekleri işlemezler, Rails bunu JavaScript ile yapar).
Ayrıca cırrent_user sayfasına gönderen bir Hesabım linkimiz de olacak.
Bunu daha açık ifadeyle şöyle de yazabilirdik.
İkisi de aynı çalışır çünkü Rails current_user nesnesinin bir User nesnesi olduğunu bildiği için ona yapılan linkleri otomatik olarak user_path(current_user) adresine çevirir.
Son olarak eğer kullanıcı giriş yapmadıysa login sayfasına bir linkimiz olmalı. Aslında bu linki daha önce yerleşime ekledik ama adresini bağlamamıştık.
Şimdi bunlar doğrultusunda uygulamamız yerleşiminin linklerinin bulunduğu header kısmi görseline bu değişiklikleri ekleyelim.
app/views/layouts/_header.html.erb
Bu haliyle çalıştırınca sadece Giriş Yap kısmı çalışacaktır ve giriş yaptığınızda şimdilik dropdown menü açılmayacak. Eğer çıkış yapmak isterseniz tarayıcıyı kapatıp açabilirsiniz. Tarayıcıyı kapatıp açmak işe yaramıyorsa tarayıcınızın ayarlarından bu site için çerezlerin pencere kapatılınca silinmesini seçmeniz gerekebilir. Örneğin Chrome tarayıcıda adresin solundaki "site bilgilerini görüntüle" tıkladıktan sonra açılan menüden "Çerezler ve site verileri", "Cihaz üzerindeki site verilerini yönetin" sekmesinde localhost seçeneklerde "Tüm pencereler kapatılınca verileri sil" seçiniz.
Menü toggle
Giriş işlemi yaptığımızda Hesabım açılır menüsü görünecek ama tıkladığımızda menü açılmayacaktır. Bunların çalıştırılması için Rails, JavaScript yardımı kullanmalıdır. Yıllar boyunca Rails uygulamalarında JavaScript kullanmak için birçok teknikler kullanılmış. Şu anda Importmap kullanılıyor, bunun için importmap-rails gem'i zaten Gemfile içinde mevcut. Bu noktada Rails'e importmap, turbo ve stimulus kullanacağımızı belirtmeliyiz.
Aslında rails new ile uygulama üretirken bunlar otomatik kuruluyormuş, ama biz --skip-bundle opsiyonu kullanıp daha sonra manual olarak bundle işlerini yaptığımız için kurulmadan kalmış. Bu komutla JavaScript manifesto dosyamızda bazı ilave dosyalar uygulamamıza eklenecektir.
app/assets/config/manifest.js
Bu komutla beraber yerleşim ana dosyamız içine de
satırı ilavesi gelecektir.
Yapılandırmamız tamam , sırada açılır menüyü çalıştırmak için yapacağımız kod ilaveleri var.
- Bir CSS active sınıfı kuralı ekleyerek açılır menünün görünür olmasını sağlayacağız.
- JavaScript kodlarımız için bir custom klasörü ve menü için bir menu.js kod dosyası ekleyeceğiz.
- Importmap yardımıyla Rails'e JavaScript dosyalarımızı nasıl kullanacağını tarif edeceğiz.
- menuı.js kod dosyamızı application.js dosyamızdan dahil edeceğiz.
Adım 1'de active sınıfını display özelliği block olacak şekilde stil dosyamıza ekliyoruz. Bu kural Bootstrap sınıfı dropdown-menu davranışını değiştirerek açılır menünün görünür olmasını sağlayacaktır.
app/assets/stylesheets/custom.scss
İkinci adım ise uygulamamız app/javascript klasörü içerisine custom adında bir alt klasör ekleyecek ve o klasörde menu.js adında bir yeni kod dosyası ekleyecek.
Şimdi en karışık olanı. menu.js dosyası içine bir olay izleme kodu ekleyerek Hesabım linki tıklanınca menüyü aktif/pasif etmek için active sınıfını elemana ekleyecek ya da çıkaracak. Olay işleyicinin sayfanın tamamen yüklenmesinden sonra devreye girmesini sağlamak için ikinci bir olay işleyici ile sayfanın yüklendiğini takip edeceğiz. Sayfanın tamamen yüklenmesini DOMContentLoaded olayını işleyerek, genel tıklama olaylarında ise click olayını işleyerek takip ederiz. Turbo dolayısıyla birinci olayın ismi farklı olacak Turbo dokümanında belirtildiği üzere olay ismimiz turbo:load olmalıdır.
app/javascript/custom/menu.js
Kısaca incelemek gerekirse , ana blok sayfa tamamen yüklenince devreye giriyor (turbo:load olayı gerçekleşince). Sayfada id değeri account olan eleman bulunuyor (ki bu bizim Hesabım linkimiz) ve o tıklanınca id değeri dropdown-menu olan elemanın sınıf isimlerine active değeri ekleniyor ya da çıkarılıyor (ki bu da şu anda sayfada görünmez olan menü listemiz). Burada konsantrasyonumuz Rails olduğu için bu JavaScript kodunu ayrıntılı incelememize gerek yok, fakat merak edenler internette JavaScript olay işleme teknikleri üzerine araştırma yapabilir.
Adım 3 bir yapılandırma ayar adımı. Importmap kullanarak Rails'e custom klasöründeki kod dosyalarını da içermesi gerektiğini anlatacağız.
config/importmap.rb
Son olarak ta ana JavaScript dosyamızdan bu yeni eklediğimiz JavaScript dosyasını uygulamamıza dahil edeceğiz.
app/javascript/application.js
Bu kodun ilk satırında importmap yapılandırmasının nasıl yapılacağına ve sebebine dair bilgileri içeren bir bağlantı adresi var, dileyen inceleyebilir.
Artık menümüz tıklanınca açılır hale gelmiş olmalı.
Mobil Stili - Rails server'a mobil cihaz ile bağlanmak
Öncelikle mobil cihazımızdan yerel ağdaki bilgisayarımızda çalışan Rails server'ına erişebilmemiz gerekiyor. Bunun için ilk önce güvenlik duvarına bir kural ekleyip 3000 portuna gelen isteklere izin vermesini sağlamalıyız. Ben Windows üzerinde çalıştığım WSL Ubuntu terminalinde bunu şöyle yaptım.
Önce uygulama arama penceresinde Güvenlik duvarı ve ağ koruması'nı buldum. Gelişmiş ayarlar bölümünde Gelen Kuralları sekmesini açtım. Yeni Kural linkine tıklayarak yeni bir kural ekleme penceresini açtım. Kural Türü olarak Bağlantı Noktası seçip Sonraki butonuna tıkladım. Protokol TCP olacak ve Belirli yerel bağlantı noktaları kutucuğuna Rails server portu olan 3000 değerini girdim ve Sonraki butonuna tıkladım. Eylem olarak Bağlantıya izin ver seçili olarak Sonraki butonuna tıkladım. Profil sekmesinde olası tüm yerel bağlantı türleri (Etki alanı, özel, ortak) seçili olarak Sonraki butonuna tıkladım. En son sekmede kuralıma bir isim vererek Son butonuna tıkladım ve yeni kuralım oluştu, böylece bilgisayarım 3000 portuna gelen bağlantı isteklerine izin verecek.
Normalde Rails server Localhost'tan gelen bağlantılara cevap veriyor. SSL bağlantı için komutumuz şöyleydi.
Bunun yerine tüm ağa cevap vermesi için bu komutu
Şeklinde girersek tüm ağa cevap verecektir. Şimdi cep telefonumuzdan bilgisayarımızın IP adresini girersek mesela bende https://192.168.1.27:3000 bilgisayarımızdaki Rails server'a erişebiliriz.
Biliyorsunuz burada Gelişmiş bağlantısına tıklayıp 192.168.1.27 sitesine ilerle linkine tıklarsam sayfam açılacak.
Buna şükür en azından cep telefonu ile Rails server'a bağlandık.
Burada amacımız Web sayfa tasarımı ile ilgili bilgi vermek olmadığı için basit bir şekilde mobil uyumlu bir görsel oluşturmaya çalışacağız. Aslında mobildeki görünümü tarayıcımızın pencere boyutunu değiştirerek de deneyebiliriz ancak gerçek bir cihazla test etmek bana daha mantıklı geldi.
İlk adımımız sayfa yerleşimimizde head kısmına viewport adında bir meta tag var mı bakmak olacak.
app/views/layouts/application.html.erb
Normalde bunun otomatik olarak eklenmesi gerek, ama erken versiyon kullananlarda eklenmemiş olabilir.
Mobil cihazlarda menü görünümü sorununu çözmek için Hamburger Menu denen yapı kullanılır. Bunu yapabilmek için header dosyamıza bazı kodlar eklemeliyiz.
app/views/layouts/_header.html.erb
En alttaki <ul> elemanı zaten oradaydı ona değişiklik yaptık, diğer satırlarsa yeni geldi. navbar-header sınıf adına sahip eleman Bootstrap CSS kuralları gereği 768 pikselden geniş ekranlarda görünmez olur, bu yüzden hamburger buton normal PC ekranında görünmez. collapse navbar-collapse sınıf isimlerinin her ikisine sahip eleman da (yani ul elemanı) 768 pikselden dar ekranlarda görünmez olur. Şimdi JavaScript kodumuza ilave yaparak açılan menü gibi bir açılma sağlayacağız, ama bu sefer active sınıf adı kullanarak değil Bootstrap'ın collapse sınıf adını kullanarak.
app/javascript/custom/menu.js
Koda eklediklerimize kısaca bakarsak, önce id değeri hamburger olan elemanı buluyoruz. Elemana tıklanınca bizim ul elemanını bulup sınıf isimlerinde collapse sınıf adını varsa yok yoksa var ediyoruz. Böylece dar ekranda hamburger butona her tıkladığımızda menü görünür ya da görünmez oluyor.
Son olarak footer bölümündeki linklerin de mobil cihazlarda yan yana değil alt alta olmasını sağlasak iyi olacak. Bu amaçla CSS media gruplaması kullanarak yazdığımız kuralların sadece 768 pikselden dar ekranlarda uygulanmasını sağlayacağız.
app/assets/stylesheets/custom.scss
Şimdi mobil cihazda footer linkleri alt alta yerleşecektir.
Görseli hallettikten sonra biraz toparlama yapalım. JavaScript kodumuz içinde iki farklı eleman için benzer kodlar yazdık, bu bize bu kodların ileride de tekrarlayabileceği olasılığını gösterir. Bu yüzden kendimizi tekrarlamamak adına (DRY prensibi) JavaScript kodumuzu daha fonksiyonel bir hale getirelim.
app/javascript/custom/menu.js
Artık bir elemana tıklanınca bir diğer elemanın sınıf isimlerine ekleme-çıkarma yapmak gerekirse sadece alt bloğa bir satır daha ekleyerek yapabiliriz.
Görsel Değişikliklerini Test Edelim
Elle kontrol ederken uygulamamızın kullanıcı girişi yapabildiğini gördük. Şimdi bunları yazılımsal görmek için bir entegrasyon testi yazacağız. Şu eylemleri test edeceğiz.
- Giriş yap sayfasını ziyaret edeceğiz
- Oturum bilgilerine geçerli veri göndereceğiz (yazılımla giriş yapacağız)
- Giriş yap linkinin yok olduğunu göreceğiz
- Çıkış yap linkinin var olduğunu göreceğiz
- Hesabım linkinin var olduğunu göreceğiz
Bu değişiklikleri görebilmemiz için test rutinlerimiz veri tabanında geçerli bir kullanıcının verileri ile giriş yapmalıdır. Bunu yapmanın default yolu Rails'in fixtures denilen özelliklerini kullanmaktır. Bunlar veritabanına yüklenecek verileri ifade etmenin bir yoludur. Şimdi bir boş dosyada kendi fixture bilgilerimizi saklayacağız.
Şu andaki hedefimize göre bize geçerli email adresi ve şifresi olan bir tek kullanıcı yeterli. Kullanıcıya giriş yaptıracağımız için Sessions kontrolörü create eylemine gönderecek geçerli bir şifremiz olması gerekiyor. Hatırlarsak şifre veritabanında olduğu gibi saklanmıyor ve bcrypt ile kripto kodlanmış olarak saklanıyor. Bu yüzden fixture bilgilerimizde password_digest özelliği bulunacak ve biz bunu elde etmek için kullanıcı şifremizi kendi yazdığımız bir digest metodu yardımıyla kripto kodlayacağız.
Daha önce gördük ki password_digest alanına yazılan değer kullanıcının kayıt olurken girdiği şifreden bcrypt ile kodlanıyor (has_secure_password sayesinde). Aynı tekniği kullanarak fixture bilgilerimizdeki şifreyi de dönüştürmeliyiz. secure password kaynak kodunu incelediğimizde bunu şöyle yapabileceğimizi görürüz.
Burada ilk parametre kodlanacak olan şifre değeridir. cost ise hesaplamanın karmaşıklık derecesini belirler, cost maliyet demek olduğuna göre bu arttıkça hesap daha karmaşık olacaktır. Hesap karmaşıklık derecesi arttıkça şifre kodlamasının çözülmesi imkansıza doğru gider. Normalde sitemiz çalışırken işi en karmaşığa getirmek güvenliği arttırır, ancak test esnasında hesabı daha basitleştirip işlemlerin kısa sürede bitmesi işimize gelir.
Rails test işlemlerinde min_cost denen bir değer kullanıyor, bu değer test için true değerinde. Bunu kullanarak cost değerini bulmak için
Eşitliğini kullanabiliriz. Eşitliğin sağ tarafındaki formüle ternary operatör işlemi denir, kısaca açıklarsak ? ve : ile ayrılmış üçdeğer var.
şeklinde çalışır. Bir karşılaştırma yapılır sonuç true ise iki nokta üstüste işaretinin solundaki değer, sonuç false ise sağındaki değer işlemden döner. Bu durumda testler çalışırken cost değerimiz BCrypt::Engine::MIN_COST olacaktır ve basitleştirilmiş kodlama yapılacaktır. Test dışında ise standart BCrypt::Engine.cost değeri kullanılacaktır.
Kendi digest metodumuzu koyabileceğimiz değişik yerler var ama mantıklısı model dosyamız olan user.rb dosyasına eklemek olacak.
app/models/user.rb
Metodumuz hazır olduğuna gör fixture değerlerimizin olduğu dosyayı yazabiliriz. Bu dosya aslında Rails tarafından uygulamamızın test/fixtures klasöründe users.yml olarak oluşturulmuş durumda ancak içine şimdi geçerli bilgiler yazacağız. Şu anda dosyada otomatik üretilmiş geçersiz bilgiler var.
Şimdi geçerli bir kullanıcı için bilgilerimizi yazalım.
test/fixtures/users.yml
Burada password_digest değerini hesaplamak için kendi yazdığımız User.digest metodunu kullandık ve "password" karakterlerinden oluşan şifremizin kodlanmasını sağladık.
Aynı görsellerin arasına Ruby kodu yazar gibi yml dosyası içindeki değerlere de Ruby kodunun hesapladığı değeri girebiliyoruz.
Şimdi fixture içinde geçerli bir kullanıcı verileri koyduğumuza göre bunlar test kodlarımızdan bu kullanıcı verilerine
olarak ulaşabiliriz. Burada users değeri bizim users.yml dosyamızın içindeki bilgilere karşılık gelir. Oradan anahtar değeri deneme olan veriler okunacak.
Artık entegrasyon test kodlarımızı yazabiliriz. Daha önce geçersiz kullanıcıları test ettiğimiz login test dosyasında ilaveler yapacağız.
test/integration/users_login_test.rb
Burada kullandıklarımız:
Kullanıcı sayfasına yönlendirildiğini bekler.
Yönlendirilen sayfaya gidilmiş olmasını bekler.
Giriş yap linklerinin olmadığını (daha doğrusu sıfır tane olduğunu) bekler.
Şimdi test işlemini çalıştırıp görelim.
ve sonuç hatasız.
Kayıt Sonrası Otomatik Giriş
Yetkilendirme sistemimiz şu anda çalışıyor , ancak yeni kullanıcılar kayıt yaptırdıktan sonra neden otomatik olarak giriş yapmış olmadıkları konusunda kafa karışıklığı yaşayabilirler. Çünkü şu anda yeni kullanıcı kayıt yaptıktan sonra kullanıcı sayfasına gidiliyor fakat hala giriş yap linkine tıklayıp form doldurması gerekiyor. Kayıt işleminin bir devamı olarak yeni kayıt yapanların sisteme girişlerini otomatik gerçekleştireceğiz. Bunu gerçekleştirmek için yapmamız gereken Users kontrolörü create eylemini işlerken log_in metodunu çağırmak. Oturumla ilgili siber saldırılara karşı da log_in metodu çağırmadan hemen önce oturumu sonlandırarak güvenliği arttıracağız.
app/controllers/users_controller.rb
Bu davranışı test etmek için de geçerli kayıt işlemini test ettiğimiz kodlara kullanıcının giriş yapmış olup olmadığını görmek için ilave yapacağız. Öncelikle sessions_helper.rb dosyasındaki logged_in? metodu gibi çalışacak bir is_logged_in? metodu tanımlayacağız. Çünkü yardımcı metod dosyalarındaki metodlara test kodları erişemez. Örneğin yine aynı dosyadaki current_user metodunu da test kodunda kullanamayız ancak session değerlerine ulaşarak yapacağız. Aslında sessions_helper.rb dosyasını test dosyamızdan çağırıp kullanabiliriz ancak bunun bazı sakıncalarını daha sonra testlerde cookie işlemleri yaparken göreceğiz. Şimdilik testlerde çalışan bir yardımcı dosya içine yazmak iyi olacak. Mevcut test_helper.rb dosyamıza ilave yapacağız.
test/test_helper.rb
Artık yeni kayıt testimizin koduna kullanıcının giriş yapmış olması beklentisini ekleyebiliriz.
test/integration/users_signup_test.rb
Test yeşil çıkmalıdır.
Çıkış Yapmak
Şimdiye kadar yaptıklarımız sayesinde kullanıcı her zaman giriş yapmış olarak kalıyor. Bu kısımda da çıkış yapabilme kabiliyeti ekleyeceğiz. Çıkış Yap linkimiz şu anda Hesabım açılır menüsü içinde mevcut ama çalışmıyor. Yapmamız gereken burası için oturumu sonlandıracak bir kontrolör eylemi tanımlamak.
Sessions kontrolörümüzün REST yapıya uygun olması için giriş yapma sayfasında new eylemini ve girişi gerçekleştirmek için create eylemini kullanıyoruz. Bu doğrultuda oturumu sonlandırmak için destroy eylemi kullanmamız gerekir.
Çıkış yapma işlemini gerçekleştirmek için log_in metodunun yaptıklarını yok etmek yeterli olacaktır. Bunun için de
kodunu kullanabiliriz. Çünkü şu anda oturumdaki tek değer user_id değeri ve şimdilik işimizi de görür. Fakat ileride oturuma başka değerler de ekleneceğini düşünerek , güvenlik amacıyla tüm oturumu sonlandıran reset_session metodunu kullanmak daha doğru olacaktır.
Oturumu sıfırlamanın dışında aktif kullanıcı bilgisini de nil yapmamız gerekir ki kullanıcı hala giriş yapmış gibi görünmesin. Çıkış yapıldıktan sonra da ana sayfaya geri dönülmesini sağlayacağız. Şimdi log_in yardımcı metodumuz gibi bir log_out yardımcı metodu kodlayarak başlayalım.
app/helpers/sessions_helper.rb
Şimdi bu log_out metodumuzu Sessions kontrolörü destroy eyleminde kullanabiliriz. Rails HTTP durum kodları sayfasında bulunan durum kodlarından 303 :see_other değerini yönlendirme yaparken kullanacağız. Bu tarayıcının gönderdiği verinin server tarafından alınmış olduğunu kabul ederek yeni sayfaya gitmesini sağlıyormuş.
app/controllers/sessions_controller.rb
Artık Hesabım açılır menüsü altındaki Çıkış Yap linkimiz işini görmeye başlamış olmalıdır. Şimdi bunun için de bir test yazalım. Bu amaçla users_login_test.rb test kodlarımıza ilaveler yapacağız.
test/integration/users_login_test.rb
Öncelikle geçersiz giriş yapıldığında is_logged_in? metodumuzun çalışmasını da teste ekledik. Sonrasında geçerli giriş test rutini arkasına çıkış yapmak için delete logout_path ile logout_path adresine bir DELETE isteği gönderiyoruz, yani çıkış yap linki tıklanmış gibi. Çıkış olduysa kullanıcı giriş yapmış görünmemelidir, durum kodunun :see_other olmasını bekliyoruz, ana sayfaya yönlendirme olmasını bekliyoruz ve sayfaya geçişi takip ediyoruz. Giriş Yap linkinin var olduğunu, Çıkış Yap linkinin yok olduğunu, Profil linkinin yok olduğunu bekliyoruz. Bu test kodu bir hayli uzun oldu inşallah ileride parçalara ayırırız.
Test çalıştıralım sorunsuz çalışacaktır
Bu Bölümde Neler Öğrendik
- Rails bir sayfadan diğerine geçerken saklaması gereken bilgileri session metodunu kullanarak çerezlerde saklayabilir.
- Giriş yapma formu ile yeni bir oturum açarak kullanıcı girişi yapıldı
- Yayınlanan sayfalarda flash.now metodu ile mesajlar gösterildi
- session metodu kullanarak tarayıcıya güvenli bir şekilde kullanıcı id değeri saklayabiliriz
- Giriş yapılmış olması durumuna göre linklerin görünmesi yada görünmemesi sağlanabilir
- Entegrasyon testlerinde yönlendirmeleri, veri tabanındaki değişimleri, yerleşimde olması gereken değişimler kontrol edilebilir
Hiç yorum yok:
Yorum Gönder