Apa yang menyebabkan kesalahan ActiveRecord :: ReadOnlyRecord ini?

203

Ini mengikuti ini pertanyaan sebelumnya, yang menjawab. Saya sebenarnya menemukan bahwa saya dapat menghapus gabungan dari kueri itu, jadi sekarang kueri yang berfungsi adalah

start_cards = DeckCard.find :all, :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]  

Ini tampaknya berhasil. Namun, ketika saya mencoba untuk memindahkan DeckCards ini ke asosiasi lain, saya mendapatkan kesalahan ActiveRecord :: ReadOnlyRecord.

Ini kodenya

for player in @game.players 
  player.tableau = Tableau.new
  start_card = start_cards.pop 
  start_card.draw_pile = false
  player.tableau.deck_cards << start_card  # the error occurs on this line
end

dan Model yang relevan (tablo adalah kartu pemain di atas meja)

class Player < ActiveRecord::Base
  belongs_to :game
  belongs_to :user
  has_one :hand
  has_one :tableau
end

class Tableau < ActiveRecord::Base
  belongs_to :player
  has_many :deck_cards
end  

class DeckCard < ActiveRecord::Base
  belongs_to :card
  belongs_to :deck  
end

Saya melakukan tindakan serupa tepat setelah kode ini, menambah DeckCardske tangan pemain, dan kode itu berfungsi dengan baik. Saya bertanya-tanya apakah saya perlu belongs_to :tableaudalam Model DeckCard, tetapi itu berfungsi dengan baik untuk penambahan ke tangan pemain. Saya punya tableau_iddan hand_idkolom di tabel DeckCard.

Saya mencari ReadOnlyRecord di api rel, dan tidak banyak bicara di luar deskripsi.

pengguna26270
sumber

Jawaban:

283

Rails 2.3.3 dan lebih rendah

Dari ActiveRecord CHANGELOG(v1.12.0, 16 Oktober 2005) :

Perkenalkan catatan hanya baca. Jika Anda memanggil object.readonly! maka itu akan menandai objek sebagai read-only dan menaikkan ReadOnlyRecord jika Anda memanggil object.save. object.readonly? melaporkan apakah objek hanya baca. Lulus: readonly => true ke metode finder apa pun akan menandai rekaman yang dikembalikan sebagai hanya-baca. Opsi: joins now menyiratkan: readonly, jadi jika Anda menggunakan opsi ini, menyimpan catatan yang sama sekarang akan gagal. Gunakan find_by_sql untuk menyiasatinya.

Menggunakan find_by_sqlsebenarnya bukan alternatif karena mengembalikan data baris / kolom mentah, tidak ActiveRecords. Anda memiliki dua opsi:

  1. Paksa variabel instance @readonlyke false dalam catatan (retas)
  2. Gunakan :include => :cardsebagai ganti:join => :card

Rel 2.3.4 dan lebih tinggi

Sebagian besar di atas tidak berlaku lagi, setelah 10 September 2012:

  • gunakan Record.find_by_sql adalah opsi yang layak
  • :readonly => truesecara otomatis disimpulkan hanya jika :joinsditentukan tanpa opsi eksplisit :select atau eksplisit (atau pencari-lingkup-diwariskan) :readonly(lihat implementasi set_readonly_option!diactive_record/base.rb untuk Rails 2.3.4, atau implementasi to_ain active_record/relation.rbdan of custom_join_sqlinactive_record/relation/query_methods.rb untuk Rails 3.0.0)
  • Namun, :readonly => trueselalu secara otomatis disimpulkan has_and_belongs_to_manyjika tabel gabungan memiliki lebih dari dua kolom kunci asing dan:joins ditentukan tanpa eksplisit :select(mis. :readonlynilai yang disediakan pengguna diabaikan - lihat finding_with_ambiguous_select?diactive_record/associations/has_and_belongs_to_many_association.rb .)
  • Kesimpulannya, kecuali jika berurusan dengan tabel gabung khusus dan has_and_belongs_to_many, maka @aaronrustadjawaban berlaku dengan baik di Rails 2.3.4 dan 3.0.0.
  • jangan tidak menggunakan :includesjika Anda ingin mencapai INNER JOIN( :includesmenyiratkan LEFT OUTER JOIN, yang kurang selektif dan kurang efisien dibandingkan INNER JOIN.)
