2008-11-24 17 views
26

Làm thế nào tôi có thể đạt được những điều sau đây? Tôi có hai mô hình (blog và độc giả) và bảng JOIN cho phép tôi có mối quan hệ N: M giữa chúng:cách tránh trùng lặp trong mối quan hệ has_many: thông qua mối quan hệ?

class Blog < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :readers, :through => :blogs_readers 
end 

class Reader < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :blogs, :through => :blogs_readers 
end 

class BlogsReaders < ActiveRecord::Base 
    belongs_to :blog 
    belongs_to :reader 
end 

Điều tôi muốn làm bây giờ là thêm người đọc vào các blog khác nhau. Tuy nhiên, điều kiện là tôi chỉ có thể thêm người đọc vào một blog ONCE. Vì vậy, không được có bất kỳ bản sao nào (cùng một số readerID, giống như blogID) trong bảng BlogsReaders. Làm thế nào tôi có thể đạt được điều này?

Câu hỏi thứ hai là, làm cách nào để có danh sách blog mà người đọc chưa đăng ký (ví dụ: để điền danh sách chọn thả xuống, sau đó có thể dùng để thêm người đọc vào blog khác) ?

Trả lời

5

gì về:

Blog.find(:all, 
      :conditions => ['id NOT IN (?)', the_reader.blog_ids]) 

Rails sẽ chăm sóc của các bộ sưu tập của id cho chúng tôi với các phương pháp liên kết! :)

http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

+0

Ngoài ra, tôi muốn đề cập rằng đây có lẽ là phương pháp tốt hơn, vì câu trả lời được chấp nhận chọn TẤT CẢ dữ liệu từ hàng (ví dụ: the_reader.blogs) trong khi câu trả lời của tôi chỉ chọn các id từ các hàng (ví dụ: the_reader. blog_ids). Đây là một hit hiệu suất lớn! –

+0

đây là giải pháp tốt hơn và nên là câu trả lời đúng. Cảm ơn Josh. –

+0

thx Josh! Trông mỏng hơn thực sự! – Sebastian

32

này nên chăm sóc của câu hỏi đầu tiên của bạn:

class BlogsReaders < ActiveRecord::Base 
    belongs_to :blog 
    belongs_to :reader 

    validates_uniqueness_of :reader_id, :scope => :blog_id 
end 
+0

Tôi đã cố gắng để con số này ra trong một thời gian dài, và điều này không bao giờ xảy ra với tôi! Giải pháp tuyệt vời! Cảm ơn! – Arel

+1

Vui lòng đọc kỹ về tính đồng thời và tính toàn vẹn tại đây http://apidock.com/rails/ActiveRecord/Validations/ClassMethods/validates_uniqueness_of –

1

Tôi đang nghĩ đến ai đó sẽ đi cùng với một câu trả lời tốt hơn thế này.

the_reader = Reader.find(:first, :include => :blogs) 

Blog.find(:all, 
      :conditions => ['id NOT IN (?)', the_reader.blogs.map(&:id)]) 

[sửa]

hãy xem câu trả lời của Josh bên dưới. Đó là con đường để đi. (Tôi biết có một cách tốt hơn ngoài kia;)

+0

bạn cũng có thể thực hiện điều này trong một câu lệnh bằng cách sử dụng find_by_sql. –

+0

Tuyệt vời! Điều này hoạt động hoàn hảo! Cảm ơn rất nhiều!! – Sebastian

69

đơn giản giải pháp đó là xây dựng thành Rails:

class Blog < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :readers, :through => :blogs_readers, :uniq => true 
    end 

    class Reader < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :blogs, :through => :blogs_readers, :uniq => true 
    end 

    class BlogsReaders < ActiveRecord::Base 
     belongs_to :blog 
     belongs_to :reader 
    end 

Lưu ý thêm các tùy chọn :uniq => true đến has_many gọi.

Ngoài ra, bạn có thể muốn xem xét has_and_belongs_to_many giữa Blog và Reader, trừ khi bạn có một số thuộc tính khác mà bạn muốn có trên mô hình tham gia (hiện tại bạn không có). Phương pháp đó cũng có một opiton :uniq.

Lưu ý rằng điều này không ngăn bạn tạo các mục nhập trong bảng, nhưng nó đảm bảo rằng khi bạn truy vấn bộ sưu tập bạn chỉ nhận được một trong mỗi đối tượng.

Cập nhật

Trong Rails 4 cách để làm điều đó là thông qua một khối phạm vi. The Above thay đổi thành.

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { uniq }, through: :blogs_readers 
end 

class Reader < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :blogs, -> { uniq }, through: :blogs_readers 
end 

class BlogsReaders < ActiveRecord::Base 
    belongs_to :blog 
    belongs_to :reader 
end 
+0

Tôi nghĩ rằng có một vấn đề với cách tiếp cận này nếu mô hình tham gia của bạn có bất kỳ trường nào khác. Ví dụ, một trường vị trí sao cho mỗi đứa trẻ có thể được định vị trong cha mẹ của nó. 'blog.readers << reader # blog_readers.position = 1;' 'blog.readers << reader # blog_readers.position = 2' Như blog_readers thứ hai có một vị trí khác, cài đặt uniq không thấy nó là hiện tại nhập và cho phép nó được tạo ra – ReggieB

+2

Nếu bạn có một phạm vi mặc định để đặt hàng cho blog của mình, bạn sẽ cần phải unscope rằng (hoặc DISTINCT sẽ thất bại), bạn có thể sử dụng: '' 'has_many: blogs, -> {unscope (: order) .uniq}, thông qua:: blog_readers''' – marksiemers

0

Cách dễ nhất là để serialize các mối quan hệ vào một mảng:

class Blog < ActiveRecord::Base 
    has_many :blogs_readers, :dependent => :destroy 
    has_many :readers, :through => :blogs_readers 
    serialize :reader_ids, Array 
end 

Sau đó, khi gán giá trị cho độc giả, bạn áp dụng chúng như

blog.reader_ids = [1,2,3,4] 

Khi gán mối quan hệ theo cách này, các bản sao sẽ tự động bị xóa.

14

Các Rails 5.1 cách

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { distinct }, through: :blogs_readers 
end 

class Reader < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :blogs, -> { distinct }, through: :blogs_readers 
end 

class BlogsReaders < ActiveRecord::Base 
    belongs_to :blog 
    belongs_to :reader 
end 
+0

Lý do: https://github.com/rails/rails/pull/9683 và https://github.com/rails/rails/commit/adfab2dcf4003ca564d78d4425566dd2d9cd8b4f – tmaier

+0

@pastullo Nhưng nó vẫn chèn dữ liệu vào các bảng blog_readers ở giữa. làm thế nào để ngăn chặn điều đó? – Vishal

0

Câu trả lời đầu hiện nói để sử dụng uniq trong proc:

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { uniq }, through: :blogs_readers 
end 

Tuy nhiên điều này đá mối quan hệ vào một mảng và có thể phá vỡ những điều mong đợi để thực hiện các hoạt động trên một mối quan hệ, không phải là một mảng.

Nếu bạn sử dụng distinct nó giữ nó như là một mối quan hệ:

class Blog < ActiveRecord::Base 
has_many :blogs_readers, dependent: :destroy 
has_many :readers, -> { distinct }, through: :blogs_readers 
end