25 Mart 2023 Cumartesi

Rails 7 Başlangıç Dökümanı

 Selam Rails versiyon 7 çıktığından beri çok ilgilenemedim. Orjinal Getting Started dökümanından bir başlangıç yapayım dedim. Burada onu paylaşacağım.

Daha önce Windows 10'da Rails 7 kurulumunu sizinle paylaşmıştım. Kurulum için oraya bakabilirsiniz, ben direk uygulamaya geçiyorum. 



Rails 7 Bir Blog Uygulaması Üretmek

Rails jeneratörler (generators) adı verilen bir çok yararlı terminal komutu içerir. Bunlardan biri yeni uygulama üretmek için kullanılır ve bize yeni bir Rails uygulamasında olması gereken tüm dosyaları  üreterek kolayca başlangıç yapmamızı sağlar. 

Ben Windows 10 işletim sistemi kullandığım için Rails 7 için en iyi yöntem olan Ubuntu Terminalinde çalışıyorum. Yeni uygulamamızı üretmek için çalışma klasörümüzde bir terminal açıp şu satırı girelim:


rails new blog

Bu komut blog adında bir klasör içinde yeni bir Rails 7 uygulamasının tüm gereken dosyalarını oluşturacak ve bundle komutunu çalıştırarak gerekecek tüm gem dosyalarını sisteme ekleyecektir. 

Terminalde bir hata mesajı olmadan işlem bittiğini gördükten sonra uygulamamızı denemeye hazırız. Önce uygulama klasörüne girmek için


    cd blog
 

yazalım. Ben VSCode uygulaması kullanıyorum editör olarak. Editörde uygulama klasörünü açmak için terminalde


    code .
 

komutu çalıştırınca Code editörü açılır, solda uygulama klasörümüz içine yerleştirilmiş bir çok klasör ve dosya gçrürüz. 

Başlangıç seviyesinde bir uygulama için en çok app, config ve db klasörü içindeki dosyalara muhatap olacağız. 

  • app klasöründe uygulama lojiği kodları bulunur. Tüm görseller , arka plan kodları, veri tabanı işleme kodları vs.
  • config klasöründe uygulamamızın yapılandırmasına ait dosyalar var. Şu seviyede en çok adreslemeleri kullanacağız. 
  • db klasöründe ise veri tabanı ile ilgili dosyalar var.



Rails 7 Web Server Çalıştırılması

Geliştirme amacıyla yerelde bilgisayarımızda çalışan bir web server gerekiyor. Bu amaçla Rails 7 ile birlikte gelen server komutunu kullanabiliriz. Blog klasörümüzdeyken terminalde şu komutu girersek deneme serverimiz çalışacaktır.


    rails server
 

ya da kısaca yazılan şekli 


    rails s
 

Server çalışır ve terminalde şuna benzer mesajlar verir

=> Booting Puma
=> Rails 7.0.4.3 application starting in development
=> Run `bin/rails server --help` for more startup options
Puma starting in single mode...
* Puma version: 5.6.5 (ruby 3.1.2-p20) ("Birdie's Version")
*  Min threads: 5
*  Max threads: 5
*  Environment: development
*          PID: 3111
* Listening on http://127.0.0.1:3000
* Listening on http://[::1]:3000
Use Ctrl-C to stop

Puma server geliştirici modunda (development) çalışmaya başladı ve bizi http://127.0.0.1:3000 adresinde dinliyor.

Bundan sonra server'ı durdurmak istemiyorsanız yeni bir terminal açıp oradan devam etmenizi tavsiye derim.

Şimdi tarayıcımızda http://127.0.0.1:3000 web adresini açarsak bu yereldeki server'ın yayınladığı blog uygulamamızı görebiliriz.

Bu Rails 7 default sayfası. Bu sayfadan başka bir şeyler ekleyelim şimdi.



Rails 7 İlk Sayfamız

Bir şeyler gösterebilmek için bize sırayla bir bağlantı adresi (route), bir kontrolör (controller) ve onun içinde bir aksiyon (action) ve bir görsel (view) gerekiyor. 

Önce bağlantı adresi ile başlayalım. Adres yönlendirmeleri /config/routes.rb dosyasında yapılır. Dosyayı editörde açalım. 

blog/config.routes.rb
Rails.application.routes.draw do
  # Define your application routes per the DSL in https:/....

  # Defines the root path route ("/")
  # root "articles#index"
end

Burada en başa şunu ekleyelim.

blog/config.routes.rb
Rails.application.routes.draw do
    get "/articles", to: "articles#index"

    # Defines the root path route ("/")
    # root "articles#index"
end

