Cosa sta causando questo errore ActiveRecord :: ReadOnlyRecord?


203

Ciò segue questa domanda precedente, alla quale è stata data una risposta. In realtà ho scoperto che avrei potuto rimuovere un join da quella query, quindi ora la query funzionante è

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

Questo sembra funzionare. Tuttavia, quando provo a spostare queste DeckCard in un'altra associazione, ottengo l'errore ActiveRecord :: ReadOnlyRecord.

Ecco il codice

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

e i relativi modelli (tableau sono le carte dei giocatori sul tavolo)

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

Sto facendo un'azione simile subito dopo questo codice, aggiungendo DeckCardsalla mano dei giocatori, e quel codice funziona bene. Mi chiedevo se fosse necessario belongs_to :tableaunel modello DeckCard, ma funziona benissimo per l'aggiunta alla mano del giocatore. Ho una tableau_ide hand_idcolonne nella tabella DeckCard.

Ho cercato ReadOnlyRecord nelle API dei binari e non dice molto oltre la descrizione.

Risposte:


283

Rotaie 2.3.3 e precedenti

Da ActiveRecord CHANGELOG(v1.12.0, 16 ottobre 2005) :

Introdurre record di sola lettura. Se chiami object.readonly! quindi contrassegnerà l'oggetto come di sola lettura e genererà ReadOnlyRecord se si chiama object.save. object.readonly? segnala se l'oggetto è di sola lettura. Passando: readonly => true per qualsiasi metodo finder contrassegnerà i record restituiti come di sola lettura. L'opzione: joins ora implica: sola lettura, quindi se si utilizza questa opzione, il salvataggio dello stesso record ora fallirà. Usa find_by_sql per aggirare.

L'uso find_by_sqlnon è in realtà un'alternativa in quanto restituisce dati grezzi di riga / colonna ActiveRecords. Hai due opzioni:

  1. Forza la variabile dell'istanza @readonlysu false nel record (hack)
  2. Usa :include => :cardinvece di:join => :card

Rotaie 2.3.4 e successive

La maggior parte di quanto sopra non vale più, dopo il 10 settembre 2012:

  • l'utilizzo Record.find_by_sql è un'opzione praticabile
  • :readonly => trueviene dedotto automaticamente solo se è :joinsstato specificato senza un'opzione esplicita :select esplicita (o ereditata dall'ambito del Finder) :readonly(vedere l'implementazione di set_readonly_option!in active_record/base.rbper Rails 2.3.4 o l'implementazione di to_ain active_record/relation.rbe di custom_join_sqlin active_record/relation/query_methods.rbper Rails 3.0.0)
  • tuttavia, :readonly => trueviene sempre dedotto automaticamente has_and_belongs_to_manyse la tabella di join ha più delle due colonne di chiavi esterne ed è :joinsstata specificata senza un esplicito :select(ovvero i :readonlyvalori forniti dall'utente vengono ignorati - vedere finding_with_ambiguous_select?in active_record/associations/has_and_belongs_to_many_association.rb.)
  • in conclusione, a meno che non si tratti di una tabella di join speciale e has_and_belongs_to_many, quindi @aaronrustad, la risposta si applica perfettamente a Rails 2.3.4 e 3.0.0.
  • non non utilizzare :includesse si vuole raggiungere un INNER JOIN( :includesimplica una LEFT OUTER JOIN, che è meno selettiva e meno efficiente rispetto INNER JOIN.)

the: include è utile nel ridurre il numero di query fatte, non lo sapevo; ma ho provato a risolverlo cambiando l'associazione Tableau / Deckcards in has_many: through, e ora sto ricevendo un messaggio "Impossibile trovare l'associazione"; Potrei dover inviare un'altra domanda per questo
user26270

@codeman, sì, il: include ridurrà il numero di query e porterà la tabella inclusa nel tuo ambito di condizione (una sorta di join implicito senza Rails che segna i tuoi record come di sola lettura, cosa che fa non appena annusa qualsiasi cosa SQL -ish nella tua ricerca, tra cui: join /: seleziona le clausole IIRC
vladr

Perché 'has_many: a, through =>: b' funzioni, deve essere dichiarata anche l'associazione B, ad es. 'Has_many: b; has_many: a,: through =>: b ', spero che sia questo il tuo caso?
Vladr,

6
Questo potrebbe essere cambiato nelle versioni recenti, ma puoi semplicemente aggiungere: readonly => false come parte degli attributi del metodo find.
Aaron Rustad,

1
Questa risposta è applicabile anche se si dispone di un'associazione has_and_belongs_to_many con un'abitudine personalizzata: join_table specificato.
Lee,

172

Oppure in Rails 3 puoi usare il metodo di sola lettura (sostituisci "..." con le tue condizioni):

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

1
Hmmm ... Ho cercato entrambi questi Railscast su Asciicasts, e nessuno dei due menziona la readonlyfunzione.
Giacca viola

45

Questo potrebbe essere cambiato nella recente versione di Rails, ma il modo appropriato per risolvere questo problema è aggiungere : readonly => false alle opzioni di ricerca.


3
Non credo che sia così, con la 2.3.4 almeno
Olly

2
Funziona ancora con Rails 3.0.10, ecco un esempio dal mio codice che recupera un ambito che ha un: join Fundraiser.donatable.readonly (false)
Houen,

16

select ('*') sembra risolverlo in Rails 3.2:

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

Giusto per verificare, omettere select ('*') produce un record di sola lettura:

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

Non posso dire di aver capito la logica, ma almeno è una soluzione rapida e pulita.


4
Stessa cosa in Rails 4. In alternativa puoi farlo select(quoted_table_name + '.*')
andorov

1
È stato un bronson brillante. Grazie.
Viaggio

Questo potrebbe funzionare, ma è più complicato dell'usoreadonly(false)
Kelvin il

5

Invece di find_by_sql, puoi specificare un: seleziona sul finder e tutto è di nuovo felice ...

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


3

Per disattivarlo ...

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

3
Il rattoppamento delle scimmie è fragile - molto facilmente rotto dalle nuove versioni di binari. Decisamente sconsigliabile dato che ci sono altre soluzioni.
Kelvin,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.