Yanıtlar:
Çoktan çoğa pek çok ilişki türü vardır; kendinize şu soruları sormalısınız:
Bu, dört farklı olasılık bırakır. Aşağıda bunların üzerinden yürüyeceğim.
Referans için: konuyla ilgili Rails belgeleri . "Çoktan çoğa" adlı bir bölüm ve elbette sınıf yöntemlerinin kendileriyle ilgili belgeler var.
Bu, kod açısından en kompakt olanıdır.
Gönderileriniz için bu temel şema ile başlayacağım:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
Çoktan çoğa herhangi bir ilişki için bir birleştirme tablosuna ihtiyacınız vardır. İşte bunun için şema:
create_table "post_connections", :force => true, :id => false do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
end
Varsayılan olarak, Rails bu tabloya katıldığımız iki tablonun adlarının bir kombinasyonu diyecektir. Ama bu posts_posts
, bu durumda olduğu gibi ortaya çıkacaktı , ben de post_connections
onun yerine almaya karar verdim .
Burada çok önemli olan :id => false
, varsayılan id
sütunun çıkarılmasıdır. Rails, bu sütunu birleştirme tabloları dışında her yerde istiyor has_and_belongs_to_many
. Yüksek sesle şikayet edecek.
Son olarak, post_id
çakışmayı önlemek için sütun adlarının da standart olmadığına (değil ) dikkat edin.
Şimdi modelinizde, Rails'e bu standart olmayan birkaç şeyi anlatmanız yeterlidir. Aşağıdaki gibi görünecek:
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
end
Ve bu basitçe çalışmalı! İşte üzerinden çalıştırılan bir irb oturumu örneği script/console
:
>> a = Post.create :name => 'First post!'
=> #<Post id: 1, name: "First post!">
>> b = Post.create :name => 'Second post?'
=> #<Post id: 2, name: "Second post?">
>> c = Post.create :name => 'Definitely the third post.'
=> #<Post id: 3, name: "Definitely the third post.">
>> a.posts = [b, c]
=> [#<Post id: 2, name: "Second post?">, #<Post id: 3, name: "Definitely the third post.">]
>> b.posts
=> []
>> b.posts = [a]
=> [#<Post id: 1, name: "First post!">]
posts
İlişkilendirmeye atamanın , post_connections
tabloda uygun şekilde kayıtlar oluşturacağını göreceksiniz .
Dikkat edilmesi gereken bazı noktalar:
a.posts = [b, c]
çıktı b.posts
ilk gönderiyi içermez.PostConnection
. Normalde bir has_and_belongs_to_many
ilişkilendirme için modeller kullanmazsınız . Bu nedenle, herhangi bir ek alana erişmeniz mümkün olmayacaktır.Doğru, şimdi ... Bugün sitenizde yılan balıklarının ne kadar lezzetli olduğu hakkında bir gönderi yapan düzenli bir kullanıcınız var. Bu tamamen yabancı, sitenize gelir, kaydolur ve normal kullanıcının beceriksizliği hakkında bir azarlama yazısı yazar. Sonuçta yılanbalıkları nesli tükenmekte olan bir türdür!
Bu nedenle, veritabanınızda B gönderisinin A gönderisine yönelik bir azarlama sözü olduğunu açıkça belirtmek istersiniz. Bunu yapmak category
için, ilişkiye bir alan eklemek istersiniz .
İhtiyacımız artık olup has_and_belongs_to_many
, ancak bir arada has_many
, belongs_to
, has_many ..., :through => ...
ve ekstra modeli masaya katılmak. Bu ekstra model, bize derneğin kendisine ek bilgi ekleme gücü veren şeydir.
İşte yukarıdakine çok benzeyen başka bir şema:
create_table "posts", :force => true do |t|
t.string "name", :null => false
end
create_table "post_connections", :force => true do |t|
t.integer "post_a_id", :null => false
t.integer "post_b_id", :null => false
t.string "category"
end
Uyarı bu durumda, nasıl, post_connections
yok bir var id
sütunu. ( Parametre yoktur :id => false
.) Bu gereklidir, çünkü tabloya erişim için normal bir ActiveRecord modeli olacaktır.
Modelle başlayacağım PostConnection
çünkü çok basit:
class PostConnection < ActiveRecord::Base
belongs_to :post_a, :class_name => :Post
belongs_to :post_b, :class_name => :Post
end
Burada olan tek şey :class_name
, gerekli olan, çünkü Rails bir Gönderiden post_a
veya post_b
burada bir Gönderiyle uğraştığımız sonucunu çıkaramaz . Açıkça söylemeliyiz.
Şimdi Post
model:
class Post < ActiveRecord::Base
has_many :post_connections, :foreign_key => :post_a_id
has_many :posts, :through => :post_connections, :source => :post_b
end
İlk ile has_many
dernek, biz katılmak için modelini anlatmak post_connections
üzerine posts.id = post_connections.post_a_id
.
İkinci dernek ile, diğer mesajları ulaşabilmesi Rayları anlatıyorsun, bu birine bağlı olanlar, bizim ilk dernek aracılığıyla post_connections
, ardından post_b
derneği PostConnection
.
Orada sadece var bir şey daha eksik ve bu biz bu Rayları anlatmak gerektiğidir PostConnection
ait olduğu mesajların bağlıdır. Birinin ya da her ikisinin ise post_a_id
ve post_b_id
vardı NULL
, sonra bağlantı çok bize, ederdi olmaz? Bunu Post
modelimizde şu şekilde yapıyoruz :
class Post < ActiveRecord::Base
has_many(:post_connections, :foreign_key => :post_a_id, :dependent => :destroy)
has_many(:reverse_post_connections, :class_name => :PostConnection,
:foreign_key => :post_b_id, :dependent => :destroy)
has_many :posts, :through => :post_connections, :source => :post_b
end
Sözdizimindeki küçük değişikliğin yanı sıra, burada iki gerçek şey farklıdır:
has_many :post_connections
Ekstra sahiptir :dependent
parametre. Değer ile :destroy
, Rails'e bu gönderi ortadan kalktığında devam edip bu nesneleri yok edebileceğini söylüyoruz. Burada kullanabileceğiniz alternatif bir değer :delete_all
, daha hızlı olan, ancak bunları kullanıyorsanız herhangi bir yok etme kancası çağırmayacaktır.has_many
için de bir dernek ekledik . Bu şekilde, Rails bunları da düzgün bir şekilde yok edebilir. Burada belirtmemiz gerektiğini unutmayın , çünkü modelin sınıf adı artık buradan çıkarılamaz .post_b_id
:class_name
:reverse_post_connections
Bunu yerine getirerek, size başka bir IRB seansı getiriyorum script/console
:
>> a = Post.create :name => 'Eels are delicious!'
=> #<Post id: 16, name: "Eels are delicious!">
>> b = Post.create :name => 'You insensitive cloth!'
=> #<Post id: 17, name: "You insensitive cloth!">
>> b.posts = [a]
=> [#<Post id: 16, name: "Eels are delicious!">]
>> b.post_connections
=> [#<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>]
>> connection = b.post_connections[0]
=> #<PostConnection id: 3, post_a_id: 17, post_b_id: 16, category: nil>
>> connection.category = "scolding"
=> "scolding"
>> connection.save!
=> true
İlişkilendirmeyi oluşturmak ve ardından kategoriyi ayrı ayrı ayarlamak yerine, sadece bir Bağlantı Sonrası oluşturabilir ve bununla işinizi bitirebilirsiniz:
>> b.posts = []
=> []
>> PostConnection.create(
?> :post_a => b, :post_b => a,
?> :category => "scolding"
>> )
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> b.posts(true) # 'true' means force a reload
=> [#<Post id: 16, name: "Eels are delicious!">]
Ayrıca post_connections
ve reverse_post_connections
ilişkilerini de manipüle edebiliriz ; posts
derneğe düzgün bir şekilde yansıyacaktır :
>> a.reverse_post_connections
=> #<PostConnection id: 5, post_a_id: 17, post_b_id: 16, category: "scolding">
>> a.reverse_post_connections = []
=> []
>> b.posts(true) # 'true' means force a reload
=> []
Normal has_and_belongs_to_many
ilişkilendirmelerde, ilişkilendirme ilgili her iki modelde de tanımlanır . Ve dernek iki yönlüdür.
Ancak bu durumda sadece bir Post modeli var. Ve ilişkilendirme yalnızca bir kez belirtilir. İşte tam da bu yüzden bu özel durumda, ilişkilendirmeler tek yönlüdür.
Aynısı ile alternatif yöntem has_many
ve birleştirme tablosu modeli için de geçerlidir.
Bu en iyi, irb'den ilişkilendirmelere erişildiğinde ve Rails'in günlük dosyasında oluşturduğu SQL'e bakıldığında görülür. Aşağıdakine benzer bir şey bulacaksınız:
SELECT * FROM "posts"
INNER JOIN "post_connections" ON "posts".id = "post_connections".post_b_id
WHERE ("post_connections".post_a_id = 1 )
İlişkilendirmeyi iki yönlü yapmak için, Rails'i OR
yukarıdaki koşullara sahip post_a_id
ve post_b_id
tersine çevirmenin bir yolunu bulmalıyız , böylece her iki yönde de görünecektir.
Maalesef, bunu yapmanın bildiğim tek yolu biraz hilekârlık. Manuel SQL için seçenekleri kullanarak belirtmek gerekir has_and_belongs_to_many
gibi :finder_sql
, :delete_sql
Güzel değil vb. (Ben de burada önerilere açığım. Kimse?)
Shteef'in sorduğu soruyu cevaplamak için:
Kullanıcılar arasındaki takipçi-takipçi ilişkisi , İki yönlü döngülü ilişkiye iyi bir örnektir. Bir Kullanıcı çok sayıda şunlara sahip olabilir:
User.rb kodu şu şekilde görünebilir:
class User < ActiveRecord::Base
# follower_follows "names" the Follow join table for accessing through the follower association
has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow"
# source: :follower matches with the belong_to :follower identification in the Follow model
has_many :followers, through: :follower_follows, source: :follower
# followee_follows "names" the Follow join table for accessing through the followee association
has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"
# source: :followee matches with the belong_to :followee identification in the Follow model
has_many :followees, through: :followee_follows, source: :followee
end
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: "follower_id", class_name: "User"
belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end
Dikkat edilmesi gereken en önemli şeyler muhtemelen terimlerdir :follower_follows
ve :followee_follows
user.rb'dir. Örnek olarak değirmen çalıştırma (döngüsüz) ilişkisini kullanmak için, bir Takımda çok sayıda: players
baştan sona olabilir :contracts
. Bu durum farklı değil Player birçok olabilir, :teams
through :contracts
sıra (örneğin boyunca Oyuncu 'ın kariyerinde). Ancak bu durumda, yalnızca bir adlandırılmış modelin (yani bir Kullanıcı ) olduğu durumlarda, through: ilişkisini aynı şekilde adlandırmak (ör through: :follow
. Veya, yukarıda yazılanlar örneğinde olduğu gibi through: :post_connections
) farklı kullanım durumları için bir adlandırma çakışmasına neden olur ( veya birleştirme tablosuna erişim noktaları. :follower_follows
ve:followee_follows
böyle bir adlandırma çakışmasını önlemek için oluşturuldu. Şimdi, kullanıcı birçok olabilir :followers
through :follower_follows
ve birçok :followees
through :followee_follows
.
Bir Kullanıcının : takipçilerini belirlemek için ( @user.followees
veri tabanına yapılan bir çağrı üzerine ), Rails artık her bir class_name: "Follow" örneğine bakabilir, burada söz konusu Kullanıcının takipçi (yani foreign_key: :follower_id
) aracılığıyla: söz konusu Kullanıcının : followee_follows olduğu. Belirlemek için Kullanıcının : takipçilerini ( @user.followers
veri tabanına yapılan bir çağrı üzerine ), Rails artık her bir class_name: "Follow" örneğine bakabilir, burada söz konusu Kullanıcının takipçisi (yani foreign_key: :followee_id
) aracılığıyla: söz konusu Kullanıcının : follower_follows.
Eğer birisi buraya Rails'de arkadaş ilişkileri kurmayı öğrenmek için geldiyse, o zaman onlara nihayet kullanmaya karar verdiğim şeye başvururdum, bu da 'Community Engine'in yaptığını kopyalamaktır.
Şunlara başvurabilirsiniz:
https://github.com/bborn/communityengine/blob/master/app/models/friendship.rb
ve
https://github.com/bborn/communityengine/blob/master/app/models/user.rb
daha fazla bilgi için.
TL; DR
# user.rb
has_many :friendships, :foreign_key => "user_id", :dependent => :destroy
has_many :occurances_as_friend, :class_name => "Friendship", :foreign_key => "friend_id", :dependent => :destroy
..
# friendship.rb
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
@ Stéphan Kochen'den esinlenerek, bu iki yönlü dernekler için işe yarayabilir
class Post < ActiveRecord::Base
has_and_belongs_to_many(:posts,
:join_table => "post_connections",
:foreign_key => "post_a_id",
:association_foreign_key => "post_b_id")
has_and_belongs_to_many(:reversed_posts,
:class_name => Post,
:join_table => "post_connections",
:foreign_key => "post_b_id",
:association_foreign_key => "post_a_id")
end
sonra post.posts
&& post.reversed_posts
her ikisi de çalışmalı, en azından benim için çalışmalı.
Çift yönlü için belongs_to_and_has_many
, önceden gönderilmiş olan harika yanıta bakın ve ardından farklı bir adla başka bir ilişkilendirme oluşturun, yabancı anahtarlar tersine çevrilir ve class_name
doğru modeli göstermeye ayarladığınızdan emin olun . Şerefe.
Çalışmak için mükemmel cevabı almakta sorun yaşayan biri varsa, örneğin:
(Nesne #inspect'i desteklemiyor)
=>
veya
NoMethodError: tanımsız yöntem `` bölme '': Görev: Sembol
Sonra çözüm değiştirmektir :PostConnection
ile "PostConnection"
elbette sınıfadı yerine,.
:foreign_key
on thehas_many :through
gerekli değildir ve çok kullanışlı:dependent
parametrenin nasıl kullanılacağına dair bir açıklama ekledimhas_many
.