Bu satır bize "/articles" adresine (yerel server için http://127.0.0.1:3000/articles) yapılan bir GET başvurusunu articles kontrolörü index aksiyonuna bağlanıyor. 

Terminalde mevcut adres bağlantılarını görmek için 


    rails routes
 

komutu girersek, bize tüm bağlantıların listesini verir. Sadece articles kontrolörü için tanımlanmış adresleri görsek daha iyi olacak.


    rails routes -c articles

  Prefix     Verb   URI Pattern            Controller#Action
  articles   GET    /articles(.:format)    articles#index

Kontrolör ve aksiyonu eklemek için Rails 7 jeneratör komutlarından birini kullanacağız. Terminalde 


    rails generate controller Articles index --skip-routes
 

-skip-routes opsiyonu kullandık çünkü routes.rb içinde ilaveyi biz daha önce yaptık. Bu komut bizim için birçok yeni dosya üretecek.

      create  app/controllers/articles_controller.rb
      invoke  erb
      create    app/views/articles
      create    app/views/articles/index.html.erb
      invoke  test_unit
      create    test/controllers/articles_controller_test.rb
      invoke  helper
      create    app/helpers/articles_helper.rb
      invoke    test_unit

En önemlisi kontrolör dosyası app/controllers/articles_controller.rb 

app/controllers/articles_controller.rb 
class ArticlesController < ApplicationController
  def index
  end
end

Kontrolörümüzün adı ArticlesController ve ApplicationController ana sınıfından kalıtım yoluyla türetiliyor. Aksiyonlar ise kontrolör sınıfı içine metodlar olarak konur. index aksiyonumuz şu anda hiç bir şey yapmıyor. Ayrıca başka bir görsele yönlendirilmediği durumda bir aksiyon çağrılınca app/views klasörü içinde aynı isimde bir görsel dosyası arar. Az evvel terminalde çalıştırdığımız jeneratör komutu bizim için kontrolör ve aksiyon kodu yanında bir de örnek görsel üretti burada, app/views/articles/index.html.erb dosyası. Bu dosya içindeki geçici görsel kodları silelim ve şunu yazalım. 

app/views/articles/index.html.erb

<h1>Merhaba Rails</h1>

Şimdi http://127.0.0.1:3000/articles sayfasını tarayıcıda açalım.



Rails 7 Uygulama Ana Sayfasını Değiştirmek

Halihazırda http://127.0.0.1:3000 sayfası Rails 7 default sayfasını göstermeye devam ediyor. Diyelim az evvel tasarladığımız sayfayı ana sayfa olarak bu adreste göstermek istiyoruz. Bunun için routes.rb içinde belirtmeliyiz. 

Rails.application.routes.draw do
    root "articles#index"

    get "/articles", to: "articles#index"
end

root "articles#index" komutu ana sayfayı articles kontrolörü index aksiyonuna bağlar. 




Rails 7 MVC Yapısı

Adres bağlamaları, kontrolörler, aksiyonlar ve görselleri gördük şimdiye kadar. Bütün bunlar MVC (Model View Controller) web uygulama yapısının parçaları. Veritabanına bağlı bir web sitesi yaparken kullanılan bir tekniktir. Model veritabanındaki tablolar ve onlara ait işlemleri içerir. View az önce gördüğümüz görselleri içerir. Controller da uygulama lojiğini içerir. 

İki tanesini gördük üçüncüye yani Model'e geçelim. Model kod olarak, veriyi temsil eden bir Ruby sınıfıdır. Ek olarak Rails'in ActiveRecord özelliğini kullanarak uygulamamız ve veritabanı arasındaki etkileşimi sağlar. Bir model tanımlamak için Rails model jeneratörünü kullanırız. Article modelimizi oluşturmak için terminalde şu satırı girelim:

 
    rails generate model Article title:string body:text

Daha önce rails server komutunun kısa hali rails s kullanılabilir demiştik. rails generate komutunun da kısaltılmışı rails g yani yukarıdaki satırı 

 
    rails g model Article title:string body:text

olarak da yazabiliriz. Bu komut bize yine bir kısım yeni dosya üretir.

En başta db/migrate klasöründe veritabanına articles adında ve istediğimiz sütunlara sahip bir tablo eklemek için kod bulunuyor. Terminalden modeli üretirken ismini Article olarak verdik. Burada İngilizce isim vermemizin sebebi Rails'in bir çok kolaylık sunması. Model ismini tekil veriyoruz çünkü model veri tablomuzdaki tek bir kaydın yapısını tarif ediyor. Birçok Article kaydının bulunduğu tabloya ise articles olarak çoğul isim veriliyor. Migration (birleştirme) dosyası başında dosyanın üretildiği tarih saat bilgisi olduğu için sizde farklı olacaktır ama _create_articles.rb kısmı aynı olacaktır. Şimdi bu dosya içine bakalım.

db/migrate/20230322073506_create_articles.rb
class CreateArticles < ActiveRecord::Migration[7.0]
    def change
        create_table :articles do |t|
            t.string :title
            t.text :body

            t.timestamps
        end
    end
end

Migrasyon dosyaları içnde veritabanında yapılabilecek bir çok modifikasyon olabilir. Burada ise articles tablosunu oluşturmak için kod bulunuyor. string olan title ve text olan body sütunlarını zaten biz terminalde model üretirken istedik (title:string body:text opsiyonları ile). t.timestamps komutu ile Rails tablomuza kayıtların ilk girildiği ve değiştirildiği zamanlar için sütun ekliyor. 

db klasörü içinde bulunan development.sqlite3 dosyası, uygulamamızın bu anda kullandığı veritabanı dosyası. Bu dosya içinde neler olduğunu VSCode editöründe görebiliriz. Ancak şimdi açarsak içinin boş olduğunu görürüz. 

articles tablomuzun üretilmesi için terminalde veritabanı migrasyonlarını çalıştıran şu komutu girmeliyiz.

 
    rails db:migrate

Komut bize şu çıktıyı verir

== 20230322073506 CreateArticles: migrating ===================================
-- create_table(:articles)
   -> 0.0044s
== 20230322073506 CreateArticles: migrated (0.0055s) ==========================

Şimdi development.sqlite3 dosyasını VSCode editörde tekrar açarsak şunu görürüz.

Bizim istediğimiz articles tablosu dışında Rails kendi kullanımı için başka tablolar da eklemiş. Tablomuzda bizim belirtmediğimiz sütunlar var. İlki id sütunu , bu sütun tablomuzun anahtar sütunu her yeni eklenen kayıt için bu sütuna da tamsayı ve diğer kayıtlardan farklı bir değer verilir. id sütunu Rails tarafından üretilen her model tablosuna otomatik olarak eklenir. Migrasyon dosyasına Rails tarafından default olarak eklenen t.timestamps satırı da sondaki iki sütunu yani created_at ve updated_at sütunlarını ekler. created_at söz konusu kaydın ilk eklendiği zamanı içerir, updated_at ise en son modifikasyon yapılan zamanı içerir. Bu bilgileri daha sonra kullanabiliriz. Örneğin Twitter'da paylaşımların başlığı yanında ne kada zaman önce paylaşıldığı yazıyor, o bilgi buna benzer bir sütundan alınır. 

Code editör dışında tablo yapımızı az evvelki terminal komutumuz sonrası üretilen db/schema.rb dosyasında da görebiliriz. 

db/schema.rb
ActiveRecord::Schema[7.0].define(version: 2023_03_22_073506) do
  create_table "articles", force: :cascade do |t|
    t.string "title"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end

Bu dosya bize veritabanımızda kullandığımız tabloların en son güncel ayarlarını gösterir. Her migrasyon sonrası bu dosya en son hale güncellenir. 

title sütunu için string ve body sütunu için text bilgi var dedik. İkisi de yazı ama string daha küçük yazılar için text ise örneğin bu makalede benim yazdıklarım gibi çok uzun yazılar için tercih edilir. 

created_at ve updated_at sütunları datetime tipinde veriler ve her kaydın mutlaka bu sütunlarda bir değere sahip olması gerekiyor. null: false şeklinde verilen opsiyon bu sütuna verilecek değer null olamaz, illaki bir değer girilmeli yoksa veriyi kaydetmem diyor. Neyse ki bu sütunları Rails otomatik dolduruyor ve bizim bir şey yapmamıza gerek yok. 




Rails 7 Modeli Kullanarak Veritabanı ile Etkileşim

Tablomuzda henüz bir kayıt bulunmuyor. Article modelini kullanarak manuel yollarla veritabanı üzerinde çalışabilmek için Rails Konsol uygulamasını kullanabiliriz. Terminalde şunu girelim:

 
    rails console

ya da kısaca 

 
    rails c

Bu bize uygulamamıza ait bilgileri içeren bir irb oturumu açacaktır.

Loading development environment (Rails 7.0.4.3)
irb(main):001:0>

Bu konsolda yeni bir Article nesnesini şöyle tanımlarız

 
irb(main):001:0> article = Article.new(title: "Merhaba Rails",
    body: "Rails ile çalışıyorum")

Tabi hepsini tek satırda yazıp enter basalım, ben burada rahat görünsün diye iki satıra böldüm

Yeni bir Article nesnesi üretildi. İçinde ne olduğunu görmek istersek

Dikkat edersek id, created_at ve updated_at sütunlarında henüz bir değer yok. Sadece bizim verdiğimiz değerler var. Çünkü article nesnemiz daha veritabanına kaydedilmedi, şimdilik hafızada bir nesne. id sütunu tablomuzun anahtar sütunu olduğu için kayıt yapılırken veritabanı tarafından otomatik olarak bir değer verilecek. Diğer zaman değerlerini ise Rails biz kaydetmesi için komut verdiğimiz andaki zaman değeri ile dolduracak. 

article nesnesini veritabanına kaydetmek için save metodunu kullanırız.

 
    irb(main):004:0> article.save
   

komut çalışınca yapılan işin bir açıklaması gelir

Gördüğümüz gibi Rails bir SQL sorgu satırı oluşturuyor ve article nesnesi içinde verdiğimiz bilgileri veritabanına kaydediyor. Dikkat edersek Rails created_at ve updated_at değerlerini otomatik olarak girmiş. Siz de bu değerlerden bu yazıyı ne zaman yazdığımı görüyorsunuz. id değerini ise veritabanı eğer gönderdiğimiz veriyi kabul eder ve kaydederse otomatik verecek. En alt satırda article.save metodundan dönen değer true olarak görünüyor. Bu bize veritabanına kayıt işleminin başarı ile bittiğini gösteriyor. Şimdi article nesnemize bir daha bakalım,

İlk kayıt olduğu için veritabanı id değerini 1 olarak girmiş. VSCode editöründe de development.sqlite3 dosyasını açarsak tablomuzda yeni gelen kaydı göreceğiz.

Bir kaydı tablodan bulmak için find metodunu parametresinde id değeri olacak şekilde girerek kullanırız. 

 
    irb(main):006:0> Article.find(1)
 

Bu bize id değeri 1 olan, az önce eklediğimiz ve tablomuzdaki tek kayıt olan kaydı verecektir.

Gördüğümüz gibi Rails yine bir SQL sorgusu hazırlayarak işimizi görüyor. Bütün diğer özellikleri geçsek bile Rails'in veritabanında sorgulama yaparken işimizi bu kadar basitleştirmesi bile kullanmamız için yeterli sebep.

Tablomuzdaki tüm kayıtları getirmek için all metodunu kullanırız.

Article.all metodu bize tablomuzda kayıtlı tüm kayıtları getirecektir. Dönen cevap bir array (köşeli parantez içinde) ama tek bir kaydımız olduğu için şimdilik 1 tane array elemanı var içinde. Kayıtlar arttıkça id: 2 , id: 3 falan gibi elemanlar da eklenecek aynı sorgu cevabına. 

Article.all ve Article.find metodlarını bizim ürettiğimiz article oluşum nesnesi ile karıştırmayalım. Tek bir kayıt değil tüm articles tablosu üzerinde işlem yapıyoruz. Bunlar modelimiz olan Article sınıfının metodları. Article sınıfı ActiveRecord sınıfından kalıtım yoluyla üretildiği için bu metodları bize ActiveRecord sınıfı sağlıyor aslında. 

Aslında kendi ürettiğimiz nesneye a1 falan bir isim de verebilirdik. Bu kadar kafa karıştırmazdı o zaman. Ancak programcılıkta genel kuraldır, sınıf isimleri büyük harfle başlar o sınıftan üretilen nesne isimleri de sınıf isminin küçük harfle başlayanı olur. Birden fazla nesne üreteceksek de article1 , article2 gibi isimlendirme yapmak genel teamüldür. Tecrübeli bir yazılımcı büyük harfle başlayanın sınıf ismi , küçük harfle başlayanın nesne ismi olduğunu bilir.



Rails 7 Tablo Kayıtlarını Sayfamızda Göstermek

Az önce konsolda eklediğimiz kaydı web sayfamızda gösterelim. Önce gösterilecek kayıtları veritabanından okumak için kontrolör içinde aksiyon kodumuza ilave yapmalıyız.

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end
end

@articles adında bir oluşum değişkenine az önce Rails konsolda kullandığımız Article.all metodu ile tablomuzdaki bütün kayıtları okuyoruz. Artık bu değişken adı ile görsellerde kullanabiliriz. 

index aksiyonunun görseli bildiğimiz gibi index.html.erb dosyası. Bu dosya içeriğini şöyle değiştirelim.

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= article.title %>
    </li>
  <% end %>
</ul>

.erb uzantılı dosyalar içinde <% ve %> işaretleri arasında Ruby kodları çalıştırabiliriz. 

  <% @articles.each do |article| %>
    ....
  <% end %>

Bir döngü bloğu oluşturuyor. Kontrolörde @articles içine burada listelemek istediğimiz tüm kayıtları okumuştuk. Bu döngü her bir kayıt için döngü yapıyor. 

      <%= article.title %>

Bu kod biraz deüişik, <%= ve %> arasında yazılmış. Bu şekil yazılırsa blok içindeki Ruby kodun çalışması sonucu oluşan değer o bulunulan noktaya yazılır. Tam bulunduğu yere o andaki kaydın title değerini yazar. Dikkat edelim bu kod sayfaya yazmıyor aslında HTML sayfa kaynağı içinde tam bulunduğu yere yazıyor. Eğer bulunduğu yer HTML kodu içinde gösterilen bir şeyse o zaman sayfada görünür. 

Daha iyi anlamak için çalıştırıp görelim, yani dosyayı kaydedip tarayıcıda sayfayı yenileyelim.

Sayfa kaynağına bakarsak (tarayıcıda), body kısmında yani görünen kısımda şunu görürüz.

  <body>
    <h1>Makaleler</h1>

<ul>
    <li>
      Merhaba Rails
    </li>
</ul>
  </body>

Burada bizim kodlarımız yok. Bizim kodlarımız server'da çalıştı ve sonucunda oluşan sayfa buraya geldi. Tekrar kodumuza dönsek ve şöyle yapsak,

....
    <li title="<%= article.title %>">
      örnek
    </li>
....

Bunun sayfada görünümü

ve sayfa kaynağına bakarsak

  <body>
    <h1>Makaleler</h1>

<ul>
    <li title="Merhaba Rails">
      örnek
    </li>
</ul>

  </body>

Dediğimiz gibi <%= ile başlayan blok HTML kodu içinde tam bulunduğu yere sonucu yazıyor. Bu örnekte de bulunduğu yer sayfada görünen bir yer olmadığı için ancak sayfa kaynağına bakınca görebiliyoruz. Ama bu şekilde kullanarak HTML elemanların özelliklerini de Ruby kodu ile değiştirebileceğimizi gördük. Neyse görsel kodumuzu eski haline döndürelim ve devam edelim. 

Şimdiye kadar öğrendiklerimizin bir üstünden geçelim. Rails'den bir sayfa talep edilince neler oluyor?

  • Tarayıcı http://127.0.0.1:3000 adresine bir GET isteğinde bulunuyor.
  • Rails uygulamamız isteği alıyor.
  • Rails router bu adres ana sayfa adresi olduğu için onu routes.rb içinde istenildiği gibi ArticlesController kontrolörü index aksiyonuna yönlendiriyor.
  • index aksiyonu Article modelini kullanarak veritabanından tüm kayıtları çekiyor.
  • Rails otomatik olarak app/views/articles/index.html.erb görselini yayınlıyor.
  • Görsel içindeki ERB kodu çalışarak yayınlanacak olan HTML kodunu modifiye ediyor.
  • Server oluşan HTML kodunu tarayıcımıza cevap olarak gönderiyor. 

MVC yapısının tüm parçalarını şimdiye kadar kullanmış olduk. Sırada Rails uygulamalarının ikinci önemli temel yapısı var. 



Rails 7 Uygulamalarda CRUD İşlemleri

Bir veriye dayalı tüm web uygulamaları CRUD işlemlerini içermek zorundadır. CRUD - Create, Read, Update, Delete kelimelerinin baş harflerinden oluşturulmuş bir kelimedir. Create yeni kayıt oluşturmak, Read kayıtları okumak, filtrelemek, Update bir kaydı değiştirmek, Delete de bir kaydı veritabanından silme işlemleridir.  Rails bize bu işlemler için birçok kolaylık sunar. Uygulamamıza daha fazla işlevsellikler kazandırarak bu işlemleri öğrenmeye başlayalım. 

Sadece bir tek kaydı gösteren bir sayfa ile başlayalım. Daha önce tüm kayıtların listesi olan index sayfasını yapmıştık. Ama burada kayıtların sadece title özelliği yani başlıkları vardı , içerikleri yoktu. Şimdi de verilen id değerine sahip tek bir kaydın ayrıntılı halini gösterecek bir sayfa için ilk önce routes.rb içinde adres yönlendirmemizi yaparız. 

config/routes.rb
Rails.application.routes.draw do
    root "articles#index"

    get "/articles", to: "articles#index"
    get "/articles/:id", to: "articles#show"
end

get "/articles/:id" şeklinde yazılınca id başında iki nokta üst üste olduğu için bu değer değişebilir ve Rails tarafından parametre olarak alınacak demek oluyor. Yani /articles/1 ya da /articles/5 yazıldığında :id parametre değeri 1 ya da 5 olacak. Daha sonra kontrolör içinde bu değeri params[:id] şeklinde parametre array'inden okuyabiliriz. 

Şimdi kontrolörümüze show aksiyonunu ekleyelim:

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    def index
        @articles = Article.all
    end
   
    def show
        @article = Article.find(params[:id])
    end
end

Bu aksiyonun görseli default olarak show.html.erb adında olacaktır. Onu da ekleyelim.

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

Şimdi http://127.0.0.1:3000/articles/1 adresinde elimizde olan yegane kaydı görebiliriz. 

Adres yazmaya uğraşmadan kullanıcının direk listeden tıklayarak makalenin ayrıntısına gitmesi için index.html.erb görselindeki listeyi link şekline döndürmeliyiz.

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
        <a href="/articles/<%= article.id %>">
            <%= article.title %>
        </a>
    </li>
  <% end %>
</ul>

HTML kodu olarak böyle. Rails 7 yardımcı metodları kullanarak bu linkin nasıl yapılacağını az sonra göreceğiz ama öncesinde bir düzenleme yapmamız gerekiyor. 




Rails 7 Resources Adres Yönlendirmesi

Terminalde articles için tanımlı adres yönlendirmelerimize bir daha bakalım.

/blog$> rails routes -c articles
  Prefix Verb URI Pattern             Controller#Action
    root GET   /                       articles#index
articles GET   /articles(.:format)     articles#index
          GET   /articles/:id(.:format) articles#show

Şu ana kadar CRUD işlemlerinin sadece Read kısmına ait işlemler yaptık. Diğerleri için kullanacağımız daha bir çok yönlendirme olacak. Bunların hepsini tek tek tanımlayacağımıza Rails'in tüm CRUD işlemleri için standart olan yönlendirmelerini şöyle kullanırız. 

config/routes.rb
Rails.application.routes.draw do
    root "articles#index"

    resources :articles
end

resources :articles komutunun bizim için eklediği adres yönlendirmelerine terminalde bir bakalım.

/blog$> rails routes -c articles
      Prefix   Verb   URI Pattern                  Controller#Action
        root   GET    /                            articles#index
    articles   GET    /articles(.:format)          articles#index
               POST   /articles(.:format)          articles#create
 new_article   GET    /articles/new(.:format)      articles#new
edit_article   GET    /articles/:id/edit(.:format) articles#edit
     article   GET    /articles/:id(.:format)      articles#show
               PATCH  /articles/:id(.:format)      articles#update
               PUT    /articles/:id(.:format)      articles#update
               DELETE /articles/:id(.:format)      articles#destroy

Gördüğümüz üzere daha önce eklediğimiz 2 adres yönlendirmesi ekleyeceklerimiz yanında çok az kalıyor. resources metodu ayrıca Prefix olarak da bu yönlendirmeler için kullanacağımız kelimeler tanımlamış. Biz bu isimlerin sonuna _path ekleyerek kodlarımız arasında yönlendirmeler için kullanabiliriz. 

Örneğin bizim show aksiyonumuz için Prefix değeri article. Biz article_path ismini kullanarak show aksiyonuna bağlanabiliriz. Bunu index.html.erb görseli içindeki linkler için yapalım.

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
        <a href="<%= article_path(article) %>">
            <%= article.title %>
        </a>
    </li>
  <% end %>
</ul>

Bu bize pek bir şey kazandırmıyor ama link_to yardımcı metodunu kullanmak bize çok şey kazandıracaktır. 

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
        <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

link_to metodunun ilk parametresi link yazısnın ne olacağı , ikinci parametre ise nereye gideceğiz. Burada az evvel bahsettiğimiz prefix isimlerden aklınız karışmasın, link_to verilen nesneye bakıp nereye gideceğine karar verir. Yani

  <% @articles.each do |x| %>
    <li>
        <%= link_to x.title, x %>
    </li>
  <% end %>

Şeklinde yazsak da çalışır. Bağlantı adresi için bir nesne verilirse link_to o nesnenin üretildiği modelin show aksiyonuna nesnenin id parametresi ile bağlanır. 




Rails 7 Tabloya Yeni Kayıt Eklemek

Sıra geldi CRUD'un Create'ine. Tipik olarak yeni bir kayıt eklemek çok adımlı bir prosestir. Önce kullanıcı yeni kayıt eklemek için bir form talebinde bulunur. Sonra bu formu doldurup gönderir. Eğer bir sıkıntı yoksa kayıt yapılır ve bir çeşit onaylama işlemi yapılır. Eğer kaydedilecek verilerde bir sıkıntı olduysa forma tekrar dönülür ve işlem tekrarlanır. 

Bir Rails uygulamasında bu proses new ve create aksiyonları ile yönetilir. Routes değerlerine bakarsak new için http GET ve create için http POST isteği beklendiğini göreceğiz. Bunları aklımızda tutup, öncelikle kontrolörümüze aksiyon metodlarını ekleyerek başlayalım. 

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    def index
        @articles = Article.all
    end

    def show
        @article = Article.find(params[:id])
    end

    def new
        @article = Article.new
    end

    def create
        @article = Article.new(title: "...", body: "...")

        if @article.save
            redirect_to @article
        else
            render :new, status: :unprocessable_entity
        end
    end
end

new metodu @article adında yeni bir Article nesnesi üretiyor ve görsele bırakıyor işi. new.html.erb görselini birazdan oluşturacağız. 

create aksiyonu ise, değerleri belli olan bir Article nesnesi oluşumu üretiyor. Aslında bu değerleri gönderilen formdan alacağız ama şimdilik göstermek amaçlı böyle yaptık. Sonrasında bu nesneyi save metodunu kullanarak kaydetme teşebbüsünde bulunuluyor. Konsolda yaptığımız denemeden hatırlarsak save metodu başarılı bir kayıt yapınca true değeri dönüyordu.  Eğer kayıt başarılı olursa o kayda ait show sayfasına gidiliyor, başarısız olursa render metodu ile tekrar new görselindeki forma dönülüyor. 

redirect_to yeni bir yönlendirme yapar ve eğer render yerine redirect_to ile new görseline gidersek daha önce forma girilen değerler sıfırlanır. render , formu en son üzerindeki verilerle açar.

Son olarak status: :unprocessable_entity opsiyonu ile new görseline geri dönerken tarayıcıya 422 Unprocessable Content cevabı da gönderir ve tarayıcıya "sen bişey gönderdin ama ben bu verileri işleyemiyorum" uyarı mesajı veriliyor. 



Rails 7 Form Builder Kullanmak

Yeni kayıt bilgilerini girmek için formumuzu new.html.erb görselinde yayınlayacağız. Formumuzu oluşturmak için Rails form builder kullanacağız.

app/views/articles/new.html.erb
<h1>Yeni Makale</h1>

<%= form_with model: @article do |form| %>
  <div>
    <%= form.label :başlık %><br>
    <%= form.text_field :title, size: 40 %>
  </div>

  <div>
    <%= form.label :gövde %><br>
    <%= form.text_area :body, cols: 40, rows: 5 %>
  </div>

  <div>
    <%= form.submit "Kaydet" %>
  </div>
<% end %>

form_with yardımcı metodu bir form builder oluşum nesnesi üretir. form_with bloğu içinde label veya text_field gibi form elemanlarını kaydımızla ilgili olarak kullanırız. form_with çağrısı sonucunda şöyle bir html form kodu oluşur. 

<form action="/articles" accept-charset="UTF-8" method="post">
  <input type="hidden" name="authenticity_token" value="...." autocomplete="off">
  <div>
    <label for="article_başlık">Başlık</label><br>
    <input size="40" type="text" name="article[title]" id="article_title">
  </div>

  <div>
    <label for="article_gövde">Gövde</label><br>
    <textarea cols="40" rows="5" name="article[body]" id="article_body"></textarea>
  </div>

  <div>
    <input type="submit" name="commit" value="Kaydet" data-disable-with="Kaydet">
  </div>
</form>




Rails 7 Zorlanmış Parametreler (Strong Parameters)

Formda gönderilen bilgiler params hash değişkenine konur. Böylece create aksiyonu formda girilen title değerine params[:article][:title] ile ve body değerine params[:article][:body] ile erişebilir. Bu değerleri ayrı ayrı Article.new metodunu kullanırken parametre olarak girebiliriz. Ancak formda girilecek veri sayısı arttıkça hata yapma olasılığımız çok artacaktır. 

Bunun yerine parametreleri tek bir hash içinde verebiliriz. Bununla birlikte hash içinde hangi verilerin olabileceğini kısıtlamak gerekir. Kötü niyetli kişiler formu gönderirken bizim kullanıcıya sunmadığımız özel alanları da ekleyip, özel verilerimize erişebilirler. Zaten Rails de params[:article] hash değerini direk olarak Article.new metoduna verdiğimizde ForbiddenAttributesError hatası vererek bu şekil değerleri kabul etmez. Bu durumda params değerlerini filtrelemek için Rails strong parameters işlemini kullanırız.

Kontrolöre article_params adında bir private metod ekleyerek params değerlerini kısıtlayalım.

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    def index
        @articles = Article.all
    end

    def show
        @article = Article.find(params[:id])
    end

    def new
        @article = Article.new
    end

    def create
        @article = Article.new(article_params)

        if @article.save
            redirect_to @article
        else
            render :new, status: :unprocessable_entity
        end
    end

    private
   
        def article_params
            params.require(:article).permit(:title, :body)
        end
end

:article için gönderilen params değerinden sadece :title ve :body alanlarını alıp geri dönüyor bu metod. Dikkat edelim yukarıda create metodunda artık bu filtre edilmiş parametre hash kullanılıyor.




Rails 7 Veri Kontrolleri (validation) ve Hata Mesajı Göstermek

Anlattığımız gibi Rails'de yeni kayıt eklemek çok adımlı bir proses. Formu açtık , kullanıcı formu doldurdu ve Kaydet butonuyla gönderdi. Şimdi gelen verilerin geçerli olmasının kontrolü var. Modelimiz içinde tabloya girilecek verilerin kontrollerini yapabiliriz. app/models/article.rb içini şöyle düzenleyelim.

app/models/article.rb
class Article < ApplicationRecord
    validates :title, presence: true
    validates :body, presence: true, length: { minimum: 10 }
end

İlk kontrol title değeri için. presence: true ile title değerine mutlaka bir şey girilmesi şartı konuyor. O kutu boş bırakılamaz. İkinci kontrolde ise body değerinin hem boş bırakılamayacağı hem de length: yani harf sayısının minimum 10 karakter olması şartı konuyor. 

Bu kontrollerden geçemezse @article.save metodu false değer dönecek ve form tekrar yayınlanacaktır. Formu tekrar verirken bir hata mesajı da vermek için görseli şöyle değiştiririz. 

app/views/articles/new.html.erb
<h1>Yeni Makale</h1>

<%= form_with model: @article do |form| %>
    <div>
        <%= form.label :başlık %><br>
        <%= form.text_field :title, size: 40 %>
        <% @article.errors.full_messages_for(:title).each do |message| %>
            <div><%= message %></div>
        <% end %>
    </div>

    <div>
        <%= form.label :gövde %><br>
        <%= form.text_area :body, cols: 40, rows: 5 %>
        <% @article.errors.full_messages_for(:body).each do |message| %>
            <div><%= message %></div>
        <% end %>
    </div>

    <div>
        <%= form.submit "Kaydet" %>
    </div>
<% end %>

Şimdi boş bir formda Kaydet butonuna basarsak

Mesajların istediğimiz mesajlar olması için model ile oynayabiliriz.

app/models/article.rb
class Article < ApplicationRecord
    validates :title, presence: { message: 'boş olamaz' }
    validates :body, presence: { message: 'boş olamaz' },
        length: { minimum: 10, message: 'en az 10 karakter olmalı' }
end

Şimdi boş form yollarsak

Son olarak yeni kayıt için ana sayfaya bir link eklemeliyiz. 

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
        <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

<%= link_to "Yeni Makale", new_article_path %>

Prefix tanımlarına bakarsak link bizi http://127.0.0.1:3000/articles/new adresine gönderecektir. Hadi yeni bir kayıt ekleyelim.

Kaydet butonuna tıklanınca yeni kaydımızı gösteren sayfaya otomatik olarak gidecektir.




Rails 7 Bir kaydı Değiştirmek

CRUD kelimesinin CR harflerini halletik , sırada Update işlemi var. Bir kaydı değiştirmek , yeni bir kayıt yapmak ile çok benzer prosedür gerektirir. Önce kullanıcı kaydı değiştirme isteği gönderir. Form bu sefer boş değil içinde değiştirilecek kayıt bilgileri ile dolu olarak getirilir. Sonra kullanıcı değişikliklerini yapar ve formu gönderir. Eğer bilgilerde sıkıntı yoksa kayıt değiştirilir. 

Bu işlemler kontrolörün edit ve update aksiyonları ile yönetilir. Şimdi bu metodların da tipik şeklini kontrolöre ekleyelim.

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    .....

    def edit
        @article = Article.find(params[:id])
    end

    def update
        @article = Article.find(params[:id])

        if @article.update(article_params)
            redirect_to @article
        else
            render :edit, status: :unprocessable_entity
        end
    end

    private
   
        ....
end

Dikkat edersek edit ve update aksiyonları new ve create aksiyonlarına çok benziyor. Fakat edit aksiyonu veritabanından söz konusu kaydı bulup @article oluşum değişkenine koyuyor. Böylece kayda ait bilgiler form oluşturulurken kullanılacak. edit aksiyonu tabi ki edit.html.erb görseline bağlı. 

update aksiyonu ise yine veritabanından söz konusu kaydı buluyor ve formdan gönderilen verileri kullanarak ve nesnenin update metodunu kullanarak veritabanında kaydı değiştirmeye çalışıyor. Kayıt için yine filtrelenmiş article_params hash değeri kullanılıyor. Eğer değer kontrollerinde sorun olmaz ve veritabanında kayıt değişmesi sorunsuz gerçekleşirse değişen kaydın son halini göstermek için show aksiyonuna yönlendiriliyor. Eğer kayıtta sorun varsa edit görseli tekrar ama değerler başa döndürülmeden yayınlanıyor. 



Rails 7 Ortak Görseller İçin Parça Görsel Yapısı

edit görselinde kullanacağımız ve daha önce new görselinde kullandığımız formlar birbirinin aynı. Böyle durumlarda aynı olan görsel kodunu parça görsel adı verilen dosyalarda toplamamız için Rails bize imkan sunuyor. Şimdi formumuzun kodlarını app/views/articles/_form.html.erb dosyasında toplayalım.

app/views/articles/_form.html.erb
<%= form_with model: article do |form| %>
    <div>
        <%= form.label :başlık %><br>
        <%= form.text_field :title, size: 40 %>
        <% article.errors.full_messages_for(:title).each do |message| %>
            <div><%= message %></div>
        <% end %>
    </div>

    <div>
        <%= form.label :gövde %><br>
        <%= form.text_area :body, cols: 40, rows: 5 %>
        <% article.errors.full_messages_for(:body).each do |message| %>
            <div><%= message %></div>
        <% end %>
    </div>

    <div>
        <%= form.submit "Kaydet" %>
    </div>
<% end %>

Parça görsel dosyaları isimleri mutlaka alt çizgi karakteri ile başlamalıdır. Buradaki form kodunun new görselinde yazdığımız koddan tek farkı @article oluşum değişkenini değil article yerel değişkenini kullanmasıdır. Şimdi new görseli dosyamızı şöyle yazarak bu parça görseli kullanacak hale getiririz. 

app/views/articles/new.html.erb
<h1>Yeni Makale</h1>

<%= render "form", article: @article %>

Dikkat edersek render ile parça görsel çağrılırken baştaki alt çizgi karakteri yazılmıyor. Parça görsel çağrılırken orada kullanılacak olan article yerel değişkenine de @article değişkenine eşitleniyor. Mesela bu şekilde buton üzerinde yazacak yazıyı da parça görseli çağıran yerden belirtebiliriz. 

app/views/articles/new.html.erb
<h1>Yeni Makale</h1>

<%= render "form", article: @article, b_caption: "Ekle" %>

Ve form parça görseli içinde buton kodunu şöyle yaparız.

app/views/articles/_form.html.erb
<%= form_with model: article do |form| %>
    .....

    <div>
        <%= form.submit b_caption %>
    </div>
<% end %>

Böylece new görselinden _form parça görselini çağırınca buton üzerinde Ekle yazacaktır. 

Benzer şekilde edit görselini de ekleyelim uygulamamıza

app/views/articles/edit.html.erb
<h1>Makaleyi Düzenle</h1>

<%= render "form", article: @article, b_caption: "Değiştir" %>

İşi bitirmek için tek makalenin ayrıntılarını gösteren show görseline kaydı düzenlemek üzere bir link ekleyelim

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
    <li><%= link_to "Düzenle", edit_article_path(@article) %></li>
</ul>

Şimdi Düzenle linkine tıklayıp bir kaydı düzenleyelim.




Rails 7 Bir Kaydın Silinmesi

CRUD kelimesi son harfi olan Delete işlemine geldi sıra. Silme işlemi yeni kayıt ya da kayıt değiştirme işlemine göre çok daha basit. Sadece bir adres yönlendirmesi ve bir kontrolör aksiyonu gerekiyor. Adres bağlaması zaten routes.rb dosyasındaki resources satırı tarafından eklendi. DELETE isteği yapan yönlendirme kontrolörün destroy aksiyonuna yönlendirilmiş. 

O zaman kontrolöre destroy aksiyonunu ekleyerek başlayalım.

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    ....

    def destroy
        @article = Article.find(params[:id])
        @article.destroy

        redirect_to root_path, status: :see_other
    end

    private
   
        def article_params
            params.require(:article).permit(:title, :body)
        end
end

destroy aksiyonu id değeri verilen kaydı veritabanından buluyor. Sonrası bu kayıt nesnesinin destroy metodunu çağırıyor ve kaydı siliyor. Sonrasında uygulama tarayıcıyı 303 See Other durum koduyla ana sayfaya gönderiyor.  

Burada yönlenilen sayfa olarak root_path seçilmiş. Burada duruma özel tercih bu. Ama birden çok tablo üzerinde çalışan bir uygulama yaparken silme işlemi sonunda silinen kaydın tablosuna ait geri kalan verilerin gösterildiği index sayfasına dönmek genel olarak en mantıklı işlem olacaktır (mesela articles_path). 

Şimdi show.html.erb görselinde en alta kaydı silmek için de bir link ekleyelim. 

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
    <li><%= link_to "Düzenle", edit_article_path(@article) %></li>
    <li><%= link_to "Sil", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "#{@article.title} silinecek emin misiniz?"
                  } %></li>