vladr
sumber
the: include membantu mengurangi # kueri yang dilakukan, saya tidak tahu tentang itu; tapi saya mencoba memperbaikinya dengan mengubah asosiasi Tableau / Deckcards menjadi has_many: through, dan sekarang saya mendapatkan pesan 'tidak dapat menemukan asosiasi'; Saya mungkin harus mengirim pertanyaan lain untuk itu
user26270
@codeman, ya,: include akan mengurangi jumlah kueri dan akan memasukkan tabel yang disertakan ke dalam lingkup kondisi Anda (semacam gabungan implisit tanpa Rails menandai catatan Anda sebagai hanya-baca, yang dilakukan segera setelah mengendus apa pun SQL ish di menemukan Anda, termasuk: bergabung /: pilih klausul IIRC
vladr
Agar 'has_many: a, through =>: b' berfungsi, asosiasi B juga harus dideklarasikan, misalnya 'has_many: b; has_many: a,: through =>: b ', saya harap ini kasus Anda?
vladr
6
Ini mungkin telah berubah dalam rilis terbaru, tetapi Anda dapat menambahkan: readonly => false sebagai bagian dari atribut metode find.
Aaron Rustad
1
Jawaban ini juga berlaku jika Anda memiliki asosiasi has_and_belongs_to_many dengan kustom: join_table ditentukan.
Lee
172

Atau di Rails 3 Anda dapat menggunakan metode readonly (ganti "..." dengan kondisi Anda):

( Deck.joins(:card) & Card.where('...') ).readonly(false)
balexand
sumber
1
Hmmm ... Saya melihat kedua Railscast itu di Asciicasts, dan tidak ada yang menyebutkan readonlyfungsinya.
Purplejacket
45

Ini mungkin telah berubah dalam rilis Rails baru-baru ini, tetapi cara yang tepat untuk menyelesaikan masalah ini adalah dengan menambahkan : readonly => false ke opsi find.

Aaron Rustad
sumber
3
Saya tidak percaya ini masalahnya, setidaknya dengan 2.3.4
Olly
2
Ini masih berfungsi dengan Rails 3.0.10, berikut ini adalah contoh dari kode saya sendiri yang mengambil ruang lingkup yang memiliki: join Fundraiser.donatable.readonly (false)
Houen
16

pilih ('*') tampaknya untuk memperbaikinya di Rails 3.2:

> Contact.select('*').joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> false

Hanya untuk memverifikasi, menghilangkan pilih ('*') menghasilkan catatan hanya baca:

> Contact.joins(:slugs).where('slugs.slug' => 'the-slug').first.readonly?
=> true

Tidak bisa mengatakan saya mengerti alasannya tetapi setidaknya itu solusi yang cepat dan bersih.

bronson
sumber
4
Hal yang sama di Rails 4. Cara lain yang dapat Anda lakukan select(quoted_table_name + '.*')
andorov
1
Itu bronson yang brilian. Terima kasih.
Perjalanan
Ini mungkin berhasil, tetapi lebih rumit daripada menggunakanreadonly(false)
Kelvin
5

Alih-alih find_by_sql, Anda dapat menentukan: pilih pada finder dan semuanya bahagia lagi ...

start_cards = DeckCard.find :all, :select => 'deck_cards.*', :joins => [:card], :conditions => ["deck_cards.deck_id = ? and cards.start_card = ?", @game.deck.id, true]


sumber
3

Untuk menonaktifkannya ...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly
lebih kotor
sumber
3
Patch-monyet rapuh - sangat mudah rusak oleh versi rel baru. Jelas tidak disarankan mengingat ada solusi lain.
Kelvin