13 Mayıs 2025 Salı

Rails 7 Denemeler 7

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

 

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.

$ rails generate controller Sessions new


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 

  resources :users

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

Rails.application.routes.draw do
  # get "sessions/new"
  root "static_pages#home"
  get "/help", to: "static_pages#help"
  get "/about", to: "static_pages#about"
  get "/contact", to: "static_pages#contact"
  get "/signup", to: "users#new"
  get "/login", to: "sessions#new"
  post "/login", to: "sessions#create"
  delete "/logout", to: "sessions#destroy"
  resources :users
.....
end


İ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

require "test_helper"

class SessionsControllerTest < ActionDispatch::IntegrationTest
  test "should get new" do
    get login_path
    assert_response :success
  end
end


Bu eklediğimiz yönlendirmelerin de bir tablosunu düşünürsek

HTTP metod   URL        Eylem      İsimlendirilmiş     Amacı
--------------------------------------------------------------------------------
GET         /login      new        login_path          Yeni oturum açma sayfası
POST        /login      create     login_path          Yeni oturum üretmek
DELETE      /logout     destroy    logout_path         Oturumu kapatma işi


Şu ana kadar bir sürü yönlendirmeyi uygulamamıza ekledik, neler var diye kontrol etmeye kalksak, terminalde görebiliriz.

$ rails routes
   Prefix    Verb   URI Pattern            Controller#Action
   root      GET    /                      static_pages#home
   help      GET    /help(.:format)        static_pages#help
   about     GET    /about(.:format)       static_pages#about
   contact   GET    /contact(.:format)     static_pages#contact
   signup    GET    /signup(.:format)      users#new
   login     GET    /login(.:format)       sessions#new
             POST   /login(.:format)       sessions#create
   logout    DELETE /logout(.:format)       sessions#destroy
   users     GET    /users(.:format)       users#index
             POST   /users(.:format)       users#create
   new_user  GET    /users/new(.:format)     users#new
   edit_user GET    /users/:id/edit(.:format)  users#edit
   user      GET    /users/:id(.:format)       users#show
             PATCH  /users/:id(.:format)       users#update
             PUT    /users/:id(.:format)       users#update
             DELETE /users/:id(.:format)       users#destroy


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.

    <%= form_with model: @user do |f| %>
      ...
...
...
    <% end %>


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. 

form_with model: @user

ile formun gönderme eyleminin /users adresine bir POST isteği olarak gerçekleşeceğini bildiriyoruz. Üretilen form elemanına bakarsak

<form action="/users" accept-charset="UTF-8" method="post">

olduğunu görürüz. Konu oturumlara gelince hedef URL ve kapsamı belirterek form oluşturacağız.

form_with url: login_path, scope: :session


Şimdi bu bilgiler doğrultusunda login sayfamızın görselini düzenleyelim.

app/views/sessions/new.html.erb

<% provide(:title, "Log in") %>
<h1>Giriş Yapınız</h1>

<div class="row">
  <div class="col-md-6 col-md-offset-3">
    <%= form_with(url: login_path, scope: :session) do |f| %>
      <%= f.label :email %>
      <%= f.email_field :email, class: 'form-control' %>
      <%= f.label :password, "Şifre" %>
      <%= f.password_field :password, class: 'form-control' %>
      <%= f.submit "Giriş", class: "btn btn-primary" %>
    <% end %>
    <br/><p>Yeni Kullanıcı? - <%= link_to "Kayıt olun!", signup_path %></p>
  </div>
</div>


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.

<form action="/login" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="..." autocomplete="off">
  <label for="session_email">Email</label>
  <input class="form-control" type="email" name="session[email]" id="session_email">
  <label for="session_password">Şifre</label>
  <input class="form-control" type="password" name="session[password]"
      id="session_password">
  <input type="submit" name="commit" value="Giriş"
      class="btn btn-primary" data-disable-with="Giriş">
</form>


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

class SessionsController < ApplicationController
  def new
  end
  def create
    render "new", status: :unprocessable_entity
  end
  def destroy
  end
end


Şimdi bir giriş yapmaya çalışıp formu gönderince debug parametrelerinde gönderdiğimiz bilgileri görebiliriz.

