สิ่งที่ทำให้เกิดข้อผิดพลาด ActiveRecord :: ReadOnlyRecord นี้?


203

นี่ตามคำถามก่อนหน้านี้ซึ่งตอบแล้ว ฉันค้นพบจริงฉันสามารถลบการเข้าร่วมจากแบบสอบถามนั้นดังนั้นตอนนี้แบบสอบถามที่ทำงานอยู่

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

ดูเหมือนว่าจะใช้งานได้ อย่างไรก็ตามเมื่อฉันพยายามย้าย DeckCards เหล่านี้ไปยังการเชื่อมโยงอื่นฉันได้รับข้อผิดพลาด ActiveRecord :: ReadOnlyRecord

นี่คือรหัส

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

และโมเดลที่เกี่ยวข้อง (tableau คือไพ่ของผู้เล่นบนโต๊ะ)

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

ฉันกำลังดำเนินการที่คล้ายกันหลังจากรหัสนี้เพิ่มDeckCardsไปยังมือผู้เล่นและรหัสนั้นทำงานได้ดี ฉันสงสัยว่าฉันจำเป็นต้องใช้belongs_to :tableauใน DeckCard Model หรือไม่ แต่มันก็ใช้ได้ดีสำหรับการเพิ่มในมือของผู้เล่น ฉันมีtableau_idและhand_idคอลัมน์ในตาราง DeckCard

ฉันค้นหา ReadOnlyRecord ในราง API และมันไม่ได้พูดอะไรมากไปกว่าคำอธิบาย

คำตอบ:


283

Rails 2.3.3 และต่ำกว่า

จากActiveRecord CHANGELOG(v1.12.0, 16 ตุลาคม 2005) :

แนะนำบันทึกแบบอ่านอย่างเดียว ถ้าคุณเรียก object.readonly! จากนั้นมันจะทำเครื่องหมายวัตถุเป็นอ่านอย่างเดียวและยกระดับ ReadOnlyRecord ถ้าคุณเรียกใช้ object.save object.readonly? รายงานว่าวัตถุนั้นเป็นแบบอ่านอย่างเดียวหรือไม่ Passing: readonly => true กับวิธีการค้นหาใด ๆ จะทำเครื่องหมายระเบียนที่ส่งคืนเป็นแบบอ่านอย่างเดียว ตอนนี้ตัวเลือก: รวมหมายถึง: อ่านอย่างเดียวดังนั้นหากคุณใช้ตัวเลือกนี้การบันทึกระเบียนเดิมจะล้มเหลว ใช้ find_by_sql เพื่อแก้ไข

ใช้find_by_sqlไม่ได้จริงๆทางเลือกที่เป็นมันจะกลับข้อมูลแถว / ActiveRecordsคอลัมน์ดิบไม่ คุณมีสองทางเลือก:

  1. บังคับให้ตัวแปรอินสแตนซ์@readonlyเป็นเท็จในเรคคอร์ด (แฮ็ค)
  2. ใช้:include => :cardแทน:join => :card

Rails 2.3.4 ขึ้นไป

ส่วนใหญ่ข้างต้นไม่เป็นความจริงอีกต่อไปหลังจากวันที่ 10 กันยายน 2555:

  • การใช้Record.find_by_sql เป็นตัวเลือกที่ทำงานได้
  • :readonly => trueอนุมานได้โดยอัตโนมัติเท่านั้นถ้า:joinsถูกระบุโดยไม่ต้องชัดเจน:select ไม่ชัดเจน (หรือค้นหาขอบเขต-รับมรดก) :readonlyตัวเลือก (ดูการดำเนินการset_readonly_option!ในactive_record/base.rbราว 2.3.4 หรือการดำเนินการto_aในactive_record/relation.rbและcustom_join_sqlในactive_record/relation/query_methods.rbราว 3.0.0)
  • อย่างไรก็ตาม:readonly => trueจะมีการอนุมานโดยอัตโนมัติเสมอhas_and_belongs_to_manyหากตารางการเข้าร่วมมีคอลัมน์ต่างประเทศมากกว่าสองคอลัมน์และ:joinsระบุไว้โดยไม่มีค่าที่ชัดเจน:select(เช่นค่าที่ผู้ใช้ระบุ:readonlyจะถูกละเว้น - ดูfinding_with_ambiguous_select?ในactive_record/associations/has_and_belongs_to_many_association.rb)
  • โดยสรุปเว้นแต่ว่าจะจัดการกับตารางการเข้าร่วมพิเศษhas_and_belongs_to_manyแล้ว@aaronrustadคำตอบก็ใช้ได้ดีใน Rails 2.3.4 และ 3.0.0
  • ไม่ได้ใช้:includesถ้าคุณต้องการที่จะประสบความสำเร็จINNER JOIN( :includesหมายถึงLEFT OUTER JOINซึ่งน้อยเลือกและมีประสิทธิภาพน้อยกว่าINNER JOIN.)

