ความหลากหลายของการโหลดที่กระตือรือร้น


106

ใช้ Rails 3.2 โค้ดนี้ผิดอะไร?

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

มันทำให้เกิดข้อผิดพลาดนี้:

ไม่สามารถโหลดการเชื่อมโยงโพลีมอร์ฟิคได้อย่างกระตือรือร้น: ตรวจสอบได้

ถ้าฉันลบreviewable.shop_type = ?เงื่อนไขมันก็ใช้ได้

ฉันจะกรองตามreviewable_typeและreviewable.shop_type(ซึ่งจริงๆแล้วshop.shop_type) ได้อย่างไร

คำตอบ:


209

ฉันเดาว่าแบบจำลองของคุณมีลักษณะดังนี้:

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

คุณไม่สามารถดำเนินการสอบถามดังกล่าวได้ด้วยเหตุผลหลายประการ

  1. ActiveRecord ไม่สามารถสร้างการเข้าร่วมโดยไม่มีข้อมูลเพิ่มเติม
  2. ไม่มีตารางที่เรียกว่าตรวจสอบได้

เพื่อแก้ปัญหานี้คุณจำเป็นต้องกำหนดอย่างชัดเจนความสัมพันธ์ระหว่างและ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

จากนั้นคุณสามารถสอบถามดังนี้:

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

ขอให้สังเกตว่าชื่อตารางเป็นและไม่ได้shops reviewableไม่ควรมีตารางที่เรียกว่าตรวจสอบได้ในฐานข้อมูล

ฉันเชื่อว่าสิ่งนี้จะง่ายและยืดหยุ่นกว่าการกำหนดjoinระหว่างReviewและอย่างชัดเจนShopเนื่องจากช่วยให้คุณสามารถโหลดได้อย่างเต็มที่นอกเหนือจากการสืบค้นตามช่องที่เกี่ยวข้อง

เหตุผลที่จำเป็นคือ ActiveRecord ไม่สามารถสร้างการรวมตามการตรวจสอบได้เพียงอย่างเดียวเนื่องจากหลายตารางแสดงถึงส่วนท้ายของการรวมอีกด้านหนึ่งและ SQL เท่าที่ฉันทราบไม่อนุญาตให้คุณเข้าร่วมตารางที่ตั้งชื่อตามค่าที่เก็บไว้ ในคอลัมน์ ด้วยการกำหนดความสัมพันธ์เพิ่มเติมbelongs_to :shopคุณกำลังให้ ActiveRecord ข้อมูลที่จำเป็นในการเข้าร่วม


6
อันที่จริงฉันลงเอยด้วยการใช้สิ่งนี้โดยไม่ได้ประกาศอะไรเพิ่มเติม:@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)
วิกเตอร์

6
ทำงานใน rail4 แต่จะแจ้งเตือนการเลิกใช้งานโดยกล่าวว่าควรใช้สไตล์เช่น has_many: spam_comments, -> {where spam: true}, class_name: 'Comment' ดังนั้นใน rail4 จะเป็น belong_to: shop, -> {where ("reviews.reviewable_type = 'Shop'")}, Foreign_key: 'reviewable_id' แต่โปรดระวัง Review.includes (: shop) จะแสดงข้อผิดพลาดก็ต้อง ต่อท้ายสัญญาเช่าโดยที่ข้อ
raykin

51
นอกจากนี้ยังมี Foreign_type ซึ่งเหมาะกับฉันสำหรับปัญหาที่คล้ายกัน:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y

15
เมื่อโหลดreviewsรวมทั้งการโหลดกระตือรือร้นที่เกี่ยวข้องshopใช้รหัสReview.includes(:shop) นิยาม belongs_to พ่นคำพูดข้อผิดพลาดbelongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' missing FROM-clause entry for table "reviews"ฉันแก้ไขโดยการอัปเดตคำจำกัดความ belong_to ในลักษณะต่อไปนี้: belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
Jignesh Gohel

2
ฉันได้ลองสิ่งนี้ แต่พบข้อบกพร่องที่น่าสนใจมาก หากมีร้านค้าและผู้ใช้ที่มีฐานข้อมูล ID เดียวกันแล้วเรียกreview.shopการสอบทานกับreviewable_typeชุดที่จะสามารถกลับมาเป็นที่ไม่เกี่ยวข้องร้านมากกว่าUser nilในบางสถานการณ์นี้อาจจะเป็นข้อมูลที่รุนแรงมากรั่วไหลเพราะผู้ใช้อาจได้รับอนุญาตให้เข้าถึงแต่ไม่ส่งกลับโดยreview Shop review.shop
ไฟแล

12

ถ้าคุณได้รับ ActiveRecord :: EagerLoadPolymorphicError ก็เพราะincludesการตัดสินใจที่จะโทรeager_loadเมื่อสมาคม polymorphic preloadรับการสนับสนุนโดยเฉพาะ ในเอกสารนี้: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

ดังนั้นควรใช้preloadสำหรับการเชื่อมโยงหลายรูปแบบเสมอ มีข้อแม้อย่างหนึ่งสำหรับสิ่งนี้: คุณไม่สามารถสืบค้นการเชื่อมโยงความหลากหลายในที่ซึ่งอนุประโยค (ซึ่งสมเหตุสมผลเนื่องจากการเชื่อมโยงความหลากหลายแทนตารางหลายตาราง)


ฉันเห็นว่าเป็นวิธีการหนึ่งที่ไม่ได้ระบุไว้ในคำแนะนำ: guide.rubyonrails.org/…
MSC

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

เมื่อคุณใช้ส่วนย่อยของ SQL กับ WHERE การอ้างอิงเป็นสิ่งจำเป็นเพื่อเข้าร่วมการเชื่อมโยงของคุณ


0

ในฐานะที่เป็นภาคผนวกคำตอบที่อยู่ด้านบนซึ่งยอดเยี่ยมมากคุณยังสามารถระบุ:includeการเชื่อมโยงได้หากแบบสอบถามที่คุณใช้ไม่รวมตารางของโมเดลด้วยเหตุผลบางประการและคุณได้รับข้อผิดพลาดของตารางที่ไม่ได้กำหนด

ชอบมาก:

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

โดยไม่ต้อง:includeเลือกถ้าคุณเพียงเข้าถึงสมาคมreview.shopในตัวอย่างข้างต้นคุณจะได้รับข้อผิดพลาด UndefinedTable (ทดสอบใน Rails 3 ไม่ 4) SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' )เนื่องจากการเชื่อมโยงจะทำ

:includeตัวเลือกที่จะบังคับให้เข้าร่วมแทน :)


5
คีย์ที่ไม่รู้จัก: เงื่อนไข คีย์ที่ถูกต้องคือ: class_name,: class,: foreign_key,: validate,: autosave,: depend,: primary_key,: inverse_of,: required,: foreign_type,: polymorphic,: touch,: counter_cache
Bengala
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.