#<ActionController::Parameters {
    "authenticity_token"=>"...",
    "session"=>{
      "email"=>"user@example.com",
      "password"=>"foobar"
    },
    "commit"=>"Giriş",
    "controller"=>"sessions",
    "action"=>"create"
  } permitted: false>


Burayı dikkatli incelersek params değerinde iç içe hash değerler mevcut , bunlardan biri

{"session"=>{ "email"=>"user@example.com",  "password"=>"foobar" } }

Yani params[:session] değişkeninde 

{ "email"=>"user@example.com",  "password"=>"foobar" }

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

class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # Kullanıcı girişi geçerli yap ve profiline gönder
    else
      # Bir hata mesajı üret
      render 'new', status: :unprocessable_entity
    end
  end

  def destroy
  end
end


İ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.

    if user && user.authenticate(params[:session][:password])

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

....
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # Kullanıcı girişi geçerli yap ve profiline gönder
    else
      flash[:danger] = 'Geçersiz email/şifre kombinasyonu' # Tam doğru değil!

      render 'new', status: :unprocessable_entity
    end
  end
....


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. 

      <% flash.each do |mesaj_tipi, mesaj| %>
        <div class="alert alert-<%= mesaj_tipi %>"><%= mesaj %></div>
      <% end %>

Bu koda göre geçersiz kullanıcı giriş yapmaya kalkınca 

<div class="alert alert-danger">Geçersiz email/şifre kombinasyonu</div>

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.

$ rails generate integration_test users_login
      invoke  test_unit
      create    test/integration/users_login_test.rb


Şimdi neler yapacağımızı kafada bir toplayalım.

  1. Login sayfasını ziyaret edeceğiz
  2. Kullanıcı giriş formunun sağlıklı yayınlandığını göreceğiz
  3. Geçersiz bilgilerle dolu bir bir params değeri ile formun gönderilmesini sağlayacağız (POST)
  4. Kullanıcı giriş formunun tekrar ve beklenen status koduyla geldiğini göreceğiz
  5. Flash mesajının da geldiğini göreceğiz
  6. Başka bir sayfayı ziyaret edeceğiz (örn. Ana Sayfa)
  7. 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

require "test_helper"

class UsersLoginTest < ActionDispatch::IntegrationTest
  test "geçersiz bilgi ile giriş" do
    get login_path
    assert_template 'sessions/new'
    post login_path, params: { session: { email: "", password: "" } }
    assert_response :unprocessable_entity
    assert_template 'sessions/new'
    assert_not flash.empty?
    get root_path
    assert flash.empty?
  end
end


Bu testi şu anda denersek başarısız olacaktır. Sadece bu testi denemek için.

$ rails test test/integration/users_login_test.rb
Running 1 tests in a single process
...
Failure:
UsersLoginTest#test_geçersiz_bilgi_ile_giriş
[test/integration/users_login_test.rb:12]:
Expected false to be truthy.
...
1 runs, 5 assertions, 1 failures, 0 errors, 0 skips

Burada 12. satırda yani 

    assert flash.empty?

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

....
  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      # Kullanıcı girişi geçerli yap ve profiline gönder
    else
      flash.now[:danger] = 'Geçersiz email/şifre kombinasyonu'
      render 'new', status: :unprocessable_entity
    end
  end
....


Ş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

module SessionsHelper
end


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

class ApplicationController < ActionController::Base
  include SessionsHelper
...




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. 

session[:user_id] = user.id


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

module SessionsHelper
  # verilen kullanıcıya giriş yaptır.
  def log_in(user)
    session[:user_id] = user.id
  end
end


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 

class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(email: params[:session][:email].downcase)
    if user && user.authenticate(params[:session][:password])
      reset_session
      log_in user
      redirect_to user
    else
      flash.now[:danger] = 'Geçersiz email/şifre kombinasyonu'
      render 'new', status: :unprocessable_entity
    end
  end

  def destroy
  end
end


Buradaki kısaltılmış yönlendirmeye bakalım

      redirect_to user


Bunu daha önce de görmüştük aslında bu satırı görünce Rails otomatik olarak 

user_url(user)

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ı

<%= current_user.name %>

veya

redirect_to current_user

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. 

User.find(session[:user_id])

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

User.find_by(id: session[:user_id])

ş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.

  def current_user
    if session[:user_id]
      User.find_by(id: session[:user_id])
    end
  end


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. 

if @current_user.nil?
  @current_user = User.find_by(id: session[:user_id])
else
  @current_user
end

Bu kodlama notasyonu Ruby'de çok tanıdık bir notasyon ama daha da kısaltılarak kullanılır.

@current_user = @current_user || User.find_by(id: session[:user_id])

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.

@current_user ||= User.find_by(id: session[:user_id])


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.

x = x + 1

Bu işlem için birçok programlama dilinde bir kısaltma vardır.

x += 1

Diğer işlemler için de benzer operatörler kullanılabilir.

$ rails c
Loading development environment (Rails 7.2.2)
>> x = 1
=> 1
>> x += 1
=> 2
>> x *= 3
=> 6
>> x -= 8
=> -2
>> x /= 2
=> -1

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.

>> @foo
=> nil
>> @foo = @foo || "bar"
=> "bar"
>> @foo = @foo || "baz"
=> "bar"

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 

@foo = @foo || "bar" yerine @foo ||= "bar"

yazılabilir. İşte bu

@current_user ||= User.find_by(id: session[:user_id])

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

module SessionsHelper
  # verilen kullanıcıya giriş yaptır.
  def log_in(user)
    session[:user_id] = user.id
  end
  # Eğer giriş yaptıysa kullanıcı nesnesini döner
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
end


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. 

<% if logged_in? %>
  # Giriş yapmış kullanıcı linkleri
<% else %>
  # Giriş yapmamış kullanıcı linkleri
<% end %>


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

module SessionsHelper
  # verilen kullanıcıya giriş yaptır.
  def log_in(user)
    session[:user_id] = user.id
  end
  # Eğer giriş yaptıysa kullanıcı nesnesini döner
  def current_user
    if session[:user_id]
      @current_user ||= User.find_by(id: session[:user_id])
    end
  end
  # Kullanıcı giriş yaptıysa true döner diğer durumlarda false
  def logged_in?
    !current_user.nil?
  end
end


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. 

<%= link_to "Kullanıcılar", '#' %>
<%= link_to "Ayarlar", '#' %>


Çıkış yapma linki de daha önce tanımladığımız logout_path adresine gönderecek.

<%= link_to "Çıkış yapın", logout_path,
    data: { "turbo-method": :delete } %>


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.

<%= link_to "Hesabım", current_user %>

Bunu daha açık ifadeyle şöyle de yazabilirdik.

<%= link_to "Hesabım", user_path(current_user) %>


İ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.

<%= link_to "Giriş Yap", login_path %>


Ş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

<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "Yeni App", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Ana Sayfa", root_path %></li>
        <li><%= link_to "Yardım", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Kullanıcılar", '#' %></li>
          <li class="dropdown">
            <a href="#" id="account" class="dropdown-toggle">
              Hesabım <b class="caret"></b>
            </a>
            <ul id="dropdown-menu" class="dropdown-menu">
              <li><%= link_to "Profil", current_user %></li>
              <li><%= link_to "Ayarlar", '#' %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Çıkış Yap", logout_path,
                  data: { "turbo-method": :delete } %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Giriş Yap", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>


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. 

$ rails importmap:install turbo:install stimulus:install


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

//= link_tree ../images
//= link_directory ../stylesheets .css
//= link_tree ../../javascript .js
//= link_tree ../../../vendor/javascript .js


Bu komutla beraber yerleşim ana dosyamız içine de

    <%= javascript_importmap_tags %>

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. 

  1. Bir CSS active sınıfı kuralı ekleyerek açılır menünün görünür olmasını sağlayacağız.
  2. JavaScript kodlarımız için bir custom klasörü ve menü için bir menu.js kod dosyası ekleyeceğiz.
  3. Importmap yardımıyla Rails'e JavaScript dosyalarımızı nasıl kullanacağını tarif edeceğiz.
  4. menuı.js kod dosyamızı application.js dosyamızdan dahil edeceğiz.












500


Hiç yorum yok:

Yorum Gönder