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.

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.)

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)

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.


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.


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]


3

Untuk menonaktifkannya ...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly

3
Patch-monyet rapuh - sangat mudah rusak oleh versi rel baru. Jelas tidak disarankan mengingat ada solusi lain.
Kelvin
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.