Rails – Action Pack – Kavramlar

Active Record modellerden sorumlu olan Rails kütüphanesi olduğu gibi, Action Pack ise view ve controller’dan sorumlu Rails kütüphanesidir.

Action Controller

  • Rails uygulamamızda gerçekleşen her türlü etkileşim ve hareket controller tarafından kontrol edilir.
  • Rails’te controller’lar Ruby sınıfları olarak tasarlanırlar.
  • Controller içerisinde, çeşitli action’lar için yazılmış metodlar bulunur.

Aksi belirtilmediği sürece herhangi bir Ruby sınıfı içerisindeki metodların durumu “public“‘tir yani herhangi bir kişi tarafından erişilebilir durumdadırlar. Eğer bir metodu “private” olarak tanımlarsanız, o metod yalnızca ait olduğu class içerisinde kullanılabilir olur. Private olarak tanımlanmış bir metodu ait olduğu class dışarısından çağırmaya çalışırsanız “NoMethodError” hatası döner.

Rails’te sıklıkla ve aksi belirtilmedikçe, bir action çalıştıktan sonra bir view render edilir. Action Controller, convention’ın bir sonucu olarak render edeceği view’ı action’ın adıyla bulmaya çalışır. Örneğin “app/controllers/articles_controller.rb” controller’ı içerisinde bulunan “new” metodu, convention gereği “/app/views/articles/new.html.erb” sayfasını render eder.

Action View

Controller ve View’ın iletişim halinde kalabilmesi için paylaşılan değişkenlerden (shared variables) faydalanırız. Bu değişlenlerin tanımı “@” sembolü ile başlar ve controller’da tanımlandıktan sonra view’dan çağırılabilirler.

Burada @articles değişkenimiz controller’dan geliyor.

Embeded Ruby (ERB)

ERB sayesinde Ruby kodlarımızı view içerisinde kullanabiliriz. View dosyalarımız içerisinde <% %> (evaluation embedding tags) ve <%= %> (output embedding tags) etiketlerini kullanarak Ruby kodlarının view’da işlemesini sağlarız. Bu iki etiketten yalnızca output türünde “=” işareti bulunduğuna dikkat ediniz. Output etiketi yani <%= %> ile, işlenen Ruby koduna ait çıktının print edilmesini istediğimizi belirtiriz. Oysa ki evaluation etiketleri yani <% %> kullanıldığında, etiketler içerisindeki kod işlenir ve süreç tamamlanır, yani herhangi bir print işlemi yapılmaz.

Helpers

Her ne kadar ERb ile view’da çeşitli Ruby kodlarını çalıştırabiliyor ve controller’da view’a dair işler yaptırabiliyor olsakta, bu durum MVC’nin yapısı ve mantığına terstir. Controller’ın asıl işi view kodu üretmek olmadığı gibi, view’in işi de mantıksal süreçleri çözümlemek değildir. View ile controller arasındaki bu boşluğu dolduran yapılara “helper” adı verilir. Helper’lar sorun yaratan bu boşluğu doldurarak controller ve view arasında bir köprü görevi görürler. Her bir controller’ın kendine ait helper’ı vardır. Örneğin “articles_controller.rb” için “articles_helper.rb” dosyası.

Action Pack Request Döngüsü

Action Pack request-response döngüsü aşağıdaki adımlardan oluşur:

  1. Rails uygulamamız dış dünyadan (çoğunlukla browser) bir request alır.
  2. Routing, çalışması gereken controller ve action’ı tespit etmek için gelen request’i parçalar.
  3. Request’i ele alacak controller belirlenir ve ilgili metodlar çağırılır.
  4. Controller model ile iletişime geçer. Çoğunlukla bir CRUD süreci gerçekleştirir.
  5. Browser’a (veya request’i yapan her ne ise) render edilecek sayfayı içeren veya yalnızca redirect’ten ibaret bir response döndürülür.

rails-action-pack-request-response-cycle

Rails – Callbacks

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

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

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

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

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

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

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

User Modelinin Güncellenmesi

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

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

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

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

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

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

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

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

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

Sürecin Özeti

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

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

KPS & MERNİS Sorgulama – Ruby, Savon ve SOAP

