Was verursacht diesen ActiveRecord :: ReadOnlyRecord-Fehler?

203

Dies folgt dieser vorherigen Frage, die beantwortet wurde. Ich habe tatsächlich festgestellt, dass ich einen Join aus dieser Abfrage entfernen kann. Jetzt funktioniert die Abfrage

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

Dies scheint zu funktionieren. Wenn ich jedoch versuche, diese DeckCards in eine andere Zuordnung zu verschieben, wird der Fehler ActiveRecord :: ReadOnlyRecord angezeigt.

Hier ist der Code

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

und die relevanten Modelle (Tableau sind die Spielerkarten auf dem Tisch)

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

Ich mache eine ähnliche Aktion direkt nach diesem Code und füge DeckCardssie der Hand des Spielers hinzu, und dieser Code funktioniert einwandfrei. Ich habe mich gefragt, ob ich belongs_to :tableaudas DeckCard-Modell brauche, aber es funktioniert gut für das Hinzufügen zur Hand des Spielers. Ich habe ein tableau_idund hand_idSpalten in der DeckCard-Tabelle.

Ich habe ReadOnlyRecord in der Rails-API nachgeschlagen, und es sagt nicht viel über die Beschreibung hinaus.

user26270
quelle

Antworten:

283

Schienen 2.3.3 und niedriger

Von der Active CHANGELOG(v1.12.0, 16. Oktober 2005) :

Führen Sie schreibgeschützte Datensätze ein. Wenn Sie object.readonly aufrufen! Dann wird das Objekt als schreibgeschützt markiert und ReadOnlyRecord ausgelöst, wenn Sie object.save aufrufen. object.readonly? meldet, ob das Objekt schreibgeschützt ist. Übergeben: readonly => true für jede Finder-Methode markiert zurückgegebene Datensätze als schreibgeschützt. Die Option: joins impliziert jetzt: readonly. Wenn Sie diese Option verwenden, schlägt das Speichern desselben Datensatzes jetzt fehl. Verwenden Sie find_by_sql, um das Problem zu umgehen.

Die Verwendung find_by_sqlist keine Alternative, da sie keine rohen Zeilen- / Spaltendaten zurückgibt ActiveRecords. Sie haben zwei Möglichkeiten:

  1. Erzwinge, dass die Instanzvariable @readonlyim Datensatz auf false gesetzt wird (Hack)
  2. Verwenden Sie :include => :cardanstelle von:join => :card

Schienen 2.3.4 und höher

Die meisten der oben genannten Punkte gelten nach dem 10. September 2012 nicht mehr:

  • Verwenden Record.find_by_sql ist eine praktikable Option
  • :readonly => trueautomatisch geschlossen wird , nur wenn :joinsangegeben wurde , ohne eine ausdrückliche :select noch eine explizite (oder Finder-scope-geerbt) :readonlyOption (siehe die Umsetzung set_readonly_option!in active_record/base.rbfür Rails 2.3.4, oder die Umsetzung to_ain active_record/relation.rbund custom_join_sqlin active_record/relation/query_methods.rbfür Rails 3.0.0)
  • wird jedoch :readonly => trueimmer automatisch abgeleitet, has_and_belongs_to_manywenn die Join-Tabelle mehr als die beiden Fremdschlüsselspalten enthält und :joinsohne explizite Angabe angegeben wurde :select(dh vom Benutzer angegebene :readonlyWerte werden ignoriert - siehe finding_with_ambiguous_select?in active_record/associations/has_and_belongs_to_many_association.rb).
  • Abschließend kommt , es sei denn mit einem speziellen Umgang Tisch und has_and_belongs_to_manydann @aaronrustad‚s Antwort ganz gut in Rails 2.3.4 und 3.0.0 gilt.
  • Sie nicht verwenden , :includeswenn Sie ein erreichen wollen INNER JOIN( :includesein impliziert LEFT OUTER JOIN, die weniger selektiv ist und weniger effizient als INNER JOIN.)
vladr
quelle
Das: include ist hilfreich, um die Anzahl der durchgeführten Abfragen zu reduzieren. Das wusste ich nicht. Aber ich habe versucht, das Problem zu beheben, indem ich die Tableau / Deckcards-Zuordnung in has_many: through geändert habe. Jetzt wird die Meldung "Zuordnung konnte nicht gefunden werden" angezeigt. Möglicherweise muss ich dafür eine andere Frage stellen
user26270
@codeman, ja, das: include reduziert die Anzahl der Abfragen und bringt die eingeschlossene Tabelle in Ihren Bedingungsbereich (eine Art impliziter Join, ohne dass Rails Ihre Datensätze als schreibgeschützt markiert, was es tut, sobald es SQL abspielt -ish in Ihrem Fund, einschließlich: join /: select Klauseln IIRC
vladr
Damit 'has_many: a, through =>: b' funktioniert, muss auch die B-Zuordnung deklariert werden, z. B. 'has_many: b; has_many: a ,: through =>: b ', ich hoffe das ist dein Fall?
Vladr
6
Dies hat sich möglicherweise in den letzten Versionen geändert, aber Sie können einfach Folgendes hinzufügen: readonly => false als Teil der Attribute der Suchmethode.
Aaron Rustad
1
Diese Antwort gilt auch, wenn Sie eine Zuordnung von has_and_belongs_to_many zu einem benutzerdefinierten Wert haben: join_table.
Lee
172

Oder in Rails 3 können Sie die Readonly-Methode verwenden (ersetzen Sie "..." durch Ihre Bedingungen):

( Deck.joins(:card) & Card.where('...') ).readonly(false)
Balexand
quelle
1
Hmmm ... Ich habe diese beiden Railscasts auf Asciicasts nachgeschlagen und die readonlyFunktion auch nicht erwähnt .
Purplejacket
45

Dies hat sich möglicherweise in der letzten Version von Rails geändert. Die geeignete Lösung für dieses Problem besteht jedoch darin, den Suchoptionen Folgendes hinzuzufügen : readonly => false .

Aaron Rustad
quelle
3
Ich glaube nicht, dass dies der Fall ist, zumindest mit 2.3.4
Olly
2
Es funktioniert immer noch mit Rails 3.0.10, hier ist ein Beispiel aus meinem eigenen Code, der einen Bereich
abruft, der Folgendes
16

select ('*') scheint dies in Rails 3.2 zu beheben:

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

Nur um dies zu überprüfen, führt das Weglassen von select ('*') zu einem schreibgeschützten Datensatz:

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

Ich kann nicht sagen, dass ich die Gründe verstehe, aber es ist zumindest eine schnelle und saubere Lösung.

Bronson
quelle
4
Das Gleiche gilt für Rails 4. Alternativ können Sie dies tun select(quoted_table_name + '.*')
andorov
1
Das war ein brillanter Bronson. Danke dir.
Reise
Dies mag funktionieren, ist aber komplizierter als die Verwendungreadonly(false)
Kelvin
5

Anstelle von find_by_sql können Sie ein: select im Finder angeben und alles ist wieder glücklich ...

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


quelle
3

Um es zu deaktivieren ...

module DeactivateImplicitReadonly
  def custom_join_sql(*args)
    result = super
    @implicit_readonly = false
    result
  end
end
ActiveRecord::Relation.send :include, DeactivateImplicitReadonly
gröber
quelle
3
Das Patchen von Affen ist zerbrechlich - sehr leicht durch neue Schienenversionen zu brechen. Auf jeden Fall nicht ratsam, da es andere Lösungen gibt.
Kelvin