the: include มีประโยชน์ในการลดจำนวนข้อความค้นหาที่ทำได้ฉันไม่รู้เกี่ยวกับสิ่งนั้น แต่ฉันพยายามแก้ไขด้วยการเปลี่ยนการเชื่อมโยง Tableau / Deckcards เป็น has_many: ถึงและตอนนี้ฉันได้รับ 'ไม่สามารถค้นหาการเชื่อมโยง' msg; ฉันอาจต้องโพสต์คำถามอื่นสำหรับที่
user26270

@codeman, ใช่,: include จะลดจำนวนการสืบค้นและจะนำตารางที่รวมอยู่ในขอบเขตเงื่อนไขของคุณ (ประเภทของการเข้าร่วมโดยนัยโดยไม่ต้อง Rails ทำเครื่องหมายบันทึกของคุณเป็นแบบอ่านอย่างเดียวซึ่งจะทำทันทีที่ดมสิ่ง SQL - ในการค้นหาของคุณรวมถึง: เข้าร่วม /: ข้อที่เลือก IIRC
vladr

สำหรับ 'has_many: a, ถึง =>: b' เพื่อทำงานสมาคม B จะต้องประกาศเช่นกันเช่น 'has_many: b; has_many: a,: through =>: b ', ฉันหวังว่านี่เป็นกรณีของคุณ?
vladr

6
สิ่งนี้อาจมีการเปลี่ยนแปลงในรุ่นล่าสุด แต่คุณสามารถเพิ่ม: readonly => false เป็นส่วนหนึ่งของคุณลักษณะวิธีการค้นหา
Aaron Rustad

1
คำตอบนี้ใช้ได้ถ้าคุณมีการเชื่อมโยง has_and_belongs_to_many กับกำหนดเอง: join_table ที่ระบุ
Lee

172

หรือใน Rails 3 คุณสามารถใช้วิธีการอ่านอย่างเดียว (แทนที่ "... " ด้วยเงื่อนไขของคุณ):

( Deck.joins(:card) & Card.where('...') ).readonly(false)

1
อืม ... ฉันค้นหาทั้งคู่ของ Railscasts บน Asciicasts และไม่ได้กล่าวถึงreadonlyฟังก์ชั่น
Purplejacket

45

สิ่งนี้อาจมีการเปลี่ยนแปลงในรีลีสล่าสุดของ Rails แต่วิธีที่เหมาะสมในการแก้ปัญหานี้คือการเพิ่ม: readonly => falseไปยังตัวเลือกการค้นหา


3
ฉันไม่เชื่อว่าเป็นกรณีนี้โดยอย่างน้อย 2.3.4
Olly

2
มันก็ยังคงทำงานร่วมกับทางรถไฟ 3.0.10 นี่คือตัวอย่างจากรหัสของตัวเองเรียกขอบเขตที่มี: เข้าร่วม Fundraiser.donatable.readonly (เท็จ)
Houen

16

select ('*') ดูเหมือนว่าจะแก้ไขใน Rails 3.2:

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

เพียงเพื่อยืนยันไม่ใช้ select ('*') จะสร้างบันทึกแบบอ่านอย่างเดียว:

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

ไม่สามารถพูดได้ว่าฉันเข้าใจเหตุผล แต่อย่างน้อยก็เป็นวิธีที่รวดเร็วและสะอาด


4
สิ่งเดียวกันใน Rails 4 หรือคุณสามารถทำได้ select(quoted_table_name + '.*')
andorov

1
นั่นคือ Bronson ที่ยอดเยี่ยม ขอบคุณ.
เดินทาง

สิ่งนี้อาจใช้งานได้ แต่มีความซับซ้อนกว่าการใช้readonly(false)
Kelvin

5

แทนที่จะเป็น find_by_sql คุณสามารถระบุ: เลือกในตัวค้นหาและทุกอย่างมีความสุขอีกครั้ง ...

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


3

ปิดการใช้งาน ...

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

3
Monkey-patching นั้นบอบบาง - แตกหักง่ายมากโดยทางรถไฟรุ่นใหม่ หลีกเลี่ยงไม่ได้แน่นอนเนื่องจากมีวิธีแก้ไขปัญหาอื่น ๆ
เคลวิน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.