Semangat memuat polimorfik

104

Menggunakan Rails 3.2, apa yang salah dengan kode ini?

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

Ini menimbulkan kesalahan ini:

Tidak dapat memuat asosiasi polimorfik dengan penuh semangat: dapat ditinjau

Jika saya menghapus reviewable.shop_type = ?kondisinya, itu berhasil.

Bagaimana cara memfilter berdasarkan reviewable_typedan reviewable.shop_type(yang sebenarnya shop.shop_type)?

Pemenang
sumber

Jawaban:

207

Dugaan saya adalah model Anda terlihat seperti ini:

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

Anda tidak dapat melakukan kueri itu karena beberapa alasan.

  1. ActiveRecord tidak dapat membuat gabungan tanpa informasi tambahan.
  2. Tidak ada tabel yang disebut dapat ditinjau

Untuk mengatasi masalah ini, Anda perlu secara eksplisit menentukan hubungan antara Reviewdan Shop.

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

Kemudian Anda dapat melakukan kueri seperti ini:

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

Perhatikan bahwa nama tabel adalah shopsdan bukan reviewable. Seharusnya tidak ada tabel yang disebut dapat ditinjau dalam database.

Saya percaya ini akan lebih mudah dan lebih fleksibel daripada secara eksplisit mendefinisikan joinantara Reviewdan Shopkarena memungkinkan Anda untuk memuat dengan bersemangat sebagai tambahan untuk membuat kueri berdasarkan bidang terkait.

Alasan mengapa hal ini diperlukan adalah karena ActiveRecord tidak dapat membuat gabungan berdasarkan dapat ditinjau saja, karena beberapa tabel mewakili ujung gabungan lainnya, dan SQL, sejauh yang saya tahu, tidak mengizinkan Anda bergabung dengan tabel yang diberi nama berdasarkan nilai yang disimpan di kolom. Dengan menentukan hubungan ekstra belongs_to :shop, Anda memberikan informasi yang dibutuhkan ActiveRecord untuk menyelesaikan penggabungan.

Sean Hill
sumber
6
Sebenarnya saya akhirnya menggunakan ini tanpa menyatakan apa-apa lagi:@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
Itu karena :reviewableitu Shop. Foto milik Toko.
Victor
6
bekerja di rails4, tetapi akan memberikan peringatan penghentian, katanya harus menggunakan gaya seperti has_many: spam_comments, -> {where spam: true}, class_name: 'Comment'. Jadi di rails4, akan menjadi milik_to: shop, -> {where ("reviews.reviewable_type = 'Shop'")}, foreign_key: 'reviewable_id'. Tapi hati-hati, Review.includes (: shop) akan menimbulkan kesalahan, itu harus tambahkan di sewa satu tempat klausul.
raykin
49
Ada juga foreign_type, yang berhasil untuk saya untuk masalah serupa:belongs_to :shop, foreign_type: 'Shop', foreign_key: 'reviewable_id'
A5308Y
14
Saat memuat reviewstermasuk eager memuat yang terkait shopmenggunakan kode Review.includes(:shop) , definisi milik_to memunculkan belongs_to :shop, -> { where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id' kesalahan yang mengatakan missing FROM-clause entry for table "reviews". Saya memperbaikinya dengan memperbarui definisi belongs_to :shop, -> { joins(:reviews) .where(reviews: {reviewable_type: 'Shop'}) }, foreign_key: 'reviewable_id'
milik_to
12

Jika Anda mendapatkan ActiveRecord :: EagerLoadPolymorphicError, itu karena includesdiputuskan untuk memanggil eager_loadketika asosiasi polimorfik hanya didukung oleh preload. Ada dalam dokumentasinya di sini: http://api.rubyonrails.org/v5.1/classes/ActiveRecord/EagerLoadPolymorphicError.html

Jadi selalu gunakan preloaduntuk asosiasi polimorfik. Ada satu peringatan untuk ini: Anda tidak dapat menanyakan asosiasi polimorfik di mana klausa (yang masuk akal, karena asosiasi polimorfik mewakili beberapa tabel.)

seanmorton
sumber
Saya melihat itulah satu metode yang tidak didokumentasikan dalam panduan: panduan.rubyonrails.org/…
MSC
0
@reviews = @user.reviews.includes(:user, :reviewable)
.where('reviewable_type = ? AND reviewable.shop_type = ?', 'Shop', 'cafe').references(:reviewable)

Saat Anda menggunakan fragmen SQL dengan WHERE, referensi diperlukan untuk menggabungkan asosiasi Anda.

un_gars_la_cour
sumber
0

Sebagai tambahan, jawaban di atas, yang sangat bagus, Anda juga dapat menentukan :includepada pengaitan jika karena alasan tertentu kueri yang Anda gunakan tidak termasuk tabel model dan Anda mendapatkan kesalahan tabel yang tidak ditentukan.

Seperti:

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

Tanpa :includeopsi, jika Anda hanya mengakses pengaitan review.shopdalam contoh di atas, Anda akan mendapatkan kesalahan UndefinedTable (diuji di Rails 3, bukan 4) karena pengaitan akan dilakukan SELECT FROM shops WHERE shop.id = 1 AND ( reviews.review_type = 'Shop' ).

The :includepilihan akan memaksa JOIN bukan. :)

Stewart Mckinney
sumber
5
Kunci tidak diketahui:: kondisi. Kunci yang valid adalah:: class_name,: class,: foreign_key,: validate,: autosave,: dependent,: primary_key,: inverse_of,: required,: foreign_type,: polymorphic,: touch,: counter_cache
Bengala