Rails: Self Joins
11 Mar 2016

Hepiniz fight club filmini izlemişsinizdir veya duymuşsunuzdur. self join filmin sonunda ana karakter için Tyler’ın(Brad Pitt) kendisi çıkması gibi bir olay. Aslında kendisiymiş ya la diyorsunuz.

Az önce spoiler yediniz. Afiyet olsun.

***

Hepimiz günlük hayatta çalışanların birbirine bağlı olduğu bir sistem içindeyiz. Herkes aslında bir çalışan ama başka bir çalışana da bağlı.

Yemek söylerken gördüğümüz çoğu menü’de kategoriler var onların altında onlara bağlı başka kategoriler var.

Herkesin sosyal medya üzerinde yüzlerce arkadaşı var, sisteme tepeden baktığımızda hepimiz birer kullanıcıyız, diğer kullanıcılara bağlıyız. (Yine mühendis kafasıyla açıklama yaptım kahrolsun!)

Ben sizlere kendi kendine bağlı olan bu sistemlerin veri modellemesini nasıl yapacağız onu anlatacağım.

One to Many (1-M)

Örneğin, bir tabloda tüm çalışanlarımızı tutmak istiyoruz ve her çalışanın bir yöneticisi olsun.

class Employee < ActiveRecord::Base
  has_many :subordinates, class_name: "Employee",
                          foreign_key: "manager_id"

  belongs_to :manager, class_name: "Employee"
end

Modelimizi bu şekilde güncelledikten sonra artık çalışanın yöneticisine ve ona bağlı olan çalışanlara erişebiliyoruz.

@employee.subordinates
@employee.manager

Tabii gerekli migration’ları eklemeyi unutmayın.

Many to Many (M-N)

Bir arkadaşlık sistemi yazacağımızı düşünelim, elimizde User isminde bir modelimiz var. User’lar User’lar ile eşleşiyor. Burada şöyle bir yol izleyebiliriz.

Friendship isminde ara bir model oluşturalım ve içinde user_id ile friend_id alanları olsun. Gerekli migration’ları ekledikten sonra modellerimizi de şöyle ilişkilendirelim.

# app/models/friendship.rb
class Friendship < ActiveRecord::Base
  belongs_to :user
  belongs_to :friend, class_name: "User"
end
# app/models/user.rb
class User < ActiveRecord::Base
  has_many :friendships
  has_many :friends, through: :friendships
end
ali = User.find_by(email: 'ali@example.com')
ayse = User.find_by(email: 'ayse@example.com')
ali.friends << ayse
ali.friends # => [ayse]

ayse.friends # => []

Hoayda! Bunu callback düzenlemeleri ile arkadaşlık ilişkisi kurulduğunda çift taraflı oluşturabiliriz. Peki nasıl?

# app/models/friend.rb
class Friend < ActiveRecord::Base
  belongs_to :user
  belongs_to :friends, class_name: "User"

  after_create :create_inverse, unless: :has_inverse?
  after_destroy :destroy_inverses, if: :has_inverse?

  def create_inverse
    self.class.create(inverse_friend_options)
  end

  def destroy_inverses
    inverses.destroy_all
  end

  def has_inverse?
    self.class.exists?(inverse_friend_options)
  end

  def inverses
    self.class.where(inverse_friend_options)
  end

  def inverse_friend_options
    { friend_id: user_id, user_id: friend_id }
  end
end

Bu eklemeler sonrası artık bir arkadaşlık kurulduğunda iki taraflı oluşacak. Silindiğinde de iki taraflı silinecek. Bu aslında biraz takip sistemine benziyor.

Facebook üzerinden konuşacak olursak, bir kişiyi takip edebilirsiniz, eğer o da sizi takip ederse arkadaş olursunuz. Şu anki haliyle ilerde böyle bir yapı da oldukça kolay bir şekilde kurulabilir.

Railscasts videosunda ise foreign_key değiştirilerek başka bir ilişki daha kuruluyor senin eklediklerin ve seni ekleyenler gibi iki ayrı ilişki var.

Kolay gelsin.

Kaynaklar

Aşkın Gedik Ruby on Rails Developer