</ul>

Normalde bir link GET eylemiyle istekte bulunur. Ama biz silmek için DELETE eylemi ile istek yapmalıyız. Bunu belirtmek için data opsiyonunu kullanıyoruz. data bloğu içinde verilen turbo_method ile DELETE eylemini kullanarak istekte bulunulmasını bildiriyoruz. Sadece turbo_method: :delete yeterli ama öylece bırakırsak kazara da olsa linke tıklandığı anda kayıt silinir. İstekte bulunulmadan önce kullanıcıya bir popup açıp onay almak için turbo_confirm özelliğine de bir mesaj değeri girerek kullanıcıya bu mesajın sorulması ve Tamam ya da OK butonu tıklanınca istekte bulunulmasını bildiriyoruz. 

Burada İptal seçilirse kayıt silinmez. Genelde programlarımızda geri dönülemeyecek işlemler yapmadan önce hep bu şekilde kullanıcının onayını almalıyız. 

Böylece CRUD işlemlerinin hepsinin nasıl yapıldığını öğrendik. Şimdi uygulamamıza ikinci bir model ekleyelim.




Rails 7 Uygulamaya İkinci Bir Model Eklemek

Makaleleri yayınladık. İkinci bir model tanımlayıp, o tabloda da yazılara yapılan yorumları saklayalım. Daha önce Article modelini oluştururken kullandığımız Rails jeneratörünü kullanalım. Terminalde:

 
    rails g model Comment commenter:string body:text article:references
   