Geçtiğimiz hafta hem TTMesaj servisini hemde KPS’i (Kimlik Paylaşım Sistemi) Rails uygulamalarına entegre etme ihtiyacımızla birlikte, ismini pek sık duyduğum SOAP ile haşır neşir olmak durumunda kaldım. Üç-dört gün sonunda SOAP bana herhangi bir üçüncü dünya ülkesinin beyaz yakalı “plaza” çalışanını çağrıştırıyor. Bu çağrışımda ülkemizdeki SOAP servisleri ile haberleşen kod örneklerinin büyük çoğunlukla C#, ASP.Net ve Java ile yazılmış olmasının payı büyük.

Ruby tarafında ise Savon GEM’i olmasa halimiz yamanmış. Eğer sizde benim gibi SOAP hakkında zerre bilgi sahibi değilseniz ve bir şekilde bu servis ile haberleştirmek zorunda olduğunuz bir Ruby uygulamanız varsa ilk önce SOAP UI ile denemeler yapmanızı tavsiye ederim.

Bu uygulama içerisinde File -> New Project yolunu takip ederek, haberleşmeye çalıştığınız WSDL adresini yazın. Daha sonra WSDL’in sunduğu operasyonlara request aracı ile envai çeşit request yapmayı deneyerek dönen response’ları inceleyebilirsiniz.

İkinci bakmanız gereken yer ise Savon SOAP Client. Sırayla basitten zora, önce TTMesaj servisiyle haberleşmeyi – sonra ise KPS ile haberleşmeyi örnekleyeceğim.

TTMesaj SOAP Client

İlk önce WSDL üzerindeki mümkün tüm operasyonları listeyelim:

Request
Response

İlgili WSDL sayfasını ziyaret ederek bu operasyonlardan herhangi birini inceleyerek nasıl bir request yapmamız gerektiğini inceleyelim. Örneğin “send_single_sms” operasyonuna bakalım:

Parametre isimlerini dikkate alarak SOAP mesajımızı hazırlayalım:

Bu çağrı sonucunda mesajınızın başarıyla iletildiğine dair “OK*” işaretli bir response alacaksınız. Uygulamanın tümüne şuradan erişebilirsiniz:

https://github.com/msdundar/TTMesaj

KPS (Kimlik Paylaşım Sistemi)

KPS tarafında işler beni umduğumdan daha çok zorladı. 3-4 gün boyunca başarılı bir response almak için uğraştım. KPS servisi TTMesaj’dan farklı olarak, requestiniz içerisinde bir takım attribute’ler ve tamamen doğru kurgulanmış bir SOAP Header görmeyi bekliyor.

Öncelikle biraz önce yaptığımız gibi bir keşif turuna çıkalım. İlgili WSDL adresini ziyaret ettiğimizde beklenen request yapısını görebiliyoruz:

Burada farklı bir çok uygulama var. Öncelikle authentication “header” altında direk değil, header altında bulunan “KPSUserInfo” içerisinde “UserName” ve “Password” ile gerçekleşiyor.

Diğer farklı durum ise, “KPSUserInfo” ile birlikte “xmlns” adresininde attibute olarak verilmesi bekleniyor. Aynı durum body’de yine karşımıza çıkıyor. Orada ise “TCKimlikNoSorgu” altında mesajın verilmesi ve xmlns attribute’ü bekleniyor. Uzun araştırmalar sonucunda Savon ile bu işin nasıl yapılabileceğini buldum. Sonuçta ortaya şöyle bir uygulama çıktı:

Burada beni en çok zora sokan “:attributes!” hakkında Savon’un hiç dökümantasyon yapmamış olması, “username” yerine “UserName” gibi camel case takıntılı SOAP servisi, ve header ile body altında kullanılan alt parametreler oldu.

Uygulamanın tümüne şuradan erişebilirsiniz:

https://github.com/msdundar/kps_soap_client

Başarılar.

Rails – Validasyonlar

Yerleşik Validasyonlar

Tüm yerleşik validasyonlar varsayılan olarak aşağıdaki iki argümanı alabilirler.

Argüman Tanım Örnek
:message Validasyon sağlanamadığı durumlarda gösterilecek mesaj. :message => “Bir hata var”
:on Validasyonun ne zaman uygulanacağını belirtir. Seçenekler, :create veya :update’dir. :on => :create

Varlık kontrolünü “validates_presence_of” deyimi ile sağlarız:

Tekillik kontrolünü (unique), validates_uniqueness_of” deyimi ile sağlarız:

Uzunluk veya boyut kontrolünü “validates_length_of” ile deyimi ile sağlarız:

“validates_lenght_of” validatörü ile kullanabileceğimiz pek çok seçenek vardır:

Seçenek Tanım
:minimum Özelliğin en az sahip olması gerektiği boyut.
:maximum Özelliğin en fazla sahip olması gerektiği boyut.
:is Özelliğin tam olarak sahip olması gerektiği boyut.
:within Özelliğin bulunması gerektiği boyut aralığı.
:allow_nil Özelliğin “nil” olabileceğini belirtir. Eğer “nil” ise validasyon pas geçilir.
:too_long Özelliğin çok uzun olması durumunda ki hata mesajı.
:too_short Özelliğin çok kısa olması durumunda ki hata mesajı.
:wrong:length Hatalı boyutta olma durumunda ki hata mesajı.

Bir attribute’ün formatını kontrolden geçirmek için “validates_format_of” deyimi kullanılır:

Doğrulama (confirmation) işlemleri için “validates_confirmation_of” metodunu kullanabiliriz:

Burada password, users tablosunda ki bir kolon olmasına rağmen, password_confirmation tamamen sanaldır ve yalnızca validasyon amacıyla bellekte barınmaktadır.

Son olarak, validates_acceptance_of metodu ile boolean alanları kontrol edebiliriz.

Özelleştirilmiş Validasyonlar

Henüz paylaşılmamış olan bir Article’a kimsenin yorum yazamayacağından emin olmak için Article’ın published_at alanının null olup olmadığını kontrol eden bir validasyon hazırlayabiliriz.

Validasyonlar oluştururken Active Record’un error nesnelerinden faydalanabiliriz. Herhangi bir validasyon hatasını “errors.add(kolon_adi, hata_mesaji)” şeklinde hazırlayabiliriz. Şimdi bu mantıkla, Comment modelimize article_should_be_published metodunu ekleyelim.

Burada validasyonumuz “if” ifadesini inceleyerek, hata durumunu uygulayıp uygulamayacağına karar veriyor.

Article’ın published olup olmadığını test etmeden önce Article’ın “nil” durumda olmadığından emin olmamız gereklidir, böylelikle yaptığımız test hata döndürmez. Article’ın “nil” durumunda olup olmadığını validates_presence_of metodu ile kontrol ederiz.

Active Record’a, hazırladığımız “article_should_be_published” metodunun, yorumlar kayıt edilmeden önce çalışması gerektiğini şu şekilde söyleyebiliriz:

“validate :article_should_be_published”

Şimdi validasyonları uygulayalım:

Şimdi bu yaptıklarımızı konsolda test edelim. Active Record’da hataları görebilmenin-alabilmenin en kolay yolu “model_ismi.errors.full_messages” metodunu kullanmaktır.

Rails – Gelişmiş Aramalar

Daha önceki yazılarda “find” metodunu örneklerle kullanmıştım. Bu yazıda ise “where” metodundan bahsedeceğim.

where Metodu ile Arama

where metodu” ile kullanacağımız arama şartlarını hash syntaxı ile yazabiliriz. Bu sözdizimi yani hash olarak yazdığınız şartlar yalnızca direk sağlandığında kullanışlıdır. Yani şartın direk eşleşmesi beklenir, SQL’deki like sorguları gibi benzerlik durumlarını içermez.

SQL Cümlelerinin Kullanımı

where” metodu ile birlikte daha karmaşık sorgular yapabilmek için SQL sorgularından yararlanabiliriz. Örneğin biraz önce “profile” üzerinde yaptığımız aramayı şimdi düz SQL sorgusu ile yapalım:

Daha karmaşık SQL sorgularıda yazabilirdik:

Bu şekilde SQL sorgularının doğrudan yazıldığı where metodları uygulama içerisinde kesinlikle kullanılmamalıdır! Aksi halde Rails uygulamanız SQL Injection saldırılarına maruz kalabilir. Konuya ilgi duyanlar devam etmeden önce şunları okuyabilir:

Şart Dizilerinin Kullanımı

Uygulamanızın SQL injection saldırılarına maruz kalmamasını istiyorsanız, SQL sorgularınızı şart dizileri halinde yazmalısınız. Örneğin bugünden önce oluşturulmuş “user“ları bulalım:

Burada “?” karakterini placeholder olarak kullandık, metodumuz ise çalıştırılırken ikinci argümanı alarak “?” karakterinin olduğu yere koyacağını zaten biliyordu. Bu uygulamayı Python programlama dilinde ki “%s” karakterine, yani string placeholder’ına benzetebilirsiniz.

Yazdığınız koşulun çalıştırıldığında SQL olarak neye benzeyeceğini incelemek için “to_sql” metodunu kullanabilirsiniz:

Şimdi ise sorgu dizimize birden çok argüman paslamayı deneyelim ve hatta biraz önce yazdığımız ve güvenlik zaafiyeti olan sorguyu tekrar yazalım:

Güvensiz Sorgu:
Güvenli Sorgu:

Active Record – Arama Metodları

Metod Örnek
where(arama_şartları) Profile.where(“created_at > ? AND name like ?”, ‘2013-01-01’, ‘%Serhat%’)
order Article.order(“title DESC”)
limit Profile.limit(10)
joins Profile.joins(:comments)

  • İlişkili tabloların sorguya (SQL JOIN sorgusunda olduğu gibi) dahil edilmesi için.
includes Profile.includes(:comments)

  • İlişkili tabloların sorguya (SQL JOIN sorgusunda olduğu gibi) dahil edilmesi ve Active Record nesneleri olarak döndürülmesi için.

Bu metodların hepsini birbirine bağlayarakta kullanabiliriz:

Default Scope

Rails uygulamalarında sıkça kullandığımız “find işlemleri üzerinde tanımlamalar yapmak için “scope“ları kullanabiliriz.

Şuan uygulamamızda Category modelinin tüm nesnelerini çağırdığımızda bunların default olarak id’lerine göre sıralandığını görebilirsiniz:

Category’leri alfabetik olarak isimlerine göre sıralamak için “default_scope” kullanabiliriz:

Tekrar deneyelim:

Named Scope

default_scope“a ekleyeceğimiz durumlar, her zaman ve her koşulda uygulanacağı için bazen kullanışlı olmayabilirler. Sıklıkla kullandığımız sorgular için “named scope“lar oluşturmak bazı durumlarda daha kullanışlıdır. Örneğin yayınlanmış olan article’ları temsilen “published“, henüz yayınlamamış olanları temsilen ise “draft” isimli scope’ları oluşturalım:

Yukarıdaki örnekte “where” metodunu diğer metodlardan bağımsız ve tek başına kullandık. Ancak arama metodlarını başka named scope’lar ilede birbirine bağlayabilirdik. Örneğin “recent” isiminde bir scope oluşturalım ve bu scope bize son 1 hafta içerisinde paylaşılmış olan article‘ları versin ancak bunu yaparken öncelikle bu article‘ların “published” durumda olup olmadığına – published scope’undan bakarak karar versin.

Burada scope’ları birbirine nasıl bağladığımıza dikkat edin:

  • lambda’lar standalone metodları tanımlayan anahtar kelimelerdir. Bu metodlar yalnızca siz onları çağırdığınızda çalışırlar.

Şu ana kadar hazırladığımız scope’lar, yalnızca bizim verdiğimiz sorguları uyguluyor ve kullanıcıyla etkileşime geçmiyorlar. Yani hard-coded olarak yazılmış durumdalar. Şimdi, kullanıcıların “title“a göre arama yapabilmelerine yardımcı olan “where_title” scope’unu hazırlayalım:

Daha yakından bakacak olursak:

Hazırladığımız scope’ları test edelim:

Devam edecek.

Rails – Active Record – Model İlişkileri – 3

Çoktan Çoğa İlişkilerin Oluşturulması

Category ve Article isminde iki modelimiz olsun. Article modelinin herhangi bir nesnesinin birden çok Category’ye ait olduğu, aynı zamanda kategori nesnesinin birden çok Article nesnesini içerdiği durumlar çoktan çoğa ilişkiye, yani has_and_belongs_to_many durumuna örnek olabilir.

has_and_belongs_to_many ilişkisi, referans foreign key’leri tutan bir “join table” yani birleştirme tablosu ile birlikte çalışır. Birleştirme tablosu, birleştirilmek istenen iki tablo arasında – yani mevcut durumda “articles” ve “categories” arasında bulunur.

Birleştirme tablosu konvansiyonel olarak, birleştirilecek tabloların isimlerinin alfabetik sırayla ve alt çizgi ile birleştirilmesinden oluşur. O halde mevcut durum için birleştirme tablosunun ismi “articles_categories” olacaktır.

has_and_belongs_to_many
Görselde bir hata var, normalde join tablosunun adı articles_categories‘tir.

Öncelikle Category modelini oluşturalım:

Ardından join table’ı, yani birleştirme tablomuzu oluşturalım:

Normal şartlar altında primary key’ler migration sırasında otomatik olarak oluşur. Ancak birleştirme tablolarında primary key’e ihtiyacımız olmadığı için, migration dosyasında bu durumun  :id => false ile belirtilmesi gereklidir ki primary key oluşturulmasın:

t.references :article;

şeklinde de kullanılabilirdi. Veritabanımızı migrate edelim:

Şimdi ise model dosyalarımızı bu kurduğumuz ilişkiden haberdar edelim:

article.rb
category.rb

Seeding Data

Uygulama iskeletini oluştururken devamlı olarak ihtiyaç duyacağımız verileri, veritabanına eklenmek üzere hazır tutan “db/seeds.rb” dosyası her Rails uygulamasında varsayılan olarak bulunur. Bazı kategorilerin ve kullanıcıların otomatik olarak oluşturması için dosyayı düzenleyelim:

DB’yi seed edelim:

Eğer ileride daha çok kategori eklememiz gerekirse seed dosyasına bunlarıda ekleyip rake task’i tekrar çalıştırabiliriz ancak bunu yaparken, seeds dosyasının verilerin varolup olmadığına dair bir kontol içermediğini unutmamak gerekir. Yani bir başka değişle, rake db:seed‘i her çalıştırdığımızda, seed dosyası içerisindeki veriler veritabanında tekrar oluşturulur. Bunun bir sonucu olarak bir çok duplicate veri ile karşılabilirsiniz. Bu durumun oluşmaması için;

çalıştırabilirsiniz. Bu rake task çalıştırıldığı zaman veritabanını sıfırdan tekrar oluşturur ve seed dosyasındaki verileri veritabanına ekler. Yaptığımız işlemleri kısaca test edelim:

Gördüğünüz gibi category ve article’ı “<<” operatörü aracılığı ile ilişkilendirdik. Yukarıda ki işlemleri, ilişkinin diğer tarafından, yani cateogry üzerinden de gerçekleştirebilirdik çünkü has_and_belongs_to_many ilişkisi çift taraflı çalışır.

Çoktan çoğa ilişkilerde, has_and_belongs_to_many hem kurulması çok kolay hemde kullanışlı bir ilişki türüdür. Ancak kurduğumuz has_and_belongs_to_many ilişkisinin join table’ında, ilişkili tablolardan gelen ID’ler dışında başka hiç bir veri tutulamadığından ve ortada join table’a ait bir model bulunmadığı için bu ilişki türü bazen yeterli gelmeyebilir.

Örneğin article ile category’nin ne zaman ilişkilendirildiğini tutan bir timestamp alanı oluşturmak veya bu ilişkiyi kuran user’ın IP adresini saklamak istesek bunun için en uygun yer şüphesiz join table olacaktır. Ancak ortada join table’a ait bir model bulunmadığı için bunlar gibi çeşitli bilgileri tutmamız mümkün değildir. Bu gibi verileri tutabileceğimiz, kendi modeli olan ve join table’ı primary key içeren çoktan çoğa ilişkileri ise has_many :through ile kurabiliriz.

Çoktan Çoğa Zengin İlişkilerin Oluşturulması

Bu ilişki türünün temel özelliği; kurulan ilişki verilerinin tutan join table için komple (primary key’e sahip) bir model oluşturulmasıdır.

Örnek uygulamamızdan yola çıkarak planlayacak olursak; normalde article’lar pek çok yoruma sahip olabilir ve biz bunu sağlamak için ilk önce comment modelimizi oluştururuz. Ardından ise bu iki modelin (comment ve article), arasında has_many ve belongs_to ikilisiyle bire-bir ilişki kurarız. Peki bir user’ın article’ına yazılmış olan tüm yorumları bulmak istesek? Buna benzer bir ilişkiyi aşağıdaki gibi kurarız:

rich_many_to_many

Comment” modelimiz için modeli ve migration’ı oluşturalım:

DB’yi migrate edelim:

Modellerimizi kurduğumuz ilişkiye göre düzenleyelim:

article.rb
comment.rb

Aslında burada kurduğumuz ilişki daha önceden kurduğumuz users-articles ilişkisi ile aynı. Aradaki fark ise bu durumda, user’ın birden çok article’a sahip olması değil – article’ın birden çok comment’e sahip olması.

Kurmaya çalıştığımız users-comments ilişkisine geri dönelim. User modeline; “bir user – articles üzerinden birden çok comment’e sahiptir” ilişkisini tanıtmak için User modelimizi düzenleyelim:

Kurduğumuz ilişkiyi console’dan test edelim: