Eager tải đa hình


104

Sử dụng Rails 3.2, mã này có gì sai?

@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe')

Nó làm phát sinh lỗi này:

Không thể tải liên kết đa hình một cách háo hức: có thể xem lại

Nếu tôi loại bỏ reviewable.shop_type = ?điều kiện, nó hoạt động.

Làm cách nào để lọc dựa trên reviewable_typereviewable.shop_type(thực tế là shop.shop_type)?

Câu trả lời:


207

Tôi đoán rằng các mô hình của bạn trông như thế này:

class User < ActiveRecord::Base
  has_many :reviews
end

class Review < ActiveRecord::Base
  belongs_to :user
  belongs_to :reviewable, polymorphic: true
end

class Shop < ActiveRecord::Base
  has_many :reviews, as: :reviewable
end

Bạn không thể thực hiện truy vấn đó vì một số lý do.

  1. ActiveRecord không thể tạo kết nối mà không có thông tin bổ sung.
  2. Không có bảng nào được gọi là có thể xem lại

Để giải quyết vấn đề này, bạn cần xác định rõ ràng mối quan hệ giữa ReviewShop.

class Review < ActiveRecord::Base
   belongs_to :user
   belongs_to :reviewable, polymorphic: true
   # For Rails < 4
   belongs_to :shop, foreign_key: 'reviewable_id', conditions: "reviews.reviewable_type = 'Shop'"
   # For Rails >= 4
   belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
   # Ensure review.shop returns nil unless review.reviewable_type == "Shop"
   def shop
     return unless reviewable_type == "Shop"
     super
   end
end

Sau đó, bạn có thể truy vấn như sau:

Review.includes(:shop).where(shops: {shop_type: 'cafe'})

Lưu ý rằng tên bảng là shopsvà không reviewable. Không nên có một bảng được gọi là có thể xem lại trong cơ sở dữ liệu.

Tôi tin rằng điều này dễ dàng và linh hoạt hơn việc xác định rõ ràng joingiữa ReviewShopvì nó cho phép bạn tải nhanh ngoài việc truy vấn theo các trường liên quan.

Lý do mà điều này là cần thiết là ActiveRecord không thể xây dựng một kết nối chỉ dựa trên có thể xem xét, vì nhiều bảng đại diện cho đầu kia của kết nối và SQL, theo như tôi biết, không cho phép bạn tham gia một bảng được đặt tên theo giá trị được lưu trữ trong một cột. Bằng cách xác định mối quan hệ bổ sung belongs_to :shop, bạn đang cung cấp cho ActiveRecord thông tin cần thiết để hoàn thành việc tham gia.


6
Trên thực tế, tôi đã kết thúc bằng cách sử dụng cái này mà không cần khai báo gì thêm:@reviews = @user.reviews.joins("INNER JOIN shops ON (reviewable_type = 'Shop' AND shops.id = reviewable_id AND shops.shop_type = '" + type + "')").includes(:user, :reviewable => :photos)
Victor

1
Đó là bởi vì :reviewableShop. Ảnh thuộc về Shop.
Victor

6
đã hoạt động trong rails4, nhưng sẽ đưa ra cảnh báo không dùng nữa, nó cho biết nên sử dụng kiểu như has_many: spam_comments, -> {where spam: true}, class_name: 'Comment'. Vì vậy, trong rails4, sẽ là thuộc_to: shop, -> {where ("reviews.reviewable_type = 'Shop'")}, Foreign_key: 'reviewable_id'.Nhưng hãy cẩn thận, Review.includes (: shop) sẽ phát sinh lỗi, nó phải ký thêm tại hợp đồng thuê một điều khoản where.
raykin

49
Ngoài ra còn có Foreign_type, đã làm việc cho tôi cho một vấn đề tương tự:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y

14
Khi tải, reviewsbao gồm cả việc tải nhanh shopmã được liên kết bằng cách sử dụng mã Review.includes(:shop) , định nghĩa thuộc_to belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' sẽ ném lỗi nói missing FROM-clause entry for table "reviews". Tôi đã sửa nó bằng cách cập nhật định nghĩa thuộc về thuộc tính theo cách sau: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

12

Nếu bạn nhận được một ActiveRecord :: EagerLoadPolymorphicError, đó là do bạn includesquyết định gọi eager_loadkhi các liên kết đa hình chỉ được hỗ trợ bởi preload. Nó có trong tài liệu ở đây: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Vì vậy, luôn luôn sử dụng preloadcho các liên kết đa hình. Có một lưu ý cho điều này: bạn không thể truy vấn liên kết đa hình trong mệnh đề (điều này có ý nghĩa, vì liên kết đa hình đại diện cho nhiều bảng.)


Tôi thấy đó là một phương pháp không được ghi trong các hướng dẫn: guide.rubyonrails.org/…
MSC

0
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Khi bạn đang sử dụng các đoạn SQL với WHERE, tham chiếu là cần thiết để tham gia liên kết của bạn.


0

Như một phụ lục, câu trả lời ở trên cùng, thật tuyệt vời, bạn cũng có thể chỉ định :includetrên liên kết nếu vì lý do nào đó mà truy vấn bạn đang sử dụng không bao gồm bảng của mô hình và bạn đang gặp lỗi bảng không xác định.

Như vậy:

belongs_to :shop, 
           foreign_key: 'reviewable_id', 
           conditions: "reviews.reviewable_type = 'Shop'",
           include: :reviews

Nếu không có :includetùy chọn, nếu bạn chỉ truy cập vào liên kết review.shoptrong ví dụ trên, bạn sẽ gặp lỗi UndefinedTable (được thử nghiệm trong Rails 3, không phải 4) vì liên kết sẽ xảy ra SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

Thay vào đó, :includetùy chọn sẽ buộc một THAM GIA. :)


5
Khóa không xác định:: điều kiện. Phím hợp lệ là:: class_name,: lớp,: foreign_key,: Validate,: tự động lưu,: phụ thuộc,: primary_key,: inverse_of,: yêu cầu,: foreign_type,: đa hình,: touch,: counter_cache
Bengala
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.