commenter yani yorum yapanın adı ya da nik name'ini saklayan sütun verileri string tipinde olacak. Yorum yazısı yine body adlı sütunda saklanacak ve text tipinde olacak. Son sütun ise Article modeline referans bilgisi içerecek. article:references yazınca Rails tabloya article_id adında bir sütun ekler ve bu sütun söz konusu yorumun yapıldığı makalenin id değerini içermelidir. Bu takibi yapabilmek için Rails bize kolaylıklar sağlar. Bunların ilkini görmek için Comment model dosyasını açalım.

app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :article
end

belongs_to :article satırı Comment kayıtlarının her birinin bir Article kaydına bağlı olması gerektiği belirtiliyor. Şimdi de jeneratörün ürettiği migrasyon dosyasına bakalım.

class CreateComments < ActiveRecord::Migration[7.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, null: false, foreign_key: true

      t.timestamps
    end
  end
end

Article modeline bir referans var ve bu değer boş olamaz. Ayrıca tablonun bu sütununun foreign_key olduğu yani başka bir tabloya bağlantı bilgisi içerdiği bildiriliyor. Şimdi de bu migrasyon çalıştığında oluşacak tabloya bir bakalım. Terminalde ,

 
    rails db:migrate
   

Oluşan tabloya VSCode editörde bir göz atalım

schema.rb dosyasında da yeni tablomuzun yapısını görebiliriz. 

ActiveRecord::Schema[7.0].define(version: 2023_03_23_175520) do
  create_table "articles", force: :cascade do |t|
    t.string "title"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "comments", force: :cascade do |t|
    t.string "commenter"
    t.text "body"
    t.integer "article_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["article_id"], name: "index_comments_on_article_id"
  end

  add_foreign_key "comments", "articles"
end

t.index ["article_id"] tabloda bir sütun değil Comment kayıtlarını article_id değerlerine göre sorgularken cevap hızı artsın diye konmuş bir indexleme. Ayrıntı isterseniz SQL sorgularda CREATE INDEX araştırabilirsiniz. 

Neyse uygulamamıza dönelim. Comment modelinde kayıtların bir Article değerine bağlı olacağını belirttik ama tersi olarak Article kayıtlarının da bir ya da birden çok Comment kaydına bağlı olabileceğini belirtmeliyiz. Article modeli koduna şu ilaveyi yapalım:

app/models/article.rb
class Article < ApplicationRecord
    has_many :comments
   
    validates :title, presence: { message: 'boş olamaz' }
    validates :body, presence: { message: 'boş olamaz' },
        length: { minimum: 10, message: 'en az 10 karakter olmalı' }
end

has_many :comments satırı bir Article kaydının birden çok Comment kaydına sahip olabileceğini bildiriyor. comment değil comments yani çoğul yazıyoruz unutmayalım. 

Bu iki model dosyası içinde yaptığımız bağlantı bildirimi sayesinde kayıtlar içinde çok kolay sorgular yapabiliriz. Mesela @article.comments bize o makaleye yapılan yorumları verir. @comment.article ise bize o yorumun yapıldığı makaleyi verir. 

Adres bağlamalarını yaparken de modeller arasındaki ilişkiye uygun yapabiliriz.

config/routes.rb
Rails.application.routes.draw do
    root "articles#index"

    resources :articles do
        resources :comments
    end
end

Bu şekilde içi içe tanımlayınca adresler nasıl oluyor terminalde bir bakalım

Prefix isimlerine dikkat edelim. Bize daha kolay yollar sunuyor. Ancak uygulamamızda yapmayı düşündüklerimizden dolayı zaten yorumlar için makalelere benzer sayfalar hazırlamayacağız. 



Rails 7 Yorumlar İçin Kontrolör Eklemek

Terminalde comments için bir kontrolör üretelim

 
    rails g controller Comments
   

Yorumlar için ayrı ayrı sayfalar yapmayacağız. Mantık olarak makaleyi okuyanlar yorumu hemen yazının altında yaparlar. Bu yüzden articles/show.html.erb içine yorum yapmak için bir form ekleyelim. 

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
    <li><%= link_to "Düzenle", edit_article_path(@article) %></li>
    <li><%= link_to "Sil", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "#{@article.title} silinecek emin misiniz?"
                  } %></li>
</ul>

<h2>Yorum yapın:</h2>
<%= form_with model: [ @article, @article.comments.build ] do |form| %>
    <p>
        <%= form.label :yorumcu %><br>
        <%= form.text_field :commenter, size: 25 %>
    </p>
    <p>
        <%= form.label :yorum %><br>
        <%= form.text_area :body, cols: 25, rows: 4 %>
    </p>
    <p>
        <%= form.submit "Gönder"%>
    </p>
<% end %>

Burada anlamadığımız tek nokta model seçerken verdiğimiz [ @article, @article.comments.build ] değeri. Biz burada elimizde olan @article nesnesi özlliklerinden giderek bu formu oluşturmaya çalışıyoruz. Terminaldeki gördüğümüz Prefix değerleri ve adreslere bakarsak formumuzu /articles/:article_id/comments adresine POST eylemi ile göndermek gerekiyor.

 Daha önce article için form doldururken /articles adresine POST eylemi ile göndermek için 

<%= form_with model: @article do |form| %>

Yazmıştık ve Rails bizi @article nesnesinin kontrolöründeki create aksiyonuna gönderdi. Şimdi bize bir comment nesnesi lazım ki buraya parametre olarak girelim. Bu nesneye ulaşmak için @article.comments.build yazarsak elimizde @article nesnesinin comment'leri ile aynı yapıda bir nesne elde ederiz. 

<%= form_with model: [ @article.comments.build ] do |form| %>

yazarsak Rails comments_path adresine göndermeye kalkacak ama tanımlı adreslere bakarsak article_comments_path adresine göndermesi gerekiyor. 

<%= form_with model: [ @article, @article.comments.build ] do |form| %>

Şeklinde yazmak bizi birleşimi olan  article_comments_path adresine yönlendirir. articles_controller.rb içinde show aksiyonuna bu @article nesnesine bağlı bir @comment nesnesi üretip göndersek daha mı kolay olurdu acaba?

    def show
        @article = Article.find(params[:id])
        @comment = Comment.new(article_id: @article.id)
    end

Ama yine de içi içe adresleme olduğu için

<%= form_with model: [ @article, @comment ] do |form| %>

şeklinde yazmak gerekiyor. Seçim size kalmış.

Şimdi kontrolör içine form gönderilince çalışacak olan create aksiyonunu yazalım.

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
    def create
        @article = Article.find(params[:article_id])
        @comment = @article.comments.create(comment_params)
        redirect_to article_path(@article)
    end

    private
        def comment_params
            params.require(:comment).permit(:commenter, :body)
        end
end

Şimdi formu doldurup göndererek makaleye yorum yapabiliriz. Ama yapılan yorumları da show görselinde göstermemiz gerekiyor.

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
    <li><%= link_to "Düzenle", edit_article_path(@article) %></li>
    <li><%= link_to "Sil", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "#{@article.title} silinecek emin misiniz?"
                  } %></li>
</ul>

<hr>
<h2>Yorumlar</h2>
<% @article.comments.each do |comment| %>
    <p>
        <strong>Yorumcu:</strong>
        <%= comment.commenter %>
    </p>

    <p>
        <strong>Yorum:</strong>
        <%= comment.body %>
    </p>
<% end %>
<hr>

<h2>Yorum yapın:</h2>
<%= form_with model: [ @article, @comment ] do |form| %>
    <p>
        <%= form.label :yorumcu %><br>
        <%= form.text_field :commenter, size: 40 %>
    </p>
    <p>
        <%= form.label :yorum %><br>
        <%= form.text_area :body, cols: 40, rows: 4 %>
    </p>
    <p>
        <%= form.submit "Gönder"%>
    </p>
<% end %>

Artık yorum ekleyebiliriz. 



Rails 7 Kolleksiyonları Parça Görselde Yayınlamak 

Yorumları eklerken biraz dağıldık. Kodumuzu biraz toparlayalım. Mesela yorumları bir parça görselde toplayalım.

app/views/comments/_comment.html.erb
    <p>
        <strong>Yorumcu:</strong>
        <%= comment.commenter %>
    </p>

    <p>
        <strong>Yorum:</strong>
        <%= comment.body %>
    </p>

Sonra show.html.erb görselinde bu parça görseli kullanalım. 

app/views/articles/show.html.erb
....

<hr>
<h2>Yorumlar</h2>
<%= render @article.comments %>
<hr>

....

Bu render komutunu @article.comments kolleksiyonu ile çağırınca döngü yazmamıza gerek kalmıyor. Rails her bir yorum kaydı için _comment.html.erb görselini tekrar tekrar kullanıyor. 

Yeni yorum ekleme formumuzu da app/view/comments klasöründe bir parça görsele taşıyalım. Orada olması daha doğru.

app/views/comments/_form.html.erb
<%= form_with model: [ @article, @comment ] do |form| %>
    <p>
        <%= form.label :yorumcu %><br>
        <%= form.text_field :commenter, size: 40 %>
    </p>
    <p>
        <%= form.label :yorum %><br>
        <%= form.text_area :body, cols: 40, rows: 4 %>
    </p>
    <p>
        <%= form.submit "Gönder"%>
    </p>
<% end %>

Form kodunda hiç bir şeyi değiştirmeden parça görsele aktardık. Şimdi show.html.erb görsel kodumuz baya bir kısaldı. 

app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
    <li><%= link_to "Düzenle", edit_article_path(@article) %></li>
    <li><%= link_to "Sil", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "#{@article.title} silinecek emin misiniz?"
                  } %></li>
</ul>

<hr>
<h2>Yorumlar</h2>
<%= render @article.comments %>
<hr>

<h2>Yorum yapın:</h2>
<%= render 'comments/form' %>

Refactoring işlerimiz de bitti. 




Rails 7 Concern Kullanmak

Concern dosyaları karmaşık kontrolörler ve modeller kullanırken işleri basitleştirmek için kullanılır. Ayrıca bir çok modelde (veya kontrolörde) ortak kullanılabilecek işler için kullanılabilir. Concern kodları modüller kullanarak tanımlanır. 

Concern kodlarını kontrolör ya da modellerimizde aynı modül kullanır gibi kullanırız. Uygulamamızı ilk üretirken Rails 2 tane klasör tanımlar.

 
    app/controllers/concerns
    app/models/concerns
   

Concern kodları nasıl kullanılır görmek için modellerimize ilave yapacağız. Sonra yazdığımız concern kodları ile bu ilave özellikleri etkili bir şekilde kullanmayı göreceğiz. 

Bir blokta yazdığımız makalelerin çeşitli durumları olabilir. Örneğin yazımızı hazırlarken daha bitmeden önce sadece bizim göreceğimiz durum - private. Makaleyi yayınladığımızda herkesin görebileceği durum - public. Bir de eski makaleleri artık yayından kaldırmak ama veritabanından silmemek için arşivlenmiş durumu - archived

Bu durumları benzer şekilde yorumlar için de kullanabiliriz. Onaylanmamış yorumlar private, yayınlananlar public ve gösterimden kaldırılanlar archived gibi. Şimdi bu bilginin saklanacağı sütunları veritabanımıza ekleyelim. Terminalde

 
    rails g migration AddStatusToArticles status:string
    rails g migration AddStatusToComments status:string
   

Rails çok akıllı tasarlanmış  migrasyona verdiğimiz AddStatusToArticles kelimesinden Add ile bir ilave yapılacağı, ToArticles ile articles tablosuna ilave yapılacağını, Status ve sonra opsiyon verilen ststus:string ile status adında string tipi bir sütun ekleneceğini anlıyor. 

Şimdi migrasyon komutunu çalıştırıp bu eklediğimiz 2 migrasyonun gerçekleşmesini sağlayalım ve tablolarımıza bakalım nasıl olmuşlar.

 
    rails db:migrate
   

Bu yeni sütunları ilk önce kontrolörlerin içindeki parametre zorlamalarına ekleyelim.

app/controllers/articles_controller.rb
....
    private
        def article_params
            params.require(:article).permit(:title, :body, :status)
        end
end

app/controllers/comments_controller.rb
....
    private
        def comment_params
            params.require(:comment).permit(:commenter, :body, :status)
        end
end

Modellerimiz içine de bu yeni sütun için değer kotrolü eklemek gerekecektir. 

app/models/article.rb
class Article < ApplicationRecord
    has_many :comments
   
    validates :title, presence: { message: 'boş olamaz' }
    validates :body, presence: { message: 'boş olamaz' },
        length: { minimum: 10, message: 'en az 10 karakter olmalı' }

    GEÇERLİ_DURUMLAR = ['public', 'private', 'archived']

    validates :status, inclusion: { in: GEÇERLİ_DURUMLAR }

    def archived?
        status == 'archived'
    end
end

status sütunundaki değer GEÇERLİ_DURUMLAR içinde tanımladığımız durumlardan biri olmalıdır. Ayrıca bir kaydın  arşivlenmiş olduğunu gösteren basit bir metod ilave ederek görsellerde arşivlenmiş kayıtları kolayca filtrelemek istiyoruz. 

Aynı şeyleri Comment modelinde de yapmamız gerekiyor. 

app/models/comment.rb
class Comment < ApplicationRecord
    belongs_to :article

    GEÇERLİ_DURUMLAR = ['public', 'private', 'archived']

    validates :status, inclusion: { in: GEÇERLİ_DURUMLAR }

    def archived?
        status == 'archived'
    end
end

Şimdi index sayfamızda arşivlenmemiş makaleleri göstermek için ilave yapalım.

app/views/articles/index.html.erb
<h1>Makaleler</h1>

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
        <li>
            <%= link_to article.title, article %>
        </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "Yeni Makale", new_article_path %>

Yorumlar için hazırladığımız parça görsele de arşivlenmemiş yorumları göstersin diye ilave yapalım.

app/views/comments/_comment.html.erb
<% unless comment.archived? %>
    <p>
        <strong>Yorumcu:</strong>
        <%= comment.commenter %>
    </p>

    <p>
        <strong>Yorum:</strong>
        <%= comment.body %>
    </p>
<% end %>

Daha önce girilmiş olan kayıtlarda status sütunu boş olduğu için hepsi gösterilmeye devam edecektir. 

Bununla birlikte dikkatimizi çeken status sütunu yüzünden iki modelimizde de aynı kodları tekrar yazdık. İleride mesela private durumlu kayıtlar için ayrı bir işlem yapmaya kalksak yine her iki modelde de aynı kodları yazacağız. Tek yerde kod yazmak için modeller iin bir concern modülü ekleyelim. Kayıtların görünmesi ya da görünmemesine karar vereceğine göre modülümüze Visible adını verelim.

app/models/concerns/visible.rb
module Visible
  def archived?
    status == 'archived'
  end
end

Veri doğrulamaları da concern içinde yapılabilir ama bunun için ActiveSupport:: Concern sınıfı metodlarını kullanmak gerekiyor. 

app/models/concerns/visible.rb
module Visible
    extend ActiveSupport::Concern

    GEÇERLİ_DURUMLAR = ['public', 'private', 'archived']

    included do
        validates :status, inclusion: { in: GEÇERLİ_DURUMLAR }
    end
   
    def archived?
        status == 'archived'
    end
end

Şimdi bu modülü modellerimiz içinde kullanalım.

app/models/article.rb
class Article < ApplicationRecord
    include Visible

    has_many :comments
   
    validates :title, presence: { message: 'boş olamaz' }
    validates :body, presence: { message: 'boş olamaz' },
        length: { minimum: 10, message: 'en az 10 karakter olmalı' }
end

app/models/comment.rb
class Comment < ApplicationRecord
    include Visible

    belongs_to :article
end

Sınıf metodları da concern içine eklenebilir. Örneğin public durumlu makale sayısını hesaplayan bir sınıf metodumuzu concern içinde ifade edelim.

app/models/concerns/visible.rb
module Visible
    extend ActiveSupport::Concern

    GEÇERLİ_DURUMLAR = ['public', 'private', 'archived']

    included do
        validates :status, inclusion: { in: GEÇERLİ_DURUMLAR }
    end

    class_methods do
        def public_count
            where(status: 'public').count
        end
    end
   
    def archived?
        status == 'archived'
    end
end

Artık diğer sınıf metodları gibi bu metodu sanki model sınıfımızın bir metoduymuş gibi kullanabiliriz. index görselimizde o anda kaç tane public durumlu makalemiz olduğunu yazalım.

app/views/articles/index.html.erb
<h1>Makaleler</h1>

Bloğumuzda <%= Article.public_count %> makale var ve sürekli artıyor!

<ul>
  <% @articles.each do |article| %>
    <% unless article.archived? %>
        <li>
            <%= link_to article.title, article %>
        </li>
    <% end %>
  <% end %>
</ul>

<%= link_to "Yeni Makale", new_article_path %>

Şimdi ana sayfaya bakarsak

Sıfır makale var diyor. Çünkü şu andaki kayıtlar biz status sütununu eklemeden önce eklenmiş kayıtlar ve status değerleri şu anda boş. Kayıtları düzenleyebilmek için form parça görsellerine status bilgisi girilmesi için de kullanıcıya seçenek sunmalıyız. Formlarda gönderme düğmesinin hemen üzerlerine bu seçeneği koyalım.

app/views/articles/_form.html.erb
<%= form_with model: article do |form| %>
    ....

    <div>
        <%= form.label :durumu %><br>
        <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
    </div>

    <div>
        <%= form.submit b_caption %>
    </div>
<% end %>

Yorumlar için şimdilik düzenleme sayfası yok ama yeni yorum eklerken kullandığımız forma da durum seçimi, ekleyelim.

app/views/comments/_form.html.erb
<%= form_with model: [ @article, @comment ] do |form| %>
    ....
    <p>
        <%= form.label :durumu %><br>
        <%= form.select :status, ['public', 'private', 'archived'], selected: 'public' %>
    </p>
    <p>
        <%= form.submit "Gönder"%>
    </p>
<% end %>




Rails 7 Yapılan Yorumu Silmek

Bloğumuza yazdığımız yazılara yapılan yorumların uygunsuz olanlarını silmek için de bir link ekleyelim. Yorumlar için parça görselimiz _comment görseline destroy aksiyonunu çağıracak bir link ekleyeceğiz. 

app/views/comments/_comment.html.erb
<% unless comment.archived? %>
    <p>
        <strong>Yorumcu:</strong>
        <%= comment.commenter %>
    </p>

    <p>
        <strong>Yorum:</strong>
        <%= comment.body %>
    </p>

    <p>
        <%= link_to "Yorumu Sil", [comment.article, comment], data: {
                    turbo_method: :delete,
                    turbo_confirm: "Yorum silinecek emin misiniz?"
                    } %>
    </p>
<% end %>

Şimdi kontrolörümüze destroy aksiyonunu ekleyelim.

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
    def create
        @article = Article.find(params[:article_id])
        @comment = @article.comments.create(comment_params)
        redirect_to article_path(@article)
    end

    def destroy
        @article = Article.find(params[:article_id])
        @comment = @article.comments.find(params[:id])
        @comment.destroy
        redirect_to article_path(@article), status: :see_other
    end

    private
        def comment_params
            params.require(:comment).permit(:commenter, :body, :status)
        end
end

@article nesnesini yorumu sildikten sonra tekrar tekrar makalenin görseline dönmek için kullanıyoruz. Geri kalan kısım Article için yaptığımızla aynı.



Rails 7 Kayda Bağlı Diğer Tablodaki Kayıtları da Silmek

Bir makaleyi sildiğimizde comments tablosunda ona bağlı yorumları silmezsek boş yere vertabanında yer kaplarlar. Rails bu amaçla bize model tanımında yapabileceğimiz bir kolaylık sağlıyor.

app/models/article.rb
class Article < ApplicationRecord
    include Visible

    has_many :comments, dependent: :destroy
   
    validates :title, presence: { message: 'boş olamaz' }
    validates :body, presence: { message: 'boş olamaz' },
        length: { minimum: 10, message: 'en az 10 karakter olmalı' }
end

has_many :comments ile article kaydının comments tablosunda birden çok kayda bağlantısı olabileceğini ifade ederken, dependent: :destroy ile eğer bir article kaydı silinirse ona bağlı comment kayıtlarının da silinmesi için destroy eylemleri bağlanıyor.

Deneme için bir kayıt ve ona yapılmış 2 yorum ekledikten sonra makaleyi sildiğimizde server konsolunda yapılan işlemler şöyle sıralanıyor:

Önce bağlı yorumları tek tek silip en son makaleyi de siliyor.




Rails 7 Basit Yetkilendirme

Bloğumuzu bu haliyle yayınlarsak sayfaya bağlanan herkes yeni yazı veya yorum ekleyebilir. Yazılan yazıları ya da yorumları silebilir. Bir yetkilendirme sistemi kurup blog sahibinin yapabileceklerini belirtmemiz gerekiyor.

Rails bize basit HTTP yetkilendirme kullanmak için imkan sağlıyor. http_basic_authenticate_with metodu sayesinde belirlenen aksiyonların erişimini sadece verilen kullanıcı adı ve şifreyi doğru giren kişilerin erişebileceği hale getirebiliriz. 

Mesela Articles kontrolöründe index ve show aksiyonlarına herkes erişebilsin ama diğer aksiyonlara sadece yetkili kişi erişebilsin şeklinde ayarlayalım.

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    http_basic_authenticate_with name: "dhh", password: "secret",
except: [:index, :show]

    def index
        @articles = Article.all
    end
....

Şimdi bir makale eklemeye , olan bir makaleyi değiştirmeye ya da silmeye kalktığımızda bize kullanıcı adı ve şifre soracaktır.

Burada kullanıcı olarak dhh ve şifre olarak secret girersek işlem devam eder yoksa erişimin engellendiğine dair bir hata mesajı çıkar. except: [:index, :show] opsiyonu ile index ve show haricinde tüm aksiyonlar için bu parola korumasının yapılması isteniyor. 

Yorum içinse sadece yorum silmek için yetki isteyelim. Zaten sadece yeni yorum ekleme ve olanı silmek üzerine aksiyonlarımız var orada.

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
    http_basic_authenticate_with name: "dhh", password: "secret",
            only: :destroy

    def create
        @article = Article.find(params[:article_id])
        @comment = @article.comments.create(comment_params)
....

Bu sefer opsiyon olarak only: :destroy verdik, böylece sadece destroy aksiyonu için şifre istenecek.

HTTP basit yetkilendirme işleminde logout yapmak baya zor. Basit şekli tarayıcınızı kapatıp tekrar açmak. JavaScript Ajax falan bulaşmadan işi Rails ile çözmek için boş bir şifreyle girilen logout aksiyonu ekleyerek işi çözebiliriz. 

app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
    http_basic_authenticate_with name: "dhh", password: "secret",
            except: [:index, :show, :logout]
    http_basic_authenticate_with name: "", password: "",
            only: :logout

    def logout
        redirect_to articles_path
    end

    def index
        @articles = Article.all
.....

Tabi ki routes.rb içinde bu aksiyona bir bağlantı eklemeliyiz. 

config/routes.rb
Rails.application.routes.draw do
    root "articles#index"

    resources :articles do
        resources :comments
    end
    get :logout, to: "articles#logout"
end

Bir de ana sayfada logout yapmak için link ekleyelim

app/views/articles/index.html.erb
<h1>Makaleler</h1>

....

<%= link_to "Yeni Makale", new_article_path %> -
<%= link_to "LOGOUT", :logout %>

Kullanıcı LOGOUT linkine tıklayınca şifre sorar eğer hiçbir şey girmeden Tamam basarsa çıkış gerçekleşir ve articles ana sayfasına döner. 

Orjinal yazı burada bitti, ben arada aklımın erdiğince bir şeyler ekledim. Umarım yardımı olur. Sonraki yazılarda buluşmak üzere kalın sağlıcakla..






Hiç yorum yok:

Yorum Gönder